Open zhjlong opened 5 years ago
掌握自定义 View
需要掌握三个流程,绘制
,布局
和触摸反馈
,由于时间关系,今天我先讲解一下布局过程
用代码的方式计算所有控件的位置和尺寸,这个过程就是布局过程,具体来说计算出每一个View
的尺寸和相对父View
的相对位置.
View
的位置与尺寸xml
无法实现我们想要的效果View
递归调用到每一级子View
的 measure()
方法,对它们进行测量View
递归调用到每一个级子View
的 layout()
方法xml
文件里写入对 View
的布局要求 layout_xxx
View
在自己的 onMeasure()
中,根据开发者在 xml
中写的对子 View
的要求,和自己可用空间,得出对子View
具体尺寸的要求View
在自己的 onMeasure()
中,根据自己的特性计算出自己的期望尺寸.
ViewGroup
,还会在这里调用每个子View
的onMeasure()
进行测量View
在子View
计算出期望尺寸后,得出子View
的实际尺寸和位置保存.View
在自己的 layout()
方法中,将父 View
传进来自己的实际尺寸和位置保存.
ViewGroup
,还会在layout()
方法里调用每个字 View
的 layout()
把他们尺寸位置传给他们.View
,简单改写他们的尺寸: 重写onMeasure
getMeasureWidth()
和 getMeasureHeight()
获取测量出的尺寸setMeasuredDimension(width,height)
把结果保存onMeasure()
solveSize()
或者solveSizeAndSate()
修正结果setMeasuredDimension(width,height)
保存结果onMeasure()
View
,用measureChildWithMargins
测量子 View
View
可能需要重新测量View
的实际位置和尺寸,并暂时保存View
的位置和尺寸后,计算出自己的尺寸,并用 setMesuredDimension(width,height)
保存onLayout()
View
,调用它们的layout()
将位置和尺寸传给它们. 听我扯这么多其实对你一点帮助也没有,而且还很累,其实真正掌握自定义View
还不如参考朱凯老师
的文章,而且我建议是细读,基础好点的至少也得花个两三周研究这个专题,看懂和会写是两回事,所以不要看这些没用的,也不要记忆任何概念,网上的博客也不一定靠谱,要彻底忘记我写的东西,如果你靠自己能实现一个仪表盘或者折线图什么的,比看我这东西强一百倍
自定义View分为三个流程:测量、布局和绘制。
一个完整的绘制过程会依次绘制以下几个内容:
draw()是总调度方法。一个 View 的整个绘制过程都发生在 draw() 方法里。
/ View.java 的 draw() 方法的简化版大致结构(是大致结构,不是源码哦):
public void draw(Canvas canvas) {
...
drawBackground(Canvas); // 绘制背景(不能重写)
onDraw(Canvas); // 绘制主体
dispatchDraw(Canvas); // 绘制子 View
onDrawForeground(Canvas); // 绘制滑动相关和前景
...
}
绘制过程分为三部分:Measure - Layout - Draw 一、Measure 过程 1、测量过程由上至下,在measure过程的最后,每个视图将存储自己的尺寸大小和测量规格。 2、measure过程会为一个View及其所有子节点的mMeasureWidth和mMeasuredHeight变量赋值, 该值可以通过getMeasuredWidth和getMeasuredHeight方法获得。 3、measure过程的核心方法: measure() - onMeasure() - setMeasuredDimension(). setMeasuredDimension 是测量阶段的终极方法,在onMeasure()方法中调用,将计算得到的尺寸,传递给该方法,测量阶段结束。在自定义视图时,不需要关心系统复杂的Measure过程,只需要调用setMeasuredDimension()设置根据MeasureSpec计算得到的尺寸即可。同时, onMeasure()方法也必须调用setMeasuredDimension()方法来设置重新测量之后的。 二、Layout 过程 1、子视图的具体位置都是相对于父视图而言的。View的onLayout()方法为空实现,而ViewGroup的onLayout为abstract,因此,自定义的View要继承ViewGroup时,必须实现onLayout函数。 2、在Layout过程中,子视图会调用getMeasuredWidth()和getMeasuredHeight()方法获取到measure过程得到mMeasuredWidth和mMeasuredHeight,作为自己的width和height。然后调用每一个子视图的layout(),来确定每个子视图在父视图中的位置。 三、Draw 过程 1、所有视图最终都是调用View的draw方法进行绘制。 在自定义视图中, 也不应该复写该方法, 而是复写onDraw()方法进行绘制, 如果自定义的视图确实要复写该方法,先调用super.draw()完成系统的绘制,再进行自定义的绘制。 2、onDraw()方法默认是空实现,自定义绘制过程需要复写方法,绘制自身的内容。 3、dispatchDraw()发起对子视图的绘制,在View中默认为空实现,ViewGroup复写了dispatchDraw()来对其子视图进行绘制。自定义的ViewGroup不应该对dispatchDraw()进行复写。
自定义控件,大致需要测量,布局,绘制这三大步。
测量:从父控件到子控件递归的调用measure方法测量自己的大小,先从最下层的子控件计算,然后往上最后算出最上层的控件的大小。
布局:父控件一次按照设定的规则,通过子控件的layout方法给子控件排座位的和座位的大小
决定子控件大小除了在测量步骤,还有布局的layout方法。
绘制:对应的方法是onDraw()方法。
onDraw 的是先绘制背景,然后绘制自身的内容,然后调用dispatchDraw方法绘制子控件,然后绘制前景。
不过一个完整的控件在完成的过程中往往伴随着动画,触摸事件,滑动等等的操作,这些操作通常也会融入在上边的三个大的步骤里边。 自定义控件需要我们多动手练,多研究别人的代码。这些理论性的东西只有真正使用到的时候才能印象深刻。
关于View的测量,布局,绘制上面很多同学都讲的差不多了,我谈谈自定View的使用场景及类别吧;
组合View 对于UI比较复杂的页面的优化,我们拥有很多手段,从拆上面来讲,最少我们可以拆成多个fragment,也可以拆成多个大View,记得凯哥讲过,MVC与MVP从概念上来讲并没有太大的区别,实际使用上MVP是分离的更好的MVC,由于MVC本身的C和V高度耦合,所以我们可以考虑把C拆出来变成presenter,也可以考虑把View通过组合的方式拆出去变成一个大View;
修改View 有些时候我们想用android自带的view但是却有一些痛点难以接受,这个时候我们可以通过小修小改来解决,比如固定长度的ListView,正方形的ImageView等等;
自定义View 这个就是上面很多同学讲到的一整个Measure - Layout - Draw流程了,以凯哥课上讲的TabLayout为例我们不难发现,作为一个ViewGroup出现的时候,往往需要在Measure - Layout这两步下大功夫,而作为View出现的时候可能就要转而在Draw上做更多的事情;当然事无绝对,比如处理滑动冲突的时候可能子父View都需要额外做一下其它的事情,比如专注于事件处理的View,可能不需要绘制,但是需要在onTouchEvent里面做更多的事情。
如上图所示:performMeasure将会调用ViewGroup的measure方法,measure方法又将调用onMeasure方法。而在onMeasure方法中,将遍历调用子View的measure方法。这样measure流程就从父控件传递到了子元素。然后子元素要重复父控件的measure流程,直到遍历完整个View树,这样就完成测量的流程。performLayout和performDraw的传递流程和perfomMeasure也是类似的。
在测量过程中,不得不提到MeasureSpec这个概念,父View可通过MeasureSpec将一些测量大小限制施加到子View上,从而达到父View控制子View测量大小的目的
DecorView的MeasureSpec由其自身LayoutParams和Window尺寸确定的.而非顶层View则和parentView的MeasureSpec和自身LayoutParams共同确定.
MeasureSpec有三种测量模式
关于View绘制的流程如下: 1.ViewRoot: ViewRoot连接windowManager和DecorView的纽带,view的三大绘制流程都是通过viewRoot来完成的 2.viewRoot的performTraversals: View的绘制流程是从ViewRoot的performTraversals开始的,依次调用performMeasure , performLayout和performDraw三个方法,分别完成View的测量,布局和绘制三大流程 3.measure: measure完成对自身的测量,接着调用onMeasure方法,对所有的子元素进行测量。这时候measure的流程就从父容器传到子容器中,完成依次测量。接着子元素重复父容器的measure过程,一层一层直到完成整个View树的测量 4.layout: layout方法用于确定自身在父容器的位置,它先判断视图是否发生变化,再调用setFrame确定四个点,这样就确定了View本身的位置。接着调用onLayout,确定所有子元素的位置。这样layout流程就从父容器传到子容器,完成依次layout。 接着子元素重复父容器的layout过程,一层一层直到完成整个View树的layout。 5.draw: 绘制背景 > 绘制自身内容 > 绘制子元素 > 绘制装饰