Android 自定义视图(View)和视图组(ViewGroup)详解
在 Android 开发中,有时候我们需要创建一些标准控件无法满足需求的自定义视图(View)和视图组(ViewGroup)。本文将详细介绍如何创建自定义视图和视图组,包括构造方法、自定义属性、绘制逻辑、测量逻辑、布局逻辑和设置布局参数等内容。
1. 自定义视图(View)
1.1 构造方法
自定义视图类通常需要实现三个构造方法,以便在不同的场景下使用:
- 无参数构造方法:用于从代码中直接创建视图。
- 带
AttributeSet
参数的构造方法:用于从 XML 布局文件中加载视图。 - 带
AttributeSet
和defStyleAttr
参数的构造方法:用于从 XML 布局文件中加载视图并支持样式主题。
public class CustomView extends View {
public CustomView(Context context) {
super(context);
init(null);
}
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs);
}
private void init(AttributeSet attrs) {
if (attrs != null) {
// 处理自定义属性
TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.CustomView);
String text = typedArray.getString(R.styleable.CustomView_customText);
int color = typedArray.getColor(R.styleable.CustomView_customColor, Color.BLACK);
typedArray.recycle();
}
}
}
1.2 自定义属性
自定义属性允许我们在 XML 布局文件中设置视图的属性。首先,需要在 res/values/attrs.xml
文件中定义自定义属性:
<resources>
<declare-styleable name="CustomView">
<attr name="customText" format="string"/>
<attr name="customColor" format="color"/>
</declare-styleable>
</resources>
然后,在 init
方法中读取并处理这些属性:
private void init(AttributeSet attrs) {
if (attrs != null) {
TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.CustomView);
String text = typedArray.getString(R.styleable.CustomView_customText);
int color = typedArray.getColor(R.styleable.CustomView_customColor, Color.BLACK);
typedArray.recycle();
}
}
1.3 绘制逻辑
重写 onDraw
方法来实现自定义绘制逻辑。onDraw
方法会在视图需要重新绘制时被调用。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint();
paint.setColor(Color.BLUE);
canvas.drawCircle(getWidth() / 2, getHeight() / 2, 50, paint);
}
1.4 测量逻辑
重写 onMeasure
方法来实现自定义测量逻辑。onMeasure
方法会在视图需要重新测量时被调用。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int suggestedMinimumWidth = getSuggestedMinimumWidth();
int suggestedMinimumHeight = getSuggestedMinimumHeight();
int width;
int height;
switch (widthMode) {
case MeasureSpec.EXACTLY:
width = widthSize;
break;
case MeasureSpec.AT_MOST:
width = Math.min(suggestedMinimumWidth, widthSize);
break;
case MeasureSpec.UNSPECIFIED:
width = suggestedMinimumWidth;
break;
default:
width = suggestedMinimumWidth;
break;
}
switch (heightMode) {
case MeasureSpec.EXACTLY:
height = heightSize;
break;
case MeasureSpec.AT_MOST:
height = Math.min(suggestedMinimumHeight, heightSize);
break;
case MeasureSpec.UNSPECIFIED:
height = suggestedMinimumHeight;
break;
default:
height = suggestedMinimumHeight;
break;
}
setMeasuredDimension(width, height);
}
1.5 使用自定义视图
XML 布局文件
在 XML 布局文件中使用自定义视图:
<com.example.myapp.CustomView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:customText="Hello, World!"
app:customColor="#FF0000" />
代码中动态添加
在 Java 或 Kotlin 代码中动态添加自定义视图:
CustomView customView = new CustomView(this);
setContentView(customView);
2. 自定义视图组(ViewGroup)
2.1 构造方法
自定义视图组类通常也需要实现三个构造方法,以便在不同的场景下使用:
- 无参数构造方法:用于从代码中直接创建视图组。
- 带
AttributeSet
参数的构造方法:用于从 XML 布局文件中加载视图组。 - 带
AttributeSet
和defStyleAttr
参数的构造方法:用于从 XML 布局文件中加载视图组并支持样式主题。
public class CustomViewGroup extends ViewGroup {
public CustomViewGroup(Context context) {
super(context);
init();
}
public CustomViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public CustomViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
// 初始化代码
}
}
2.2 定义自定义 LayoutParams
首先,定义一个自定义的 LayoutParams
类,继承自 ViewGroup.LayoutParams
,并添加自定义属性。
public class CustomViewGroupLayoutParams extends ViewGroup.LayoutParams {
public int customAttribute;
public CustomViewGroupLayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.CustomViewGroupLayoutParams);
customAttribute = a.getInt(R.styleable.CustomViewGroupLayoutParams_customAttribute, 0);
a.recycle();
}
public CustomViewGroupLayoutParams(int width, int height) {
super(width, height);
}
public CustomViewGroupLayoutParams(ViewGroup.LayoutParams source) {
super(source);
}
}
2.3 定义自定义属性
在 res/values/attrs.xml
文件中定义自定义属性。
<resources>
<declare-styleable name="CustomViewGroupLayoutParams">
<attr name="customAttribute" format="integer" />
</declare-styleable>
</resources>
2.4 创建自定义 ViewGroup
类
创建一个自定义的 ViewGroup
类,并重写 generateLayoutParams
方法来处理自定义的 LayoutParams
。
public class CustomViewGroup extends ViewGroup {
public CustomViewGroup(Context context) {
super(context);
init();
}
public CustomViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public CustomViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
// 初始化代码
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width = 0;
int height = 0;
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
measureChild(child, widthMeasureSpec, heightMeasureSpec);
width = Math.max(width, child.getMeasuredWidth());
height += child.getMeasuredHeight();
}
if (widthMode == MeasureSpec.AT_MOST) {
width = Math.min(width, widthSize);
}
if (heightMode == MeasureSpec.AT_MOST) {
height = Math.min(height, heightSize);
}
setMeasuredDimension(width, height);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childCount = getChildCount();
int currentTop = 0;
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
int childWidth = child.getMeasuredWidth();
int childHeight = child.getMeasuredHeight();
child.layout(l, currentTop, l + childWidth, currentTop + childHeight);
currentTop += childHeight;
}
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new CustomViewGroupLayoutParams(getContext(), attrs);
}
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof CustomViewGroupLayoutParams;
}
@Override
protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
return new CustomViewGroupLayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
}
}
2.5 使用自定义 ViewGroup
XML 布局文件
在 XML 布局文件中使用自定义 ViewGroup
,并设置自定义属性。
<com.example.myapp.CustomViewGroup
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.example.myapp.CustomButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:customAttribute="10" />
<com.example.myapp.CustomTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:customAttribute="20" />
</com.example.myapp.CustomViewGroup>
代码中动态添加
在 Java 或 Kotlin 代码中动态添加自定义 ViewGroup
和子视图,并设置布局参数。
CustomViewGroup customViewGroup = new CustomViewGroup(this);
CustomButton customButton = new CustomButton(this);
CustomViewGroupLayoutParams buttonLayoutParams = new CustomViewGroupLayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
);
buttonLayoutParams.customAttribute = 10;
customButton.setLayoutParams(buttonLayoutParams);
customViewGroup.addView(customButton);
CustomTextView customTextView = new CustomTextView(this);
CustomViewGroupLayoutParams textViewLayoutParams = new CustomViewGroupLayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
);
textViewLayoutParams.customAttribute = 20;
customTextView.setLayoutParams(textViewLayoutParams);
customViewGroup.addView(customTextView);
setContentView(customViewGroup);
3. 性能优化
3.1 避免不必要的重绘
在 onDraw
方法中避免不必要的绘制操作,例如缓存复杂的绘制结果。
3.2 使用硬件加速
在 AndroidManifest.xml
中启用硬件加速:
<application
android:hardwareAccelerated="true">
</application>
3.3 减少布局层次
尽量减少嵌套的布局层次,避免过度嵌套导致性能下降。
总结
通过本文,我们详细介绍了如何创建自定义视图和视图组,包括构造方法、自定义属性、绘制逻辑、测量逻辑、布局逻辑和设置布局参数等内容。自定义视图和视图组可以让我们更灵活地创建满足特定需求的 UI 组件,提高应用的用户体验。
希望这篇文章对你有所帮助!如果有任何问题或建议,请随时留言交流。
标签:ViewGroup,自定义,int,视图,attrs,Android,public From: https://www.cnblogs.com/ruiruizhou/p/18524792