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
Measure
是View
绘制三个过程中的第一步,提到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中进行宽高的测量,这个方法会从父布局中接收两个参数
widthMeasureSpac
和heightMeasureSpac
,所以子布局的宽高大小需要受限于父布局。
在自定义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)
}
}
从ViewGroup
到View
对尺寸和模式进行了一次封装和拆解,其目的是为了减少对象的创建,避免造成不必要的内存浪费。
LayoutParams
在刚接触Android的时候经常有一个疑问,为什么View设置自己的宽高,还要创建一个xxx.LayoutParams
?前面也提到了,子View的宽高是要受限于父布局的,所以不能通过setWidth
或者setHeight
直接设置宽高的,另外 LayoutParams
的作用不仅如此,比如一个View的父布局是RelativeLayout
,可以通过设置RelativeLayout.LayoutParams
的above
,below
等属性来调整在父布局中的位置。
自定义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;
}
从代码分析可知,获取mode
和size
后会分别对三种测量模式进行判断,UNSPECIFIED
使用默认尺寸,而AT_MOST
和EXACTLY
使用父布局给出的测量尺寸。尺寸计算完毕后通过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()
确定View
在ViewGroup
中的位置。位置确定完毕会调用onLayout(l,t,r,b)
对子View进行摆放。
onLayout()
View
和ViewGroup
在执行完setFrame()
后都会调用onLayout()
方法,但上面也有提到该方法的作用是对子View进行位置摆放,所以单一View是不需要重写此方法。而ViewGroup
会根据自己的特性任意对子View进行摆放。
2.3 Draw
相信很多学习自定义View的同学都是奔着有朝一日自己也实现那些眼花缭乱的效果,起码我自己就是。我们在手机上看到的那些五彩缤纷的图片,动画都是在这个方法内绘制而成。
相比于measure和layout阶段,draw阶段中View和ViewGroup变得没那么紧密了,View的绘制过程中不需要考虑ViewGroup,而ViewGroup也只需触发子View的绘制方法即可。
performDraw()
执行后同样会从根布局开始逐层对每个View进行draw操作,在View中绘制操作时通过draw()
进行,来看一下其主要源码:
public void draw(Canvas canvas) {
…
// 绘制背景
drawBackground(canvas);
// 绘制内容
onDraw(canvas);
// 绘制子View
dispatchDraw(canvas);
// 绘制装饰,如scrollBar
onDrawForeground(canvas)
…
}
draw()方法中主要包含四部分内容,其中我们开发者只需要关心onDraw(canvas)即可,即自身的内容绘制。
绘制内容简述
关于绘制内容这部分可利用到的知识点很多,多到可以写一本书出来,所以仅靠本文全部详细描述显然是不现实的。下面我罗列一部分常用内容供大家参考:
- Canvas:画布,不管是文字,图形,图片都要通过画布绘制而成
- Paint:画笔,可设置颜色,粗细,大小,阴影等等等等,一般配合画布使用
- Path:路径,用于形成一些不规则图形。
- Matrix:矩阵,可实现对画布的几何变换。
总结
==
文章从四个方面总结了View的绘制流程:绘制时机
,宽高测量
,位置摆放
,图像绘制
,因为侧重于流程所以只是把这四部分的精华给拎出来分享给大家,起到一个抛砖引玉的作用,想要透彻理解启动流程、玩转自定义View还需要对各部分知识系统的学习。
如何系统学习Android呢?
这里今天给大家分享一份Android进阶学习资料,主要为安卓相关知识点及面试资料为主,在这个PDF中,通过详解各大互联网公司的 Android 常见面试题为主线,从面试的角度带你介绍必备知识点,以及该知识点在项目中的实际应用。
帮你在现在的基础上,重新梳理和建立 Android 开发的知识体系。无论是你短期内想提升 Android 内功实力,突破自己工作中的能力瓶颈,还是准备参加 Android 面试,都会在这个PDF中有所收获。一些基础不好的,这里也有一份安卓基础资料包,帮助巩固基础。
以下是这份PDF主要内容:
-
Android 核心技术:介绍 Android 开发中常用的核心技术,比如自定义 View、Handler,以及一些开源框架的原理实现,来夯实你的底层能力。只有底层能力足够出色,之后的进阶之路才会更加轻松。
-
常见问题剖析:介绍一些项目中常见的疑难问题,使你能够对现有项目做出合理的重构优化。
1、确定好方向,梳理成长路线图
不用多说,相信大家都有一个共识:无论什么行业,最牛逼的人肯定是站在金字塔端的人。所以,想做一个牛逼的程序员,那么就要让自己站的更高,成为技术大牛并不是一朝一夕的事情,需要时间的沉淀和技术的积累。
关于这一点,在我当时确立好Android方向时,就已经开始梳理自己的成长路线了,包括技术要怎么系统地去学习,都列得非常详细。
知识梳理完之后,就需要进行查漏补缺,所以针对这些知识点,我手头上也准备了不少的电子书和笔记,这些笔记将各个知识点进行了完美的总结。
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
最后
那我们该怎么做才能做到年薪60万+呢,对于程序员来说,只有不断学习,不断提升自己的实力。我之前有篇文章提到过,感兴趣的可以看看,到底要学习哪些知识才能达到年薪60万+。
通过职友集数据可以查看,以北京 Android 相关岗位为例,其中 【20k-30k】 薪酬的 Android 工程师,占到了整体从业者的 30.8%!
北京 Android 工程师「工资收入水平 」
今天重点内容是怎么去学,怎么提高自己的技术。
1.合理安排时间
2.找对好的系统的学习资料
3.有老师带,可以随时解决问题
4.有明确的学习路线
当然图中有什么需要补充的或者是需要改善的,可以在评论区写下来,一起交流学习。
一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!
AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算
图中有什么需要补充的或者是需要改善的,可以在评论区写下来,一起交流学习。
[外链图片转存中…(img-BiVp7UZl-1712149237327)]
一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!
AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算