Window、WindowManger
window
Window对象代表一个窗口,这是一块矩形的图像绘制区域。WindowManagerService不关心各种View,只管理和调度Window对象,Activity的View树不管有多么复杂,最后输出的也只是一张由各个View合成的图像而已。
Window是一个抽象的概念,每一个Window都对应着一个View和一个ViewRootImpl,Window又通过ViewRootImpl与View建立联系,View才是Window的实体。
对Window的访问必须通过WindowManager,其实就是对view的操作(添加,更新,删除),这些操作又都是经过ViewRootImpl实现的。 WindowManager->Window->ViewRootImpl->View
java
interface WindowManager extends ViewManager
public interface ViewManager {
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
WMS/WindowManager/WindowManagerGlobal 关系
在每个Android应用中,都存在一个唯一的WindowManagerGlobal对象,这个对象中包含了能和WMS进行双向Binder通信的通道。如上图所示;
java
public final class WindowManagerGlobal {
private static final String TAG = "WindowManager";
private static WindowManagerGlobal sDefaultWindowManager;
//窗口服务WindowManagerService的引用类
private static IWindowManager sWindowManagerService;
private static IWindowSession sWindowSession;
private final Object mLock = new Object();
private final ArrayList<View> mViews = new ArrayList<View>();
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
private final ArrayList<WindowManager.LayoutParams> mParams =
new ArrayList<WindowManager.LayoutParams>();
private final ArraySet<View> mDyingViews = new ArraySet<View>();
private WindowManagerGlobal() {
}
}
这三个集合,保存了应用中所有顶层View相关信息。
- mViews:保存的是所有顶层View的对象,(实际上是DecorView对象)。
- mRoots:保存的是和顶层View关联的ViewRootImpl对象。
- mParams保存的是创建顶层View的layout参数;
Window 类型
总的来说Window分为三大类型,每个大类型中又分很多小类型,通过LayoutParams.type设置。
- 应用窗口,对应于一个Activity。加载Activity由AmS完成,创建一个应用窗口只能在Activity内部完成(层级1~99)。
- 子窗口,必须依附于任何类型的父窗口,比如对话框等(层级1000~1999)。
- 系统窗口,不需要对应任何Activity,比如输入法,Toast,墙纸等,普通应用程序不能创建系统窗口,必须要有系统应用权限.(层级2000~2999)。
WindowManager为这个三类进行了细化,把每一种类型都有int常量标识,WmS进行窗口叠加的时候会按照该int常量的大小分配不同层,int值越大层位置越靠上面。层级在 2000(FIRST_SYSTEM_WINDOW)以下的是不需要申请弹窗权限的。
Dialog的Window创建过程
- 创建Window------同样是通过PolicyManager的makeNewWindow方法完成,与Activity创建过程一致
- 初始化DecorView并将Dialog的视图添加到DecorView中------和Activity一致(setContentView)
- 将DecorView添加到Window中并显示------在Dialog的show方法中,通过WindowManager将DecorView添加到Window中(mWindowManager.addView(mDecor, 1))
- Dialog关闭时会通过WindowManager来移除DecorView:mWindowManager.removeViewImmediate(mDecor)
- Dialog可以采用Activity的Context,也可以将Dialog的Window通过type设置为系统Window(SYSTEM_ALERT,需要申请权限)。
Dialog和PopupWindow的区别
Dialog在创建时会新建一个PhoneWindow,和activity的window是相互独立的,同时也会创建DecorView作为根View,然后在调用show方法时使用ViewRootImpl来管理DecroView,简直和Activity一毛一样。
但是PopupWindow就不一样了,不会创建新的window。PopupWindow的DecorView是PopupDecorView,PopupDecorView是继承FrameLayout,所以本质上PopupWindow就是一个View,使用WindowManager将PopupView添加到当前界面的DecorView里。
Activity、view绑定
activity 在创建、启动时,主要会经历如下流程:
- 调用Activity.attach():创建Window,window关联WindowManager。
- 调用Activity.onCreate():setContentView
- 调用Activity的onResume():处理View的呈现。performResumeActivity()处理Activity的onRestart onResume的生命周期,通过wm.addView 时才创建 ViewRootImpl,且关联 ViewRootImpl、DecorView。
attach
在Activity.attach()方法执行时会创建PhoneWindow,并为Window关联WindowManager。
scss
final void attach(Context context, ActivityThread aThread,
CharSequence title, Activity parent, String id,) {
attachBaseContext(context);
//创建Window
mWindow = new PhoneWindow(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
//设置UI线程
mUiThread = Thread.currentThread();
mMainThread = aThread;
//关联WindowManager
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
if (mParent != null) {
mWindow.setContainer(mParent.getWindow());
}
mWindowManager = mWindow.getWindowManager();
}
setContentView
Activity.setContentView()内又直接调用了 PhoneWindow.setContentView(),主要是DecorView的创建。DecorView的布局虽然不同,但它们都一个Id为R.id.content的FrameLayout。Activity.setContentView()就是在这个FrameLayout中添加子View。
执行到这里,Activity、Window、DecorView都已创建完成,接下来就是他们之间的逻辑交互了。
scss
private DecorView mDecor;
//setContentView传过来的View会被add到mContentParent中。mContentParent的Id是R.id.content。
private ViewGroup mContentParent;
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
}
private void installDecor() {
if (mDecor == null) {
//初始化DecorView
mDecor = generateDecor();//new DecorView(context, featureId, this, getAttributes())
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
//省略代码。。。
}
}
handleResumeActivity
handleResumeActivity()处理的事情比较多:
- performResumeActivity()处理Activity的onRestart onResume的生命周期。
- 将DecorView设置为InVisible。
- 通过WindowManager.addView()将DecorView绘制完成。
- 将DecorView设置为Visiable。
- 通知AMS Activity启动完成。
ini
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume) {
//处理Activity的onRestart onResume生命周期。
ActivityClientRecord r = performResumeActivity(token, clearHide);
if (r != null) {
if (r.window == null && !a.mFinished) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
//设置DecorView不可见
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
if (a.mVisibleFromClient) {
a.mWindowAdded = true;
//利用WindowManager添加DecorView。
wm.addView(decor, l);
}
}
if (!r.activity.mFinished && r.activity.mDecor != null) {
if (r.activity.mVisibleFromClient) {
//设置DecorView可见。
r.activity.makeVisible();
}
}
//IPC调用,通知AMS Activity启动完成。
ActivityManagerNative.getDefault().activityResumed(token);
} else {
ActivityManagerNative.getDefault().finishActivity(token, Activity.RESULT_CANCELED, null, false);
}
}
WindowManger.addView()
- WindowManagerImpl.addView会调用WindowManagerGlobal.addView()。
- addView 时首先会创建ViewRootImpl且绑定DecorView,调用ViewRootImpl.setView()方法。
less
public final class WindowManagerImpl implements WindowManager {
private IBinder mDefaultToken;
@Override
//这里的View是PhoneWindow内创建的DecorView。
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
}
addView 时才创建 ViewRootImpl 且关联 DecorView:
scss
//这里的View是PhoneWindow内创建的DecorView。
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
//省略代码....
root = new ViewRootImpl(view.getContext(), display);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
root.setView(view, wparams, panelParentView); //关联 decorView
}
ViewRootImpl
先看下 ViewRootImpl 的功能
Android2.2以后用ViewRootImpl代替ViewRoot,ViewRootImpl 实现了 ViewParent,但它却不是 View。主要作用如下:
- 链接WindowManager和DecorView的纽带,实现了View与WindowManager之间的通信协议。
- 完成View的绘制过程,包括measure、layout、draw过程。
- 向DecorView分发收到的用户发起的event事件,如按键,触屏等事件。
一个Activity有多少个ViewRootImpl
ViewRootImpl是管理这window中一个View链的所有View,当Activity调用一次mWindowManager.addView时,就有创建一个新的ViewRootImpl。比如我在一个界面,添加了3个悬浮框,那就有4个ViewRootImpl,UI刷新互不干扰。
上段中 addView 时 调用 ViewRootImpl.setView,继续看。
- ViewRootImpl.setView() 将DecorView设置到了ViewRootImpl。
- 检查 ui 绘制线程是在此处,并且开始scheduleTraversals,发送一个异步消息。
- 因为mWindowSession.addToDisplay()就是通过IPC通知WMS去渲染。
scheduleTraversals会把本次请求封装成一个TraversalRunnable对象,这个对象最后会交给Handler去处理。最后ViewRootImpl.performTraversals()被调用。performTraversals()主要是处理View树的measure、layout、draw等流程。
scss
public class ViewRootImpl{
View mView; //PhoneWindow内创建的DecorView
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
//省略代码。。。
requestLayout();
//IPC通信,通知WMS渲染。
mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes);
}
}
}
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
//检查当前执行的线程是不是UI线程
checkThread();
//处理DecorView的measure、layout、draw。
scheduleTraversals();
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) {
mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
//省略代码。。。
}
}
}
从以上分析可以看出,应用UI线程的绘制最终是通过往Choreographer中放入一个CALLBACK_TRAVERSAL类型的绘制任务而触发,Choreographer会先向系统申请Vsync信号,待Vsync信号到来后,向应用主线程MessageQueue发送一个异步消息,触发在主线程中执行ViewRootImpl#doTraversal绘制任务动作。我们接着看看ViewRootImpl的doTraversal函数执行绘制流程的简化代码流程:
scss
/*frameworks/base/core/java/android/view/ViewRootImpl.java*/
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
// 调用removeSyncBarrier及时移除主线程MessageQueue中的Barrier同步栏删,以避免主线程发生"假死"
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
...
// 执行具体的绘制任务
performTraversals();
...
}
}
private void performTraversals() {
...
windowSizeMayChange |= measureHierarchy(...);
...
if (mFirst...) {
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
}
...
performLayout(lp, mWidth, mHeight);
...
performDraw();
...
}
View 绘制流程
scss
private void performTraversals() {
...
//mWidth 屏幕尺寸, lp.width: match_parent
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
...
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
...
performDraw();
}
父子执行顺序
1、测量过程是先测量子View,再测量父View(当前View),因为父View的宽高需要用到子View的测量结果。
2、View布局的核心方法是View#layout方法,先对父View(当前View)进行布局,然后调用onLayout方法对子View进行布局定位。
3、View绘制的核心方法是View#draw方法,先对父View(当前View)进行绘制,然后调用dispatchDraw方法对子View进行绘制。
measure
基本概念
MeasureSpec
MeasureSpec是一个32位的整形值,高2位表示测量模式SpecMode,低30位表示某种测量模式下的规格大小SpecSize。
- UNSPECIFIED: 不指定测量模式, 父视图没有限制子视图的大小,子视图可以是想要的任何尺寸,如ScrollView
- EXACTLY: 精确测量模式,视图宽高(LayoutParam)指定为match_parent或具体数值时生效,表示父视图已经决定了子视图的精确大小,这种模式下View的测量值就是SpecSize的值。
- AT_MOST: 最大值测量模式,当视图宽高(LayoutParam)指定为wrap_content时生效,此时子视图的尺寸可以是不超过父视图允许的最大尺寸的任何尺寸。
measureSpec从哪来的
每个View 的spec是从父view测量时传递下来的,父view根据子View的layoutParam、父view尺寸来确定最终的mode、size再传递给子View( 见 下文)。而顶层DecorView的跟activity 跟屏幕尺寸有关,dialog可能是wrap content.
measure与onMeasure关系
- measure 入口即 measure()方法是View中定义的final类型,子类不能重写此方法,所以无论是 View 还是 ViewGroup 最终逻辑都是在 onMeasure 方法。
- setMeasuredDimension():存储测量后的View宽 / 高, 保存在mMeasuredWidth/mMeasuredHeight,父View获取子View的测量尺寸时获取的就是这些值。
arduino
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 :
mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
ViewGroup onMeasure
不同的ViewGroup(如LinearLayout、RelativeLayout、自定义ViewGroup子类等)具备不同的布局特性,这导致它们测量方法各有不同,所以onMeasure()的实现也会有所不同。复写onMeasure()的步骤主要分为三步:
-
遍历所有子View及测量:measureChildren()
-
合并所有子View的尺寸大小,最终得到ViewGroup父视图的测量值:需自定义实现
-
存储测量后View宽/高的值:setMeasuredDimension()
根据不同的父容器的模式及子View的layoutParams来决定子View的规格尺寸模式,父容器的MeasureSpec和子View的LayoutParams的共同影响了子View的MeasureSpec,见getChildMeasureSpec。接着会调用View#measure,child.measure方法,则继续调用onMeasure方法,直到它的所有子view的大小都测量完成为止,这在上面说过了,这里不再赘述
看下FrameLayout的测量
ini
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//获取布局内子View数量
int count = getChildCount();
final boolean measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
mMatchParentChildren.clear();
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
//遍历所有子View中可见的View,也就不为GONE的View;
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
// 测量子view
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
// 获取子view的布局参数
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
// 记录子view的最大宽度和高度
maxWidth = Math.max(maxWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
maxHeight = Math.max(maxHeight,
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
childState = combineMeasuredStates(childState, child.getMeasuredState());
// 记录所有跟父布局有着相同宽或高的子view
if (measureMatchParentChildren) {
if (lp.width == LayoutParams.MATCH_PARENT || lp.height == LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
}
}
}
}
// 子view的最大宽高计算出来后,还要加上父View自身的padding
maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
...
//保存测量结果
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
//从子View中获取match_parent的个数
count = mMatchParentChildren.size();
if (count > 1) {
for (int i = 0; i < count; i++) {
final View child = mMatchParentChildren.get(i);
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec;
// 如果子view的宽是MATCH_PARENT,那么宽度 = 父view的宽 - 父Padding - 子Margin
if (lp.width == LayoutParams.MATCH_PARENT) {
final int width = Math.max(0, getMeasuredWidth()
- getPaddingLeftWithForeground() - getPaddingRightWithForeground()
- lp.leftMargin - lp.rightMargin);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
width, MeasureSpec.EXACTLY);
} else {
childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
lp.leftMargin + lp.rightMargin,
lp.width);
}
...
//对于这部分的子View,重新执行measure
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
}
onMeasure中有个measureChildWithMargin:它主要作用是测量子View,根据父/子View的状态生成子View的 MeasureSpec,那么我们直接看这个方法,ViewGroup#measureChildWithMargins。
ini
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
// 这里对应的是 child 自己想要的尺寸
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);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);//本view
int specSize = MeasureSpec.getSize(spec);//与子View无关
//size 表示子View可用空间:父容器尺寸减去padding
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// 父容器给子View确切的size,(具体数值或MATCH_PARENT)的情况下
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// 子view想成为父容器的大小
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
//子View确定自己的的size,但是不能超过父容器
resultSize = size;//注意这里给的是child最大可用,至于child怎么用看child本身实现
resultMode = MeasureSpec.AT_MOST;
}
break;
// 父容器对子View施加了最大的限制(即父容器大小赋值为WRAP_CONTENT)的情况下
case MeasureSpec.AT_MOST:
...
break;
// 父容器不限制子View大小,子View需要多大就多
case MeasureSpec.UNSPECIFIED:
...
break;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
View onMeasure
arduino
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
//作用:存储测量后的View宽 / 高
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
public static int getDefaultSize(int size, int measureSpec) {
// 设置默认大小
int result = size;
// 获取宽/高测量规格的模式 & 测量大小
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
// 模式为UNSPECIFIED时,使用提供的默认大小 = 参数Size
case MeasureSpec.UNSPECIFIED:
result = size;
break;
// 模式为AT_MOST,EXACTLY时,使用View测量后的宽/高值 = measureSpec中的Size
//view的测量在父类中实现,见下文
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
// 返回View的宽/高值
return result;
}
可以看到测试规格的模式(mode)是UNSPECIFIED时,使用的是提供的默认大小(即getDefaultSize()的第一个参数size)即:getSuggestedMinimumWidth() / getSuggestedMinimumHeight()。
csharp
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth,mBackground.getMinimumWidth());
}
// 逻辑说明
// 1. 若View无设置背景,那么View的宽度 = mMinWidth
// 即android:minWidth属性所指定的值,若无指定则为0.
// 2. 若View设置了背景,View的宽度为mMinWidth和mBackground.getMinimumWidth()中的最大值
// mBackground.getMinimumWidth()的大小 = 背景图Drawable的原始宽度
getWidth/getMeasuredWidth 区别
getMeasuredWidth: 获得 View测量的宽 / 高 是onMeasure 以后拿到的值,获得的值是setMeasuredDimension方法设置的值
getWidth: 获得View最终的宽 / 高 是onLayout 以后拿到的值,获得是layout方法中传递的四个参数中的mRight-mLeft
arduino
// 获得View测量的宽 / 高
public final int getMeasuredWidth() {
return mMeasuredWidth & MEASURED_SIZE_MASK;
// measure过程中返回的mMeasuredWidth
}
// 获得View最终的宽 / 高
public final int getWidth() {
return mRight - mLeft;
// View最终的宽 = 子View的右边界 - 子view的左边界。
}
一般情况下,二者获取的宽 / 高是相等的。那么,"非一般"情况是什么?通过重写View的 layout或者 viewgroup的onLayout 强行设置
java
@Override
public void layout( int l , int t, int r , int b){
// 改变传入的顶点位置参数
super.layout(l,t,r+100,b+100);
}
layout
- layout()中对自身View进行了位置计算:setFrame() / setOpticalFrame(),都是设置 left/right/top/bottom 值
- view的layout可重写,viewgroup final修饰不可以重写,两者都有 layout/onLayout
- onLayout 调用子View的 layout,单一View是没有子View的,故onLayout()是一个空实现
- ViewGroup 先在layout()中设置自己的位置(即setFrame),再在onLayout中调用子view的layout。
View的layout过程
ini
//确定View本身的位置,即设置View本身的四个顶点位置
public void layout(int l, int t, int r, int b) {
// 当前视图的四个顶点
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
// 1. 确定View的位置:setFrame() / setOpticalFrame(),两者逻辑相似
boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
// 2. 若视图的大小 & 位置发生变化会重新确定该View所有的子View在父容器的位置:onLayout()
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
}
}
protected boolean setFrame(int left, int top, int right, int bottom) {
// 通过以下赋值语句记录下了视图的位置信息,从而确定了视图的位置
if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
changed = true;
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
}
return changed;
}
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
ViewGroup layout过程
在自定义ViewGroup时必须复写onLayout()!
遍历子View 、计算当前子View的四个位置值 ,下面是 LinearLayout 的 布局实现:
java
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// 根据自身方向属性,而选择不同的处理方式
if (mOrientation == VERTICAL) {
layoutVertical(l, t, r, b);
} else {
layoutHorizontal(l, t, r, b);
}
}
// 此处仅分析垂直方向(Vertical)
void layoutVertical(int left, int top, int right, int bottom) {
// 子View的数量
final int count = getVirtualChildCount();
// 1. 遍历子View
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
if (child == null) {
childTop += measureNullChild(i);
} else if (child.getVisibility() != GONE) {
// 2. 计算子View的测量宽 / 高值
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
// 3. 确定子View的位置
setChildFrame(child, childLeft, childTop + getLocationOffset(child),childWidth, childHeight);
// childTop逐渐增大,即后面的子元素会被放置在靠下的位置
// 这符合垂直方向的LinearLayout的特性
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
i += getChildrenSkipCount(child, i);
}
}
}
// setChildFrame()仅仅只是调用了子View的layout()而已
private void setChildFrame( View child, int left, int top, int width, int height){
child.layout(left, top, left ++ width, top + height);
}
draw
draw 也可以重写,但一般建议重写 onDraw.
View的draw过程
scss
public void draw(Canvas canvas) {
...
if (!dirtyOpaque) {//这里 view/viewgroup 值会不一样
drawBackground(canvas);
}
final int viewFlags = mViewFlags;
if (!verticalEdges && !horizontalEdges) {
if (!dirtyOpaque) //这里 view/viewgroup 值会不一样
onDraw(canvas);
//绘制子View View中:默认为空实现
dispatchDraw(canvas);
//绘制装饰,如滑动条、前景色等等
onDrawScrollBars(canvas);
return;
}
...
}
ViewGroup的draw过程
draw/onDraw 方法调用的是View里的,覆写了 dispatchDraw
java
protected void dispatchDraw(Canvas canvas) {
......
// 1. 遍历子View
final int childrenCount = mChildrenCount;
......
for (int i = 0; i < childrenCount; i++) {
......
if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE || transientChild.getAnimation() != null) {
// 2. 绘制子View视图 ->>分析1
more |= drawChild(canvas, transientChild, drawingTime);
}
....
}
}
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
// 最终还是调用了子 View 的 draw ()进行子View的绘制
return child.draw(canvas, this, drawingTime);
}
onDraw() 和dispatchDraw()的区别
- 绘制View本身的内容,通过调用View.onDraw(canvas)函数实现
- 绘制自己的孩子通过dispatchDraw(canvas)实现
draw过程会调用onDraw(Canvas canvas)方法,然后就是dispatchDraw(Canvas canvas)方法, dispatchDraw()主要是分发给子组件进行绘制
默认状态下,ViewGroup为什么不走onDraw()
在默认状态下,ViewGroup的背景为透明的,而在View中的draw方法中表示只有不透明才执行onDraw,这也就导致后续ViewGroup的onDraw失效。
解决办法
- 可将onDraw中的处理移至到dispatchDraw()中。
- setWillNotDraw(false); 默认情况下:View 不启用该标记位(设置为false);ViewGroup 默认启用(设置为true)
- 直接在ViewGroup的xml下添加一个背景。
为什么View没有限制
在View的初始化中对于Flag的设置就与ViewGroup相反,也就导致在默认情况下View是不受影响的。
常用Layout measure性能对比
FrameLayout会measure一次或两次,见上面viewGroup measure源码。两次测量的情况:
RelativeLayout的child 大多数情况下会做两次measure。这是由于RelativeLayout是基于相对位置的,需要在横向和纵向分别进行一次measure过程。
LinearLayout 无weight情况下会进行一次测量,有weight情况下会做两次测量;在有weight情况下,且有类似 FrameLayout 两次测量那种场景下是会有三次测量的。
ConstraintLayout针对测量次数做了大量优化,尽可能的降低了绘制次数。child大部分情况下是一次,即使它支持的特性很多,更重要的是它在降低层级上有绝对的优势。
invalide/requestLayout 区别
- view的invalidate不会导致ViewRootImpl的invalidate被调用,而是递归调用父view的invalidateChildInParent,直到ViewRootImpl的peformTraversals,会导致当前view被重绘。由于mLayoutRequested为false,不会导致onMeasure和onLayout被调用,而OnDraw会被调用
- requestLayout会直接递归调用父窗口的requestLayout,直到ViewRootImpl,然后触发peformTraversals,由于mLayoutRequested为true,会导致onMeasure和onLayout被调用。不一定会触发OnDraw,requestLayout触发onDraw可能是因为在在layout过程中发现l,t,r,b和以前不一样,那就会触发一次invalidate
- 一般来说,只要刷新的时候就调用invalidate,需要重新measure就调用requestLayout。