美文网首页Android
Android View的工作原理

Android View的工作原理

作者: 漆先生 | 来源:发表于2022-03-09 14:26 被阅读0次

一、概念

Activity:一个Activity是一个应用程序组件,提供一个屏幕,用户可以用来交互为了完成某项任务,例如拨号、拍照。
View:作为所有图形的基类。
ViewGroup:对View继承,扩展为视图容器类,容器的基类。
Window:它概括了Android窗口的基本属性和基本功能。(抽象类)
PhoneWindow:Window的子类。
DecorView:界面的根View
ViewRootImpl:ViewRoot是GUI管理系统与GUI呈现系统之间的桥梁。
WindowManangerService:简称WMS,它的作用是管理所有应用程序中的窗口,并用于管理用户与这些窗口发生的的各种交互。

二、ViewRoot和DecorView

ViewRoot对应于ViewRootImpl类,它是链接WindowManager和DecorView的纽带。ActivityThread中,Activity被创建后,会将DecorView添加到Window中,同时创建ViewRootImpl对象,并将ViewRootImpl对象和DecorView建立关联(WindowMangerGlobal#addView中处理)。


image.png

setView的view就是DecorView

View的三大流程是通过ViewRoot来完成,从performTraversals方法开始,经过messure,layout,draw
三个过程将View绘制出来。

  • messure:测量View的宽和高
  • layout:确定View在父容器中的位置
  • draw:将View绘制在屏幕上
image.png

DecorView作为顶级View,是一个FrameLaout,View层事件都要经过它。里边包含一个竖直方向的LinearLayout,有一个topBar和一个内容栏,平时我们setContentView就是把我们布局设置在内容栏显示。

三、理解MeasureSpec

1.MeasureSpec

MeasureSpec代表一个32位的int值,高2位代表SpecMode测量模式,低30位代表SpecSize规格大小。
SpecMode有三类:

  • UNSPECIFIED:父容器不对View有任何限制,要多大给多大,这种模式一般用于系统内部,表示一种测量的状态。
  • EXACTLY:父容器已经检测出View所需的精确大小,这个时候View的最终大小就是SpecSize所指定的值。他对应于LayoutParams中的match_parents和具体的数值这两种模式。
  • AT_MOST:父容器指定了一个可用大小SpecSize,View的大小不能大于这个值。具体什么值要看View的具体实现。它对应于LayoutParams中的wrap_content。

2.MeasureSpec和LayoutParams的关系

系统将View的LayoutParams根据父容器约束规则转换成对应的MeasureSpec,然后再根据这个measureSpec来测量View的宽高。MeasureSpec很大程度上决定了一个View的尺寸规格。LayoutParams和父容器一起才能决定View的MeasureSpec。


image.png

MeasureSpec由父容器额MeasureSpec和自身的LayoutParams共同决定。

四、View的工作流程

View的主要工作流程是指measure、layout、draw这三大流程。

1.measure过程

如果是一个原始的View,只需要measure方法就完成其测量过程,如果是ViewGroup,除了完成其自身测量,还会遍历去调用所有子元素的measure方法。各个子元素再递归去执行这个流程。

View的measure

measure是一个final类型,不能被扩展。measure方法里边会调用onMeasure方法。

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

setMeasureDimemension方法设置View宽高测量值。

    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;
    }

getDefaultSize返回的大小就是measureSpec中的specSize,是View测量后的大小。

    protected int getSuggestedMinimumHeight() {
        return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
    }
    protected int getSuggestedMinimumWidth() {
        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
    }

getSuggestedMininumWidth方法逻辑如下:
如果当前View没有设置背景,返回的就是android:miniWidth这个属性的值;
如果当前View设置了背景,则返回android:miniWidth和背景的最小宽度这两者的最大值。
getSuggestedMininumWidth方法返回的就是UNSPECIFIED情况下的测量宽高。

ViewGroup的measure过程

ViewGroup除了完成自己的measure方法,还会遍历去调用所有子元素的measure方法,各个子元素再递归去执行。ViewGroup是一个抽象类,没有定义具体的测量过程,没有去重写onMeasure方法,需要子类各自去实现。提供了measureChildren方法和measureChild方法。

    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);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

measureChildren对每一个子元素进行measure,调用measureChild。measureChild取出子元素的LayoutParams,然后再用getChildMeasureSpec方法创建子元素的MeasureSpec,再将MeasureSpec传递给View的measure方法进行测量。

measure完成后可以通过getMeasureWidth和getMeasureHeight获取View测量到的宽高。某些极端情况,可能要measure几次才能确定最终的测量宽高,这时候在onMeasure方法中拿到的测量宽高很可能是不准确的,所以最好在onLayout方法中获取View的测量宽高或者最终宽高。

在activity启动时获取View的宽高,不能直接在onstart方法里边获取,因为View的measure过程和Activity的声明周期不是同步执行的,无法保证已经测量完毕。提供如下四种方法获取:

  • 在onWindowFoucusChanged方法参数是true时处理,可能会被多次调用。
  • view.post(runable)
  • ViewTreeObserver的onGlobalLayoutListener回调。也是会多次获取,获取完后需要移除监听
  • view.measure,手动对View进行meausre来得到View的宽高。

2.layout过程

ViewGroup用来确定子元素的位置。当ViewGroup位置确认后,在onLayout方法中会遍历所有子元素并调用其layout方法,在layout方法中,onLayout方法又会被调用。
layout确定View本身的位置,onLayout确定所有子元素的位置。

    public void layout(int l, int t, int r, int b) {
        if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
            onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        }

        int oldL = mLeft;
        int oldT = mTop;
        int oldB = mBottom;
        int oldR = mRight;

        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            onLayout(changed, l, t, r, b);

            if (shouldDrawRoundScrollbar()) {
                if(mRoundScrollbarRenderer == null) {
                    mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
                }
            } else {
                mRoundScrollbarRenderer = null;
            }

            mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;

            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnLayoutChangeListeners != null) {
                ArrayList<OnLayoutChangeListener> listenersCopy =
                        (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
                int numListeners = listenersCopy.size();
                for (int i = 0; i < numListeners; ++i) {
                    listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
                }
            }
        }

        final boolean wasLayoutValid = isLayoutValid();

        mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
        mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;

layout方法中,首先会通过setFrame方法来设定View的四个顶点位置,接着调用onLayout方法,确定其在父容器中的位置。

onLayout的具体实现和具体的布局有关,View和ViewGroup均没有实现其方法。

3.draw过程

绘制步骤如下:

  • 绘制背景background.draw(canvas)
  • 绘制自己(onDraw)
  • 绘制children(dispatchDraw)
  • 绘制装饰(onDrawForeground)
    public void draw(Canvas canvas) {
        final int privateFlags = mPrivateFlags;
        mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;

        /*
         * Draw traversal performs several drawing steps which must be executed
         * in the appropriate order:
         *
         *      1. Draw the background
         *      2. If necessary, save the canvas' layers to prepare for fading
         *      3. Draw view's content
         *      4. Draw children
         *      5. If necessary, draw the fading edges and restore layers
         *      6. Draw decorations (scrollbars for instance)
         *      7. If necessary, draw the default focus highlight
         */

        // Step 1, draw the background, if needed
        int saveCount;

        drawBackground(canvas);

        // skip step 2 & 5 if possible (common case)
        final int viewFlags = mViewFlags;
        boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
        boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
        if (!verticalEdges && !horizontalEdges) {
            // Step 3, draw the content
            onDraw(canvas);

            // Step 4, draw the children
            dispatchDraw(canvas);

            drawAutofilledHighlight(canvas);

            // Overlay is part of the content and draws beneath Foreground
            if (mOverlay != null && !mOverlay.isEmpty()) {
                mOverlay.getOverlayView().dispatchDraw(canvas);
            }

            // Step 6, draw decorations (foreground, scrollbars)
            onDrawForeground(canvas);

            // Step 7, draw the default focus highlight
            drawDefaultFocusHighlight(canvas);

            if (isShowingLayoutBounds()) {
                debugDrawFocus(canvas);
            }

            // we're done...
            return;
        }
...
}

View绘制过程是通过dispatchDraw来实现的。遍历子元素的draw方法。
setWillNotDraw,view不需要绘制任何内容的时候可以开启,view是默认关闭,ViewGroup是默认开启的,开启后系统会进行相应的优化。当ViewGroup需要onDraw来绘制内容时,需要显示的关闭WILL_NOT_DRAW这个标记位。

五、自定义View

自定义View是一个综合的技术体系,设计View的层次结构、事件的分发机制和View的工作原理等技术细节。

1.自定义View的分类

  • 继承View重写onDraw方法
    主要用于实现一些不规则的效果。当需要静态或者动态的实现一些不规则的图形的时候。
  • 继承ViewGroup派生出特殊的Layout
    主要用户实现自定义布局,当某种效果看起来很像几种View组合在一起的时候。
  • 继承特定的View
    比较常见,一般是扩展现有View的功能。
  • 继承特定的ViewGroup
    和第二个接近,但是不需要自己处理ViewGroup的测量和布局这两个过程。

2.自定义View须知

  • 让View支持wrap_content
  • 如果有必要,让你的View支持padding
  • 尽量不要在View中使用Handler,,没必要
  • View中如果有线程或者动画,需要即时停止
  • View带有滑动的嵌套情形时,需要处理滑动冲突

相关文章

网友评论

    本文标题:Android View的工作原理

    本文链接:https://www.haomeiwen.com/subject/ilsarrtx.html