《Android开发艺术探索》读书笔记 (4) 第4章 View的工作原理

   

《Android开发艺术探索》读书笔记 (4) 第4章 View的工作原理

本节和《Android群英传》中的第3章Android控件架构与自定义控件详解有关系,建议先阅读该章的总结

   除了掌握View的三大流程外,View常见的回调方法也 需要熟练掌握的,比如构造方法,onTttach、onVisibilityChanged、onDetach等,自定义View的类型,有直接继承自View和ViewGroup,有的则选择继承现有的系统控件

4.1 初始ViewRoot和DecorView

(1)ViewRoot对应ViewRootImpl类,它是连接WindowManagerDecorView的纽带,View的三大流程均通过ViewRoot来完成。
(2)ActivityThread中,Activity创建完成后,会将DecorView添加到Window中,同时创建ViewRootImpl对象,并建立两者的关联。

               root = new ViewRootImpl(view.getContext(),display);
		root.setView(view,wparams,panelParentView);

(3)View的绘制流程从ViewRoot的 performTraversals 方法开始,经过 measure layout draw 三大流程。
(4)preformTranversals会依次调用performMeasure、performLayout和preformDraw三个方法,这三个方法分别完成顶级View的measure、layout、和draw这三大流程、 performMeasure 方法中会调用 measure 方法,在 measure 方法中又会调用 onMeasure 方法,在onMeasure方法中会对所有的子元素进行measure过程,这个时候measure流程就从父容器传递到子元素了,这样就完成了一次measure过程,layout和draw的过程类似。 (书中175页画出详细的图示)

  

(5)measure过程决定了view的宽高,在几乎所有的情况下这个宽高都等同于view最终的宽高。layout过程决定了view的四个顶点的坐标和view实际的宽高,通过getWidthgetHeight方法可以得到最终的宽高。draw过程决定了view的显示。
(6)DecorView其实是一个FrameLayout,其中包含了一个竖直方向的LinearLayout,上面是标题栏,下面是内容栏(id为android.R.id.content)

4.2 理解MeasureSpec

  (1)MeasureSpecLayoutParams的对应关系
在view测量的时候,系统会将LayoutParams在父容器的约束下转换成对应的MeasureSpec,然后再根据这个MeasureSpec来确定View测量后的宽高,这里面的宽/高是测量宽/高,不一定等于View的最终宽/高。

   http://blog.csdn.net/lmj623565791/article/details/24252901

   headerView.measure(0, 0); // 系统会帮我们测量出headerView的高度

   headerViewHeight = headerView.getMeasuredHeight();

(2)普通view的MeasureSpec的创建规则 (书中182页列出详细的表格)
当view采用固定宽高时,不管父容器的MeasureSpec是什么,view的MeasureSpec都是精确模式,并且大小是LayoutParams中的大小。
当view的宽高是match_parent时,如果父容器的模式是精确模式,那么view也是精确模式,并且大小是父容器的剩余空间;如果父容器是最大模式,那么view也是最大模式,并且大小是不会超过父容器的剩余空间。
当view的宽高是wrap_content时,不管父容器的模式是精确模式还是最大模式,view的模式总是最大模式,并且大小不超过父容器的剩余空间。

 4.3 view的工作流程

   如果只是一个原始的View,那么通过measure方法就完成了其测量过程,如果是一个ViewGroup,除了完成了自己的测量过程外,还需要遍历去调用所有子元素的measure方法

   1、view测量measure过程

         View的measure过程由其measure方法来完成,measure方法是一个final类型的方法,这意味着子类不能重写此方法,在View的measure方法中会去调用View的onMeasure方法,因此只需要看onMeasure的实现即可,我们得出如下结论:直接继承View的自定义控件需要重写onMeasure方法,并设置wrap_content时的自身大小,否则在布局中使用wrap_content就相当于使用match_parent,如果是使用wrap_content,那么它的specMode是AT_MOST模式,这里面的代码有写wrap_content的时候怎么测量大小http://blog.csdn.net/lmj623565791/article/details/24252901

  2、ViewGroup的measure过程

     Viewgroup在measure时,会对每一个子元素进行measure,measureChile这个方法,需要注意的是,在某些极端情况,系统可能需要多次measure才能确定最终的测量宽/高,在这种情形下,在onMeasure方法中拿到的测量宽/高很可能不准确的,一个比较好的习惯是在onLayout方法中去获取View的测量的宽/高或者最终宽/高。

   如果我们要在activity的onCreate中获取View的宽/高,如果直接获取就可能是0,下面给出四种方法来解决这个问题:

(1)view的measure过程和Activity的生命周期方法不是同步执行的,因此无法保证Activity执行了onCreateonStartonResume时某个view已经测量完毕了。如果view还没有测量完毕,那么获得的宽高就都是0。下面是四种解决该问题的方法:

  1.Activity/View # onWindowFocusChanged方法
onWindowFocusChanged方法表示view已经初始化完毕了,宽高已经准备好了,这个时候去获取宽高是没问题的。这个方法会被调用多次,当Activity继续执行或者暂停执行的时候,这个方法都会被调用,典型的代码如下;

@Override
	public void onWindowFocusChanged(boolean hasWindowFocus) {
		super.onWindowFocusChanged(hasWindowFocus);
		if(hasWindowFocus)
		{
			int width = view.getMeasuredWidth();
			int height = view.getMeasuredHeight();
		}
	}
2. view.post(runnable)
通过post将一个runnable投递到消息队列的尾部,然后等待Looper调用此runnable的时候,view也已经初始化好了

@Override

    protected void onStart() {
    	super.onStart();
    	 view.post(new Runnable() {
			
			@Override
			public void run() {
				int width = view.getMeasuredWidth();
				int height = view.getMeasuredHeight();
			}
		});
    }
3. ViewTreeObserver
使用 ViewTreeObserver 的众多回调方法可以完成这个功能,比如使用 onGlobalLayoutListener 接口,当view树的状态发生改变或者view树内部的view的可见性发生改变时, onGlobalLayout 方法将被回调。 伴随着view树的状态改变,这个方法也会被多次调用。

4.view.measure(int widthMeasureSpec, int heightMeasureSpec)
通过手动对view进行measure来得到view的宽高,这个要根据view的LayoutParams来处理:
match_parent:无法measure出具体的宽高,原因很简单,根据View的measure过程,构造此种MeasureSpec需要知道parentSize,即父容器的剩余空间,而这个时候我们无法知道parentSize的大小,所以理论上不可能测量出View的大小;
wrap_content:如下measure,设置最大值

int widthMeasureSpec = MeasureSpec.makeMeasureSpec((1 << 30) - 1, MeasureSpec.AT_MOST);
int heightMeasureSpec = MeasureSpec.makeMeasureSpec((1 << 30) - 1, MeasureSpec.AT_MOST);
view.measure(widthMeasureSpec, heightMeasureSpec);

精确值:例如100px

int widthMeasureSpec = MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY);
int heightMeasureSpec = MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY);
view.measure(widthMeasureSpec, heightMeasureSpec);
(2) 在view的默认实现中,view的测量宽高和最终宽高是相等的,只不过测量宽高形成于measure过程,而最终宽高形成于layout过程

4.3.2 layout过程

    Layout的作用是ViewGroup用来确定子元素的位置

(3)draw过程大概有下面几步:

     1.绘制背景:background.draw(canvas)

    2.绘制自己:onDraw()

    3.绘制children:dispatchDraw
    4.绘制装饰:onDrawScrollBars

    View绘制过程的传递是通过dispatchDraw来实现的,dispatchDraw会遍历调用所有子元素的draw方法,如此draw事件就一层层地传递下去,这里面有一个setWillNotDraw方法,这个方法的意义是,当我们的自定义控件继承于Viewgroup并且本身不具备绘制功能时,就可以开启这个标记位从而便于系统进行后续的优化,当然,当明确知道一个ViewGroup需要通过onDraw来绘制内容时,我们需要显示地关闭WILL_NOT_DRAW这个标记位。

4.4 自定义view

   4.4.1 自定义view的分类

   1、继承View重写onDraw方法

    这种方法主要用于实现一些不规则的效果,即重写onDraw方法,采用这种方式需要自己支持wrap_content,并且padding也需要自己处理。

   2、继承ViewGroup派生特殊的layout

    这种方法主要用于实现自定义的布局,当某种效果看起来像几种View组合在一起的时候,采用这种方式,需要处理ViewGroup的测量,布局这两个过程,并同时处理子元素的测量和布局的过程。

   3、继承特定的View(比如TextView)

    这种方式比较常见,一般是用于扩展某种已有的View的功能,不需要自己支持wrap_content和padding等

   4、继承特定的ViewGroup(比如LinearLayout)

   这种方式不需要自己处理ViewGroup的测量和布局的两个过程

  4.4.2 自定义View须知

    1、让View支持wrap_content

    2、如果有必要,让你的View支持padding

    这是因为直接继承View的控件,如果不在draw方法中处理padding,那么padding属性是无法起作用的,另外,直接继承自ViewGroup的控件需要在onMeasure和onLayout中考虑padding和子元素的margin对其造成的影响,不然将导致padding和子元素的marign失效。

   3、尽量不要在View中使用Handler,没必要

   这是因为View内部本身就提供了post系列的方法,完全可以替代Handler的作用,当然除非你很明确地要使用Handler来发送消息。

   4、View中如果有线程或者动画,需要及时停止,参考View#onDetachedFromWindow

     在onDetachedFromWindow是一个很好的时机,这个和onAttachedToWindow对应,否则会造成内存泄露。

   5、View带的滑动嵌套情形时,需要处理好滑动冲突

    接下来是原书中的自定义view的示例,推荐阅读源码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值