Android - View框架的layout机制

Posted 沈页

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android - View框架的layout机制相关的知识,希望对你有一定的参考价值。

系统为什么要有layout过程?

view框架经过measure之后,可以算出每一个view的尺寸大小,但是如果想要将view绘制的屏幕上,还需要知道view对应的位置信息。除此之外,对一个ViewGroup而言,还需要根据自己特定的layout规则,来正确的计算出子View的绘制位置,已达到正确的layout目的。

位置是View相对于父布局坐标系的相对位置,而不是以屏幕坐标系为准的绝对位置。这样更容易保持树型结构的递归性和内部自治性。而View的位置,可以无限大,超出当前ViewGroup的可视范围,这也是通过改变View位置而实现滑动效果的原理。

layout过程做了什么事?

由于View是以树结构进行存储,所以典型的数据操作就是递归操作,所以,View框架中,采用了内部自治的layout过程。

每个叶子节点根据父节点传递过来的位置信息,设置自己的位置数据,每个非叶子节点,除了负责根据父节点传递过来的位置信息,设置自己的位置数据外(如果有父节点的话),还需要根据自己内部的layout规则(比如垂直排布等),计算出每一个子节点的位置信息,然后向子节点传递layout过程

View对象的位置信息,在内部是以4个成员变量的保存的,分别是mLeft、mRight、mTop、mBottom

源码分析

我们知道,整棵View树的根节点是DecorView,它是一个FrameLayout,所以它是一个ViewGroup,所以整棵View树的测量是从一个ViewGroup对象的layout方法开始的。

//分配一个位置信息到一个View上面,每个parent会调用children的layout方法来设置children的
//位置.最好不要覆写该方法,有children的viewGroup,应该覆写onLayout方法
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;
    
    //暂存old的位置信息
    int oldL = mLeft;
    int oldT = mTop;
    int oldB = mBottom;
    int oldR = mRight;

    boolean changed = isLayoutModeOptical(mParent) ?
            setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) 
        onLayout(changed, l, t, r, b);

        if (shouldDrawRoundScrollbar()) 
            if(mRoundScrollbarRenderer == null) 
                mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
            
         else 
            mRoundScrollbarRenderer = null;
        

        mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;

        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnLayoutChangeListeners != null) 
            ArrayList<OnLayoutChangeListener> listenersCopy =
                    (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
            int numListeners = listenersCopy.size();
            //回调layoutChange事件
            for (int i = 0; i < numListeners; ++i) 
                listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
            
        
    

    final boolean wasLayoutValid = isLayoutValid();

    mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
    mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;

    if (!wasLayoutValid && isFocused()) 
        mPrivateFlags &= ~PFLAG_WANTS_FOCUS;
        if (canTakeFocus()) 
            clearParentsWantFocus();
         else if (getViewRootImpl() == null || !getViewRootImpl().isInLayout()) 
            clearFocusInternal(null, /* propagate */ true, /* refocus */ false);
            clearParentsWantFocus();
         else if (!hasParentWantsFocus()) 
            clearFocusInternal(null, /* propagate */ true, /* refocus */ false);
        
     else if ((mPrivateFlags & PFLAG_WANTS_FOCUS) != 0) 
        mPrivateFlags &= ~PFLAG_WANTS_FOCUS;
        View focused = findFocus();
        if (focused != null) 
            if (!restoreDefaultFocus() && !hasParentWantsFocus()) 
                focused.clearFocusInternal(null, /* propagate */ true, /* refocus */ false);
            
        
    

    if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) 
        mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
        notifyEnterOrExitForAutoFillIfNeeded(true);
    

    notifyAppearedOrDisappearedForContentCaptureIfNeeded(true);
 

进入onLayout方法

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

根据布局规则,计算每一个子View的位置,View类默认是空实现。

ViewGroup中,只需要覆写onLayout方法,来计算出每一个子View的位置,并且把layout流程传递给子View

进入ViewGroup中找onLayout方法,发现是一个抽象方法,实现应该是子类处理

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

进入LinearLayout类中查看onLayout方法

@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);
    
 

进入layoutVertical

void layoutVertical(int left, int top, int right, int bottom) 
    final int paddingLeft = mPaddingLeft;

    int childTop;
    int childLeft;

    final int width = right - left;
    int childRight = width - mPaddingRight;

    // Space available for child
    int childSpace = width - paddingLeft - mPaddingRight;

    final int count = getVirtualChildCount();

    final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
    final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;

    switch (majorGravity) 
       case Gravity.BOTTOM:
           // mTotalLength contains the padding already
           childTop = mPaddingTop + bottom - top - mTotalLength;
           break;

           // mTotalLength contains the padding already
       case Gravity.CENTER_VERTICAL:
           childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
           break;

       case Gravity.TOP:
       default:
           childTop = mPaddingTop;
           break;
    
    //遍历字View
    for (int i = 0; i < count; i++) 
        final View child = getVirtualChildAt(i);
        if (child == null) 
            childTop += measureNullChild(i);
         else if (child.getVisibility() != GONE) 
            final int childWidth = child.getMeasuredWidth();
            final int childHeight = child.getMeasuredHeight();

            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:
                    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;
            setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                    childWidth, childHeight);
            childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);

            i += getChildrenSkipCount(child, i);
        
    
 

进入setChildFrame方法

private void setChildFrame(View child, int left, int top, int width, int height) 
    child.layout(left, top, left + width, top + height);
 

总结

一般来说,自定义View,如果该View不包含子View,类似于TextView这种的,是不需要覆写onLayout方法的。而含有子View的,比如LinearLayout这种,就需要根据自己的布局规则,来计算每一个子View的位置

最后

按照国际惯例,给大家分享一套十分好用的android进阶资料:《全网最全Android开发笔记》。

整个笔记一共8大模块、729个知识点,3382页,66万字,可以说覆盖了当下Android开发最前沿的技术点,和阿里、腾讯、字节等等大厂面试看重的技术。

因为所包含的内容足够多,所以,这份笔记不仅仅可以用来当学习资料,还可以当工具书用。

如果你需要了解某个知识点,不管是Shift+F 搜索,还是按目录进行检索,都能用最快的速度找到你要的内容。

相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照整个知识体系编排的。

(一)架构师必备Java基础

1、深入理解Java泛型

2、注解深入浅出

3、并发编程

4、数据传输与序列化

5、Java虚拟机原理

6、高效IO

……

(二)设计思想解读开源框架

1、热修复设计

2、插件化框架设计

3、组件化框架设计

4、图片加载框架

5、网络访问框架设计

6、RXJava响应式编程框架设计

……

(三)360°全方位性能优化

1、设计思想与代码质量优化

2、程序性能优化

  • 启动速度与执行效率优化
  • 布局检测与优化
  • 内存优化
  • 耗电优化
  • 网络传输与数据储存优化
  • APK大小优化

3、开发效率优化

  • 分布式版本控制系统Git
  • 自动化构建系统Gradle

……

(四)Android框架体系架构

1、高级UI晋升

2、Android内核组件

3、大型项目必备IPC

4、数据持久与序列化

5、Framework内核解析

……

(五)NDK模块开发

1、NDK开发之C/C++入门

2、JNI模块开发

3、Linux编程

4、底层图片处理

5、音视频开发

6、机器学习

……

(六)Flutter学习进阶

1、Flutter跨平台开发概述

2、Windows中Flutter开发环境搭建

3、编写你的第一个Flutter APP

4、Flutter Dart语言系统入门

……

(七)微信小程序开发

1、小程序概述及入门

2、小程序UI开发

3、API操作

4、购物商场项目实战

……

(八)kotlin从入门到精通

1、准备开始

2、基础

3、类和对象

4、函数和lambda表达式

5、其他

……

好啦,这份资料就给大家介绍到这了,有需要详细文档的小伙伴,可以微信扫下方二维码免费领取哈~

以上是关于Android - View框架的layout机制的主要内容,如果未能解决你的问题,请参考以下文章

View的layout机制

Android View体系从源码解析View的layout和draw流程

Android View框架的measure机制

Android View事件分发机制理解

android开发,Layout中add入一个view,占据它的全部,怎么触发Layout的点击事件呢??

Android View体系从源代码解析View的layout和draw流程