通过直接继承ViewGroup来实现自定义ViewGroup的方法简介

  本文通过实现一个可以垂直滑动的列表型ViewGroup,介绍通过继承ViewGroup来实现自定义ViewGroup的方法。

1. 创建继承ViewGroup的自定义ViewGroup

  首先创建VerticalScrollView直接继承自ViewGroup:

public class VerticalScrollView extends ViewGroup {
}

2. 自定义ViewGroup的属性和构造器

  类似于自定义View,也可以用同样的方法为自定义ViewGroup添加属性,并在构造器中获取这些属性。具体方法可以参考通过直接继承View来实现自定义View的方法简介,这里不在赘述。

3. 自定义ViewGroup的测量

  由于VerticalScrollView可以用于盛放其他View,对VerticalScrollView的测量就需要首先通过measureChildren() 对其中盛放的子元素进行测量。

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    int measuredWidth = 0;
    int measuredHeight = 0;
    final int childCount = getChildCount();
    measureChildren(widthMeasureSpec, heightMeasureSpec);

    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    int widthSpec = MeasureSpec.getMode(widthMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    int heightSpec = MeasureSpec.getMode(heightMeasureSpec);
    ...

  然后开始对VerticalScrollView的测量过程。首先考虑VerticalScrollView中没有盛放任何子元素的情况,此时设置VerticalScrollView的长和宽都为0:
        if (childCount == 0) {
            setMeasuredDimension(0, 0);
        }

  对于VerticalScrollView的宽和高都被设置为wrap_content的情况,VerticalScrollView的宽和高由其中盛放的子元素决定,即VerticalScrollView的宽度是子元素的宽度,VerticalScrollView的高度是子元素的高度总和(这里假设VerticalScrollView中盛放的子元素尺寸都相同):
        } else if (widthSpec == MeasureSpec.AT_MOST && heightSpec == MeasureSpec.AT_MOST) {
            final View childView = getChildAt(0);
            measuredWidth = childView.getMeasuredWidth();
            measuredHeight = childView.getMeasuredHeight() * childCount;
            setMeasuredDimension(measuredWidth, measuredHeight);
        }

  类似地,继续考虑VerticalScrollView的宽和高有一个设置为wrap_content的情况:
        } else if (heightSpec == MeasureSpec.AT_MOST) {
            final View childView = getChildAt(0);
            measuredHeight = childView.getMeasuredHeight() * childCount;
            setMeasuredDimension(widthSize, measuredHeight);
        } else if (widthSpec == MeasureSpec.AT_MOST) {
            final View childView = getChildAt(0);
            measuredWidth = childView.getMeasuredWidth();
            setMeasuredDimension(measuredWidth, heightSize);
        }

4. 自定义ViewGroup的布局

  VerticalScrollView是一个垂直列表,需要把其中的子元素按垂直方向放置:

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    int childTop = 0;
    final int childCount = getChildCount();
    mChildrenSize = childCount;

    for (int i = 0; i < childCount; i++) {
        final View childView = getChildAt(i);
        if (childView.getVisibility() != View.GONE) {
            final int childHeight = childView.getMeasuredHeight();
            mChildHeight = childHeight;
            childView.layout(0, childTop, childView.getMeasuredWidth(), childTop + childHeight);
            childTop += childHeight;
        }
    }
}

这里遍历所有子元素,累积子元素顶部的位置childTop 依次为各个子元素布局。

【完整代码】

5. 自定义ViewGroup的使用

  首先在布局文件中加入VerticalScrollView:

<com.nex3z.examples.simplecustomview.VerticalScrollView
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
</com.nex3z.examples.simplecustomview.VerticalScrollView>

  然后就可以为VerticalScrollView添加子元素:
mContainer = (VerticalScrollView) findViewById(R.id.container);
LayoutInflater inflater = getLayoutInflater();
Random rnd = new Random();
for (int i = 0; i < 10; ++i) {
    ViewGroup layout = (ViewGroup) inflater.inflate(R.layout.item, mContainer, false);

    BlockView blockView = (BlockView) layout.findViewById(R.id.block);
    int color = Color.argb(255, rnd.nextInt(256), rnd.nextInt(256), rnd.nextInt(256));
    blockView.setBlockColor(color);

    mContainer.addView(layout);
}

6. 完整代码

  完整代码可以在这里找到。

7. 参考资料

  《Android开发艺术探索》 第4章 View的工作原理