Android View绘制过程,基于源码的解析呀

View绘制过程,首页第一步了解整个android的view框架,这其中涉及到几个基本的概念。
 
Activity:基本的页面单元,承载着整window,view实际上是绘制在这个window之上的。
 
View:最基本的UI组件,可以的布局中直接使用。
 
    public class View implements Drawable.Callback, KeyEvent.Callback,  AccessibilityEventSource
 
实现了Drawable,和KeyEvent的回调方法
 
Window:window是一个抽象类,表示一个顶层对象。用来管理界面和响应事件,每个activity均会创建一个
 
PhoneWindow:继承自window对象,是整个activity与view的交互接口,该类中包含一个 DecorView,DecorView继承自FrameLayout
    private final class DecorView extends FrameLayout implements RootViewSurfaceTaker;
也就是说任何activity的window的rootView都是一个DecorView,实际上,DecorView是在framelayout中添加一点东西而已,包括一个通用的titleBar,
所以我们在布局的时候不设置requestWindowFeature的时候默认会有一个titlebar
 
ViewRoot:很抱歉,在最新得framework代码中并没有找到这个类,在最新的framework中 ViewRootImpl这个类,整个view的绘制起点就是此处
 
 
整个基础框架大致是如此。
 
view的绘制过程会中 ViewRootImpl的doTraversal()方法中开始,此方法会调用view的measure方法
void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().removeSyncBarrier(mTraversalBarrier);
 
            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }
 
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "performTraversals");
            try {
                performTraversals();
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }
 
            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }
 
在此方法中会执行performTraversals()方法。我们在famework中继续了解此方法:
 
这个方法代码较多,基本逻辑是判断是否有位移变化,是否有可见性变化,以及是否需要重新绘制,请求大小,位置,和重绘等
 
if (!mStopped) {
                boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
                        (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
                if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
                        || mHeight != host.getMeasuredHeight() || contentInsetsChanged) {
                    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);//获取
                    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
 
                    if (DEBUG_LAYOUT) Log.v(TAG, "Ooops, something changed! mWidth="
                            + mWidth + " measuredWidth=" + host.getMeasuredWidth()
                            + " mHeight=" + mHeight
                            + " measuredHeight=" + host.getMeasuredHeight()
                            + " coveredInsetsChanged=" + contentInsetsChanged);
 
                     // Ask host how big it wants to be
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
 
                    // Implementation of weights from WindowManager.LayoutParams
                    // We just grow the dimensions as needed and re-measure if
                    // needs be
                    int width = host.getMeasuredWidth();
                    int height = host.getMeasuredHeight();
                    boolean measureAgain = false;
 
                    if (lp.horizontalWeight > 0.0f) {
                        width += (int) ((mWidth - width) * lp.horizontalWeight);
                        childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
                                MeasureSpec.EXACTLY);
                        measureAgain = true;
                    }
                    if (lp.verticalWeight > 0.0f) {
                        height += (int) ((mHeight - height) * lp.verticalWeight);
                        childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
                                MeasureSpec.EXACTLY);
                        measureAgain = true;
                    }
 
                    if (measureAgain) {
                        if (DEBUG_LAYOUT) Log.v(TAG,
                                "And hey let's measure once more: width=" + width
                                + " height=" + height);
                         performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                    }
 
                    layoutRequested = true;
                }
    } 
可以看到在此处会执行 performMeasure,请求计算view的大小。接下来我们看到view的measure方法
 
measure接受两个参数widthMeasureSpec,heightMeasureSpec。
这两个参数由 ViewRootImpl中的 getRootMeasureSpec中获取
方法
/**
     * Figures out the measure spec for the root view in a window based on it's
     * layout params.
     *
     * @param windowSize
     * The available width or height of the window
     *
     * @param rootDimension
     * The layout params for one dimension (width or height) of the
     * window.
     *
     * @return The measure spec to use to measure the root view.
     */
    private static int getRootMeasureSpec(int windowSize, int rootDimension) {
        int measureSpec;
        switch (rootDimension) {
 
        case ViewGroup.LayoutParams.MATCH_PARENT:
            // Window can't resize. Force root view to be windowSize.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
            break;
        case ViewGroup.LayoutParams.WRAP_CONTENT:
            // Window can resize. Set max size for root view.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
            break;
        default:
            // Window wants to be an exact size. Force root view to be that size.
            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
            break;
        }
        return measureSpec;
    }
 
        public static int makeMeasureSpec(int size, int mode) {
            return size + mode;
        }
}
 
onMeasure(int widthMeasureSpec,int heightMeasureSpec)
measureSpec由specSie和specMode组成
specMode有三个值: 
EXACTLY:父视图希望子视图的大小是由specSize决定   height = heightSize;
AT_MOST:不超过specSize,一般在计算完理论大小的时候执行Math.min(width,specSize);
UNSPECIFIED:
在这个方法最后调用setMeasuredDimension(width, height);
 
onLayout()
在ViewRootImpl中的1844行中会调用 performLayout(lp, desiredWindowWidth, desiredWindowHeight);
 
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
        mLayoutRequested = false;
        mScrollMayChange = true;
        mInLayout = true;
 
        final View host = mView;
        if (DEBUG_ORIENTATION || DEBUG_LAYOUT) {
            Log.v(TAG, "Laying out " + host + " to (" +
                    host.getMeasuredWidth() + ", " + host.getMeasuredHeight() + ")");
        }
 
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
        try {
             host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
 
            mInLayout = false;
            int numViewsRequestingLayout = mLayoutRequesters.size();
            if (numViewsRequestingLayout > 0) {
                // requestLayout() was called during layout.
                // If no layout-request flags are set on the requesting views, there is no problem.
                // If some requests are still pending, then we need to clear those flags and do
                // a full request/measure/layout pass to handle this situation.
                ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,
                        false);
                if (validLayoutRequesters != null) {
                    // Set this flag to indicate that any further requests are happening during
                    // the second pass, which may result in posting those requests to the next
                    // frame instead
                    mHandlingLayoutInLayoutRequest = true;
 
                    // Process fresh layout requests, then measure and layout
                    int numValidRequests = validLayoutRequesters.size();
                    for (int i = 0; i < numValidRequests; ++i) {
                        final View view = validLayoutRequesters.get(i);
                        Log.w("View", "requestLayout() improperly called by " + view +
                                " during layout: running second layout pass");
                        view.requestLayout();
                    }
                    measureHierarchy(host, lp, mView.getContext().getResources(),
                            desiredWindowWidth, desiredWindowHeight);
                    mInLayout = true;
                    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
 
                    mHandlingLayoutInLayoutRequest = false;
 
                    // Check the valid requests again, this time without checking/clearing the
                    // layout flags, since requests happening during the second pass get noop'd
                    validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true);
                    if (validLayoutRequesters != null) {
                        final ArrayList<View> finalRequesters = validLayoutRequesters;
                        // Post second-pass requests to the next frame
                        getRunQueue().post(new Runnable() {
                            @Override
                            public void run() {
                                int numValidRequests = finalRequesters.size();
                                for (int i = 0; i < numValidRequests; ++i) {
                                    final View view = finalRequesters.get(i);
                                    Log.w("View", "requestLayout() improperly called by " + view +
                                            " during second layout pass: posting in next frame");
                                    view.requestLayout();
                                }
                            }
                        });
                    }
                }
 
            }
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
        mInLayout = false;
    }
 
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
中会调用view的layout(0,0,getMeasureWidth(),getMeasureHeight);
 
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); //判断整个view的大小是否发生变化
 
        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            onLayout(changed, l, t, r, b);
            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;
    }
 
在view的layout方法中最后会回调 onLayout(changed, l, t, r, b);以及 onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);此处是一个监听回调函数,可以为空
在view的onLayout方法中并没有做什么操作,所以view最后的坐标会是0,0,getMeasureWidth(),getMeasureHeight();在此处可以研究一下viewgroup的onlayout方法
因为view在位置并不是自己决定的,而是由viewgroup的onlayout方法决定的,viewgroup中的onlayout是一个抽象方法,所以自定义viewgroup一定要实现onlayout方法
 
所以我们可以这么理解,在measure结束后整个view的大小是确定了,在onlayout方法中要确定的是view的一个位置。
 
最后我们会进入到ondraw方法,ondraw方法实际上是对整个view的一个绘制过程。
在最新版本的framework代码中,view的ondraw是一个空的方法
protected void onDraw(Canvas canvas) {
}
google为我们提供了一个绘制顺序:
1、绘制背景
2、如果有必要保存canvas的一些属性,保存画层
3、绘制view的content。
4、绘制view的childen
5、如果有必要会边缘的渐变的内容
6、绘制滚动条
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值