Android View绘制机制
Android View绘制机制
View绘制
基本
ViewRoot
DecorView
顶层ViewGroup,FrameLayout子类
![View根布局关系图](https://upload-images.jianshu.io/upload_images/944365-34992eb46bdf93e7.png?imageMogr2/auto-orient/strip|imageView2/2/w/981/format/webp)
注意这里是View的三步骤不是连续执行的,而是基于每一步骤大过程分开执行的,先总体执行measure再layout再draw
![ViewRoot绘制流程图](https://upload-images.jianshu.io/upload_images/944365-c1adb9dd2d22c056.png?imageMogr2/auto-orient/strip|imageView2/2/w/983/format/webp)
MeasureSpec
决定View的测量过程,测量过程中,系统将LayoutParam所加的约束转换为MeasureSpec,根据改MeasureSpec测量View的宽高
组成:32位int值,高2位表示SpecMode, 低30位代表SpecSize
模式
- UNSPECIFIED:父容器不对View有任何限制,要多大给多大
- EXACTLY:父容器已经测出View所需要的精确大小,View最终大小就是SpecSize所指定的值,对应于Layoutparams中的 指定值以及MATCH_PARENT
- AT_MOST:父容器内指定一个可用大小SpecSize,View大小不能大于这个值,对应WRAP_CONTENT
DecorView绘制流程
// Activity
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
// 在Activity启动时通过ActivityThread performLaunchActivity中调用
final void attach(Context context, ...) {
// 初始化mWindow,实例为PhoneWindow
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mUiThread = Thread.currentThread();
mMainThread = aThread;
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
}
// PhoneWindow
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
// 设置自定义布局界面在content中
mLayoutInflater.inflate(layoutResID, mContentParent);
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
// 回调, 通知内容改变
cb.onContentChanged();
}
}
private void installDecor() {
if (mDecor == null) {
// 生成DecorView
mDecor = generateDecor(-1);
// ...
}
// 初始化mContentParent为
mContentParent = generateLayout(mDecor);
}
protected ViewGroup generateLayout(DecorView decor) {
int layoutResource;
mDecor.startChanging();
// 根据样式设置DecorView内容
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
// 绑定mContentParent(自定义View所在的主界面)
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
return contentParent;
}
// DecorView
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
final View root = inflater.inflate(layoutResource, null);
// 判断逻辑,并addView进行初始化(核心)
if (mDecorCaptionView != null) {
if (mDecorCaptionView.getParent() == null) {
addView(mDecorCaptionView,
new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mDecorCaptionView.addView(root,
new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
} else {
addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mContentRoot = (ViewGroup) root;
}
那么自定义布局在DecorView中便加载好了,接下来如何显示DecorView呢?
// ActivityThread
public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest, boolean isForward, String reason) {
if (!performResumeActivity(r, finalStateRequest, reason)) {
return;
}
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
// 设置DecorView不可见
decor.setVisibility(View.INVISIBLE);
// 添加DecorView到WindowManager中
wm.addView(decor, l);
}
if (!r.activity.mFinished && willBeVisible && r.activity.mDecor != null && !r.hideForNow) {
// 设置DecorView为VISBILE
r.activity.makeVisible();
}
}
void makeVisible() {
// DecorView还没有被添加到WindowManager中
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
// 设置DecorView为VISIBLE
mDecor.setVisibility(View.VISIBLE);
}
// WindowManagerImpl
// 在Android31版本中此处代码为空, 不知道通过什么步骤创建的WindowManager,因此这里采用Android28版本代码
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
// WindowManagerGlobal
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
ViewRootImpl root;
synchronized (mLock) {
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
}
// 构建ViewRootImpl对象
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
// 添加view、root到指定ArrayList中
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// 核心方法
root.setView(view, wparams, panelParentView);
}
}
// ViewRootImpl
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
// ...
requestLayout();
// ...
}
}
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
// 检查是否在主线程中
checkThread();
mLayoutRequested = true;
//
scheduleTraversals();
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 通过Handler异步地发送Runnable操作
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
// mTraversalRunnable为TraversalRunnable,只做了一件事:doTraversal()
// ViewRootImpl
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
// 核心方法,进行View绘制的三大流程
performTraversals();
}
}
performTraversals()做了以下几件事:
- 测量窗口大小
- 调用performMeasure,实际调用测mView.measure方法
- performDraw
Measure
View
// View
// widthMeasureSpec – Horizontal space requirements as imposed by the parent
// heightMeasureSpec – Vertical space requirements as imposed by the parent
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
// 缓存判断, 此处对View宽高有缓存,并在需要重新测绘时会比较
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// 核心方法
onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
// 存储测量后得到的宽高值
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
// ...
}
// 根据View的测量规格来测量View的宽或高
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
// UNSPECIFIED:使用默认大小,即getSuggestedMinimumWidth或getSuggestedMinimumHeight
case MeasureSpec.UNSPECIFIED:
result = size;
break;
// AT_MOST,EXACTLY:使用View测量后的宽高值
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
protected int getSuggestedMinimumWidth() {
// 若View无背景,则View的宽度为android:minWidth指定的
// 若View有背景,则View的宽度为android:minWidth与背景Drawable宽度的最大值
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
ViewGroup
由于不同的ViewGroup具有不同的布局特性,因此各自的子View测量方法不同,所以onMeasure方法各有不同
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);
}
}
}
// 取出child的layoutParam,再计算得到MeasureSpec进而进行测量
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
View的measure过程是三大流程中最复杂的一个,measure完成后,通过getMeasuredWith/Height能获取到View的正确测量宽高,但在一些情况下,系统可能需要多次measure才能确定最终测量宽高,因此最好不要在onMeasure方法中拿宽高,而是在onLayout去获取
Activity周期与View Measure问题
以前常犯的一个错误就是经常在onCreate中去获取View的宽高,有时经常显示宽高为0。这是因为View的measure过程与Activity生命周期不是同步的,回顾之前的代码,doTraversal是通过Handler去异步启动的,所以导致了不同步现象,那如何正确获取到View宽高呢?
- onWindowFocusChanged
- view.post
- ViewTreeObserver
- view.measure
为什么onMeasure会执行多次
Layout
View
public void layout(int l, int t, int r, int b) {
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
// 视图大小、位置发生改变,重新layout确定view位置
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
}
}
// 保存View四个顶点位置
// Returns: true if the new size and position are different than the previous ones @hide
protected boolean setFrame(int left, int top, int right, int bottom) {
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
}
// setOpticalFrame:追加效果边距长度
// 单一View是没有子View的,且layout中已经对自身View进行了位置计算,因此不需要再计算了
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {}
![View layout流程](https://upload-images.jianshu.io/upload_images/944365-756f72f8ccc58d2c.png?imageMogr2/auto-orient/strip|imageView2/2/w/310/format/webp)
ViewGroup
![ViewGroup Layout流程](https://upload-images.jianshu.io/upload_images/944365-8324440a7750863b.png?imageMogr2/auto-orient/strip|imageView2/2/w/1200/format/webp)
ViewGroup同样也只有抽象方法onLayout,因此继承ViewGroup类时必须重写onLayout方法
分析一下LinearLayout中的onLayout方法
@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) {
final int paddingLeft = mPaddingLeft;
int childTop;
int childLeft;
// Where right end of child should go
final int width = right - left;
int childRight = width - mPaddingRight;
// Space available for child
int childSpace = width - paddingLeft - mPaddingRight;
final int count = getVirtualChildCount();
// 获取Layout Gravity
final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
switch (majorGravity) {
case Gravity.BOTTOM:
// mTotalLength contains the padding already
childTop = mPaddingTop + bottom - top - mTotalLength;
break;
// mTotalLength contains the padding already
case Gravity.CENTER_VERTICAL:
childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
break;
case Gravity.TOP:
default:
childTop = mPaddingTop;
break;
}
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
if (child == null) {
childTop += measureNullChild(i);
// 如果VISBILITY不是GONE,则计算child位置
} else if (child.getVisibility() != GONE) {
// 获取child 宽高
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
final LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) child.getLayoutParams();
int gravity = lp.gravity;
if (gravity < 0) {
gravity = minorGravity;
}
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
// 根据Child layout_gravity判断child布局
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = paddingLeft + ((childSpace - childWidth) / 2)
+ lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
childLeft = childRight - childWidth - lp.rightMargin;
break;
case Gravity.LEFT:
default:
childLeft = paddingLeft + lp.leftMargin;
break;
}
// 判断是否有Divier
if (hasDividerBeforeChildAt(i)) {
childTop += mDividerHeight;
}
childTop += lp.topMargin;
// 等于调用child.layout
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
// childTop逐渐增大,后续的子元素会放在靠下位置
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
i += getChildrenSkipCount(child, i);
}
}
}
getHeight与getMeasuredHeight有什么不一样?
在非人为情况下,其值永远是相等的
Draw
View
public void draw(Canvas canvas) {
// 绘制背景
if (!dirtyOpaque) {
drawBackground(canvas);
}
if (!verticalEdges && !horizontalEdges) {
// 绘制View本身内容
if (!dirtyOpaque) {
onDraw(canvas);
}
// 分发子View Draw事件
dispatchDraw(canvas);
// 绘制装饰:前景、滑动条
onDrawForeground(canvas);
// 绘制默认高亮
drawDefaultFocusHighlight(canvas);
}
}
private void drawBackground(Canvas canvas) {
final Drawable background = mBackground;
if (background == null) {
return;
}
// 设置Drawable边界限制
setBackgroundBounds();
// 绘制背景Drawbale, 可能会有偏移值
final int scrollX = mScrollX;
final int scrollY = mScrollY;
if ((scrollX | scrollY) == 0) {
background.draw(canvas);
} else {
canvas.translate(scrollX, scrollY);
background.draw(canvas);
canvas.translate(-scrollX, -scrollY);
}
}
// 不同View不同实现
protected void onDraw(Canvas canvas) {}
// View中无子View,不需要Dispatch
protected void dispatchDraw(Canvas canvas) {}
ViewGroup
不同Layout中可能重写onDraw方法,进行基于布局特性的绘制
![ViewGroup绘制流程](https://upload-images.jianshu.io/upload_images/944365-ed43e7a8a91e3d69.png?imageMogr2/auto-orient/strip|imageView2/2/w/1200/format/webp)
@Override
protected void dispatchDraw(Canvas canvas) {
final View[] children = mChildren;
for (int i = 0; i < childrenCount; i++) {
while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
final View transientChild = mTransientViews.get(transientIndex);
if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
transientChild.getAnimation() != null) {
// 绘制子View逻辑
more |= drawChild(canvas, transientChild, drawingTime);
}
}
}
}
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
自定义View中需要注意的问题
支持特定属性
WRAP_CONTENT
重写onMeasure方法并设置默认宽高,当LayoutParams宽高为WRAP_CONTENT时,setMeasuredDimension(mWidth, mHeight);
paddling与margin
动画效果内存泄露
view.onDetachedFromWindow时进行动画取消