美文网首页Android自定义View系统知识
Android自定义View onLayout详解

Android自定义View onLayout详解

作者: 1a473fcb13b0 | 来源:发表于2019-04-01 12:02 被阅读77次

    onLayout的作用:

    决定子View在ViewGroup中的位置

    onLayout使用范围

    只针对ViewGroup,且必须实现

    onLayout(boolean changed, int l, int t, int r, int b)内参数个代表什么?

    changed: 当前View的大小和位置改变了
    l: 相对于父容器左边的距离
    t: 相对于父容器上边的距离
    r: 相对于父容器右边的距离
    b: 相对于父容器下边的距离

    示例图

    layout() 和 onLayout()

    layout() 和 onLayout() 。layout()方法用来确定View本身的位置,onLayout()方法则用来确定所有子元素的位置。View和ViewGroup中都有layout()和onLayout()两个方法,但两个类中都没有实现onLayout(),其原因和ViewGroup中没有onMeasure()方法是相同的:因为不同ViewGroup的子类对布局的要求不一样。

    当我们自定义了一个ViewGroup的时候,会先确定这个ViewGroup的位置,然后,通过重写 onLayout() 方法,遍历所有的子元素并调用其 layout() 方法,在layout()方法中onLayout()方法又会被调用。ViewGroup就是通过这个过程,递归地对所有子View进行了布局。

    示例代码:

    /**
         *当这个view和其子view被分配一个大小和位置时,被layout调用。
         * @param changed 当前View的大小和位置改变了
         * @param l       相对于父容器左边的距离
         * @param t       相对于父容器上边的距离
         * @param r       相对于父容器右边的距离
         * @param b      相对于父容器下边的距离
         */
        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            mAllViews.clear();
            mLineHeight.clear();
    
            // 当前ViewGroup的宽度
            int width = getWidth();
    
            int lineWidth = 0;
            int lineHeight = 0;
    
            // 获取每一行的view集合
            List<View> viewList = new ArrayList<>();
            /**
             * 1、通过getChildCount,获取子View的个数view个数
             */
            int childCount = getChildCount();
            /**
             * 2、遍历childCount,通过getChildAt获取到对应的view,并按每一行的viewList记录,记录在mAllViews内
             */
            for (int i = 0; i < childCount; i++) {
                View childView = getChildAt(i);
                MarginLayoutParams lp = (MarginLayoutParams) childView.getLayoutParams();
                int childWidth = childView.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
                int childHeight = childView.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
                /**
                 * 如果换行则添加则把高度加入mLineHeight,把当前行的viewList加入到mAllViews,并viewList,lineWidth,lineHeight
                 * 如果不换行则记录宽度则直接添加当前控件的宽度到lineWidth内
                 */
                if (lineWidth + childWidth > width - getPaddingLeft() - getPaddingRight()) {
                    // 记录当前行的Views
                    mAllViews.add(viewList);
                    // 记录LineHeight
                    mLineHeight.add(lineHeight);
                    // 重置当前行的Views
                    viewList = new ArrayList<>();
                    // 重置宽
                    lineWidth = childWidth;
                    // 重置高
                    lineHeight = childHeight;
                } else {
                    lineWidth += childWidth;
                    /**
                     * 取当前控件的高度和之前记录的高度的最大值
                     */
                    lineHeight = Math.max(lineHeight, childHeight);
                }
                viewList.add(childView);
            }
            /**
             * 处理最后一行
             */
            mAllViews.add(viewList);
            mLineHeight.add(lineHeight);
    
            /**
             * 3、遍历mAllViews,通过view.layout(lc, tc, rc, bc);确定各个子View的位置
             */
            // 行的个数
            int lineNum = mAllViews.size();
            // view的初始位置
            int left = getPaddingLeft();
            int top = getPaddingTop();
            for (int i = 0; i < lineNum; i++) {
                viewList = mAllViews.get(i);
                lineHeight = mLineHeight.get(i);
    
                for (View view : viewList) {
                    if (view.getVisibility() == GONE) {
                        continue;
                    }
                    MarginLayoutParams lp = (MarginLayoutParams) view.getLayoutParams();
                    int lc = left + lp.leftMargin;
                    int tc = top + lp.topMargin;
                    int rc = lc + view.getMeasuredWidth();
                    int bc = tc + view.getMeasuredHeight();
                    Log.d("onLayout","getMeasuredHeight="+view.getMeasuredHeight());
                    Log.d("onLayout","getHeight="+view.getHeight());
                    view.layout(lc, tc, rc, bc);
                    left += view.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
                }
                left = getPaddingLeft();
                top += lineHeight;
            }
        }
    

    getMeasuredWidth()、getMeasuredHeight()与getWidth()、getHeight()存在什么样的差别?

    image.png image.png

    1、getMeasuredWidth、getMeasuredHeight经过measure测量之后才能获取到的。
    而getWidth、getHeight是经过layout设置过view布局位置之后才能获取到。所以,在onLayout使用getWidth和getHeight是不能得到对应的宽高的。
    2、通常情况下,getWidth、getHeight和getMeasureWidth、getMeasureHeight得到的值是一致的,但是layout的四个坐标点是可以随便设置的,不一定会根据measure测量的大小对应来设置,所以两种方式存在不等的情况。

    具体源码分析请参考:
    https://blog.csdn.net/wsq_tomato/article/details/80323719

    参考文章:
    https://www.cnblogs.com/itgungnir/p/6721868.html

    相关文章

      网友评论

        本文标题:Android自定义View onLayout详解

        本文链接:https://www.haomeiwen.com/subject/ixkwdqtx.html