美文网首页
View 的工作原理

View 的工作原理

作者: simplehych | 来源:发表于2019-11-20 16:30 被阅读0次

View的三个流程:measure、layout、draw
View常见的回调方法:构造方法、onAttach、onVisibilityChanged、onDetach等

对于DecorView来说:

对于普通的View,其MeasureSpec由父容器MeasureSpec和自身的LayoutParams来共同决定。

getChildMeasureSpec

getChildMeasureSpec

View 的工作流程

View的工作流程主要是指measure、layout、draw三大流程,即测量、布局和绘制。

measure:确定View的测量宽/高。
layout:确定View的最终宽/高 和 四个顶点的位置。
draw:将View绘制到屏幕上

measure过程

分情况:

  1. View
  2. ViewGroup,除了自己测量外,还需要遍历子元素的measure方法

View 的 measure 过程

View 的 measure 过程由 measure 方法来完成。

measure 方法是一个 final 方法。意味着子类不能重新写此方法。

在 View 的 measure 方法中会调用 View 的 onMeasure 方法,如下:

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

上述代码简洁,当并不代表简单。

ViewGroup 的 measure 过程

对于ViewGroup来说,除了完成自己 measure 过程以外,还会遍历去调用所有子元素的 measure 方法,各个子元素再递归去执行这个过程。

和 View 不同的是,ViewGroup 是一个抽象类,因此它没有重写View的onMeasure方法,但是它会提供一个叫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);
        }
    }
}

从上述代码来看,ViewGroup 在 measure 时,会对每一个子元素进行measure,measureChild方法如下:

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

很显然,measureChild 的思想就是取出子元素的LayoutParams,然后再通过getChildMeasureSpec来创建MeasureSpec,然后将Measure直接传递给View的measure方法来进行测量。getChildMeasureSpec的工作过程已经在上面进行了详细的分析,通过上表可以清楚的了解它的逻辑。

ViewGroup并没有定义其测量的具体过程,这是因为ViewGroup是一个抽象类,其测量过程的onMeasure 方法需要各个子类去具体实现,比如LinearLayout、RelativeLayout等。不同的ViewGroup由不同的布局特性,这导致了它们的测量细节各不相同,比如LinearLayout和RelativeLayout布局特性显然不同,所以ViewGroup无法做统一实现。

分析LinearLayout的onMeasure方法
获取View宽/高的方式

View 的 measure 过程是三大流程中最负责的一个,measure完成后可以通过 getMeasureWidth/Height方法就可以正确地获取到View的测量宽/高。
注意,某些情况,系统需要多次measure才能确定最终的测量宽高,在这种情形下,在onMeasure中拿到测量宽高很可能是不准确的。一个好的习惯是在onLayout中获取View的测量宽高或最终宽高。

  1. Activity/View#onWindowFocusChanged
    View 初始化完毕,宽/高已经准备好。

注意:会被多次调用,当Activity的窗口得到焦点和失去焦点时均会被调用一次。(onResume和onPause频繁会被调用)

@Override
public void onWindowFocusChanged(boolean hasFocus) {
    super.onWindowFocusChanged(hasFocus);
    if (hasFocus){
        int width = textView.getMeasuredWidth();
        int height = textView.getMeasuredHeight();
    }
}
  1. view.post(runnable)
    通过post可以将一个runnable投递到消息队列的尾部,然后等待Looper调用此runnable的时候,View也已经初始化好了。
@Override
protected void onStart() {
    super.onStart();
    textView.post(new Runnable() {
        @Override
        public void run() {
            int width = textView.getMeasuredWidth();
            int height = textView.getMeasuredHeight();
        }
    });
}
  1. ViewTreeObserver
    使用ViewTreeObserver的中国回调可以完成这个功能,比如使用onGlobalLayoutListener接口,当View树的状态发生改变或者View树内部的View可见性发生改变时,onGlobalLayout方法将被回调,因此这是获取View的宽高一个很好的时机。需要注意的是,伴随着View树的状态改变等,onGlobalLayout会被调用多次。
ViewTreeObserver observer = textView.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() {
        textView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
        int width = textView.getMeasuredWidth();
        int height = textView.getMeasuredHeight();
    }
});
  1. view.measure(int widthMeasureSpec, int heightMeasureSpec)
    通过手动对View进行measure来得到View的宽高。
    这种方法比较复杂,需要分情况处理,根据LayoutParams来分:match_parent,dp/px,wrap_content。

match_parent
直接放弃,无法measure出具体的宽高。
原因很简单,根据View的measure过程,构造此MeasureSpec需要知道parentSize,即父容器的剩余空间,而这个时候我们无法知道parentSize的大小,所以理论上不可能测量出View的大小。

dp/px
比如宽和高都是100px

int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
textView.measure(widthMeasureSpec, heightMeasureSpec);

int width = textView.getMeasuredWidth();
int height = textView.getMeasuredHeight();

wrap_content

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

int width = textView.getMeasuredWidth();
int height = textView.getMeasuredHeight();

注意到 (1 << 30) -1,通过分析MeasureSpec的实现可以知道,View的尺寸使用30位二进制表示,也就是说最大是30个1(即 2^30 - 1),也就是(1 << 30) -1 ,在最大化模式下,我们用View理论上能支持的最大值去构造MeasureSpec是合理的。

两个错误的用法:

  1. 违背了系统的内部实现规范,无法通过错误的MeasureSpec去得出合法的SpecMode,从而导致measure过程出错。
  2. 不能保证一定能measure出正确的结果,可能凑巧正确

错误用法一

int widthMeasureSpec = MeasureSpec.makeMeasureSpec(-1, MeasureSpec.UNSPECIFIED);
int heightMeasureSpec = MeasureSpec.makeMeasureSpec(-1, MeasureSpec.UNSPECIFIED);
view.measure(widthMeasureSpec, heightMeasureSpec);

错误用法二

view.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTNET);

layout 过程

作用:ViewGroup 用来确定子元素的位置,当ViewGroup的位置被确定后,它在onLayout中会遍历所有的子元素并调用其layout方法,layout方法中调用onLayout方法,layout方法确定View本身的位置,而onLayout方法则会确定所有子元素的位置,先看View的layout方法:

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

    mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
    mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;

    if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) {
        mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
        notifyEnterOrExitForAutoFillIfNeeded(true);
    }
}

layout 方法的大致流程:通过setFrame方法来设定View的四个顶点的位置,即初始化mLeft、mRight、mTop、mBottom四个值,View的四个顶点一旦确定,那么View在父容器中的位置也就确定了;接着会调用onLayout方法,这个方法的用途是父容器确定子元素的位置,和onMeasure方法类似,onLayout的具体实现同样和具体的布局有段,所以View和ViewGroup均没有真正实现onLayout方法。

测量的宽高和最终的宽高可能不一致。

draw过程

作用:将View绘制到屏幕上面。
步骤:

  1. 绘制背景 background.draw(canvas)
  2. 绘制自己 onDraw
  3. 绘制children dispatchDraw
  4. 绘制装饰 onDrawScrollBars
public void draw(Canvas canvas) {
    final int privateFlags = mPrivateFlags;
    final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
            (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
    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)
     */

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

    if (!dirtyOpaque) {
        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
        if (!dirtyOpaque) 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 (debugDraw()) {
            debugDrawFocus(canvas);
        }

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

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

View有一个特殊的方法 setWillNotDraw,先看一下源码:

/**
 * If this view doesn't do any drawing on its own, set this flag to
 * allow further optimizations. By default, this flag is not set on
 * View, but could be set on some View subclasses such as ViewGroup.
 *
 * Typically, if you override {@link #onDraw(android.graphics.Canvas)}
 * you should clear this flag.
 *
 * @param willNotDraw whether or not this View draw on its own
 */
public void setWillNotDraw(boolean willNotDraw) {
    setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}

从setWillNotDraw方法的注释中可以看出,如果一个View不需要绘制任何内容,那么设置这个标记位为true以后,系统会进行相应的优化。默认情况下,View没有启动这个优化标记位,但是ViewGroup会默认启用这个标记位。

这个标记位对实际开发的意义:当我们的自定义控件继承于ViewGroup并且自身不具备绘制功能时,就可以开启这个标记位,从而让系统进行后续的优化。如果明确ViewGroup自身需要绘制内容时,我们需要显示关闭WILL_NOT_DRAW这个标记位。

自定义View

自定义View的分类

分类标准不唯一

  1. 继承 View 重写 onDraw 方法
    实现一些不规则的效果,不方便通过布局的组合来达到,需要重写onDraw方法。
    这种方式需要自己支持 wrap_content,并且padding也需要自己处理
  2. 继承 ViewGroup 派生特殊的Layout
    重新定义一种新布局,类似LinearLayout或RelativeLayout。
    当某种效果看起来像几种View组合在一起时,可以采用这种方式实现。
    采用这种方式稍微复杂一些,需要合适地处理ViewGroup的测量、布局这两个过程,并同时处理子元素的测量和布局过程
  3. 继承特定的View(比如TextView)
    用于扩展已有View的功能,比如TextView
    这种实现比较容易,不需要自己支持wrap_content和padding等
  4. 继承特定的ViewGroup(比如LinearLayout)
    当某种效果看起来很像几种View组合在一起的时候,可以采用这种方式实现。
    这种方式不需要自己处理ViewGroup的测量和布局过程。
    需要注意和方式2的区别,方法2更接近View的底层

margin 属性是父容器控制的,padding需要自己处理

自定义View须知

一些自定义View过程中的注意事项,如果这些问题处理不好,有些会影响View的正常使用,而有些则会导致内存泄露等。

  1. 让View支持wrap_content
    在onMeasure中对wrap_content做特殊处理,否则当外界在布局中使用wrap_content时就无法达到预期的效果,和match_parent效果类似
  2. 让View支持padding
    在draw中对padding做处理,否则padding属性无法起作用。另直接继承自ViewGroup的控件需要在onMeasure和onLayout中考虑padding和子元素的margin对其造成的影响,都这将导致padding和子元素的margin失效。
  3. 尽量不在View中使用Handler,没必要
    View内部本身提供了post系列的方法,完全可以替代Handler的作用,当然除非很明确要使用Handler来发送消息。
  4. View中有线程或动画,需要及时停止 onDetachedFromWindow:当包含此View的Activity退出或者当前View被remove时,该方法会被调用。
    和此方法对应的时onAttachedToWindow。
    当View变得不可见时我们也需要停止线程和动画,如果不及时处理这种问题,有可能会造成内存泄漏
  5. View带有滑动嵌套的情形,需要处理好滑动冲突

另附示例:

  1. 自定义View
public class CircleView extends View {

    private int mColor = Color.RED;
    private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

    private int defaultWidth = 200;
    private int defaultHeight = 200;

    public CircleView(Context context) {
        super(context);
        init();
    }

    public CircleView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleView);
        mColor = a.getColor(R.styleable.CircleView_color, Color.RED);
        a.recycle();
        init();
    }

    private void init() {
        mPaint.setColor(mColor);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int wMode = MeasureSpec.getMode(widthMeasureSpec);
        int wSize = MeasureSpec.getSize(widthMeasureSpec);
        int hMode = MeasureSpec.getMode(heightMeasureSpec);
        int hSize = MeasureSpec.getSize(heightMeasureSpec);

        if (wMode == MeasureSpec.AT_MOST && hMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(defaultWidth, defaultHeight);

        } else if (wMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(defaultWidth, hSize);

        } else if (hMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(wSize, defaultHeight);
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int width = getWidth() - getPaddingLeft() - getPaddingRight();
        int height = getHeight() - getPaddingTop() - getPaddingBottom();
        int radius = Math.min(width, height) / 2;
        canvas.drawCircle(getPaddingLeft() + width / 2, getPaddingRight() + height / 2, radius, mPaint);
    }
}

  1. 自定义ViewGroup
    假定了每个子元素的宽高相同(以第一个为准)

示例不规范的地方:

  1. 没有子元素的时候不应该直接把宽高设置为0,而应该根据LayoutParams中的宽高来做相应的处理
  2. 在测量HorizontalScrollViewEx宽高时,没有考虑到它的padding以及子元素的margin,因为它自身的padding以及子元素的margin会影响到HorizontalScrollViewEx的宽高。
public class HorizontalScrollViewEx extends ViewGroup {

    private static final String TAG = "HorizontalScrollViewEx";

    private int mChildrenSize;
    private int mChildWidth;
    private int mChildIndex;

    private int mLastX = 0;
    private int mLastY = 0;

    private int mLastXIntercept = 0;
    private int mLastYIntercept = 0;

    private Scroller mScroller;
    private VelocityTracker mVelocityTracker;

    public HorizontalScrollViewEx(Context context) {
        super(context);
        init();
    }

    public HorizontalScrollViewEx(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        mScroller = new Scroller(getContext());
        mVelocityTracker = VelocityTracker.obtain();
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        boolean intercepted = false;
        int x = (int) event.getX();
        int y = (int) event.getY();

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                intercepted = false;
                if (!mScroller.isFinished()) {
                    mScroller.abortAnimation();
                    intercepted = true;
                }
                break;
            case MotionEvent.ACTION_MOVE:
                int deltaX = x - mLastXIntercept;
                int deltaY = y - mLastXIntercept;
                if (Math.abs(deltaX) > Math.abs(deltaY)) {
                    intercepted = true;
                } else {
                    intercepted = false;
                }
                break;
            case MotionEvent.ACTION_UP:
                intercepted = false;
                break;
            default:
                break;
        }

        Log.d(TAG, "intercepted = " + intercepted);

        mLastX = x;
        mLastY = y;
        mLastXIntercept = x;
        mLastYIntercept = y;

        return intercepted;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mVelocityTracker.addMovement(event);
        int x = (int) event.getX();
        int y = (int) event.getY();

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                if (!mScroller.isFinished()) {
                    mScroller.abortAnimation();
                }
                break;
            case MotionEvent.ACTION_MOVE:
                int deltaX = x - mLastX;
                int deltaY = y - mLastY;
                scrollBy(-deltaX, 0);
                break;
            case MotionEvent.ACTION_UP:
                int scrollX = getScrollX();
                mVelocityTracker.computeCurrentVelocity(1000);
                float xVelocity = mVelocityTracker.getXVelocity();
                if (Math.abs(xVelocity) >= 50) {
                    mChildIndex = xVelocity > 0 ? mChildIndex - 1 : mChildIndex + 1;
                } else {
                    mChildIndex = (scrollX + mChildWidth / 2) / mChildWidth;
                }
                mChildIndex = Math.max(0, Math.min(mChildIndex, mChildrenSize - 1));
                int dx = mChildIndex * mChildWidth - scrollX;
                smoothScrollBy(dx, 0);
                mVelocityTracker.clear();
                break;
            default:
                break;

        }

        mLastX = x;
        mLastY = y;

        return true;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int measuredWidth = 0;
        int measuredHeight = 0;
        final int childCount = getChildCount();
        measureChildren(widthMeasureSpec, heightMeasureSpec);

        int wMode = MeasureSpec.getMode(widthMeasureSpec);
        int wSize = MeasureSpec.getSize(widthMeasureSpec);
        int hMode = MeasureSpec.getMode(heightMeasureSpec);
        int hSize = MeasureSpec.getSize(heightMeasureSpec);

        if (childCount == 0) {
            setMeasuredDimension(0, 0);

        } else if (wMode == MeasureSpec.AT_MOST && hMode == MeasureSpec.AT_MOST) {
            View childView = getChildAt(0);
            measuredWidth = childView.getMeasuredWidth() * childCount;
            measuredHeight = childView.getMeasuredHeight();
            setMeasuredDimension(measuredWidth, measuredHeight);

        } else if (hMode == MeasureSpec.AT_MOST) {
            View childView = getChildAt(0);
            measuredHeight = childView.getMeasuredHeight();
            setMeasuredDimension(wSize, measuredHeight);

        } else if (wMode == MeasureSpec.AT_MOST) {
            View childView = getChildAt(0);
            measuredWidth = childView.getMeasuredWidth() * childCount;
            setMeasuredDimension(measuredWidth, hSize);
        }

    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int childLeft = 0;
        int childCount = getChildCount();
        mChildrenSize = childCount;
        for (int i = 0; i < childCount; i++) {
            View childView = getChildAt(i);
            if (childView.getVisibility() != View.GONE) {
                int childWidth = childView.getMeasuredWidth();
                mChildWidth = childWidth;
                childView.layout(childLeft, 0, childLeft + childWidth, childView.getMeasuredHeight());
                childLeft += childWidth;
            }
        }
    }

    private void smoothScrollBy(int dx, int dy) {
        mScroller.startScroll(getScrollX(), 0, dx, 0, 500);
        invalidate();
    }

    @Override
    public void computeScroll() {
        super.computeScroll();
        if (mScroller.computeScrollOffset()) {
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            postInvalidate();
        }
    }

    @Override
    protected void onDetachedFromWindow() {
        mVelocityTracker.recycle();
        super.onDetachedFromWindow();
    }
}

参考资料

感谢以下文章作者
《Android开发艺术探索》第4章 View的工作原理

相关文章

  • View 的测量

    接着上篇 View 基础 来讲 View 的工作原理,View 的工作原理中最重要的就是测量、布局、绘制三大过程,...

  • 【Android】自定义ViewGroup

    关于View的工作原理、绘制流程等,在第4章 View的工作原理[https://www.jianshu.com/...

  • View 工作原理

    1、 ViewRoot 和 DecorView 介绍 ViewRoot 对应于 ViewRootImpl 类,它...

  • View工作原理

    参考书籍:Android开发艺术探索注:京东链接https://item.jd.com/11760209.html...

  • View工作原理

    View工作原理 首先先来说明一下要掌握的知识 View绘制工作整体流程 Measure Layout Draw ...

  • View工作原理

    1、起步分析 在Activity启动分析中 知道,Activity的创建是在ActivityThread.perf...

  • View工作原理

  • View工作原理

    view有三大工作流程:测量、布局、绘制,分别对应着方法mesure、layout、draw ViewRoot和D...

  • View工作原理

    ViewRoot对应ViewRootImpl类,它是连接WindowManager和DecorView的纽带,Vi...

  • View的工作原理

    ViewRoot对应于ViewRootImpl类,是连接Windowmanager和DecorView的纽带,Vi...

网友评论

      本文标题:View 的工作原理

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