Android的自定义View和自定义ViewGroup

Android 自定义视图(View)和视图组(ViewGroup)详解

在 Android 开发中,有时候我们需要创建一些标准控件无法满足需求的自定义视图(View)和视图组(ViewGroup)。本文将详细介绍如何创建自定义视图和视图组,包括构造方法、自定义属性、绘制逻辑、测量逻辑、布局逻辑和设置布局参数等内容。

1. 自定义视图(View)

1.1 构造方法

自定义视图类通常需要实现三个构造方法,以便在不同的场景下使用:

  1. 无参数构造方法:用于从代码中直接创建视图。
  2. AttributeSet 参数的构造方法:用于从 XML 布局文件中加载视图。
  3. AttributeSetdefStyleAttr 参数的构造方法:用于从 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 构造方法

自定义视图组类通常也需要实现三个构造方法,以便在不同的场景下使用:

  1. 无参数构造方法:用于从代码中直接创建视图组。
  2. AttributeSet 参数的构造方法:用于从 XML 布局文件中加载视图组。
  3. AttributeSetdefStyleAttr 参数的构造方法:用于从 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 组件,提高应用的用户体验。

希望这篇文章对你有所帮助!如果有任何问题或建议,请随时留言交流。


posted @ 2024-11-04 11:17  还要再努力一些吧  阅读(1509)  评论(0)    收藏  举报