Android 面试官:简述一下 View 的绘制流程,这个都答不出来就别想拿Offer了(2)

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip204888 (备注Android)
img

正文

if (mDecor == null) {

//实例化DecorView

mDecor = generateDecor(-1);

}

} else {

mDecor.setWindow(this);

}

if (mContentParent == null) {

//获取Content

mContentParent = generateLayout(mDecor);

}

}

protected DecorView generateDecor(int featureId) {

return new DecorView(context, featureId, this, getAttributes());

}

通过generateDecor()new一个DecorView,然后调用generateLayout()获取DecorViewcontent,最终通过inflateActivity视图添加到DecorView中的content中,但此时DecorView还未被添加到Window中。添加操作需要借助ViewRootImpl

ViewRootImpl的作用是用来衔接WindowManagerDecorView,在Activity被创建后会通过WindowManagerDecorView添加到PhoneWindow中并且创建ViewRootImpl实例,随后将DecorViewViewRootImpl进行关联,最终通过执行ViewRootImplperformTraversals()开启整个View树的绘制。

关于Activity在何时将DecorView添加到Window以及何时创建 ViewRootImpl,这块内容牵扯面比较广,涉及到Activity启动流程、ActivityManagerService(AMS)、WindowManagerService(WMS),内容太过于深入加上作者能力有限就不误人子弟了。如有兴趣推荐查阅刘皇叔《Android进阶解密》,书中对这方面内容讲解还是比较全面的 。

2. 绘制过程


从第一小节可知,View的绘制是从ViewRootImplperformTraversals()方法开始,从最顶层的View(ViewGroup)开始逐层对每个View进行绘制操作,下面来看一下该方法部分源代码:

private void performTraversals() {

//measur过程

performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

//layout过程

performLayout(lp, desiredWindowWidth, desiredWindowHeight);

//draw过程

performDraw();

}

这方法大概有几百行,机智的作者抽出三句精华呈现给大家~~~

  • measure:为测量宽高过程,如果是ViewGroup还要在onMeasure中对所有子View进行measure操作。
  • layout:用于摆放View在ViewGroup中的位置,如果是ViewGroup要在onLayout方法中对所有子View进行layout操作。
  • draw:往View上绘制图像。

示意图如下: 确实不想画图了,从刚哥的书里拍一张吧~~~

2.1 Measure

performMeasure()源码

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {

if (mView == null) {

return;

}

try {

mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);

} finally {

Trace.traceEnd(Trace.TRACE_TAG_VIEW);

}

}

可以看出从mView(最顶层ViewGroup)开始进行测量操作,然后逐层遍历View并执行measure操作。

MeasureSpac

MeasureView绘制三个过程中的第一步,提到Measure就不得不提MeasureSpac它是一个32位int类型数值,高两位SpacMode代表测量模式,低30位SpacSize代表测量尺寸,是View的内部类,源码如下:

public class MeasureSpec {

private static final int MODE_SHIFT = 30;

private static final int MODE_MASK = 0x3 << MODE_SHIFT;

public static final int UNSPECIFIED = 0 << MODE_SHIFT;

public static final int EXACTLY = 1 << MODE_SHIFT;

public static final int AT_MOST = 2 << MODE_SHIFT;

}

内部也包含三种测量模式:

  • **UNSPECIFIED :**父布局不会对子View做任何限制,例如我们常用的ScrollView就是这种测量模式。

  • **EXACTLY :**精确数值,比如使用了match_parent或者xxxdp,表示父布局已经决定了子View的大小,通常在这种情况下View的尺寸就是SpacSize

  • **AT_MOST :**自适应,对应wrap_content子View可以根据内容设置自己的大小,但前提是不能超出父ViewGroup的宽高。

注意点:

在我们自定义View的过程中都会在onMeasure中进行宽高的测量,这个方法会从父布局中接收两个参数widthMeasureSpacheightMeasureSpac,所以子布局的宽高大小需要受限于父布局。

在自定义View宽高测量的过程中,我们需要获取MeasurSpac中的宽高和测量模式,自定义ViewGroup也必须给子View传递MeasurSpac,Android也给我们提供了计算MeasurSpac 和通过MeasurSpac 获取相应值的方式,都位于MeasurSpac中,具体代码如下:

public static class MeasureSpec {

public static int makeMeasureSpec( int size, int mode) {

if (sUseBrokenMakeMeasureSpec) {

return size + mode;

} else {

return (size & ~MODE_MASK) | (mode & MODE_MASK);

}

}

public static int getMode(int measureSpec) {

//noinspection ResourceType

return (measureSpec & MODE_MASK);

}

public static int getSize(int measureSpec) {

return (measureSpec & ~MODE_MASK)

}

}

ViewGroupView对尺寸和模式进行了一次封装和拆解,其目的是为了减少对象的创建,避免造成不必要的内存浪费。

LayoutParams

在刚接触Android的时候经常有一个疑问,为什么View设置自己的宽高,还要创建一个xxx.LayoutParams?前面也提到了,子View的宽高是要受限于父布局的,所以不能通过setWidth或者setHeight直接设置宽高的,另外 LayoutParams的作用不仅如此,比如一个View的父布局是RelativeLayout,可以通过设置RelativeLayout.LayoutParamsabovebelow等属性来调整在父布局中的位置。

自定义View宽高测量演示

创建一个类继承View,重写其onMeasure()方法

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

//默认宽

int defaultWidth = 0;

//默认高

int defaultHeight = 0;

setMeasuredDimension(

getDefaultSize(defaultWidth, widthMeasureSpec),

getDefaultSize(defaultHeight, heightMeasureSpec));

}

一般的自定义View中,如果对宽高没有特殊需求可直接通过getDefaultSize()方法获取,该方法位于View中源码如下:

public static int getDefaultSize(int size, int measureSpec) {

//默认尺寸

int result = size;

//获取测量模式

int specMode = MeasureSpec.getMode(measureSpec);

//获取尺寸

int specSize = MeasureSpec.getSize(measureSpec);

switch (specMode) {

case MeasureSpec.UNSPECIFIED:

result = size;

break;

case MeasureSpec.AT_MOST:

case MeasureSpec.EXACTLY:

result = specSize;

break;

}

return result;

}

从代码分析可知,获取modesize后会分别对三种测量模式进行判断,UNSPECIFIED使用默认尺寸,而AT_MOSTEXACTLY使用父布局给出的测量尺寸。尺寸计算完毕后通过setMeasuredDimension(width,height)设置最终宽高。

2.2 Layout

performLayout()部分源码:

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,

int desiredWindowHeight) {

final View host = mView;

if (host == null) {

return;

}

host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

}

跟measure类似,同样是从mView(最顶层ViewGroup)开始进行layout操作,随后逐层遍历。layout(l,t,r,b)四个参数分别对应左上右下的位置,从而确定View在ViewGroup中的位置。下面来看一下layout()部分源码:

public void layout(int l, int t, int r, int b) {

//通过setOpticalFrame()和setFrame()老确定四个点的位置

boolean changed = isLayoutModeOptical(mParent) ?

setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

//调用onLayout(),ViewGroup须重写此方法

onLayout(changed, l, t, r, b);

}

结合源码可知layout()会将四个位置参数传递给setOpticalFrame()或者setFrame(),而setOpticalFrame()内部会调用setFrame(),所以最终通过setFrame()确定ViewViewGroup中的位置。位置确定完毕会调用onLayout(l,t,r,b)对子View进行摆放。

onLayout()

ViewViewGroup在执行完setFrame()后都会调用onLayout()方法,但上面也有提到该方法的作用是对子View进行位置摆放,所以单一View是不需要重写此方法。而ViewGroup会根据自己的特性任意对子View进行摆放。

2.3 Draw

相信很多学习自定义View的同学都是奔着有朝一日自己也实现那些眼花缭乱的效果,起码我自己就是。我们在手机上看到的那些五彩缤纷的图片,动画都是在这个方法内绘制而成。

学习分享

在当下这个信息共享的时代,很多资源都可以在网络上找到,只取决于你愿不愿意找或是找的方法对不对了

很多朋友不是没有资料,大多都是有几十上百个G,但是杂乱无章,不知道怎么看从哪看起,甚至是看后就忘

如果大家觉得自己在网上找的资料非常杂乱、不成体系的话,我也分享一套给大家,比较系统,我平常自己也会经常研读。

2021最新上万页的大厂面试真题

七大模块学习资料:如NDK模块开发、Android框架体系架构…

只有系统,有方向的学习,才能在段时间内迅速提高自己的技术。

这份体系学习笔记,适应人群:
第一,学习知识比较碎片化,没有合理的学习路线与进阶方向。
第二,开发几年,不知道如何进阶更进一步,比较迷茫。
第三,到了合适的年纪,后续不知道该如何发展,转型管理,还是加强技术研究。如果你有需要,我这里恰好有为什么,不来领取!说不定能改变你现在的状态呢!
由于文章内容比较多,篇幅不允许,部分未展示内容以截图方式展示 。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注Android)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

,才能在段时间内迅速提高自己的技术。

这份体系学习笔记,适应人群:
第一,学习知识比较碎片化,没有合理的学习路线与进阶方向。
第二,开发几年,不知道如何进阶更进一步,比较迷茫。
第三,到了合适的年纪,后续不知道该如何发展,转型管理,还是加强技术研究。如果你有需要,我这里恰好有为什么,不来领取!说不定能改变你现在的状态呢!
由于文章内容比较多,篇幅不允许,部分未展示内容以截图方式展示 。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注Android)
[外链图片转存中…(img-ibHBGhrj-1713226005413)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 28
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值