美文网首页
Android View的排版

Android View的排版

作者: 我叫王菜鸟 | 来源:发表于2018-06-01 14:51 被阅读0次

    由于performLayout之前是performMeasure()操作,所以不熟悉测量的小伙伴看我上一篇博客Android View 测量原理
    我想了想,如果直接从ViewGroup里面的方法谈起,可能和网上很多博客一样了,但是如果只是向framework开发者分析哪些,又分析不到应用层,所以我觉得应该从performLayout()这个方法开始分析测量,因为如果在向framework层深入,那就会接触到WindowManagerService,这个过程需要掌握Binder知识,但是Binder知识很多人一时半会掌握不了,尤其是对于application开发者,不关注这些,所以从performLayout()说起。

        private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
                int desiredWindowHeight) {
            ...
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
            try {
                host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
                ...
            }finally {
                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }
    }
    

    这里的host是decorView,decorView对应的布局是一个FrameLayout,所以我们进入FrameLayout的layout方法

    //传递进来的是左上右下的值
    public void layout(int l, int t, int r, int b) {
        if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
            onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        }
    
        int oldL = mLeft;
        int oldT = mTop;
        int oldB = mBottom;
        int oldR = mRight;
        //判断是不是左上右下这些值有所改变,如果改变的话为true,并且在setFrame中给mLeft...mRight赋值
        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
        //true进入调用到onLayout方法
        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            onLayout(changed, l, t, r, b);
            ......
        }
        .....
    }
    

    继续进入onLyout方法中,我们会发现是空方法,所以我们此时想到了ViewGroup

    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    }
    

    继续看ViewGroup的onLayout方法,可想而知每个子类都有自己的实现,我们用LinearLayout举例

    @Override
    protected abstract void onLayout(boolean changed,
            int l, int t, int r, int b);
    

    LinearLayout实现方法是:

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (mOrientation == VERTICAL) {
            layoutVertical(l, t, r, b);
        } else {
            layoutHorizontal(l, t, r, b);
        }
    }
    

    针对于垂直方向和水平方向不同

    void layoutVertical(int left, int top, int right, int bottom) {
        final int paddingLeft = mPaddingLeft;
        int childTop;
        int childLeft;
    
        // Where right end of child should go
        final int width = right - left;
        int childRight = width - mPaddingRight;
    
        // 水平可用宽度
        int childSpace = width - paddingLeft - mPaddingRight;
    
        final int count = getVirtualChildCount();//调用getChildCount()
    
        final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
        final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
        //gravity属性配置的值
        switch (majorGravity) {
            //当配置bottom时候
           case Gravity.BOTTOM:
               // 看出来是已父容器总内容宽度为基准的最下面
               childTop = mPaddingTop + bottom - top - mTotalLength;
               break;
    
               // 同理配置的center
           case Gravity.CENTER_VERTICAL:
               childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
               break;
                // 同理配置的top
           case Gravity.TOP:
           default:
               childTop = mPaddingTop;
               break;
        }
    
        for (int i = 0; i < count; i++) {
            //得到每一个孩子View
            final View child = getVirtualChildAt(i);
            if (child == null) {
                childTop += measureNullChild(i);
            } else if (child.getVisibility() != GONE) {
                final int childWidth = child.getMeasuredWidth();//view的宽
                final int childHeight = child.getMeasuredHeight();//view的高
                final LinearLayout.LayoutParams lp =
                        (LinearLayout.LayoutParams) child.getLayoutParams();
    
                int gravity = lp.gravity;
                if (gravity < 0) {
                    gravity = minorGravity;
                }
                final int layoutDirection = getLayoutDirection();//得到方向
                final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                    case Gravity.CENTER_HORIZONTAL:
                        //水平方向的话view左侧的距离
                        childLeft = paddingLeft + ((childSpace - childWidth) / 2)
                                + lp.leftMargin - lp.rightMargin;
                        break;
    
                    case Gravity.RIGHT:
                        childLeft = childRight - childWidth - lp.rightMargin;
                        break;
    
                    case Gravity.LEFT:
                    default:
                        childLeft = paddingLeft + lp.leftMargin;
                        break;
                }
                //有没有分割线
                if (hasDividerBeforeChildAt(i)) {
                    childTop += mDividerHeight;
                }
    
                childTop += lp.topMargin;
                //设置子view的坐标
                setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                        childWidth, childHeight);
                //加上子view的坐标继续向下排列
                childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
                //下一个view
                i += getChildrenSkipCount(child, i);
            }
        }
    }
    

    通过上面标记的注释我们知道了对每一个子view进行排列。

    同时注意几个方法

    • setFrame
        当size和position变化时,返回true。如果发生了变化,会在setFrame方法内部调用invalidate。

    • onLayout
        View中onLayout什么都没有做,在ViewGroup中,根据各自实际规则(Linear、Relative 等)对内部Views进行布局安排。

    • getMeasuredWidth与getWidth

    可以调用的时机不同:getMeasuredWidth在measure后即可调用,getWidth要在layout后才可以调用。(在发生时机之前调用的话均返回0)
    含义不同:getMeasuredWidth是View计算出自己的实际大小,getWidth是在布局后的大小。最简单的,在ScrollLayout中,getHeight返回屏幕内的高度,getMeasuredHeight返回屏幕内+屏幕外的总高度。

    相关文章

      网友评论

          本文标题:Android View的排版

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