Android中的View

Android自定义View的基石——View工作原理总结

Android中View的更新方法:invalidate()和requestLayout()

Activity创建View

视图层级

DecorView下是一个竖直方向的LinearLayout,包含一个id为android.R.id.content的子View,在Activity的onCreate()中调用setContentView()就是设置这个contentView。

Activity的onCreate()方法调用之前,创建Window(PhoneWindow)
Activity的onCreate方法中调用setContentView()方法,创建DecorViewcontentView,将指定的布局文件加载到contentView中
Activity的onResume方法调用之后,将DecorView添加到Window中,之后依次开始View的measure、layout和draw流程

三大流程

performTraversals()方法内会依次调用measureHierarchy()performLayout()performDraw(),进而开始View的三大流程:测量measure、布局layout、绘制draw

measure

measure流程的作用是对View的大小进行测量。

MeasureSpec类,由一个32位int值表示,高2位表示SpecMode(测量模式),低30位表示SpecSize(测量尺寸大小),通过位运算实现。

三种测量模式:

  • UNSPECIFIED:父View不会限制子View的大小,一般用于系统内部,开发中使用很少
  • EXACTLY:父View能够确定子View的大小,如match_parent或给定精确尺寸(dp或px)
  • AT_MOST:子View的大小不能超过父View尺寸,具体尺寸需要由子View自身来确定,对应wrap_content

确定View尺寸的方法:

  • 当子View的LayoutParams指定为精确数值时,不管父View的测量模式是什么,子View的测量模式均为EXACTLY,测量尺寸为LayoutParams指定的值
  • 当子View的LayoutParams指定为match_parent时,子View的测量模式取决于父View,即如果父View的测量模式为EXACTLY,那么子View的测量模式为EXACTLY;如果父View的测量模式为AT_MOST,那么子View的测量模式为AT_MOST,子View的测量尺寸均为父View可用空间大小
  • 当子View的LayoutParams指定为wrap_content时,不管父View的测量模式是什么,子View的测量模式均为AT_MOST,测量尺寸为父View可用空间大小

对于最顶层的DecorView:

  • DecorView的LayoutParams指定为MATCH_PARENT时,它的测量模式为EXACTLY,测量尺寸为屏幕尺寸
  • DecorView的LayoutParams指定为WRAP_CONTENT时,它的测量模式为WRAP_CONTENT,测量尺寸为屏幕尺寸

View的measure流程:

View的measure流程

ViewGroup的measure流程:

ViewGroup中并没有重写onMeasure()方法,因为每个ViewGroup的布局方式都不一样,无法得出一个统一的实现方式,需要不同ViewGroup自己去实现。

遍历子View,获取每个子View的MeasureSpec,然后调用每个子View的measure()方法进行测量。再根据子View的测量结果对ViewGroup自身进行测量。

ViewGroup的measure流程

layout

layout的作用是根据测量大小确定View的最终位置。

layout坐标系

layout()方法中比较left、right、top、bottom是否改变,若改变则执行onLayout()。对于单一的View,onLayout()中不做任何事。对于ViewGroup,调用抽象的onLayout()方法(同样需要子类自己去实现),遍历子View并调用其layout()方法确定位置,不断递归。

draw

draw的作用是将View绘制到屏幕上。

  • 调用drawBackground()方法绘制背景
  • 调用onDraw()方法绘制自身内容
  • 调用dispatchDraw()方法绘制子View
  • 调用onDrawForeground()方法绘制装饰,包括滚动条和前景

其中onDraw()是抽象方法,需要不同的View自己去实现绘制方式。

在单一的View中dispatchDraw()是空方法,ViewGroup则实现了其逻辑,遍历所有的子View并调用其draw()方法。

draw流程

invalidate、postInvalidate 与 requestLayout

三者都是View中的方法,用于刷新视图,最终都会调用ViewRootImplperformTraversals()方法。

  • invalidate不会执行measurelayout流程,只执行draw流程。
  • invalidate与requestLayout都必须在主线程调用,要在子线程中刷新视图用postInvalidate方法

调用Viewinvalidate()方法后会逐级调用父View的方法,最终导致ViewRootImplscheduleTraversals()方法被调用,进而调用performTraversals()方法。由于mLayoutRequested的值为false,因此不会执行measurelayout流程,只执行draw流程。

draw流程的执行过程和是否开启硬件加速有关。如果关闭了硬件加速,从DecorView开始的所有View都会重新完成绘制。如果开启了硬件加速,只有调用invalidate()方法的View(包括它的子View)会完成重新绘制。由此也可以看出,开启硬件加速确实可以提高重绘的效率。

postInvalidate其实就是通过Handler完成了线程的切换,使得invalidate()方法在主线程中被调用。