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机制的主要内容,如果未能解决你的问题,请参考以下文章
Android View体系从源码解析View的layout和draw流程