Android View的加载和绘制过程

View 的加载过程

先附一张视图构成

(参考:https://www.jianshu.com/p/217b205f84b8
https://blog.csdn.net/yanbober/article/details/45970721 )

在这里插入图片描述

(参考:https://www.jianshu.com/p/012745b7c1c8 )

从Activity 的 onCreate() 方法执行 setContentView(view) 开始,执行PhoneWindow 的setContentView(view) 方法(基于android api 28)
在这里插入图片描述PhoneWindow 是Window的一个实现,setContentView源码解释如下:将屏幕内容设置为显式视图。该视图被直接放进了屏幕的视图层次结构中。它本身可以成为一个复杂的视图层次。(最后一个单词拼错了,应该是hierarchy)
在这里插入图片描述
在PhoneWindow具体实现的setContentView方法如下:(文件路径:D:\android_studio_sdk\sdk\sources\android-28\com\android\internal\policy )
在这里插入图片描述
如果第一次设置 mContentParent (ViewGroup) 为空,则执行 installDecor 方法

private void installDecor() {
    mForceDecorInstall = false;
    //如果DecorView为空就开始设置DecorView
    if (mDecor == null) {
        //Decor就是在这里创建的,我们看一下这个方法
        mDecor = generateDecor(-1);
        //获取焦点
        mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
        mDecor.setIsRootNamespace(true);
        if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
            mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
        }
    } else {
        mDecor.setWindow(this);
    }
   ......
}

generateDecor 方法 直接创建了 DecorView,将其添加到了Window中

protected DecorView generateDecor(int featureId) {
    // System process doesn't have application context and in that case we need to directly use
    // the context we have. Otherwise we want the application context, so we don't cling to the
    // activity.
    Context context;
    if (mUseDecorContext) {
        Context applicationContext = getContext().getApplicationContext();
        if (applicationContext == null) {
            context = getContext();
        } else {
            context = new DecorContext(applicationContext, getContext());
            if (mTheme != -1) {
                context.setTheme(mTheme);
            }
        }
    } else {
        context = getContext();
    }
    return new DecorView(context, featureId, this, getAttributes());
}

创建完mDecorView 之后,需要显示到界面上。当启动Activity调运完ActivityThread的main方法之后,接着调用ActivityThreadperformLaunchActivity来创建要启动的Activity组件,在创建Activity组件的过程中,还会为该Activity组件创建窗口对象和视图对象;接着Activity组件创建完成之后,通过调用ActivityThread类的handleResumeActivity将它激活。

先看下handleResumeActivity方法一个重点,如下:

final void handleResumeActivity(IBinder token,
        boolean clearHide, boolean isForward, boolean reallyResume) {
    // If we are getting ready to gc after going to the background, well
    // we are back active so skip it.
    ......
    // TODO Push resumeArgs into the activity for consideration
    ActivityClientRecord r = performResumeActivity(token, clearHide);

    if (r != null) {
        ......
        // If the window hasn't yet been added to the window manager,
        // and this guy didn't finish itself or start another activity,
        // then go ahead and add the window.
        ......
        // If the window has already been added, but during resume
        // we started another activity, then don't yet make the
        // window visible.
        ......
        // The window is now visible if it has been added, we are not
        // simply finishing, and we are not starting another activity.
        if (!r.activity.mFinished && willBeVisible
                && r.activity.mDecor != null && !r.hideForNow) {
            ......
            if (r.activity.mVisibleFromClient) {
                r.activity.makeVisible();
            }
        }
        ......
    } else {
        // If an exception was thrown when trying to resume, then
        // just end this activity.
        ......
    }
}

调用ActivitymakeVisible方法显示我们上面通过setContentView创建的mDecor视图族。所以我们看下Activity的makeVisible方法,如下:

void makeVisible() {
    if (!mWindowAdded) {
        ViewManager wm = getWindowManager();
        wm.addView(mDecor, getWindow().getAttributes());
        mWindowAdded = true;
    }
    mDecor.setVisibility(View.VISIBLE);
}

几个类的职责:

1、Window是一个抽象类,提供了绘制窗口的一组通用API。

2、PhoneWindow是Window的具体继承实现类。而且该类内部包含了一个DecorView对象,该DectorView对象是所有应用窗口(Activity界面)的根View。

3、DecorView是PhoneWindow的内部类,是FrameLayout的子类,是对FrameLayout进行功能的修饰(所以叫DecorXXX),是所有应用窗口的根View

4、ViewManager接口定义了一组规则,也就是add、update、remove的操作View接口。

5、WindowManager接口除了实现ViewManager接口定义的方法外,还定义一些与窗口显示相关的方法。

6、WindowManagerImpl为WindowManager的实现类,一般里面的方法都是具体的实现方法,但它不是!

7、WindowManagerGlobal是WindowManagerImpl的具体实现类,类似代理,但没有实现同样的接口,且没有继承关系!☆

8、ViewRoot 对应于ViewRootImpl类,它是链接WindowManager和DecorView的纽带,View的三大流程均是通过ViewRoot来完成的。在ActivityThread中,当Activity对象被创建后,会将DecorView添加到Window中,同时会创建ViewRootImpl对象,并将ViewRootImpl对象和DecorView建立关联。

过渡:

在Activity的生命周期中从视图的加载到绘制是从 ActivityThread 的 handleResumeActivity开始的(https://www.jianshu.com/p/4f5f42949262)

final void handleResumeActivity(IBinder token,
    boolean clearHide, boolean isForward, boolean reallyResume) {
    //Part01
    //调用activity.onResume,把activity数据记录更新到ActivityClientRecord
    ActivityClientRecord r = performResumeActivity(token, clearHide);

    //Part02
    if (r != null) {
        final Activity a = r.activity;
        //activity.mStartedActivity是用来标记启动Activity,有没有带返回值,一般我们startActivity(intent)是否默认是startActivityForResult(intent,-1),默认值是-1,所以这里mStartedActivity = false
        boolean willBeVisible = !a.mStartedActivity;
        ...
        //mFinished标记Activity有没有结束,而r.window一开始activity并未赋值给ActivityClientRecord,所以这里为null
        if (r.window == null && !a.mFinished && willBeVisible) {
            r.window = r.activity.getWindow(); //赋值
            View decor = r.window.getDecorView();
            decor.setVisibility(View.INVISIBLE);//默认设置DecorView不可见
            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与WindowManager绑定一起
                wm.addView(decor, l);
            }

        ...
        
        
        if (!r.activity.mFinished && willBeVisible
                && r.activity.mDecor != null && !r.hideForNow) {
                
         //Part03
         //标记当前的Activity有没有设置新的配置参数,比如现在手机是横屏的,而之后你转成竖屏,那么这里的newCofig就会被赋值,表示参数改变
            if (r.newConfig != null) {
                r.tmpConfig.setTo(r.newConfig);
                if (r.overrideConfig != null) {
                    r.tmpConfig.updateFrom(r.overrideConfig);
                }
                //然后调用这个方法回调,表示屏幕参数发生了改变
                performConfigurationChanged(r.activity, r.tmpConfig);
            ...
            WindowManager.LayoutParams l = r.window.getAttributes();
            ...//改变之后update更新当前窗口的DecorView
                if (r.activity.mVisibleFromClient) {
                    ViewManager wm = a.getWindowManager();
                    View decor = r.window.getDecorView();
                    wm.updateViewLayout(decor, l);
                }
            }
            
            //参数没改变
            r.activity.mVisibleFromServer = true;
            mNumVisibleActivities++;
            
            //Part04
            if (r.activity.mVisibleFromClient) {
            //由于前面设置了INVASIBLE,所以现在要把DecorView显示出来了
                r.activity.makeVisible();
            }
        }
        
       //通知ActivityManagerService,Activity完成Resumed
         ActivityManagerNative.getDefault().activityResumed(token);

}

关系图解:
在这里插入图片描述当DecorView加载至WindowManager的使用,真正调用的是WindowManagerGlobal.addView()方法。该方法是通过ViewRoot的setView()方法将View传递给WindowManager。ViewRoot实现了View和WindowManager之间的消息传递。

视图的绘制流程

(摘自:https://www.jianshu.com/p/b755d459d0f4)
由前文可知,DecorView是通过WindowManager.addView进行添加的。而具体的实现则为WindowManagerGlobal的addView

public void addView(View view, ViewGroup.LayoutParams params,
    Display display, Window parentWindow) {
    ...
    ViewRootImpl root;
    View panelParentView = null;
    ...
    // ViewRootImpl 初始化
    root = new ViewRootImpl(view.getContext(), display);
    view.setLayoutParams(wparams);
    mViews.add(view);
    mRoots.add(root);
    mParams.add(wparams);
    
    //ViewRootImpl开始绘制view
    root.setView(view, wparams, panelParentView);
    ...
}
拓展

ViewRootImpl是一个视图层次结构的顶部,它实现了View和WindowManager之间所需要的协议。在WindowManagerGlobal的实现方法中,大部分都是调用了ViewRootImpl。例如addView,removeView,updateViewLayout等

理解

WindowManagerImpl -> WindowManagerGlobal -> ViewRootImpl

ViewRootImpl构造方法
public ViewRootImpl(Context context, Display display) {
    mContext = context;
    mWindowSession = WindowManagerGlobal.getWindowSession();
    ...
    mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this);
    ...
}

AttachInfo为View里的一个静态内部类

AttachInfo(IWindowSession session, IWindow window, Display display,
        ViewRootImpl viewRootImpl, Handler handler, Callbacks effectPlayer) {
    mSession = session;
    mWindow = window;
    mWindowToken = window.asBinder();
    mDisplay = display;
    mViewRootImpl = viewRootImpl;
    mHandler = handler;
    mRootCallbacks = effectPlayer;
}

可以看到viewRootImpl在的构造方法里赋值了,且View与ViewRootImpl绑定在一起了。以后就可以通过view.getViewRootImpl获取到,而在Window也可以获取到,因为Window里有DecorView。

View
public ViewRootImpl getViewRootImpl() {
    if (mAttachInfo != null) {
        return mAttachInfo.mViewRootImpl;
    }
    return null;
}
DecorView
private ViewRootImpl getViewRootImpl() {
    if (mDecor != null) {
        ViewRootImpl viewRootImpl = mDecor.getViewRootImpl();
        if (viewRootImpl != null) {
            return viewRootImpl;
        }
    }
    throw new IllegalStateException("view not added");
}

两个方法都是@hide注解,不能直接调用,需用反射等方法间接调用。

一个View会对应一个ViewRootImpl吗?

测试:

Log.e(TAG, "getViewRootImpl: textView: " + tv.getViewRootImpl() );
Log.e(TAG, "getViewRootImpl: button: " + btn.getViewRootImpl() ); 

结果:
在这里插入图片描述可以看到,都是同一个对象,共用一个ViewRootImpl。

ViewRootImpl

接着我们看ViewRootImpl的setView

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    if (mView == null) {
        mView = view;
        ...
        //渲染UI
        requestLayout();
        ...
        try {
        ...
        res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                getHostVisibility(), mDisplay.getDisplayId(),
                mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                mAttachInfo.mOutsets, mInputChannel);
        }
        mAttachInfo.mRootView = view;
    }
}

判断ViewRootImpl的mView是否为空,为空进行赋值。接着调用requestLayout()。表示添加Window之前先完成第一次layout布局过程,以确保收到任何系统时间后重新布局。requestLayout最终会调用performTraversals方法来进行View的绘制。

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

//判断当前线程是否为UI线程
void checkThread() {
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
                "Only the original thread that created a view hierarchy can touch its views.");
    }
}

先判断当前的线程是否为UI线程,如果不是抛出异常。

拓展

在子线程里更新UI,会报错吗?此例子不会报错!为什么???

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    tv = (TextView) findViewById(R.id.tv);
    new Thread(new Runnable() {
        @Override
        public void run() {
            tv.setText("Hohohong Test");
        }
    }).start();
}

这与ViewRootImpl的初始化有关,由于在onCreate的时候,此时的View还没有绘制出来,ViewRootImpl也还未初始化,所以更不用去CheckThread。

ViewRootImpl — scheduleTraversals()
void scheduleTraversals() {
    if (!mTraversalScheduled) {
        ...
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
      ...
    }
}

//实现了Runnable接口
final class TraversalRunnable implements Runnable {
    @Override
    public void run() {
        doTraversal();
    }
}

scheduleTraversals()中会通过handler去异步调用mTraversalRunnable接口。mChoreographer可以理解为Handler的一个封装类。

ViewRootImpl — doTraversal()
void doTraversal() {
    ...
    performTraversals();
    ...
}
    
private void performTraversals() {  
    ......  
    //测量View的宽高
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    ...
    //布置View的位置
    performLayout(lp, desiredWindowWidth, desiredWindowHeight);
    //监听事件
    if (triggerGlobalLayoutListener) {
        mAttachInfo.mRecomputeGlobalAttributes = false;
        //触发OnGlobalLayoutListener的onGlobalLayout()函数
        mAttachInfo.mTreeObserver.dispatchOnGlobalLayout();
    }
    ......  
    //渲染View
    performDraw();
    }
    ......  
}

可以看到doTraversal()最终调用了performTraversals()。这就是渲染UI的核心代码了,此时视图已经被绘制出来了。

关系图

在这里插入图片描述

拓展

由于此方法是在onResume调用,所以在onCreate获取视图的宽高是等于0的,因为此时view压根都还没有测量宽高。而在onResume中,由于此方法是异步的,所以在onResume中获取视图的宽高有可能会得到0。

我们如何准确获取视图的宽高呢?

view.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() {
    // TODO Auto-generated method stub
    }
});

由于此接口是在View的Measure和Layout之后调用的,所以可以得到视图的宽高了。

总结

1、WindowManager的视图操作,具体是都是通过ViewRootImpl进行操作。

2、在ViewRootImpl未初始化的时候,可以进行子线程UI更新。而它的创建是在activity.handleResumeActivity方法调用,即DecorView被添加到WindowManager的时候
3、ViewRootImpl绘制View的时候会检查当前线程是否为主线程,是的话才可以继续绘制。

PS: ViewRootImpl不仅仅有绘制的功能,它还有事件分发的功能

绘制流程Measure – 测量并设置视图宽 / 高

视图的绘制工作大概分为Measure、Layout和Draw三个主要的流程。而启动这些流程的一个入口则是ViewRootImpl类的performTraversals()

private void performTraversals() {
    ......
    //最外层的根视图的widthMeasureSpec和heightMeasureSpec由来
    //lp.width和lp.height在创建ViewGroup实例时等于MATCH_PARENT
    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
    ......
    performMeasure(...) -> mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    ......
    performLayout(...) -> mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
    ......
    performDraw() -> draw(fullRedrawNeeded) -> drawSoftware(...) -> mView.draw(canvas);
    ......
} 

该函数做的执行过程主要是根据之前设置的状态,判断是否重新计算视图大小(measure)、是否重新放置视图的位置(layout)、以及是否重绘 (draw),其核心也就是通过判断来选择顺序执行这三个方法中的哪个。

在这里插入图片描述

ViewRootImpl — getRootMeasureSpec
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.
        //Window无法调整大小,强制根视图的宽高为Window的宽高,即全屏
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
        break;
     ......
    }
    return measureSpec;
}

由注释得此MeasureSpec是测量RootView的。由于默认的rootDimension为MATCH_PARENT,所以switch走的是MATCH_PARENT,然后使用MeasureSpec.makeMeasureSpec方法组装一个MeasureSpec,specMode=EXACTLY,specSize=WindowSize,也就是为何根视图是全屏的原因。

对于DecorView,其MeasureSpec由窗口的尺寸和其自身的LayoutParams来共同决定;对于普通的View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams来共同决定,MeasureSpec一旦确定后,onMeasure中就可以确定View的测量宽高了。

View — measure()

View -> ViewGroup -> FrameLayout -> DecorView

//final方法,子类不可重写
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    ......
    //回调onMeasure()方法
    onMeasure(widthMeasureSpec, heightMeasureSpec);
    ......
}

这里执行的是View中的measure(子类未重写),measure为整个View树计算实际的大小,然后设置实际的宽高。每个View空间的实际宽高都是由父视图和自身决定的。由于measure为final,View的子类无法重写该方法,因此View子类只能通过重写onMeasure实现自己的测量逻辑,measure方法最终回调了View的onMeasure方法。

measure传入了两个重要测量参数MeasureSpce,详情了解 (https://www.jianshu.com/p/9d550fb8cb30)

View — onMeasure(…)

如果是一个没有子视图的View的话,其执行View的onMeasure(就是如下的方法),而如果当前视图是DecorView的话这里应该首先执行 DecorView 中的 onMeasure 方法,然后执行 FrameLayout 中的onMeasure 方法,然后并未执行根View中的 onMeasure,因为FrameLayout的 onMeasure方法中并没有 super.onMeasure(…)

//View的onMeasure默认实现方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    //设置DecorView的宽高
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

真正设置View的宽高的即setMeasuredDimension方法,它对View的成员变量mMeasuredWidth和mMeasuredHeight变量赋值。如果想使用现成的View想通过getMeasuredWidth()getMeasuredHeight() 方法来获取View测量的宽高,必须保证这两个方法在onMeasure流程之后被调用才能返回有效值。

如果不调用此方法的话会出现异常

xxView#onMeasure() did not set the measured dimension by calling setMeasuredDimension()

默认系统会给我们测量一个默认的宽高,具体如下

//获取建议的最小宽度
protected int getSuggestedMinimumWidth() {
    return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
//获取建议的最小高度
protected int getSuggestedMinimumHeight() {
    return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}

建议的最小宽度和高度都是由View的Background尺寸与通过设置View的miniXXX属性共同决定的。

public static int getDefaultSize(int size, int measureSpec) {
    int result = size;
    //通过MeasureSpec解析获取mode与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;
}

如果specMode等于AT_MOST或EXACTLY就返回specSize,这就是系统默认的规格。

DecorView测量子视图

到这里一次简单的View视图Measure已经完成。DecorView作为根视图,系统默认设置全屏显示。而DecorView也是一个父视图,那DecorView如何测量自己的所有子视图呢?接着上面,在执行 View中的measure 方法的时候,onMeasure就执行到了DecorView 中,也执行到了 FrameLayout -> onMeasure中

这里要再次明确视图关系:View -> ViewGroup -> FrameLayout -> DecorView
以下三个方法是在 ViewGroup 中

ViewGroup是一个抽象类,需要具体的视图去实现,比如这里的

FrameLayout.onMeasure --> ViewGroup.measureChildWithMargins(…) -->child.measure(…)
其他具体实现类 LinearLayout、RelativeLayout、ConstraintLayout

1、LinearLayout.onMeasure --> LinearLayout.measureVertical / measureHorizontal --> LinearLayout.measureChildBeforeLayout --> ViewGroup.measureChildWithMargins --> child.measure

2、RelativeLayout.onMeasure --> RelativeLayout.measureChildHorizontal --> child.measure

3、AbsoluteLayout.onMeasure --> ViewGroup.measureChildren --> ViewGroup.measureChild --> child.measure

4、ConstraintLayout.onMeasure --> child.measure

此时有3个重要的方法measureChildren, measureChild, measureChildWithMargins

1、measureChildren内部循环调用measureChild
2、measureChild和measureChildWithMargins内部调用了子视图的measure,而两者的区别就在于是否要将margin作子自视图的大小

protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
    final int size = mChildrenCount;
    final View[] children = mChildren;
    for (int i = 0; i < size; ++i) {
        final View child = children[i];
        if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
        }
    }
}

protected void measureChild(View child, int parentWidthMeasureSpec,
        int parentHeightMeasureSpec) {
    //获取子视图的LayoutParams
    final LayoutParams lp = child.getLayoutParams();
    
    //区别之处:第二入参
    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
            mPaddingLeft + mPaddingRight, lp.width);
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
            mPaddingTop + mPaddingBottom, lp.height);
            
    //调运子View的measure方法,子View的measure中会回调子View的onMeasure方法
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

protected void measureChildWithMargins(View child,
        int parentWidthMeasureSpec, int widthUsed,
        int parentHeightMeasureSpec, int heightUsed) {
    //获取子视图的LayoutParams
    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
    
    //区别之处:第二入参
    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
            mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                    + widthUsed, lp.width);
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
            mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                    + heightUsed, lp.height);
                    
    //调运子View的measure方法,子View的measure中会回调子View的onMeasure方法
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

两个方法就是对父视图提供的measureSpec参数结合自身的LayoutParams参数进行了调整,然后再来调用child.measure()方法,具体通过方法getChildMeasureSpec来进行参数调整。

ViewGroup — getChildMeasureSpec
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
    //获取当前Parent View的Mode和Size
    int specMode = MeasureSpec.getMode(spec);
    int specSize = MeasureSpec.getSize(spec);
    //获取Parent size与padding差值(也就是Parent剩余大小),若差值小于0直接返回0
    int size = Math.max(0, specSize - padding);
    //定义返回值存储变量
    int resultSize = 0;
    int resultMode = 0;
    //依据当前Parent的Mode进行switch分支逻辑
    switch (specMode) {
    // Parent has imposed an exact size on us
    //默认Root View的Mode就是EXACTLY
    case MeasureSpec.EXACTLY:
        if (childDimension >= 0) {
            //如果child的layout_widh属性在xml或者java中给予具体大于等于0的数值
            //设置child的size为真实layout_widh属性值,mode为EXACTLY
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
            //如果child的layout_widh属性在xml或者java中给予MATCH_PARENT
            // Child wants to be our size. So be it.
            //设置child的size为size,mode为EXACTLY
            resultSize = size;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            //如果child的layout_widh属性在xml或者java中给予WRAP_CONTENT
            //设置child的size为size,mode为AT_MOST
            // Child wants to determine its own size. It can't be
            // bigger than us.
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        }
        break;
    ......
    //其他Mode分支类似
    }
    //将mode与size通过MeasureSpec方法整合为32位整数返回
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

getChildMeasureSpec的逻辑是通过将父视图提供的MeasureSpec参数得到spceMode和spectSize,然后通过计算出来的size以及子View的childDimension(layout_width或layout_height)来计算出ChildMeasureSpec,子视图通过ChildMeasureSpec调用measure函数,计算子视图的宽高。

总结

测量(Measure)是从顶层父View向子View递归调用view.measure的方法。

1、MeasureSpec(View的内部类)作为测量视图的重要数据,不仅包含了size还有mode,详细了解 https://www.jianshu.com/p/9d550fb8cb30

2、顶层DecorView所需的MeasureSpec是由ViewRootImpl中的getRootMeasureSpect提供的(LayoutParams宽高参数均为MATCH_PARENT,specMode是EXACTLY,specSize为物理屏幕大小)

3、ViewGroup类提供了measureChildren,measureChild和measureChildWithMargins方法,简化了父子View的尺寸计算

4、View的布局大小由父View和子View共同决定。对于DecorView,其MeasureSpec由窗口的尺寸和其自身的LayoutParams来共同决定;对于普通的View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams来共同决定,MeasureSpec一旦确定后,onMeasure中就可以确定View的测量宽高了

5、使用View的getMeasuredWidth()和getMeasuredHeight()方法来获取View测量的宽高,必须保证这两个方法在onMeasure流程之后被调用才能返回有效值。

(重写 onMeasure 的例子:https://blog.csdn.net/haibo_bear/article/details/52223915

绘制流程Layout — 确定视图的位置

(摘自:https://www.jianshu.com/p/9501944a723a

关系图
在这里插入图片描述
前面是View的测量(Measure)过程。接着我们开始聊布局(Layout),以下是起始ViewRootImpl 的 performTraversals方法。

ViewRootImpl – performTraversals

private void performTraversals() {
    ......
    performMeasure(...) -> mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    ......
    // 以下重点
    performLayout(...) -> mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
    ......
    performDraw() -> draw(fullRedrawNeeded) -> drawSoftware(...) -> mView.draw(canvas);
    ......
} 

可以看见layout方法接收了4个参数,分别表示左、上、右、下。从上可以看出mView(DecorView)的左上都为0,右为width,下为height。

Layout 过程图
在这里插入图片描述
视图层次:View --> ViewGroup --> FrameLayout --> DecorView

mView即DecorView(FrameLayout)是ViewGroup的子类,我们来看下ViewGroup的layout方法。注意 ViewGroup中重写了 View中的layout方法并设置为final类型,子类不可重写,且通过super.layout传到View的layout方法中,而View中的measure方法是final类型不可为其他子类重写的,包括ViewGroup

View — layout
public void layout(int l, int t, int r, int b) {
    ......
    //实质都是调用setFrame方法把参数分别赋值给mLeft、mTop、mRight和mBottom这几个变量
    //判断View的位置是否发生过变化,以确定有没有必要对当前的View进行重新layout
    boolean changed = isLayoutModeOptical(mParent) ?
            setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
    //需要重新layout
    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
        //回调onLayout
        onLayout(changed, l, t, r, b);
        ......
    }
    ......
}
//空方法
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}

layout完成后,随即调动用onLayout。这里与measure类似。View的onlayout是一个空方法。

ViewGroup — onLayout
@Override
protected abstract void onLayout(boolean changed, int l, int t, int r, int b);

ViewGrpup的onLayout为抽象方法,也就意味着所有ViewGroup的子类都得实现onLayout方法。重写onLayout的目的就是安排其children在父View的具体位置。重写onLayout通常做法就是写一个for循环调用每一个子视图的layout(l,t,r,b)方法,通过传入不同的数据来排列所有的子视图。(这里可以参考onMeasure)

FrameLayout — onLayout

在DecorView中

@Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        ......
    }

在FrameLayout 中

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
//具体的实现方法
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();
    //开始遍历!
    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;
                //判断水平方向,给子视图的left赋值
            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;
            }
            //判断竖直方向,给子视图top赋值
            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;
            }
            //左、上坐标已知,宽高已知,可得右、下坐标。排列子视图
            child.layout(childLeft, childTop, childLeft + width, childTop + height);
        }
    }
}

从上面对DecorView(FrameLayout)的onLaout分析可看出。DecorView通过获取子视图(measure)的宽高,且通过gravity计算出子视图对应的左、上坐标,从而得出右、下左标。从而实现一个子视图的布局。

当然各种ViewGroup有各种onLayout的实现,本文这里简单讲解FrameLayout。

拓展

我们以前遇到getWidth()、getHeight()和getMeasureWidth()、getMeasureHeight()傻傻不能分清楚,那他们的区别是什么呢?

public final int getMeasuredWidth() {
    return mMeasuredWidth & MEASURED_SIZE_MASK;
}

public final int getMeasuredHeight() {
    return mMeasuredHeight & MEASURED_SIZE_MASK;
}

public final int getWidth() {
    return mRight - mLeft;
}

public final int getHeight() {
    return mBottom - mTop;
}

public final int getLeft() {
    return mLeft;
}

public final int getRight() {
    return mRight;
}

public final int getTop() {
    return mTop;
}

public final int getBottom() {
    return mBottom;
}

getMeasureWith()、getMeasureHeight()我们的上文已经说过了,必须在onMeasure之后才有效。而getWith()和getHeight()要在onLayout之后才有效。(例子:https://blog.csdn.net/haibo_bear/article/details/52223915

总结

1、View.layout可重写,ViewGroup.layout不可重写。ViewGroup.onlayout为抽象方法,子类必须实现该方法。

2、measure操作后得到的是measureWidth和measureHeight;layout操作后得到的是坐标left,top,right,bottom,当然这些值是相对于父View来说的

3、getWith()和getHeight()方法必须在onLayout后调用才有值。

绘制流程Draw —

(摘自:https://www.jianshu.com/p/7eee148dec11
https://www.cnblogs.com/xinmengwuheng/p/7070092.html

关系图、结构图
在这里插入图片描述

上面我们对View的测量(Measure)进行讲解了。接着我们开始聊绘制(Draw),以下是我们熟悉的performTraversals方法。

private void performTraversals() {
    ......
    performMeasure(...) -> mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    ......
    performLayout(...) -> mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
    ......
	 // 以下重点
    performDraw() -> draw(fullRedrawNeeded) -> drawSoftware(...) -> mView.draw(canvas);
    ......
} 

其中在 drawSoftware 中 在执行 mView.draw(canvas) 之前会 生成 canvas对象

canvas = mSurface.lockCanvas(dirty);

我们看出ViewRootImpl创建一个canvas,然后mView(DecorView)调用draw方法并传入canvas,此方法执行具体的绘制工作。与Measure和Layout类似的需要递归绘制。

理解图
在这里插入图片描述

View — draw

接着上面代码,从DecorView执行 draw开始,代码如下:

@Override
public void draw(Canvas canvas) {
    super.draw(canvas);
    if (mMenuBackground != null) {
        mMenuBackground.draw(canvas);
    }
 }

由于ViewGroup没有重写View的draw方法,我们看下View的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
     *      5. If necessary, draw the fading edges and restore layers
     *      6. Draw decorations (scrollbars for instance)
     */

    // Step 1, draw the background, if needed
    ......
    if (!dirtyOpaque) {
        drawBackground(canvas);
    }

    // skip step 2 & 5 if possible (common case)
    ......

    // Step 2, save the canvas' layers
    ......
    if (drawTop) {
        canvas.saveLayer(left, top, right, top + length, null, flags);
    }
    ......

    // Step 3, draw the content
    if (!dirtyOpaque) onDraw(canvas);

    // Step 4, draw the children
    dispatchDraw(canvas);

    // Step 5, draw the fade effect and restore layers
    ......
    if (drawTop) {
        matrix.setScale(1, fadeHeight * topFadeStrength);
        matrix.postTranslate(left, top);
        fade.setLocalMatrix(matrix);
        p.setShader(fade);
        canvas.drawRect(left, top, right, top + length, p);
    }
    ......
    
    // Step 6, draw decorations (foreground, scrollbars)
    onDrawForeground(canvas);
    ......
}

整个的绘制流程分成6步,通过注释可以知道第2步和第5步(skip step 2 & 5 if possible (common case))可以忽略跳过,我们对剩余4步就行分析。

View — draw — drawBackground①

第一步:对View的背景进行绘制

private void drawBackground(Canvas canvas) {
    //获取xml中通过android:background属性或者代码中setBackgroundColor()、setBackgroundResource()等方法进行赋值的背景Drawable
    final Drawable background = mBackground;
    ......
    //根据layout过程确定的View位置来设置背景的绘制区域
    if (mBackgroundSizeChanged) {
        background.setBounds(0, 0,  mRight - mLeft, mBottom - mTop);
        mBackgroundSizeChanged = false;
        rebuildOutline();
    }
    ......
    //调用Drawable的draw()方法来完成背景的绘制工作
    background.draw(canvas);
    ......
}

draw方法通过调运drawBackground(canvas);方法实现了背景绘制。

View — draw — onDraw ③

第三步:对View的内容进行绘制

protected void onDraw(Canvas canvas) {
}

ViewGroup没有重写该方法,view的方法也紧紧是一个空方法而已。大家都知道不同的View是显示不同的内容的,所以这块必须是子类去实现具体逻辑。

View — draw — dispatchDraw ④

第四步:对当前View的所有子View进行绘制

protected void dispatchDraw(Canvas canvas) {
}

View的dispatchDraw()方法是一个空方法。这个我们也比较好理解,本身View自身就没有所谓的子视图,而拥有子视图的就是ViewGroup!所以我们可以看下ViewGroup的dispatchDraw

ViewGroup – dispatchDraw
@Override
protected void dispatchDraw(Canvas canvas) {
    ......
    final int childrenCount = mChildrenCount;
    final View[] children = mChildren;
    ......
    for (int i = 0; i < childrenCount; i++) {
        ......
        if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
            more |= drawChild(canvas, child, drawingTime);
        }
    }
    ......
    // Draw any disappearing views that have animations
    if (mDisappearingChildren != null) {
        ......
        for (int i = disappearingCount; i >= 0; i--) {
            ......
            more |= drawChild(canvas, child, drawingTime);
        }
    }
    ......
}
    
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
    return child.draw(canvas, this, drawingTime);
}

可见,Viewgroup重写了dispatchDraw()方法,该方法内部会遍历每个子View,然后调用drawChild()方法。而drawChild方法内部是直接由子视图调用draw()方法。

View — draw ---- onDrawForeground
public void onDrawForeground(Canvas canvas) {
	onDrawScrollIndicators(canvas);
    onDrawScrollBars(canvas);
	......
	foreground.draw(canvas);
}

该方法绘制装饰(滚动指示器、滚动条、和前景)

TextView 的绘制过程可参考:https://www.cnblogs.com/bvin/p/5370490.html

总结

(摘自:https://www.cnblogs.com/xinmengwuheng/p/7070092.html

单一View的绘制,只需要绘制自身
在这里插入图片描述
ViewGroup的绘制图解如下:

在这里插入图片描述

拓展

1、View 中有一个特殊的方法:setWillNotDraw()

public void setWillNotDraw(boolean willNotDraw) {
    setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}

该方法用于设置 WILL_NOT_DRAW 标记位,该标记位的作用是:当一个View不需要绘制内容时,系统进行相应优化 。默认情况下:View 不启用该标记位(设置为true);ViewGroup 默认启用(设置为false)

应用场景:

① setWillNotDraw参数设置为true:当自定义View继承自 ViewGroup 、且本身并不具备任何绘制时,设置为 true 后,系统会进行相应的优化。

② setWillNotDraw参数设置为false:当自定义View继承自 ViewGroup 、且需要绘制内容时,那么设置为 false,来关闭 WILL_NOT_DRAW 这个标记位。

2、我们经常在自定义View的时候会遇到两种方法invalidatepostinvalidate。我们来看看两个方法与视图的绘制有什么样的联系呢?

invalidate方法源码分析,由于ViewGroup并没有重写该方法,所以我们直接看View的invalidate

// invalidate只能在UI Thread中使用,别的Thread用postInvalidate方法,View是可见的才有效,回调onDraw方法,针对局部View

public void invalidate(Rect dirty) {
    final int scrollX = mScrollX;
    final int scrollY = mScrollY;
    //实质还是调用invalidateInternal方法
    invalidateInternal(dirty.left - scrollX, dirty.top - scrollY,
            dirty.right - scrollX, dirty.bottom - scrollY, true, false);
}

//public,只能在UI Thread中使用,别的Thread用postInvalidate方法,View是可见的才有效,回调onDraw方法,针对局部View
public void invalidate(int l, int t, int r, int b) {
    final int scrollX = mScrollX;
    final int scrollY = mScrollY;
    //实质还是调运invalidateInternal方法
    invalidateInternal(l - scrollX, t - scrollY, r - scrollX, b - scrollY, true, false);
}

//public,只能在UI Thread中使用,别的Thread用postInvalidate方法,View是可见的才有效,回调onDraw方法,针对整个View
public void invalidate() {
    //invalidate的实质还是调运invalidateInternal方法
    invalidate(true);
}


//default的权限,只能在UI Thread中使用,别的Thread用postInvalidate方法,View是可见的才有效,回调onDraw方法,针对整个View
void invalidate(boolean invalidateCache) {
    //实质还是调运invalidateInternal方法
    invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}

//这是所有invalidate的终极调运方法!!!!!!
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
        boolean fullInvalidate) {
        ......
        // Propagate the damage rectangle to the parent view.
        final AttachInfo ai = mAttachInfo;
        final ViewParent p = mParent;
        if (p != null && ai != null && l < r && t < b) {
            final Rect damage = ai.mTmpInvalRect;
            //设置刷新区域
            damage.set(l, t, r, b);
            //传递调运Parent ViewGroup的invalidateChild方法
            p.invalidateChild(this, damage);
        }
        ......
}

Viewinvalidate方法最终调动invalidateInternal方法。而invalidateInternal方法是将要刷新的区域传递给父视图,并调用父视图的invalidateChild

ViewGroup — invalidateChild
public final void invalidateChild(View child, final Rect dirty) {
    ViewParent parent = this;
    final AttachInfo attachInfo = mAttachInfo;
    ......
    do {
        ......
        //循环层层上级调运,直到ViewRootImpl会返回null
        parent = parent.invalidateChildInParent(location, dirty);
        ......
    } while (parent != null);
}

这个过程不断的向上寻找父亲视图,当父视图为空时才停止。所以我们可以联想到根视图的ViewRootImpl

ViewRootImpl — invalidateChildInParent
@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
    ......
    //View调运invalidate最终层层上传到ViewRootImpl后最终触发了该方法
    invalidate --> scheduleTraversals();
    ......
    return null;
}

返回为空。刚好符合上面的循环!接着我们看scheduleTraversals这个方法是不是感觉很熟悉呢?这就是前面ViewRootImpl正准备调用绘制View视图的代码

void scheduleTraversals() {
    mChoreographer.postCallback(
           Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
    
//实现了Runnable接口
final class TraversalRunnable implements Runnable {
    @Override
    public void run() {
        doTraversal();
    }
}
    
void doTraversal() {
     performTraversals();
}
    
private void performTraversals() {
    //测量
    mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    //布局
    mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
    //绘制
    mView.draw(canvas);
} 

这样一组看下来就很清晰了。View调用invalidate方法,其实是层层往上递,直到传递到ViewRootImpl后出发sceheduleTraversals方法,然后整个View树开始进行重绘制任务

理解图:

在这里插入图片描述

postInvalidate方法源码分析

上面也说道invalidate方法只能在UI线程中执行,其他需要postInvalidate方法

View — postInvalidate
public void postInvalidate() {
    postInvalidateDelayed(0);
}
    
public void postInvalidateDelayed(long delayMilliseconds) {
    // We try only with the AttachInfo because there's no point in invalidating
    // if we are not attached to our window
    final AttachInfo attachInfo = mAttachInfo;
    //核心,实质就是调运了ViewRootImpl.dispatchInvalidateDelayed方法
    if (attachInfo != null) {
        attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds);
    }
}

此方法必须是在视图已经绑定到Window才能使用,即attachInfo是否为空。随后调用ViewRootImpl的dispatchinvalidateDelayed

ViewRootImpl — dispatchInvalidateDelayed
public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
    Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
    mHandler.sendMessageDelayed(msg, delayMilliseconds);
}

Handler乱入!此时ViewrootImpl类的Handler发送了一条MSG_INVALIDATE消息。哪里接收这个消息呢?

ViewRootImpl
final class ViewRootHandler extends Handler {
    
    public void handleMessage(Message msg) {
        ......
        switch (msg.what) {
        case MSG_INVALIDATE:
            ((View) msg.obj).invalidate();
            break;
        ......
        }
        ......
    }
}

实质上还是在UI线程中调用了View的invalidate()方法

postInvalidate是在子线程中发消息,UI线程接收消息并刷新UI。

在这里插入图片描述

常见的引起invalidate方法操作的原因主要有:

1、直接调用invalidate方法.请求重新draw,但只会绘制调用者本身

2、触发setSelection方法。请求重新draw,但只会绘制调用者本身。

3、触发setVisibility方法。 当View可视状态在INVISIBLE转换VISIBLE时会间接调用invalidate方法,继而绘制该View。当View的可视状态在INVISIBLE / VISIBLE 转换为GONE状态时会间接调用requestLayout和invalidate方法,同时由于View树大小发生了变化,所以会请求measure过程以及draw过程,同样只绘制需要“重新绘制”的视图。

4、触发setEnabled方法。请求重新draw,但不会重新绘制任何View包括该调用者本身。

5、触发requestFocus方法。请求View树的draw过程,只绘制“需要重绘”的View。

requestLayout方法源码分析

和invalidate类似,层层往上传递。

public void requestLayout() {
    ......
    if (mParent != null && !mParent.isLayoutRequested()) {
        //由此向ViewParent请求布局
        //从这个View开始向上一直requestLayout,最终到达ViewRootImpl的requestLayout
        mParent.requestLayout();
    }
    ......
}

获取父类对象,调用requestlayout(),最后到达ViewRootImpl

@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        //View调运requestLayout最终层层上传到ViewRootImpl后最终触发了该方法
        scheduleTraversals();
    }
}

与invalidate是不是很像呢?reqeustLayout()方法会调用measure和layout过程,不会调用draw过程,也不会重新绘制任何View,包括调用者本身。而invalidate则绘制draw为主。

(以下摘自:https://blog.csdn.net/zoky_ze/article/details/54892971
ViewRootImpl的requestLayout方法,可以看到mLayoutRequested变true了,然后触发了scheduleTraversals 方法,requestLayout与invalidate的调用过程类似,只是设置的标志位不同,导致View的绘制流程中执行方法不同而已。

我们可以简单的认为mLayoutRequested为true会触发perfomMeasure(内部会调用onMeasure)和performLayout(内部会调用onLayout)。然后在performDraw内部draw的过程中发现mDirty为空,所以onDraw不会被调用,不重绘。
这么看来requestLayout不会导致onDraw调用了?

也不见得,我们知道requestLayout会导致perfomMeasure和performLayout,如果在layout过程中发现l,t,r,b和以前不一样,那就会触发一次invalidate。代码在View的setFrame中,这个会在layout时被调用。

public void layout(int l, int t, int r, int b) {
......
boolean changed = isLayoutModeOptical(mParent) ?
            setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

.....
}

private boolean setOpticalFrame(int left, int top, int right, int bottom) {
        Insets parentInsets = mParent instanceof View ?
                ((View) mParent).getOpticalInsets() : Insets.NONE;
        Insets childInsets = getOpticalInsets();
        return setFrame(
                left   + parentInsets.left - childInsets.left,
                top    + parentInsets.top  - childInsets.top,
                right  + parentInsets.left + childInsets.right,
                bottom + parentInsets.top  + childInsets.bottom);
    }

此处一定会执行 setFrame方法。

 protected boolean setFrame(int left, int top, int right, int bottom) {
        boolean changed = false;

        if (DBG) {
            Log.d(VIEW_LOG_TAG, this + " View.setFrame(" + left + "," + top + ","
                    + right + "," + bottom + ")");
        }

        if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
            changed = true;
			......
			// Invalidate our old position
        	invalidate(sizeChanged);
         }
 }

 public void invalidate(boolean invalidateCache) {
      invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
 }

invalidateInternal 该方法就是前面提到的 invalidate 实际执行的方法。

(以下摘自:https://blog.csdn.net/lhl5281/article/details/80136240
在继承至ViewGroup的自定义控件中,invalidate是默认不重新绘制子view的。
有以下两种方法来触发重新绘制的过程:

方法一:在构造函数中调用setWillNotDraw(false);

方法二:给ViewGroup设置背景。调用setBackground。

通过源码分析,两个方法都是一个逻辑:需要将ViewGroup的dirtyOpaque设置为false.
这样其父类的draw方法会调用ondraw执行绘制过程。

ondraw方法():
 // Step 3, draw the content
 if (!dirtyOpaque) onDraw(canvas);

final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE 

setWillNotDraw(false) 触发重新绘制的过程会一直执行,需要setWillNotDraw(true)来清除改标志位。

setBackBackground也是类似,改变prviateFlags值,触发ondraw重新绘制。

(invalidate 、requestLayout 和 Choroegrapher 分析可见: https://www.cnblogs.com/tiger-wang-ms/p/6592189.html

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android中实现加载过程动画可以使用ProgressBar或者自定义View来实现。以下是两种实现方式: 1. 使用ProgressBar ProgressBar是Android系统自带的控件,可以实现加载过程动画。可以通过以下代码实现: ``` <ProgressBar android:id="@+id/progress_bar" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:indeterminate="true" /> ``` 其中,android:indeterminate="true"表示ProgressBar是一个不确定进度的动画。 2. 自定义View 可以通过自定义View来实现更加个性化的加载过程动画。以下是一个简单的示例代码: ``` public class LoadingView extends View { private Paint mPaint; private RectF mRectF; private float mStartAngle = 0; private float mSweepAngle = 45; private int mWidth; private int mHeight; public LoadingView(Context context) { super(context); init(); } public LoadingView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); init(); } public LoadingView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { mPaint = new Paint(); mPaint.setColor(Color.RED); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeWidth(10); mPaint.setAntiAlias(true); mRectF = new RectF(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); mRectF.set(0, 0, mWidth, mHeight); canvas.drawArc(mRectF, mStartAngle, mSweepAngle, false, mPaint); mStartAngle += 5; if (mStartAngle >= 360) { mStartAngle = 0; } postInvalidateDelayed(10); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); mWidth = MeasureSpec.getSize(widthMeasureSpec); mHeight = MeasureSpec.getSize(heightMeasureSpec); } } ``` 该自定义View绘制一个旋转的圆弧,可以通过改变mStartAngle和mSweepAngle的值来改变动画效果。在使用时,直接将该View加入布局即可: ``` <com.example.myapplication.LoadingView android:layout_width="100dp" android:layout_height="100dp" android:layout_centerInParent="true" /> ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值