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

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 listenersCopy =
(ArrayList)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、绘制滚动条

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值