欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

UI绘制流程及原理

程序员文章站 2022-04-13 12:08:05
...

setContentView解析

UI绘制流程及原理首先,我们的setContentView是在我们的Activity.java类中发现为getWindow().setContentView(layoutResID),其中的getWindow来自Window类,而PhoneWindow为Window类的唯一实现类,所以可以在PhoneWindow中找到setContentView的方法。在这个方法中,可以看到调用了installDecor()方法和mLayoutInflater.inflate(layoutResID, mContentParent),其中后一个为将layoutResID传入到mContentParent容器中。这样我们可以看下installDecor()到底做了什么:

installDecor()方法

进入installDecor()方法我们可以看到此方法做了两件事情:
1.通过generateDecor()方法,返回新创建的DecorView布局;
2.将得到的DecorView传到generateLayout(mDecor)方法中,得到mContentParent容器;

在这个方法中,我们可以找到这两行代码:

int layoutResource;
int features = getLocalFeatures();

其中layoutResource为系统布局id,根据features的不同,赋给layoutResource不同的布局。一直到

mDecor.onResourcesLoaded(mLayoutInflater, layoutResource)

将layoutResource传入到DecorView的onResourcesLoaded()方法中,通过调用addView方法将layoutResource添加到DecorView上。
接着通过ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT),获取id为public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;的系统组件得到contentParent容器,并返回赋给mContentParent。
然后就可以在setContentView()方法中得到mContentParent容器,通过mLayoutInflater.inflate(layoutResID, mContentParent)将我们传入的布局添加到mContentParent上。

View的绘制流程

绘制方法步骤

绘制入口:ActivityThread.handleResumeActivity()方法中:

r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (r.mPreserveWindow) {
    a.mWindowAdded = true;
    r.mPreserveWindow = false;
    // Normally the ViewRoot sets up callbacks with the Activity
    // in addView->ViewRootImpl#setView. If we are instead reusing
    // the decor view we have to notify the view root that the
    // callbacks may have changed.
    ViewRootImpl impl = decor.getViewRootImpl();
    if (impl != null) {
         impl.notifyChildRebuilt();
    }
}
if (a.mVisibleFromClient) {
    if (!a.mWindowAdded) {
         a.mWindowAdded = true;
         wm.addView(decor, l);
     } else {
     // The activity will get a callback for this {@link LayoutParams} change
     // earlier. However, at that time the decor will not be set (this is set
     // in this method), so no action will be taken. This call ensures the
     // callback occurs with the decor set.
        a.onWindowAttributesChanged(l);
     }
}

wm.addView(decor, l);将decorView添加到vm,即ViewManager中,ViewManager为接口类,通过a.getWindowManager()找到Window的getWindowManager,其中返回的mWindowManager的方法为mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);进入createLocalWindowManager(),为WindowManagerImpl.java类的方法,即vm.addView方法为WindowManagerImpl.java中的。在addView方法中调用了WindowManagerGlobal的addView方法,方法root.setView(view, wparams, panelParentView);中又调用了requestLayout();–>scheduleTraversals();通过mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);调用了runable的run()方法–>doTraversal();–>performTraversals();中,得到:
1.测量方法:performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
2.布局方法:performLayout(lp, mWidth, mHeight);
3.绘制方法:performDraw();

详细绘制

performMeasure

调用performMeasure方法后,调用View.java的mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);方法,通过MeasureSpec方法计算布局的宽高,最后调用setMeasuredDimensionRaw()方法得到结果。

  1. 对于ViewGroup : measure() —> onMeasure()(需要测量子View的宽高) -> measureChildWithMargins() -> 子View调用measure()测量宽高(传入的MeasureSpec为父布局的值) -> setMeasuredDimension() -> setMeasuredDimensionRaw()(保存自己的宽高)
  2. 对于View : measure() —> onMeasure() -> setMeasuredDimension() -> setMeasuredDimensionRaw()(保存自己的宽高)

MeasureSpec测量

其中有三种模式:

  1. UNSPECIFIED:父容器不对View做任何限制,一般系统内部使用
  2. EXACTLY : 父容器检测出View的大小,View的大小就是SpecSize
    对应xml的LayoutParams.MATCH_PARENT 或者 固定尺寸大小
  3. AT_MOST : 父容器指定一个可用大小,View的大小不能超过这个值
    对应xml的LayoutParams.WARP_CONTENT

其中makeMeasureSpec()方法可以将mode和size进行组合成为MeasureSpec值。
getMode和getSize方法可以将MeasureSpec转为mode和size值。

在自定义View中,我们设置MATCH_PARENT和WRAP_CONTENT效果一样的原因为:
View的onMeasure方法setMeasuredDimension()

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

中的值调用了getDefaultSize()方法:

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

其中MeasureSpec.AT_MOST和MeasureSpec.EXACTLY所返回的size均为specSize

performLayout

  1. ViewGroup : layout(来确定自己的位置,4个点的位置) —> onLayout(进行子View的布局)
  2. View : layout(来确定自己的位置,4个点的位置)
    自定义ViewGroup时要重写onLayout方法确定其中的子View的布局位置

performDraw

ViewGroup :

  1. 绘制背景 drawBackground(canvas)
  2. 绘制自己onDraw(canvas)
  3. 绘制子View dispatchDraw(canvas) ViewGroup已经实现了
  4. 绘制前景,滚动条等装饰onDrawForeground(canvas)

View :

  1. 绘制背景 drawBackground(canvas)
  2. 绘制自己onDraw(canvas)
  3. 绘制前景,滚动条等装饰onDrawForeground(canvas)