android自定义view实现原理,Android自定义View基础——View的工作原理

在了解View工作原理之前,需要先了解一些基础概念:

ab02617430cf

Activity层次图.png

DecorView:顶层视图,将要显示的具体内容呈现在PhoneWindow上,是当前Activity所有View的根节点。

ViewRootImpl:连接WindowManager和DecorView的纽带,View的绘制流程正是从通过ViewRootImpl来完成的。

一、View的绘制流程是从哪里开始

这可能得从Android的启动过程说起,但是此处不深究Android的启动过程,只是抽丝剥茧的取出其中与View的绘制过程关系较为直接的来说:

当Activity初始化Window和将布局添加到DecorView之后,ActivityThread类会调用handleResumeActivity会通过WindowManager将DecorView视图添加到窗口上,大致调用过程为:

ActivityThread.handleResumeActivity()

| WindowManager.addView()

|| WindowManagerGlobal.addView()

||| ViewRootImpl.setView() -> requestLayout() -> scheduleTraversals()

|||| TraversalRunnable.run() -> ViewRootImpl.doTraversal()

||||| ViewRootImpl.performTraversals()

关键点就是ViewRootImpl.performTraversals(),展开一看究竟

private void performTraversals(){

......

performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

......

performLayout(lp, desiredWindowWidth, desiredWindowHeight);

......

performDraw();

......

}

从上面的代码可以看出,performTraversals()依次调用了performMeasure、performLayout 和 performDraw方法,正是测量、布局、绘制。所以,View的绘制流程是从ViewRootImpl的performTraversals开始的。

performMeasure()会调用顶级View的measure()方法,measure又去调用onMeasure(),onMeasure中会对所有子元素进行measure过程;接着子元素会重复父容器的measure过程,如此反复至完成整个View树的遍历。layout和draw同理。

ab02617430cf

View绘制流程.png

二、View绘制流程之measure

1. 理解MeasureSpec

在搞清楚measure过程之前,得先明白MeasureSpec的概念,因为在measure过程中,MeasureSpec将会贯穿其中。

MeasureSpec是一个32位int值,高2位是SpecMode(测量模式),低30位代表SpecSize(某种测量模式下的规格大小)

SpecMode的3种模式:

UNSPECIFIED:父容器不对View有任何限制,要多大给多大,这种情况一般用于系统内部。

EXACTLY(精确模式):父容器已测出View所需要的精确大小,View的尺寸就是父容器给的确切值。对应LyaoutParams中的match_parent或具体数值。

AT_MOST(最大模式):父容器给出可用大小,View的大小不能超过该值,具体要看View的具体实现。对应LayoutParams中的wrap_content。

普通View的MeasureSpec的创建规则对照表:

parentSpecMode / chlidLayoutParams

EXACTLY

AT_MOST

UNSPECIFIED

dp/px

EXACTLY childSize

EXACTLY childSize

EXACTLY childSize

match_parent

EXACTLY parentSize

AT_MOST parentSize

UNSPECIFIED 0

wrap_content

AT_MOST parentSize

AT_MOST parentSize

UNSPECIFIED 0

其中竖列为chlidLayoutParams,横为parentSpecMode。

表格的推演由来,可参考:ViewGrop.getChildMeasureSpec()

2. measure过程

measure过程由measure完成,measure是final方法,不能被重写,所以重点看里面调用的onMeasure。容器类型的View,需要先测量子View。

单个View的测量:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

// 设置宽高的测量值

setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),

getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));

}

public static int getDefaultSize(int size, int measureSpec) {

int result = size;

int specMode = MeasureSpec.getMode(measureSpec);

int specSize = MeasureSpec.getSize(measureSpec);

switch (specMode) {

case MeasureSpec.UNSPECIFIED:

result = size;

break;

// AT_MOST & EXACTLY的返回值是一样的

case MeasureSpec.AT_MOST:

case MeasureSpec.EXACTLY:

result = specSize;

break;

}

return result;

}

从getDefaultSize可以看出,默认AT_MOST和EXACTLY的返回值是一样的,所以直接继承View的控件需要重写onMeasure设置specMode=wrap_content时的大小,不然wrap_content效果=match_parent。

ViewGroup测量:

ViewGroup除了测量自身,还需要去调用所有子元素的measure方法来测量子元素,子元素在递归去执行该过程。

ViewGroup提供了measureChildren方法:

protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {

final int size = mChildrenCount;

final View[] children = mChildren;

for (int i = 0; i < size; ++i) {

final View child = children[i];

if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {

measureChild(child, widthMeasureSpec, heightMeasureSpec);

}

}

}

protected void measureChild(View child, int parentWidthMeasureSpec,

int parentHeightMeasureSpec) {

final LayoutParams lp = child.getLayoutParams();

final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,

mPaddingLeft + mPaddingRight, lp.width);

final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,

mPaddingTop + mPaddingBottom, lp.height);

// 调用子View的 measure方法

child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

}

在measure时,遍历所有子元素,调用子元素的measure方法。虽然ViewGroup的子类容器布局不一定使用measureChildren方法,但是主要逻辑是一样的,都是遍历子View进行测量。

三、View绘制流程之layout

layout的作用是用来确定容器自身以及子元素的位置,首先调用layout()确定自身位置,layout中又会调用onLayout来确定子元素位置。

来一张图看layout的流程:

ab02617430cf

layout过程.png

四、View绘制流程之draw

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

绘制顺序如下:

绘制背景:background.draw(canvas)

绘制自己:onDraw(canvas)

绘制children:dispatchDraw(canvas)

绘制装饰:onDrawScrollBars(canvas)

子View的绘制是通过dispatchDraw方法来往下传递的,它会遍历调用所有子元素的draw方法,所有都绘制完成后,在执行自身的onDrawScrollBars。

protected void dispatchDraw(Canvas canvas) {

......

for (int i = 0; i < childrenCount; i++) {

while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {

final View transientChild = mTransientViews.get(transientIndex);

if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||

transientChild.getAnimation() != null) {

// 调用 child.draw方法

more |= drawChild(canvas, transientChild, drawingTime);

}

......

}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值