从Android源码的角度了解UI的绘制流程

前言:内容稍显枯燥,若有兴趣,一起交流经验

View是如何被添加到屏幕窗口上

1、创建顶层布局容器DecorView

首先,从activity的setContentView方法入手,其实是调用了 Window的setContentView方法:

public void setContentView(int layoutResID) {
        //这里的getWindow就是得到一个window对象
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

Window其实是一个抽象类,在顶部的注释中有这样一句话:

The only existing implementation of this abstract class is
android.policy.PhoneWindow, which you should instantiate when needing a Window.
当需要Window时,PhoneWindow是唯一的实现。

而PhoneWindow是Window的实现类,那么activity的的setContentView最终也是在PhoneWindow的setContentView方法实现一系列的逻辑。去PhoneWindow类找到对应方法,发现主要做了两件事:

@Override
    public void setContentView(int layoutResID) {
        if (mContentParent == null)
            //①第一件事情,具体是什么下面再说...
            installDecor();
        } else if(){
           //省略
        }

        if ()) {
           //省略
        } else {
        //②第二件事情,解析了在activity传入的布局资源
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        //省略many
    }

我们先看installDecor()做了什么操作,发现最终直接new了一个DecorView,跟踪发现其实DecorView是集成自FrameLayout,说明它是一个容器(埋下伏笔)

private void installDecor() {
        if (mDecor == null) {
            mDecor = generateDecor();
        }
        //省略
        
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);
}

protected DecorView generateDecor() {
        return new DecorView(getContext(), -1);
    }

2、在顶层布局中加载基础布局ViewGroup

protected ViewGroup generateLayout(DecorView decor) {
    //省略一些通过主题设置的一些参数的代码
    
    //下面这一段有一个共同点,都是 layoutResource = R.layout.xxx,说明是根据不同的情况设置不同不急资源
    
    // Inflate the window decor.
    int layoutResource;
    int features = getLocalFeatures();
    if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
            layoutResource = R.layout.screen_swipe_dismiss;
        } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogTitleIconsDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else {
                layoutResource = R.layout.screen_title_icons;
            }
            // XXX Remove this once action bar supports these features.
            removeFeature(FEATURE_ACTION_BAR);
            // System.out.println("Title Icons!");
        } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
                && (features & (1 << FEATURE_ACTION_BAR)) == 0) {
            // Special case for a window with only a progress bar (and title).
            // XXX Need to have a no-title version of embedded windows.
            layoutResource = R.layout.screen_progress;
            // System.out.println("Progress!");
        } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
            // Special case for a window with a custom title.
            // If the window is floating, we need a dialog layout
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogCustomTitleDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else {
                layoutResource = R.layout.screen_custom_title;
            }
            // XXX Remove this once action bar supports these features.
            removeFeature(FEATURE_ACTION_BAR);
        } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
            // If no other features and not embedded, only need a title.
            // If the window is floating, we need a dialog layout
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogTitleDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
                layoutResource = a.getResourceId(
                        R.styleable.Window_windowActionBarFullscreenDecorLayout,
                        R.layout.screen_action_bar);
            } else {
                layoutResource = R.layout.screen_title;
            }
            // System.out.println("Title!");
        } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
            layoutResource = R.layout.screen_simple_overlay_action_mode;
        } else {
            // Embedded, so no decoration is needed.
            layoutResource = R.layout.screen_simple;
            // System.out.println("Simple!");
        }
        
        //最后在这里,创建了一个View,然后把View添加到了decor上
        View in = mLayoutInflater.inflate(layoutResource, null);
        decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        mContentRoot = (ViewGroup) in;
        //不同的布局为文件布局都是framelayout并且ID都是 com.android.internal.R.id.content
        

3、将ContentView添加到基础布局的FrameLayout

    //继续跟踪源码
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    /** 
     * 这个ID定义在Window中,不再此方法中类中
     * The ID that the main layout in the XML layout file should have.主容器中一定存在的ID
     */
    public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
    
    //最后在这里直接返回了contentParent
    return contentParent;
    
    //最后在setContentView方法中,将我们的父容器添加到基础容器中
    mLayoutInflater.inflate(layoutResID, mContentParent);
从一张图看整个流程

image
其实从上面的分析来看,在activity设置的 R.layout.xxx 最后都显示到contentParent上了,也就是基础布局的framelayout;难得码文字了,一张图小结一下:

image

View的绘制流程

1、绘制入口、类和方法

ActivityThread类中找到一个handleMessage() 方法,这个方法存在于一个集成自handlerH 的对象,是主线程处理消息的一个对象,找到case LAUNCH_ACTIVITY:

case LAUNCH_ACTIVITY: 
   //省略源码
   
   这里启动一个activity
    handleLaunchActivity(r, null);
    
//进入handleLaunchActivity()方法
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    //省略源码

    //这里执行了启动activity的操作,在performLaunchActivity方法中调用了activity的attach()方法,在attach()执行了并创建上一节讲到的window相关的东西
    Activity a = performLaunchActivity(r, customIntent);
    
    //继续往下看
    if (a != null) {
        //省略源码
        //这里执行了下面这个方法
        handleResumeActivity(r.token, false, r.isForward,
                    !r.activity.mFinished && !r.startsNotResumed);
                    
        //进入这个方法
        final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume) {
        // TODO Push resumeArgs into the activity for consideration
        //这里其实就是回调了activity的onResume()方法
        ActivityClientRecord r = performResumeActivity(token, clearHide);
        
        //继续往下看
        if (r.window == null && !a.mFinished && willBeVisible) {
                //获取到DecorView对象
                r.window = r.activity.getWindow();
                View decor = r.window.getDecorView();
                decor.setVisibility(View.INVISIBLE);
                
                //获取ViewManager对象
                ViewManager wm = a.getWindowManager();
                WindowManager.LayoutParams l = r.window.getAttributes();
                a.mDecor = decor;
                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
                l.softInputMode |= forwardBit;
                if (a.mVisibleFromClient) {
                    a.mWindowAdded = true;
                    //将DecorView添加到ViewManager去
                    wm.addView(decor, l);
                }

            // If the window has already been added, but during resume
            // we started another activity, then don't yet make the
            // window visible.
            }
        
        //a.getWindowManager()肯定返回的是WindowManager对象,追踪源码得到如下,而mWindow上面说到是Window对象并且只有PhoneWindow的唯一实现类
        mWindowManager = mWindow.getWindowManager();
    
        //然后去PhoneWindow寻找getWindowManager(),发现其实调用了其父类的getWindowManager()方法,最后在追踪到Window的setWindowManager方法:
        public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
            boolean hardwareAccelerated) {
        mAppToken = appToken;
        mAppName = appName;
        mHardwareAccelerated = hardwareAccelerated
                || SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
        if (wm == null) {
            wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
        }
        
        //这里最终的到了mWindowManager并强转成了WindowManagerImpl
        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
    }
    
    //继续追踪WindowManagerImpl,去寻找在handleResumeActivity()中调用的addView()方法
    mGlobal.addView(view, params, mDisplay, mParentWindow);
    
    //继续追踪mGlobal
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    
    //继续追踪WindowManagerGlobal的addView方法
    //省略部分源码
    
    //创建了ViewRootImpl实例
    root = new ViewRootImpl(view.getContext(), display);
    root.setView(view, wparams, panelParentView);
    
    //继续跟踪ViewRootImpl的setView()方法,寻找关键的方法
    requestLayout();
    
    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }
    
    //追踪checkThread()方法,表示当前线程是否在主线程
    void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }
    
    //追踪scheduleTraversals()方法
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
           //省略部分源码
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
                    
            //这里有个mTraversalRunnable其实是一个Runnable
        }
    }
    
    //追踪mTraversalRunnable这个线程
    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
    
    //继续追踪doTraversal()方法
    void doTraversal() {
        if (mTraversalScheduled) {
            //省略源码
            performTraversals();
            //省略源码
        }
    }
    
    //继续追踪performTraversals()方法
    private void performTraversals() {
        //省略很多行源码
        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
        //再省略很多行源码
        performLayout(lp, desiredWindowWidth, desiredWindowHeight);
        //继续省略源码
        performDraw();
    }
}

其实分析到这里,从方法名就可以发现这里主要就是执行了绘制的三大步骤了,具体下节继续分析;

这里小结一下:
//①绘制入口
ActivityThread.handleResumeActivity()
    ->WindowManagerImpl.addView(dercorView,layoutParams)
    ->WindowManagerGlobal.addView()

//②绘制的类及方法
ViewRootImpl.setView(decorView, layoutParams, panelParentView)
    ->ViewRootImpl.requestLayout()
                    ->scheduleTraversals()
                    ->doTraversal()
                    ->performTraversals()//这里执行真正的绘制流程三大步骤

2、绘制三大步骤

onMeasure(测量):确定DecorView的MeasureSpec
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
        try {
            //这里调用的View的measure方法,最终调用了onMeasure()方法
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
}

//继续跟踪发现执行了以下的方法调用
onMeasure(widthMeasureSpec, heightMeasureSpec)
    ->setMeasuredDimension(measuredWidth, measuredHeight)
    ->setMeasuredDimensionRaw(measuredWidth, measuredHeight)
    
    //最终在这里确定控件的了宽高
    private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
        mMeasuredWidth = measuredWidth;
        mMeasuredHeight = measuredHeight;
    }
    
    //那么宽高是如何确定的,回到setMeasuredDimension()方法,其实他并不是直接传的measuredWidth和measuredHeight,而是通过一个叫getDefaultSize(getSuggestedMinimumWidth()(或getSuggestedMinimumHeight()), widthMeasureSpec(或heightMeasureSpec),getSuggestedMinimumXXX()方法返回的是默认的最小宽度(高度)的最大值和背景的最小宽度(高度)
    //这里有一个MeasureSpec对象,其实是封装了大小尺寸和模式
    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;
    }
    
    //MeasureSopec类部分源码
    public static class MeasureSpec {
        private static final int MODE_SHIFT = 30;//mode移位数
    
        //以下定义了三种模式,会根据不同的模式确定控件的大小,还有一个向左移位30位的操作,MeasureSpec其实是mode和一个32位的int值的封装,而要区分三种状态至少要两位所以mode就是前两位的值,而后30位表示宽高的值
        /**
         * Measure specification mode: The parent has not imposed any constraint
         * on the child. It can be whatever size it wants.
         * 未指定大小,这种情况很少见
         */
        public static final int UNSPECIFIED = 0 << MODE_SHIFT;

        /**
         * Measure specification mode: The parent has determined an exact size
         * for the child. The child is going to be given those bounds regardless
         * of how big it wants to be.
         * 精确模式:父容器没有施加任何约束在子控件,宽高就是传入的固定宽高(match_parent或30dp等)
         */
        public static final int EXACTLY = 1 << MODE_SHIFT;

        /**
         * Measure specification mode: The child can be as large as it wants up
         * to the specified size.
         * 最大模式:父容器已经约束了子控件最大只能达到自己的大小,那么此时控件就是自适应的大小(wrap_content)
         */
        public static final int AT_MOST = 2 << MODE_SHIFT;
    }
    
    //那么执行测量前,控件宽高的MeasureSpec对象来自哪里,回溯源码在performTraversals()方法:
    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
     // Ask host how big it wants to be
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    
    /**
     * @param windowSize 窗口的宽或高
     * @param rootDimension 布局的传入的宽高参数
     * 根据不同的参数打包封装不同的MeasureSpec对象
     */
    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;
    }

小结一下,DecorView的MeasureSpec由窗口大小和自身的LayoutParams决定:

  • LayoutParams.MATCH_PARENT:精确模式,窗口大小,
  • LayoutParams.MATCH_PARENT:最大模式,最大为窗口大小,
  • 固定大小(30dp/px):精确模式,大小为30dp/px;
onMeasure(测量):确定View的MeasureSpec

前面介绍到,DecorView上的基础容器和父容器都是FrameLayout,继续去看FrameLayout的**onMeasure()**方法:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int count = getChildCount();//这里获取子view的数量

    //循环每个子View并调用measureChildWithMargins()
    for (int i = 0; i < count; i++) {
        final View child = getChildAt(i);
        if (mMeasureAllChildren || child.getVisibility() != GONE) {
            measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
        }
    }
}

//继续跟踪measureChildWithMargins()
protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
    //获取子view的参数
    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
    //设置子view宽的MeasureSpec对象
    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
            mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width);
    //设置子view宽的MeasureSpec对象
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                        + heightUsed, lp.height);
    //开始子view的测量
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

/**
 * 跟踪getChildMeasureSpec()方法
 *
 * @param spec 父容器的MeasureSpec
 * @param padding 子view设置的各种边距
 * @param childDimension 子view的宽/高参数
 */
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        int specMode = MeasureSpec.getMode(spec);//获取父容器的模式
        int specSize = MeasureSpec.getSize(spec);//获取父容器的宽/高参数

        int size = Math.max(0, specSize - padding);

        int resultSize = 0;
        int resultMode = 0;
        
        //先判断父容器的模式,因为父容器的模式会影响子view的大小测量
        switch (specMode) {
        // 父容器为精确模式
        case MeasureSpec.EXACTLY:
            if (childDimension >= 0) { 
                //子view是固定大小,大小就为固定大小,模式为精确
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) { 
                //大小为父容器大小减去padding,模式为精确
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // 最大不能超过父容器剩余的大小,这是view自己决定的,暂时就给父容器剩余的大小
                // 模式为最大模式
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent has imposed a maximum size on us
        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
                // Child wants a specific size... so be it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size, but our size is not fixed.
                // Constrain child to not be bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent asked to see how big we want to be
        case MeasureSpec.UNSPECIFIED:
            if (childDimension >= 0) {
                // Child wants a specific size... let him have it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size... find out how big it should
                // be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size.... find out how
                // big it should be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        //noinspection ResourceType
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

当一个容器测量时,会先遍历进行子view的测量,先获取子view的MeasureSpec然后调用子view的onMeasure()方法;

View的MeasureSpec由父容器的MeasureSpec和自身的LayoutParams决定,整理如下图:
image

当子view测量完成后,父容器要确定自己的大小:

setMeasuredDimension(int measuredWidth, int measuredHeight) //可以看出是根据子控件大大小来确定自身大小

下面通过一张图来说明父容器最终的大小,也就是需要找到所有子view中最右边的和最下边的子view的宽高
image
小结一下:

viewgroup的测量:
    measure -> onMeasure(测量子view的宽高) -> setMeasuredDimension(根据子view确定自己的宽高) -> setMeasuredDimensionRaw(保存自身的宽高)
view的测量:
    child.measure() -> onMeasure() -> setMeasuredDimension() -> setMeasuredDimensionRaw(保存自身的宽高)
onLayout 确定布局位置

在ViewRootImpl中找到布局的performLayout()方法追踪源码:

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
    
    //省略源码
    
    //将DecorView赋值给host
    final View host = mView;
    host.layout(
                0, 
                0, 
                host.getMeasuredWidth(),  //获取测量得到的宽
                host.getMeasuredHeight()); //获取测量得到的高
    
    //省略源码
}

//继续追踪host.layout()方法
public void layout(int l, int t, int r, int b) {
   //省略源码
   
   //这里确定了控件位置setOpticalFrame()最终还是调用了setFrame()
    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);
}

//追踪setFrame(),其实这里就明确的给view分配了大小的将要绘制的位置
protected boolean setFrame(int left, int top, int right, int bottom) {
        if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
            changed = true;

            // Remember our drawn bit
            int drawn = mPrivateFlags & PFLAG_DRAWN;

            int oldWidth = mRight - mLeft;
            int oldHeight = mBottom - mTop;
            int newWidth = right - left;
            int newHeight = bottom - top;
            boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);

            // Invalidate our old position
            invalidate(sizeChanged);

            mLeft = left;
            mTop = top;
            mRight = right;
            mBottom = bottom;
            mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
}

//在layout()方法中有一个onLayout()方法,追踪发现这是一个空方法,如果我们是一个普通view,那么不需要实现和处理,如果是一个容器,那么需要重写这个方法,确定子view的位置
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {}

image
FrameLayout是一个容器,去看一下他的onLayout()方法:

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        layoutChildren(left, top, right, bottom, false /* no force left gravity */);
    }

    //确定子view的位置
    void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
        final int count = getChildCount();

        final int parentLeft = getPaddingLeftWithForeground();
        final int parentRight = right - left - getPaddingRightWithForeground();

        final int parentTop = getPaddingTopWithForeground();
        final int parentBottom = bottom - top - getPaddingBottomWithForeground();
        
        //遍历子view
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();

                final int width = child.getMeasuredWidth();
                final int height = child.getMeasuredHeight();

                int childLeft;
                int childTop;

                int gravity = lp.gravity;
                if (gravity == -1) {
                    gravity = DEFAULT_CHILD_GRAVITY;
                }

                final int layoutDirection = getLayoutDirection();
                final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
                final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;

                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                    case Gravity.CENTER_HORIZONTAL:
                        childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
                        lp.leftMargin - lp.rightMargin;
                        break;
                    case Gravity.RIGHT:
                        if (!forceLeftGravity) {
                            childLeft = parentRight - width - lp.rightMargin;
                            break;
                        }
                    case Gravity.LEFT:
                    default:
                        childLeft = parentLeft + lp.leftMargin;
                }

                switch (verticalGravity) {
                    case Gravity.TOP:
                        childTop = parentTop + lp.topMargin;
                        break;
                    case Gravity.CENTER_VERTICAL:
                        childTop = parentTop + (parentBottom - parentTop - height) / 2 +
                        lp.topMargin - lp.bottomMargin;
                        break;
                    case Gravity.BOTTOM:
                        childTop = parentBottom - height - lp.bottomMargin;
                        break;
                    default:
                        childTop = parentTop + lp.topMargin;
                }
                
                //这里又调用了子view的layout(),实现一个递归操作
                child.layout(childLeft, childTop, childLeft + width, childTop + height);
            }
        }
    }

小结一下:

view 调用layout()确定其位置;
viewGroup 调用onLayout()确定子view位置,子view在调用layout()
View的绘制

在ViewRootImpl类找到performDraw()方法,依次寻找关键方法:

performDraw()
    ->draw()
    ->drawSoftware()
    ->mView.draw(canvas)这里调用了View的draw()
    
    //追踪进入draw()
    public void draw(Canvas canvas) {
      
        /*
         * 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 绘制子View
         *      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);
        }

        if (!verticalEdges && !horizontalEdges) {
           
            //②、绘制自身内容
            if (!dirtyOpaque) onDraw(canvas);
        
            //③、绘制子View
            dispatchDraw(canvas);

            //绘制前景等装饰
            onDrawForeground(canvas);
    }

view的draw(canvas)和dispatchDraw(canvas)都是空方法,所以自定义View需要实现draw(),如果是自定义容器就还需要实现和dispatchDraw()
小结一下:

View的绘制分一下四个步骤:
    1、绘制背景drawBackground(canvas)
    2、绘制自己onDraw(canvas)
    3、绘制子View dispatchDraw(canvas)(当是容器时)
    4、绘制前景、滚动条等装饰onDrawForekground(canvas)

以上就是从Window到最终的绘制的流程,如果需要自定义View,那么需要重写以下方法:

onMeasure() -> onLayout(是容器时必须重写,只是单纯的view可不重写) -> onDraw(当是容器时可不用重写)

后记:工作之余,偶尔深入,如有披露,望指正

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值