Personalizar el proceso de dibujo Vista-Vista

La actividad en Android existe como portador de la aplicación, que representa una interfaz de usuario completa y proporciona una ventana para dibujar varias vistas. Cuando comience la actividad, estableceremos una vista de contenido a través del método setContentView. Esta vista de contenido es la interfaz de usuario que ver. Entonces, ¿cómo se relacionan la vista y la actividad?

Sistema de renderizado de nivel de interfaz de usuario de Android

5540252-67dc96d2b0f12b7d.pngLa imagen de arriba es la relación entre Vista y Actividad Permítanme presentarles la imagen de arriba primero.

  • PhoneWindow: cada actividad creará una ventana para albergar la visualización de la vista. Window es una clase abstracta, y PhoneWindow es la única clase de implementación de Window, que contiene un DecorView.
  • DecorView: la vista de nivel superior, la vista hereda de FrameLayout y contiene dos partes, una es ActionBar, la otra es ContentView,
  • ContentView: el diseño pasado en nuestro setContentView() se carga y se muestra en la Vista
  • ViewRootImpl: esta clase tiene una instancia de DecorView, a través de la cual se controla DecorView, y finalmente se inicia el dibujo de todo el árbol de vistas ejecutando performTraversals() de ViewRootImpl,

Ver proceso de carga

  • Cuando se llama al método setContentView de la Actividad, se llama al método setContentView de la clase PhoneWindow
public void setContentView(@LayoutRes int layoutResID) {
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}
复制代码
  • Eventualmente se generará un objeto DecorView en el método setContentView de la clase 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());
}
复制代码
  • El contenedor DecorView contiene el diseño raíz. El diseño raíz contiene un diseño FrameLayout con una identificación de contenido. El xml de la Actividad carga el diseño. Finalmente, el contenido en el archivo xml se analiza en una jerarquía de Vista a través de LayoutInflater, y finalmente se agrega al diseño de FrameLayout con una identificación de contenido.
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");
    }

}
复制代码

En este punto, el dibujo de Actvity está completo.

Análisis del proceso de dibujo de la vista de la vista

  • 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) {
}

复制代码

El método onDraw de View es solo una plantilla, el método de implementación específico se deja en manos de los desarrolladores.

En este punto, se completa el proceso de dibujo de la Vista.

  • requestLayout vuelve a dibujar la vista

La Vista secundaria llama al método requestLayout, que marcará la Vista actual y el contenedor principal, y lo enviará capa por capa hasta que ViewRootImpl procese el evento.

  • invalidar la vista de redibujado en el subproceso de la interfaz de usuario

Cuando la Vista secundaria llama al método de invalidación, agregará un bit de marcador para la Vista y, al mismo tiempo, solicitará continuamente una actualización del contenedor principal. El contenedor principal calcula el área que debe volver a dibujarse por sí mismo hasta que se pasa a ViewRootImpl, y finalmente activa el método performTraversals Inicie el proceso de redibujado del árbol de vistas (solo dibuje las vistas que necesitan ser redibujadas).

  • postInvalidar vista de redibujado en subproceso que no es de interfaz de usuario

Este método tiene la misma función que el método invalidate, ambos redibujan el árbol View, pero las condiciones de uso de los dos son diferentes. postInvalidate se llama en un subproceso que no es de UI, mientras que invalidate se llama en el subproceso de UI.

Supongo que te gusta

Origin juejin.im/post/7085242683664367629
Recomendado
Clasificación