ViewRoot DecorView
ViewRoot是ViewRootImpl类 用于连接WindowManager DecorView
三大流程都是通过ViewRoot来完成的。ActivityThread中Activity对象创建完毕以后会把DecorView添加到Window中同时创建ViewRootImpl对象 并把ViewRootImpl和DecorView相关联
View 绘制 从ViewRoot的performTraversal开始 经过measure(测量) layout (位置)draw(绘制)绘制出一个view
View树的概念:一次onMeasure会调用到所有的子元素 以此循环 类似的 从最顶级的节点循环到所有的view中 另外两个方法是类似的。当然只有最后draw方法完成了view的内容才会呈现出来
DecorView是FramLayout的子类(可以从源码中看到)。DecorView作为顶级View一般情况下包括一个竖直方向的线性布局 上方是标题栏 下方是内容栏。通过基础设置的布局就是在内容栏里的。获取方法
ViewGroup viewGroup = findViewById(android.R.id.content);
设置View
viewGroup.getChildAt(0);
MeasureSpec
MeasureSpec很大程度上决定了一个View的尺寸规格,受父容器影响。测量过程中系统把View的LayoutParams根据父容器的绘制规则转换成对应的MeasureSpec,再根据这个MeasureSpec来测量view长宽高(不一定等于view最终样式????)
MeasureSpec代表一个32位的int,高2位代表specMode测量模式 低30位代表SpecSize规格大小
工作流程
measure
ViewGroup没有定义具体测量过程,具体方法需要子类自己实现。因为不同ViewGroup子类具有不同布局特性导致测量细节不一样
所有的view测量完成后通过getMeasuredWidth/Height就可以正确的得到View的测量数值。但是一个比较好的习惯是在onLayout方法中再去获得这些数值。
onCreate Start Resume 不能得到View准确信息 因为measure和Activity周期不是同步执行的 无法保证执行了方法时某个View已经测量完毕 (此时获得的长度就是0了)
准确获得长度方法
①onWindowFocusChanged
View已经初始化完毕 长度已经准备好了 得到 失去焦点的时候都会被调用
②view.post(runnable)
等待Looper调用此Runnable时View已经初始化好了
③ViewTreeObserver
使用ViewTreeObserver的众多回调可以完成这个功能 当View树状态发生改变 方法回调
protected void onStart() {
super.onStart();
ViewTreeObserver observer = this.getCurrentFocus().getViewTreeObserver();
observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
getCurrentFocus().getViewTreeObserver().removeOnGlobalLayoutListener(this);
int x = getCurrentFocus().getMeasuredWidth();
int y = getCurrentFocus().getMeasuredHeight();
}
});
}
④view.measure()
手动测量 太麻烦了 不写了
Layout过程
确定子元素位置 。ViewGroup位置确定之后会再onLayout中遍历所有子元素并且调用layout方法 在layout方法中onLayout方法又被调用 遍历tree
流程 :setFrame方法初始化四个顶点 (确定子View在父容器中位置)onLayout 方法确定子元素位置 这个方法具体实现和具体布局有关 类似于onMeasure
View默认实现中测量长度和最终长度是相等的,不过测量长度形成于measure 最终长度形成于layout 。日常开发中可以认为这两者相等
Draw过程
把View绘制到屏幕上
绘制背景 canvas
绘制自己 ondraw
绘制children dispatchDraw
(事件一层层传递)
绘制装饰 onDrawScrollBars
一个view不需要绘制任何内容 setWillNotDraw标记位会被设置 用于优化 默认ViewGroup启用 View不启用
自定义View
①自定义view分类
继承View重写onDraw
实现一些不规则效果
需要自己支持wrap_content padding 也要自己支持
实例代码:
public class MyView extends View {
public MyView(Context context) {
super(context);
paint.setColor(Color.RED);
}
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
final int paddingLeft = getPaddingLeft();
final int paddingRight = getPaddingRight();
final int paddiingTop = getPaddingTop();
final int paddingBottom = getPaddingBottom();
int width = getWidth()-paddingLeft-paddingRight;
int height = getHeight() - paddiingTop - paddingBottom;
int radius = Math.min(width,height)/2 ;
canvas.drawCircle(paddingLeft+width/2,paddiingTop+height/2,radius,paint);
}
}
如上所示 padding没有生效 wrap_content的效果也会是和matchparents一样
在xml中自定义属性
步骤1:创建xml文件
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MyView"><--自定义属性集合 可以有很多属性-->
<attr name="circle_color" format="color"/>
</declare-styleable>
</resources>
步骤2:
在View构造方法中解析自定义属性的数值并且做好相应处理
public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray array = context.obtainStyledAttributes(attrs,R.styleable.MyView);
mColor = array.getColor(R.styleable.MyView_circle_color,Color.RED);
array.recycle();
paint.setColor(mColor);
}
步骤3:
对应的布局文件中就可以使用这个属性了
注意事项:
自定义属性前缀
xmlns:app="http://schemas.android.com/apk/res-auto"
继承ViewGroup派生的特殊Layout
实现自定义布局
继承特定View
扩展某种已有View的功能
继承特定ViewGroup
方法2更接近底层
②自定义View事项
让view支持wrap_content
不做特殊处理就无法达到预期效果
如果有必要 让view支持padding
直接继承View控件 不在draw方法中处理padding 那么padding将无法起作用
继承ViewGroup控件需要在onMeasure和onLayout中考虑padding和子元素margin造成的影响
尽量不在View中使用handler
内部提供了post系列方法
有线程或者动画 需要停止 使用 onDetachedFromWindow
有这种需求时 onDetachedFromWindow是一个很好的时机 包含此View的Activity退出或者当前View被remove时 方法调用。View不可见的时候应该停止线程和动画 否则导致内存泄漏
处理好嵌套滑动冲突