任何一个视图都是要通过很是科学的绘制流程后才能显示出来的,每个视图的绘制过程其实就是一个完整的生命周期,咱们从这里开始入手,一块儿学习自定义View。android
一.准备工做canvas
布局文件:ide
<org.daliang.xiaohehe.androidartstudy.MyView android:id="@+id/my_view" android:layout_width="match_parent" android:layout_height="match_parent"> </org.daliang.xiaohehe.androidartstudy.MyView>
Activity代码:函数
public class FiveActivity extends AppCompatActivity { private MyView myView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.e("log", "Activity生命周期:onCreate"); setContentView(R.layout.activity_five); initView(); } private void initView() { myView = (MyView) findViewById(R.id.my_view); } @Override protected void onStart() { super.onStart(); Log.e("log", "Activity生命周期:onStart"); } @Override protected void onResume() { super.onResume(); Log.e("log", "Activity生命周期:onResume"); } @Override protected void onRestart() { super.onRestart(); Log.e("log", "Activity生命周期:onRestart"); } @Override protected void onPause() { super.onPause(); Log.e("log", "Activity生命周期:onPause"); } @Override protected void onStop() { super.onStop(); Log.e("log", "Activity生命周期:onStop"); } @Override protected void onDestroy() { super.onDestroy(); Log.e("log", "Activity生命周期:onDestroy"); } }
自定义View代码:工具
public class MyView extends View { public MyView(Context context, AttributeSet attrs) { super(context, attrs); Log.e("log", "onCreate"); } @Override protected void onFinishInflate() { super.onFinishInflate(); Log.e("log", "onFinishInflate"); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); Log.e("log", "onAttachedToWindow"); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); Log.e("log", "onMeasure"); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); Log.e("log", "onSizeChanged"); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); Log.e("log", "onLayout"); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); Log.e("log", "onDraw"); } @Override public void onWindowFocusChanged(boolean hasWindowFocus) { super.onWindowFocusChanged(hasWindowFocus); Log.e("log", "onWindowFocusChanged" + " " + hasWindowFocus); } }
很简单的代码,整个布局文件里面一个自定义View,进入这个界面,而后返回。依次打印各个生命周期,咱们看一下打印的结果:布局
二.结果分析学习
1.Activity生命周期:onCreatespa
Activity生命周期的第一个方法,表示此Activity正在被建立,这里咱们主要进行一些初始化的工做。好比调用setContentView()方法去加载界面布局资源。.net
2.View生命周期:onCreaterest
当Activity在onCreate加载界面布局资源的时候,咱们自定义的View会在xml文件中被加载,而且调用构造函数 MyView(Context context, AttributeSet attrs)来加载自定义View。
3.View生命周期:onFinishInflate
源码是这样告诉咱们的:
本身蹩脚的英语水平加上网上的一些解释,以为这样理解仍是比较靠谱的:
onFinishInflate是在自定义View中全部的子控件均被映射成xml后调用。这样View就完成了初始准备工做,Activity也完成了对应布局资源的加载。
4.View生命周期:onAttachedToWindow
再来瞅一眼源码:
onAttachedToWindow是在将view绑定到activity所在window时调用,附加到window后,程序开始进行自定义View的绘制。
5.View生命周期:onMeasure
参考资料:
Android视图绘制流程彻底解析,带你一步步深刻了解View(二)
做为自定义View三部曲的第一步,onMeasure方法有着极其重要的做用,因此深刻理解其中原理对咱们之后自定义View开发有着很大的帮助。
MeasureSpec测量规范:
系统显示一个View,首先须要经过测量(measure)该View来获取其长和宽从而肯定显示该View时须要多大的空间。在测量的过程当中MeasureSpec贯穿全程,发挥着不可或缺的做用,先瞅一眼源码:
从上面这张图片中咱们能够获取到许多重要的信息:
1.MeasureSpec封装了父布局传递给子View的布局要求
2. MeasureSpec能够表示宽和高
3 MeasureSpec由size(大小)和mode(模式)组成
MeasureSpec是一个32位的int数据,其中高2位表明SpecMode即某种测量模式,低30位为SpecSize表明在该模式下的规格大小.
能够经过以下方式分别获取这两个值:
获取SpecSize
int specSize = MeasureSpec.getSize(measureSpec)
获取specMode
int specMode = MeasureSpec.getMode(measureSpec)
经过这两个值生成新的MeasureSpec
int measureSpec=MeasureSpec.makeMeasureSpec(size, mode);
SpecMode一共有三种:
MeasureSpec.UNSPECIFIED,MeasureSpec.EXACTLY , MeasureSpec.AT_MOST , 咱们依次看看每种测量模式表明什么意思:
MeasureSpec.UNSPECIFIED:父视图不对子视图施加任何限制,子视图能够获得任意想要的大小,表示子布局想要多大就多大。这种模式通常用做Android系统内部,或者ListView和ScrollView等滑动控件,不多使用。
MeasureSpec.EXACTLY :父视图但愿子视图的大小是specSize中指定的大小,在该模式下,View的测量大小即为SpecSize。
MeasureSpec.AT_MOST:父容器未能检测出子View所须要的精确大小,可是指定了一个可用大小即specSize ,在该模式下,View的测量大小不能超过SpecSize
可用表格来规整各一下MeasureSpec的生成:
子View的MeasureSpec由其父容器的MeasureSpec和该子View自己的布局参数LayoutParams共同决定。
当设置View的参数等于MATCH_PARENT的时候,MeasureSpec的specMode就等于EXACTLY,当设置View的参数等于WRAP_CONTENT的时候,MeasureSpec的specMode就等于AT_MOST。而且MATCH_PARENT和WRAP_CONTENT时的specSize都是等于windowSize的,也就意味着根视图老是会充满全屏的。因此自定义View在重写onMeasure()的过程当中应该手动处理View的宽或高为wrap_content的状况。
通过测量得出的子View的MeasureSpec是系统给出的一个指望值(参考值),咱们也可摒弃系统的这个测量流程,直接调用setMeasuredDimension( )设置子View的宽和高的测量值。
须要注意的是,在setMeasuredDimension()方法调用以后,咱们才能使用getMeasuredWidth()和getMeasuredHeight()来获取视图测量出的准确的宽度与高度。
因而可知,视图大小的控制是由父视图、布局文件、以及视图自己共同完成的,父视图会提供给子视图参考的大小,开发人员能够在XML文件中指定视图的大小,最后视图自己才会肯定最终的大小。
6.View生命周期:onSizeChanged
继续看看源码是怎么解释的:
onSizeChanged是在布局文件中自定义View的大小发生改变时被调用。四个参数依次表明变化以后的宽高以及变化以前的宽高。在onMeasure方法结束以后第一次进行调用,将测量的宽高做为前两个参数,0做为后两个默认参数。
7.View生命周期:onLayout
测量过程结束后,视图的大小就已经测量好了,接下来就是layout的过程了。正如其名字所描述的同样,这个方法是用于给视图进行布局的,也就是肯定视图的具体位置。
看看源码怎么解释的:
onLayout是在layout方法中指定子View的大小和位置。其实就是ViewGroup会调用onLayout()决定子View的显示位置。其中四个参数l, t, r, b分别表示子View相对于父View的左、上、右、下的坐标。
getMeasureWidth()方法在measure()过程结束后就能够获取到了,而getWidth()方法要在layout()过程结束后才能获取到。在某些复杂或者极端的状况下系统会屡次执行measure过程,因此在onMeasure()中去获取View的测量大小获得的是一个不许确的值。为了不该状况,最好在onMeasure()的下一阶段即onLayout()中去获取View的宽高。
8.View生命周期:onDraw
看看源码的解释:
onDraw是真正地开始对视图进行绘制。
在Andorid官方文档中将该过程概况成了六步:
第一步:绘制背景:Draw the background
第二步:保存当前画布的堆栈状态并在该画布上建立Layer用于绘制View在滑动时的边框渐变效果,一般状况下咱们不须要处理这一步:If necessary, save the canvas’ layers to prepare for fading。
第三步:绘制View的内容:Draw view’s content。这一步是整个draw阶段的核心,在此会调用onDraw()方法绘制View的内容。 以前咱们在分析layout的时候发现onLayout()方法是一个抽象方法,具体的逻辑由ViewGroup的子类去实现。与之相似,在此onDraw()是一个空方法;由于每一个View所要绘制的内容不一样,因此须要由具体的子View去实现各自不一样的需求。
第四步:调用dispatchDraw()绘制View的子View:Draw children
第五步:绘制当前视图在滑动时的边框渐变效果,一般咱们是不须要处理这一步的:If necessary, draw the fading edges and restore layers
第六步:绘制View的滚动条:Draw decorations (scrollbars for instance)
在绘图时须要明确四个核心的东西(basic components):
用什么工具画?
这个小问题很简单,咱们须要用一支画笔(Paint)来绘图。
固然,咱们能够选择不一样颜色的笔,不一样大小的笔。
把图画在哪里呢?
咱们把图画在了Bitmap上,它保存了所绘图像的各个像素(pixel)。
也就是说Bitmap承载和呈现了画的各类图形。
画的内容?
根据本身的需求画圆,画直线,画路径。
怎么画?
调用canvas执行绘图操做。
好比,canvas.drawCircle(),canvas.drawLine(),canvas.drawPath()将咱们须要的图像画出来。
关于绘制View的内容,这里就很少作讨论,之后实战中根据实际状况来进行详细的分析,继续下一个生命周期。
9.View生命周期:onWindowFocusChanged
瞅一眼源码:
判断view是否获取焦点,参数hasWindowFocus 对应返回true 和false 能够用来判断view是否进出后台。第一次进入当前Activity的时候,返回true,返回上一个Activity,返回了false。