首页 > 其他分享 >Android - View框架的layout机制

Android - View框架的layout机制

时间:2023-06-22 14:39:08浏览次数:41  
标签:layout int Gravity child Android final View


系统为什么要有layout过程?

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

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

layout过程做了什么事?

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

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

Android - View框架的layout机制_sed

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

Android - View框架的layout机制_Android_02

源码分析

我们知道,整棵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的位置

标签:layout,int,Gravity,child,Android,final,View
From: https://blog.51cto.com/u_16163510/6534843

相关文章

  • 一天被艾特@48次!35岁Android程序员处境堪比生产队的驴!
    缘起随着互联网和移动互联网的快速发展,各类应用软件(app)如雨后春笋般涌现,许多应用程序甚至成为超级app,一些活跃用户过亿的应用程序成为国民app,这些app的兴起与程序员这个群体密不可分。快速发展的行业、互联网巨头的光环、国民级的应用程序带来的成就感、远超出普通行业的薪水,每年......
  • Android 开发之Activity的启动模式-SingleTop
    接下来,介绍下Activity的另一种启动模式-栈顶复用模式(SingleTop)SingleTopsingleTop模式,它的表现几乎和standard模式一模一样,一个singleTopActivity的实例可以无限多,唯一的区别是如果在栈顶已经有一个相同类型的Activity实例,Intent不会再创建一个Activity,而是通过onNewIntent()被......
  • Android:克服这些困难,让你直达阿里P7!
    写在前面;Android开发前几年火爆一时,市场饱和后Android程序员每一名程序员都想进阶,甚至成为架构师,但这期间,需要付出的辛苦和努力远超过我们的想象。就我这几年对所接触的Android工程师调研:97%的Android开发技术人都会面临这些困境(可能也是你的困惑)主要困境;**外包公司/小型团队技术......
  • Android:大厂技术面试过不了怎么办?别急!这些知识体系让你的面试稳如泰山!
    前言年年寒冬,年年也挡不住一个安卓程序员追求大厂的决心。想要进入大厂,我们需要掌握哪些知识点呢?这里,我为大家梳理了一个整体的知识架构。整体包括Java、Android、算法、计算机基础等等,相应的知识点的面试题都整理出来了。希望大家阅读之后,能帮助大家完善与整理自己的知识体系。祝......
  • Android-Kotlin-单例模式
    先看一个案例,非单例模式的案例:描述Dog对象:packagecn.kotlin.kotlin_oop08classDog(varname:String,varcolor:String){/***显示狗狗的名字*/funshowDogName(){println("狗狗的名字是:${this.name}")}/***显示狗狗的颜......
  • Android-Kotlin-枚举ENUM
    为什么要用枚举?枚举的好处有:1.使程序更容易编写和维护2.防止用户乱输入,是一种约束来看两个案例案例一星期:星期的枚举:enumclass类名{}packagecn.kotlin.kotlin_oop09/***定义星期的枚举类*/enumclassMyEnumerateWeek{星期一,星期二,星期三,星......
  • Android-Kotlin-函数表达式&String与int转换$异常处理
    Kotlin的函数表达式:packagecn.kotlin.kotlin_base03/***函数第一种写法*/funaddMethod1(number1:Int,number2:Int):Int{returnnumber1+number2}/***函数第二个种写法*/funaddMethod2(number1:Int,number2:Int)=number1+number2/***......
  • Android-kotlin-接口与多态的表现
    上一篇博客介绍了Android-Kotlin-抽象类与多态的表现;而这一篇博客专门介绍下接口与多态的表现1.选择包名,然后右键:2.选择Class类型,会有class:3.选择File类型,不会自动有class:5.选择interface,是创建接口:6.目录结构:1.定义手机充电接口标准规范InterfacePhone:packagecn.kotlin.kot......
  • Android Xml文件生成,Xml数据格式写入
    生成xml文件格式数据,Android提供了Xml.newSerializer();,可以理解为Xml序列化;序列化:把内存里面的数据(file,databases,xml等等)丢给某一个地方; 反序列化:把某个地方的数据(file,databases,xml等等),拿到内存中;既然是Android操作Xml,就用Android所提供的API,不用Java所提供的API,DOM......
  • Android-Kotlin-When&类型推断
    Kotlin的when表达式TextEngine描述文字处理对象:packagecn.kotlin.kotlin_base02/***描述文字处理对象**valtextContent传入进来的文字内容val是常量*/classTextEngine(valtextContent:String){/***处理文字,然后返回*返回完整的字符串......