Android 开源库WheelView源码分析:View的工作流程


上一篇文章立下的flag,要读WheelView的源码,出一篇源码分析的文章,这就来了。

不过,在分析WheelView源码之前,先来重温下“View的工作流程”和“自定义View”的知识吧!

View的工作流程

View的工作流程主要就是指Measure(测量)、Layout(布局)、Draw(绘制)三个流程。

1、Measure——测量过程

(1)View的Measure

View的Measure最终是通过onMeasure方法实现的。

// 如果需要自定义测量,子类需重写这个方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
    
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
	……
}

//如果View没有重写onMeasure方法,默认会直接调用getDefaultSize方法
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;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
}

可以看到,onMeasure方法实际上就是通过setMeasuredDimension方法实现的,而setMeasuredDimension方法的作用是通过传入的参数设置View的宽高

我们来看下getDefaultSize方法中的三种specMode:

首先,UNSPECIFIED,一般用于系统内部的测量过程,应用开发中很少用到,我们暂不关心。

然后,AT_MOST 和 EXACTLY,这两种模式下,getDefaultSize方法返回的大小实际上就是measureSpec的specSize,specSize是View的测量后大小。
测量后,意味着是在layout过程后确定的大小。

先简单说明一下:
View的MeasureSpec,是由其父容器的MeasureSpec和其自身的LayoutParams共同确定的;
而其父容器的MeasureSpec又是由再上一级容器的MeasureSpec和其自身的LayoutParams共同确定的;
……;
一直到顶级DecorView,其MeasureSpec是由窗口的尺寸和其自身的LayoutParams共同确定的。

(2)ViewGroup的Measure

ViewGroup的Measure过程和View不同,它除了要完成自己的Measure过程以外,还会遍历去调用所有子元素的measure方法。

——先看一下ViewGroup自己的Measure过程:

ViewGroup是一个抽象类,没有实现View的onMeasure方法,都交给它的子类去具体实现了。原因在于,ViewGroup不同的子类(如LinearLayout、RelativeLayout等)的布局特性不同,自然有着不同的测量细节,ViewGroup是没有办法进行统一定义的。

不同子类是怎样实现onMeasure的,这里就不再赘述了,感兴趣的小伙伴可以去查看具体子类的源码。

——关于ViewGroup遍历调用所有子元素的measure方法,是在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);

		// 调用每一个子元素的measure方法
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

可以看到,其中的getChildMeasureSpec方法可用来获取子元素的MeasureSpec,从其参数的构成不难理解,子元素MeasureSpec的创建于父容器的MeasureSpec和子元素本身的LayoutParams有关,此外还与另一参数padding有关,这个padding是父容器的已占用空间大小。
通过这几个参数,最终实现了子元素的计算需求。

2、Layout——布局过程

Layout过程用来确定View在父容器中的布局位置,它是从ViewRootImpl 的 performLayout 方法开始的:

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
    ...
    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
    ...
}

上述方法中调用了View的layout方法确定View的位置。当View的位置确定后,会通过onLayout方法确定其所有子元素的位置,从来完成整个Layout过程。

public void layout(int l, int t, int r, int b) {
        ……
        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
        ……
       
        onLayout(changed, l, t, r, b);
		……
}

上述代码中的setFrame方法,是用来确定View四个顶点的位置的,一旦View的四个顶点位置确定,那么View在父容器中的位置也就确定了。

需要注意的是,onLayout与onMeasure类似,View和ViewGroup都没有具体的实现onLayout方法,而是由其不同的子类容器进行具体实现的。

原因也是类似的:不同的子类容器(如LinearLayout、RelativeLayout等)的布局特性不同,怎样确定其子元素的位置的细节自然不同,View或ViewGroup是没有办法进行统一定义的。

3、Draw——绘制过程

Draw过程用来将控件绘制出来,它是从 ViewRootImpl 的 performDraw方法开始的:

private void performDraw() {
    boolean canUseAsync = draw(fullRedrawNeeded);
}

private boolean draw(boolean fullRedrawNeeded) {
    ……
    if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty, surfaceInsets)) {
    	return false;
    }
}

private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
     ...
     mView.draw(canvas);
     ...
}

最终调用View的draw方法进行绘制每一个View,具体可分为 6 步:

  public void draw(Canvas canvas) {
    ...
    // 1. Draw the background
    if (!dirtyOpaque) {
      drawBackground(canvas);
    }
    ...
    // 2. If necessary, save the canvas' layers to prepare for fading
    saveCount = canvas.getSaveCount();
    ...
    // 3. Draw view's content
    if (!dirtyOpaque) onDraw(canvas);

    // 4. Draw children
    dispatchDraw(canvas);

    // 5. If necessary, draw the fading edges and restore layers
    canvas.drawRect(left, top, right, top + length, p);
    ...
    canvas.restoreToCount(saveCount);
    ...
    // 6. Draw decorations (scrollbars for instance)
    onDrawScrollbars(canvas);	// onDrawForeground(canvas);
  }

View 绘制过程的传递,是通过 dispatchDraw 方法实现的,dispatchDraw 会遍历调用所有子元素的 draw 方法。

dispatchDraw 方法是在ViewGroup中实现的:

protected void dispatchDraw(Canvas canvas) {
	……
	drawChild(canvas, transientChild, drawingTime);
	……
}

protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
	return child.draw(canvas, this, drawingTime);
}

至此,View 的工作流程,算是简单的分析完了”。

感谢:
任玉刚 · 《Android开发艺术探索》
https://www.jianshu.com/p/c151efe22d0d

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值