Android 自定义view(三) 继承ViewGroup

前言:

本篇博客讲解内容主要是来自 鸿洋的博客Android 自定义ViewGroup 实战篇 -> 实现FlowLayout

上篇文章 自定义view(二),继承view。今天来看自定义View的第二种情况,继承自viewgroup,虽然viewGroup也是继承view控件,但是ViewGroup和View还是有很多方法区别的,顾名思义,这是一个控件的集合控件。

1.

我们来通过自定义viewGroup来实现瀑布流的效果。效果图如下:
这里写图片描述

2.

继承viewGroup主要来重写onMearsure和onLayout,剩下的onDraw更多是用来绘图的,所以不需要来重写。所以我们来看看代码

onMeasure

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        //获取测量模式和测量大小
        int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
        int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
        int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
        int modeHeight = MeasureSpec.getMode(heightMeasureSpec);

            /**以下代码主要是针对测量模式是wrap_content时需要动态的计算,如果是一些具体的大小或者match_parent时根本不不需要写onMearsure
            */
        //整个自定义viewgroup的宽,高的最大值进行记录
        int width = 0;
        int height = 0;
        //判断是否要换行时使用
        int lineWidth = 0;
        int lineHeight = 0;
        //计算子view的个数
        int cCount = getChildCount();
        //for循环计算每个子view在viewgroup中的大小
        for (int i = 0; i < cCount; i++)
        {
            View child = getChildAt(i);
            //measureChild这个方法来计算子view的大小和mode,这个viewGroup定义的一个方法,是个很重要的方法
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
            // 得到child的lp,MarginLayoutParams可以得到Margin数据。
            MarginLayoutParams lp = (MarginLayoutParams) child
                    .getLayoutParams();
            //measureChild方法过后,child.getMeasuredWidth()才能计算得到值,不然会为0
            int childWidth = child.getMeasuredWidth() + lp.leftMargin
                    + lp.rightMargin;
            int childHeight = child.getMeasuredHeight() + lp.topMargin
                    + lp.bottomMargin;
            //判断是否需要换行
            if (lineWidth + childWidth > sizeWidth - getPaddingLeft() - getPaddingRight())
            {
                //需要换行了,记录宽的最大值
                width = Math.max(width, lineWidth);
                //换行后的新的lineWidth等于控件的宽
                lineWidth = childWidth;
                height += lineHeight;
                lineHeight = childHeight;
            } else
            {   //不需要换行
                lineWidth += childWidth;
                lineHeight = Math.max(lineHeight, childHeight);
            }
            //最后一个view
            if (i == cCount - 1)
            {
                width = Math.max(lineWidth, width);
                height += lineHeight;
            }
        }
        //不同的mode进行一个简单的判断,viewGroup的真正的大小实际上就是通过这个方法进行设置的
        setMeasuredDimension(
                //
                modeWidth == MeasureSpec.EXACTLY ? sizeWidth : width + getPaddingLeft() + getPaddingRight(),
                modeHeight == MeasureSpec.EXACTLY ? sizeHeight : height + getPaddingTop() + getPaddingBottom()//
        );

    }

上面的注释其实也是十分的详细了,上面有个很关键的方法,是进行测试子view的
measureChild(child, widthMeasureSpec, heightMeasureSpec);这个viewgroup的方法,我们来看下源码:

    /**
     * Ask one of the children of this view to measure itself, taking into
     * account both the MeasureSpec requirements for this view and its padding.
     * The heavy lifting is done in getChildMeasureSpec.
     *
     * @param child The child to measure
     * @param parentWidthMeasureSpec The width requirements for this view
     * @param parentHeightMeasureSpec The height requirements for this view
     */
    protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {
        final LayoutParams lp = child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

getChildMeasureSpec()和measure()这两个方法的源码大家可以自己看看,主要是对子view进行测量。

3.

测量好了大小后,我们来看看是如何布局的,此时需要重写onLayout方法:

    //记录所有的子view,并且再按行来进行一个区别记录
    private List<List<View>> mAllViews = new ArrayList<List<View>>();  
    //记录没行的最大高度 
    private List<Integer> mLineHeight = new ArrayList<Integer>();  
    @Override  
    protected void onLayout(boolean changed, int l, int t, int r, int b)  
    {  
        mAllViews.clear();  
        mLineHeight.clear();  

        int width = getWidth();  

        int lineWidth = 0;  
        int lineHeight = 0;  
        // 存储每一行所有的childView  
        List<View> lineViews = new ArrayList<View>();  
        int cCount = getChildCount();  
        // 遍历所有的孩子  
        for (int i = 0; i < cCount; i++)  
        {  
            View child = getChildAt(i);  
            MarginLayoutParams lp = (MarginLayoutParams) child  
                    .getLayoutParams();  
            int childWidth = child.getMeasuredWidth();  
            int childHeight = child.getMeasuredHeight();  

            // 如果已经需要换行  
            if (childWidth + lp.leftMargin + lp.rightMargin + lineWidth > width)  
            {  
                // 记录这一行所有的View以及最大高度  
                mLineHeight.add(lineHeight);  
                // 将当前行的childView保存,然后开启新的ArrayList保存下一行的childView  
                mAllViews.add(lineViews);  
                lineWidth = 0;// 重置行宽  
                lineViews = new ArrayList<View>();  
            }  
            /** 
             * 如果不需要换行,则累加 
             */  
            lineWidth += childWidth + lp.leftMargin + lp.rightMargin;  
            lineHeight = Math.max(lineHeight, childHeight + lp.topMargin  
                    + lp.bottomMargin);  
            lineViews.add(child);  
        }  
        // 记录最后一行  
        mLineHeight.add(lineHeight);  
        mAllViews.add(lineViews);  

        int left = 0;  
        int top = 0;  
        // 得到总行数  
        int lineNums = mAllViews.size();  
        for (int i = 0; i < lineNums; i++)  
        {  
            // 每一行的所有的views  
            lineViews = mAllViews.get(i);  
            // 当前行的最大高度  
            lineHeight = mLineHeight.get(i);  

            // 遍历当前行所有的View  
            for (int j = 0; j < lineViews.size(); j++)  
            {  
                View child = lineViews.get(j);  
                if (child.getVisibility() == View.GONE)  
                {  
                    continue;  
                }  
                MarginLayoutParams lp = (MarginLayoutParams) child  
                        .getLayoutParams();  

                //计算childView的left,top,right,bottom  
                int lc = left + lp.leftMargin;  
                int tc = top + lp.topMargin;  
                int rc =lc + child.getMeasuredWidth();  
                int bc = tc + child.getMeasuredHeight();  

                //以上所有的计算都是为了获取这四个参数,来进行子view的布局。
                child.layout(lc, tc, rc, bc);  

                left += child.getMeasuredWidth() + lp.rightMargin  
                        + lp.leftMargin;  
            }  
            left = 0;  
            top += lineHeight;  
        }  

    } 

代码也不是很难,细心一点。

4.

实际上,我们可以发现,该自定义控件实现的逻辑挺简单的,主要是:
(1)onMeasure中,通过计算每个子view的大小才确定viewgroup的大小。
(1)onLayout中,动态的计算子view的位置坐标然后进行一个布局。
由此也可以发现,自定义viewgroup最重要的也就是这两个方法,但是,其中很多小的细节也是需要我们注意到的,比如:
(a)只有子view调用了measure方法,才能调用getMeasureSpec获取到值,不然为0.
(b)用MarginLayoutParams来支持margin属性。
代码:

@Override  
public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs)  
{  
    return new MarginLayoutParams(getContext(), attrs);  
} 

以上代码就可以简单实现自定义view的流式布局了。

最近搞了个Android技术分享的公众号,欢迎关注投稿。
这里写图片描述

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值