Personnaliser le processus de dessin Vue-Vue

L'activité dans Android existe en tant que support de l'application, qui représente une interface utilisateur complète et fournit une fenêtre pour dessiner diverses vues. Lorsque l'activité démarre, nous allons définir une vue de contenu via la méthode setContentView. Cette vue de contenu est l'interface utilisateur que vous voir. Alors, comment la vue et l'activité sont-elles liées ?

Système de rendu au niveau de l'interface utilisateur d'Android

5540252-67dc96d2b0f12b7d.pngL'image ci-dessus est la relation entre la vue et l'activité. Permettez-moi d'abord de présenter l'image ci-dessus.

  • PhoneWindow : chaque activité créera une fenêtre pour héberger l'affichage de la vue. Window est une classe abstraite et PhoneWindow est la seule classe d'implémentation de Window qui contient un DecorView.
  • DecorView : la vue de niveau supérieur, la vue hérite de FrameLayout, et elle contient deux parties, l'une est ActionBar, l'autre est ContentView,
  • ContentView : la mise en page transmise dans notre setContentView() est chargée et affichée dans la vue
  • ViewRootImpl : cette classe a une instance de DecorView, à travers laquelle le DecorView est contrôlé, et enfin le dessin de l'ensemble de l'arborescence de la vue est lancé en exécutant performTraversals() de ViewRootImpl,

Afficher le processus de chargement

  • Lorsque la méthode setContentView de l'activité est appelée, la méthode setContentView de la classe PhoneWindow est appelée
public void setContentView(@LayoutRes int layoutResID) {
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}
复制代码
  • Un objet DecorView sera éventuellement généré dans la méthode setContentView de la classe PhoneWindow
@Override
public void setContentView(int layoutResID) {
    if (mContentParent == null) {
         //在这里生成一个DecorView
        installDecor();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        mContentParent.removeAllViews();
    }
    ...
}


private void installDecor() {
    mForceDecorInstall = false;
    //mDecor  为DecorView
    if (mDecor == null) {
        mDecor = generateDecor(-1);
        mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
        mDecor.setIsRootNamespace(true);
        if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
            mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
        }
    } else {
        mDecor.setWindow(this);
    }
    ...
 }
 
 
protected DecorView generateDecor(int featureId) {
   ...
   // 在这里直接 new 了一个DecorView
   return new DecorView(context, featureId, this, getAttributes());
}
复制代码
  • Le conteneur DecorView contient la mise en page racine. La mise en page racine contient une mise en page FrameLayout avec un identifiant de contenu. Le xml de l'activité charge la mise en page. Enfin, le contenu du fichier xml est analysé dans une hiérarchie de vues via LayoutInflater, et enfin ajouté à la mise en page FrameLayout avec un identifiant de contenu. .
protected ViewGroup generateLayout(DecorView decor) {
    //做一些窗体样式的判断
       ...
     //给窗体进行装饰
    int layoutResource;
    int features = getLocalFeatures();
    // System.out.println("Features: 0x" + Integer.toHexString(features));
    //加载系统布局 判断到底是加载那个布局
    if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
        layoutResource = R.layout.screen_swipe_dismiss;
        setCloseOnSwipeEnabled(true);
    }  
...
    mDecor.startChanging();
    //将加载到的基础布局添加到mDecor中
    mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
    //通过系统的content的资源ID去进行实例化这个控件
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    if (contentParent == null) {
        throw new RuntimeException("Window couldn't find content container view");
    }

}
复制代码

À ce stade, le dessin d'Activity est terminé

Analyse du processus de dessin de vue de vue

  • DecorView被加载到Window中

在ActivityThread的 handleResumeActivity() 方法中通过WindowManager将DecorView加载到Window中,通过ActivityThread中一下代码可以得到应征

final void handleResumeActivity(IBinder token,
        boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
      ...
      //在此处执行Activity的onResume方法
    r = performResumeActivity(token, clearHide, reason);

    if (r != null) {
        final Activity a = r.activity;

        if (localLOGV) Slog.v(
            TAG, "Resume " + r + " started activity: " +
            a.mStartedActivity + ", hideForNow: " + r.hideForNow
            + ", finished: " + a.mFinished);

        final int forwardBit = isForward ?
                WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0;
        boolean willBeVisible = !a.mStartedActivity;
        if (!willBeVisible) {
            try {
                willBeVisible = ActivityManagerNative.getDefault().willActivityBeVisible(
                        a.getActivityToken());
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
        if (r.window == null && !a.mFinished && willBeVisible) {
            //获取window对象
            r.window = r.activity.getWindow();
            //获取DecorView
            View decor = r.window.getDecorView();
            decor.setVisibility(View.INVISIBLE);
            //获取WindowManager,在这里getWindowManager()实质上获取的是ViewManager的子类对象WindowManager
            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;
                 //获取ViewRootImpl对象
                ViewRootImpl impl = decor.getViewRootImpl();
                if (impl != null) {
                    impl.notifyChildRebuilt();
                }
            }
            if (a.mVisibleFromClient && !a.mWindowAdded) {
                a.mWindowAdded = true;
                //在这里WindowManager将DecorView添加到PhoneWindow中
                wm.addView(decor, l);
            }
        } 
复制代码

总结:在ActivityThread的handleResumeActivity方法中WindowManager将DecorView添加到PhoneWindow中,addView()方法执行时将视图添加的动作交给了ViewRootImpl处理,最后在ViewRootImpl的performTraversals中开始View树的绘制

ViewRootImpl的performTraversals()方法完成具体的视图绘制流程

private void performTraversals() {

    if (!mStopped || mReportNextDraw) {
        ...
        int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
        int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
          ...
         // Ask host how big it wants to be
         //View绘制:开始测量 View的测量时递归逐层测量,由父布局与子布局共同确认子View的测量模式,在子布局测量完毕时确认副布局的宽高,
         //在此方法执行完毕后才可获取到View的宽高,否侧获取的宽高都为0
        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
       }
       
      
    if (didLayout) {
    //开始摆放,该方法是ViewGroup中的方法,例如 LinerLayout...
        performLayout(lp, mWidth, mHeight);
    }
    
    if (!cancelDraw && !newSurface) {
        //开始绘制,执行View的onDraw()方法
        performDraw();
    }
}
复制代码

下面开始对performMeasure(),performLayout(),performDraw()进行解析

  • performMeasure()
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
    try {
        mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}
复制代码

通过以上这段代码,我们可以看到两个重要的参数 childWidthMeasureSpec,childHeightMeasureSpec,这两个Int类型的参数包含了View的测量模式和宽高信息,因此在onMeasure()方法中我们可以通过该参数获取到测量模式,和宽高信息,我们在onMeasue中设置宽高信息也是通过MeasureSpec设置,

 */
public static class MeasureSpec {
    //int类型占4个字节,其中高2位表示尺寸测量模式,低30位表示具体的宽高信息
    private static final int MODE_SHIFT = 30;
    private static final int MODE_MASK  = 0x3 << MODE_SHIFT;

    /** @hide */
    @IntDef({UNSPECIFIED, EXACTLY, AT_MOST})
    @Retention(RetentionPolicy.SOURCE)
    public @interface MeasureSpecMode {}

    
    //如下所示是MeasureSpec中的三种模式:UNSPECIFIED、EXACTLY、AT_MOST 
    //UNSPECIFIED:未指定模式,父容器不限制View的大小,一般用于系统内部的测量
    public static final int UNSPECIFIED = 0 << MODE_SHIFT;
    //AT_MOST:最大模式,对应于在xml文件中指定控件大小为wrap_content属性,子View的最终大小是父View指定的大小值,并且子View的大小不能大于这个值
    public static final int EXACTLY     = 1 << MODE_SHIFT;
    //EXACTLY :精确模式,对应于在xml文件中指定控件为match_parent属性或者是具体的数值,父容器测量出View所需的具体大小
    public static final int AT_MOST     = 2 << MODE_SHIFT;

   
    //获取测量模式
    @MeasureSpecMode
    public static int getMode(int measureSpec) {
        //noinspection ResourceType
        return (measureSpec & MODE_MASK);
    }

   //获取宽高信息
    public static int getSize(int measureSpec) {
        return (measureSpec & ~MODE_MASK);
    }
     ...
}
复制代码

performMeasure()会继续调用mView.measure()方法

 public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        boolean optical = isLayoutModeOptical(this);
        if (optical != isLayoutModeOptical(mParent)) {
            Insets insets = getOpticalInsets();
            int oWidth  = insets.left + insets.right;
            int oHeight = insets.top  + insets.bottom;
            //根据原有宽高计算获取不同模式下的具体宽高值
            widthMeasureSpec  = MeasureSpec.adjust(widthMeasureSpec,  optical ? -oWidth  : oWidth);
            heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
        }
        ...
        if (forceLayout || needsLayout) {
            // first clears the measured dimension flag
            mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;

            resolveRtlPropertiesIfNeeded();

            int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
            if (cacheIndex < 0 || sIgnoreMeasureCache) {
                // measure ourselves, this should set the measured dimension flag back
                //在该方法中子控件完成具体的测量
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                ...
            } 
         ...
    }
复制代码

从上述代码片段中可以看到执行到了onMeasure()方法,如果该控件为View的话,测量到此结束,如果是ViewGroup的话,会继续循环获取所有子View,调用子View的measure方法,下面以LinearLayout为例,继续看

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    if (mOrientation == VERTICAL) {
        measureVertical(widthMeasureSpec, heightMeasureSpec);
    } else {
        measureHorizontal(widthMeasureSpec, heightMeasureSpec);
    }
}
复制代码

LinearLayout通过不同的摆放布局执行不同的测量方法,以measureVertical为例,向下看

void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
    //获取子View的个数
    final int count = getVirtualChildCount();
        ...
    //循环获取所有子View
    for (int i = 0; i < count; ++i) {
        //获取子View
        final View child = getVirtualChildAt(i);
        //调用子View的measure方法
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }
    ....
}
复制代码

至此,View的测量流程结束

View的layout流程分析

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
        int desiredWindowHeight) {
  
        final View host = mView;
          // 在此处调用mView的layout()摆放开始
        host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
     }
复制代码

/*  
 *@param l view 左边缘相对于父布局左边缘距离 
 *@param t view 上边缘相对于父布局上边缘位置 
 *@param r view 右边缘相对于父布局左边缘距离 
 *@param b view 下边缘相对于父布局上边缘距离 
 */  
public void layout(int l, int t, int r, int b) {
      ...

   //记录 view 原始位置  
    int oldL = mLeft;
    int oldT = mTop;
    int oldB = mBottom;
    int oldR = mRight;

  //调用 setFrame 方法 设置新的 mLeft、mTop、mBottom、mRight 值,  
  //设置 View 本身四个顶点位置  
  //并返回 changed 用于判断 view 布局是否改变  
    boolean changed = isLayoutModeOptical(mParent) ?
            setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

 //第二步,如果 view 位置改变那么调用 onLayout 方法设置子 view 位置 
    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
        //开始调用 onLayout  在此处根据子View的宽高及相关规则进行摆放
        onLayout(changed, l, t, r, b);
          ...
            }
        }
    }
}
复制代码

View的Draw流程分析

private void performDraw() {
        ...
          //调用draw方法
        draw(fullRedrawNeeded);
        ...
    }
    
    
private void draw(boolean fullRedrawNeeded) {
        ...
     //View的绘制流程调用的   drawSoftware() 该方法
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
    return;
}


private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
        boolean scalingRequired, Rect dirty) {
    final Canvas canvas;
       ...
    //初始化画布
    canvas = mSurface.lockCanvas(dirty);
    ...
    //开始调用ViewGroup 和  View的draw方法
    mView.draw(canvas);
    ...
}


public void draw(Canvas canvas) {
 
    drawBackground(canvas);

    //ViewGroup  默认是不会调用OnDraw方法的
    if (!dirtyOpaque) onDraw(canvas);

    //这个方法主要是ViewGroup循环调用 drawChild()进行对子View的绘制

    dispatchDraw(canvas); 

}



protected void onDraw(Canvas canvas) {
}

复制代码

La méthode onDraw de View n'est qu'un modèle, la méthode d'implémentation spécifique est laissée aux développeurs pour l'implémenter

À ce stade, le processus de dessin de la vue est terminé

  • requestLayout redessine la vue

La vue enfant appelle la méthode requestLayout, qui marquera la vue actuelle et le conteneur parent, et la soumettra couche par couche jusqu'à ce que ViewRootImpl traite l'événement.

  • invalider la vue de redessin dans le fil de l'interface utilisateur

Lorsque la vue enfant appelle la méthode invalidate, elle ajoute un bit de marqueur pour la vue et, en même temps, elle demande en permanence une actualisation au conteneur parent. Le conteneur parent calcule lui-même la zone qui doit être redessinée jusqu'à ce qu'il est passé à ViewRootImpl, et déclenche finalement la méthode performTraversals. Démarrez le processus de rafraîchissement de l'arborescence des vues (ne dessinez que les vues qui doivent être redessinées).

  • postInvalidate redessiner la vue dans le fil non-UI

Cette méthode a la même fonction que la méthode invalidate, qui redessine l'arborescence de la vue, mais les conditions d'utilisation des deux sont différentes. postInvalidate est appelé dans un thread non-UI, tandis que invalidate est appelé dans le thread UI.

Je suppose que tu aimes

Origine juejin.im/post/7085242683664367629
conseillé
Classement