View的绘制
有过自定义View经验的同学都清楚,自定义View要处理好两件事:View的绘制和事件的处理。这一篇简单总结了一下自己对View绘制的理解,关于事件处理请戳这里Android事件分发。
View和ViewGroup的几个主要方法如下图
图1-1.jpg1.每个View都只有一个父View
2.整个视图只有一个根View即DocorView DocorView类继承了Freamlayout定义在phoneWindow.java里
3.我们通过setContentView设置的View并不是最跟View。
图1-2.png3.1 隐藏标题栏需要在setContentView之前设置。
4.一个View如何显示到屏幕上来
4.1view有多大 -- measure --- onMeasure
4.2view的位置在哪儿 -- layout --- onLayout
4.3view长什么样子 -- draw --- onDraw
5.不是所有的View都能添加子View
5.1 就是ViewGroup的子类,因为只有ViewGroup实现了ViewParent接口
5.2 我们通过getParent拿到的虽然是ViewParent,但是在它不为null的前提下,我们可以放心的强转为ViewGroup
其中measure和layout方法都是final的,无法重写,虽然draw不是final的,但是也不建议重写该方法。这三个方法都已经写好了View的逻辑,如果我们想实现自身的逻辑,而又不破坏View的工作流程,可以重写onMeasure、onLayout、onDraw方法。
6.measure只有View有这个方法,ViewGroup没有重载测量自身的大小
MeasureSpec:用来辅助计算View的大小
View.MeasureSpec.AT_MOST;// 最大
View.MeasureSpec.EXACTLY;// 精确
View.MeasureSpec.UNSPECIFIED;// 未知
目前未知没有使用,因为每一个View被添加到视图上时,都必须制定宽高,就算你不指定,也会有一个默认的(每个ViewGroup生成默认宽高都不一样)
通过 MeasureSpec.makeMeasureSpec() 来合成模式和宽高
通过 MeasureSpec.getMode和MeasureSpec.getSize 来获取模式和宽高
代码中设置View的宽和高必须要通过LayoutParams来设置,所有影响子View和父View之间的关系的属性全部需要通过LayoutParams来设置,也可以根据xml布局中属性名是否带有layout_开头来判断。
LayoutParams有很多种类,基本上是每一种布局都有一个自己的实现,因为每个布局都有各自的特征,没办法用一个统一类来描述
如果父亲的mode是 EXACTLY, 子View是WRAP_CONTENT 那么mode 是AT_MOST, size 是父亲的最大size。
如果父亲的mode是 EXACTLY, 子View是MATH_PARENT 那么mode 是EXACTLY, size 是父亲的最大size。
如果父亲的mode是 EXACTLY, 子View是DIP 那么mode 是EXACTLY, size 是DIP。
如果父亲的mode是 AT_MOST, 子View是WRAP_CONTENT 那么mode 是AT_MOST, size 是父亲的最大size。
如果父亲的mode是 AT_MOST, 子View是MATH_PARENT 那么mode 是EXACTLY, size 是父亲的最大size。 // ---(大多数View实现都是EXACTLY,但是有些View实现是AT_MOST,我们要尽量避免这种情况)
如果父亲的mode是 AT_MOST, 子View是DIP 那么mode 是EXACTLY, size 是DIP。
如果你设定宽高为MATH_PARENT 或者 DIP 则模式为 EXACTLY,如果是WRAP_CONTENT,则为AT_MOST
如果你设定的宽高为DIP,那么你的size就是dip,其他情况都是父View的最大值,或者父View剩余的最大值
7.通过调用requestLayout/requestFocus都将发起一个View树的测量。测量完毕后会进行布局,布局完毕后就会绘制。
如果View的大小没有发生改变,布局也没有变化,只是显示的内容发生了变化,则可以通过invalidate来请求绘制,此时将不会测量和布局,直接从绘制开始。
8.View内部的mPrivateFlags变量
View中有一个私有int变量mPrivateFlags,用于保存View的状态,int型32位,通过0/1可以保存32个状态的true或者false,采用这种方式可以有效的减少内存占用,提高运算效率。
当某一个View发起了测量请求时,将会把mPrivateFlags中的某一位从0变为1,同时请求父View,父View也会把自身的该值从0变为1,同时也将会把其他子View的值从0变为1。这样一层一层传递,最终传到到DecorView,DecorView的父View是ViewRoot,所以最终都将由ViewRoot来进行处理。
ViewRoot收到请求后,将会从上至下开始遍历,检查标记,只要有相对应的标记就执行测量/布局/绘制
9.Measure测量流程
图1-3.jpg10.LayoutParams
- 每个View都包含一个ViewGroup.LayoutParams类或者其派生类,LayoutParams中包含了View和它的父View之间的关系,而View大小正是View和它的父View共同决定的。
- 我们设置View的大小,有match_parent、wrap_content和具体的dip值。
- match_parent对应值为-1、wrap_conten对应值为-2,具体dip对应其设定的值。在测量时,View的父类从Layout中读出宽高值,根据不同的值设置不同的计算模式。
- 布局文件中所有layout_开头的在代码中都是需要通过LayoutParams来设置。
- 当我们通过addView添加一个子View时,如果它没有LayoutParams或者是LayoutParams的类型不匹配,那么将会创建一个默认的LayoutParams。
MeasureSpec - MeasureSpe描述了父View对子View大小的期望。里面包含了测量模式和大小。
- MeasureSpe类把测量模式和大小组合到一个int型的数值中,其中高2位表示模式,低30位表示大小。
- 我们可以通过以下方式从MeasureSpec中提取模式和大小,该方法内部是采用位移计算。
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec); - 也可以通过MeasureSpec的静态方法把大小和模式合成,该方法内部只是简单的相加。
MeasureSpec.makeMeasureSpec(specSize,specMode);
参考博客:
安卓中的颜色相关内容,包括颜色的定义,创建颜色的几种方式,以及颜色的混合模式等
自定义View分类与流程
Canvas之画布操作
Canvas之图片文字