Android自定义View之View的绘制流程

源码版本为 Android 10(Api 29),不同Android版本可能有一些差别

View 的绘制从哪里开始

《Activity常见问题》的 Activity 在 onResume 之后才显示的原因是什么? 部分中我们知道了View是在 onResume() 回调之后才显示出来的,显示过程主要是通过 WindowManagerImpl#addView() -> WindowManagerGlobal#addView() -> ViewRootImpl#setView() 这个过程,我们再次看一下 ViewRootImpl#setView() 的代码(核心代码):

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
     synchronized (this) {
         if (mView == null) {
             mView = view;
             // 调用 requestLayout() 方法,进行布局(包括measue、layout、draw)
             requestLayout();
             
             mOrigWindowType = mWindowAttributes.type;
             mAttachInfo.mRecomputeGlobalAttributes = true;
             collectViewAttributes();
             // 通过调用 Session 的 addToDisplay() 方法
             res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                     getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
                     mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                     mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
                     mTempInsets);
             setFrame(mTmpFrame);
         }
     }
 }

有这样一行 requestLayout() ,表示请求布局,我们界面的绘制也是从这一行代码开始的,接下来,我们就来看一下跟踪一下这段代码(ViewRootImpl#requestLayout()):

@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }
}

调用 ViewRootImpl#scheduleTraversals() 方法:

@UnsupportedAppUsage
void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

在 scheduleTraversals() 方法中通过 mChoreographer.postCallback() 方法发送一个要执行的实现了 Runnable 的 TraversalRunnable 的对象 mTraversalRunnable:

1. mChoreographer.postCallback() 方法内部就是通过Handler机制
2. TraversalRunnable 为 ViewRootImpl 的内部类

final class TraversalRunnable implements Runnable {
    @Override
    public void run() {
        doTraversal();
    }
}

调用 ViewRootImpl#doTraversal() 方法

void doTraversal() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

        if (mProfile) {
            Debug.startMethodTracing("ViewAncestor");
        }

        performTraversals();

        if (mProfile) {
            Debug.stopMethodTracing();
            mProfile = false;
        }
    }
}

调用 ViewRootImpl#performTraversals() 方法,该方法中关于绘制的代码

private	void performTraversals(){
	……
	// 方法测量组件的大小
	performMeasure(childWidthMeasureSpec,childHeightMeasureSpec);
	……
	// 方法用于子组件的定位(放在窗口的什么地方)
	performLayout(lp,desiredWindowWidth,desiredWindowHeight);
	……
	// 绘制组件内容
	performDraw();
	……
}

而在performMeasure()、performLayout()和performDraw()方法的调用过程可以用下面的图来表示:

从图中可以看出系统的View类已经写好了measure()、layout()和draw()方法:

1. 在系统View类中measure()方法用了final修饰,不能被重写(我觉得这应该是google不想让开发者更改measure()方法里面的逻辑而设计的,但是开发者有时又有需求需要自己测量,所以提供了onMeasure()方法可以重写);
代码
2. 在系统View类中的layout()方法在调用onLayout()方法前调用了setFrame()方法,这个方法作用是判断View的位置是否发生改变,如果没有发生改变,就不调用onLayout()方法,主要是为了提高性能,在View类中onLayout()方法是空实现,这是因为view没有子类,而当在自定义的控件如果是直接继承ViewGroup时就必须重写onLayout()方法;
代码
3. 在系统View类中的draw()方法,开发者一般不会重写,因为当我们如果重写draw()时,就需要按照系统定义好的步骤一步一步的画,否则会显示不出来,相对来说比较麻烦。而如果我们实现onDraw()方法,我们只要关注我们画的内容即可(画出来的内容就是显示到界面的内容);
代码
4. 当开发者在自定义控件时

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值