当开发者通过setContentView方法,将自己的layout资源添加到mContextParent中之后,只是将该layout进行了加载,这个view还是不可见的,那这个layout是如何显示,测量和布局的尼?
在这里我们主要解决如下几个问题
- 自定义view是如何显示在屏幕上的
- 自定义view的onMeasure onLayou onDraw是如何调用的
- 自定义view的onMeasure onLayou 为什么调用两次,onDraw只调用一次
- 自定义view中的onMeasure方法的参数的具体含义
- 在activity的onCreate和onResume方法中是否能获取到自定义view的宽高
当activity执行onCreate方法之后,紧接着就会执行onResume方法,那么这是什么时候发生的?以及在调用activity的onResume方法的时候又发生了什么尼?
Activity执行onResume方法是因为ActivityThread.java的handleResumeActivity方法导致的,至于Activity的生命周期的启动过程,不在这里叙述了,那么我们详细看一下handleResumeActivity方法
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
ActivityClientRecord r = mActivities.get(token);
if (!checkAndUpdateLifecycleSeq(seq, r, "resumeActivity")) {
return;
}
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
unscheduleGcIdler();
mSomeActivitiesChanged = true;
// TODO Push resumeArgs into the activity for consideration
//performResumeActivity就是迫使Activity回调onResume方法的根本
r = performResumeActivity(token, clearHide, reason);
if (r != null) {
final Activity a = r.activity;
final int forwardBit = isForward ? WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0;
// 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.
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) {
r.window = r.activity.getWindow();
//获得Activity的根View:DecorView,其包含了去除状态栏之外的全部界面
View decor = r.window.getDecorView();
//设置DecorView为不可见,但是其mContentParent添加的view依旧可见
decor.setVisibility(View.INVISIBLE);
//a.getWindowManager()具体的实现类是WindowManagerImpl;
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 impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
}
}
if (a.mVisibleFromClient && !a.mWindowAdded) {
a.mWindowAdded = true;
//通过WindowManager的addView方法将DecorView及其子view添加到屏幕上
wm.addView(decor, l);
}
... ...
}
紧接着,我们分析下
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
其实现是在WindowManagerGlobal.java中的addView
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
/* 省略非关键代码*/
ViewRootImpl root;
View panelParentView = null;
/* 构建ViewRootImpl,并调用其setview方法完成DecorView的测量,布局,以及绘制,ViewRootImpl可以理解为Decorview的管理类*/
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
}
// do this last because it fires off messages to start doing things
try {
//通过此方法完成DecorView的测量布局和绘制
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
synchronized (mLock) {
final int index = findViewLocked(view, false);
if (index >= 0) {
removeViewLocked(index, true);
}
}
throw e;
}
}
接着看ViewRootImpl的setView的关键方法
/**
* We have one child
*/
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
//通过此方法完成DecorView的测量布局和绘制
requestLayout();
}
}
}
requestLayout方法的重点方法是scheduleTraversals
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
在scheduleTraversals方法中,启动一个线程mTraversalRunnable,进行View的测量布局和绘制
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
}
}
通过线程的方式去实现测量布局和绘制
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
在doTraversal方法中,关键代码是performTraversals
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "performTraversals");
try {
performTraversals();
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
通过对ViewRootImpl.java的performTraversals分析,我们才能详细了解自定义view是如何工作的,这段代码比较多,我们细细看
private void performTraversals() {
//host即之前的DecorView
final View host = mView;
if (host == null || !mAdded)
return;
mIsInTraversal = true;
mWillDrawSoon = true;
boolean windowSizeMayChange = false;
boolean newSurface = false;
boolean surfaceChanged = false;
//之前的窗口属性
WindowManager.LayoutParams lp = mWindowAttributes;
//屏幕宽
int desiredWindowWidth;
//屏幕高
int desiredWindowHeight;
Rect frame = mWinFrame;
if (mFirst) {
mFullRedrawNeeded = true;
mLayoutRequested = true;
//根据window属性获取屏幕宽高
if (lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL
|| lp.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) {
// NOTE -- system code, won't try to do compat mode.
Point size = new Point();
mDisplay.getRealSize(size);
desiredWindowWidth = size.x;
desiredWindowHeight = size.y;
} else {
DisplayMetrics packageMetrics =
mView.getContext().getResources().getDisplayMetrics();
desiredWindowWidth = packageMetrics.widthPixels;
desiredWindowHeight = packageMetrics.heightPixels;
}
} else {
/*省略代码 */
}
/*省略代码 */
if (collectViewAttributes()) {
params = lp;
}
if (mAttachInfo.mForceReportNewAttributes) {
mAttachInfo.mForceReportNewAttributes = false;
params = lp;
}
/*省略代码 */
if (!mStopped) {
boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
(relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged) {
//根据mWidth即窗口宽度,计算宽的测量大小,lp.width为match或者wrap或者UNSPECIFIED
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
//根据mHeight即窗口高度,计算高的测量大小,lp.height为match或者wrap或者UNSPECIFIED
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// Ask host how big it wants to be
//performMeasure将调用View的measure方法,在measure将调用view的onMeasure方法,从而实现
//自定义view的onMeasure的回调
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
// Implementation of weights from WindowManager.LayoutParams
// We just grow the dimensions as needed and re-measure if
// needs be
int width = host.getMeasuredWidth();
int height = host.getMeasuredHeight();
boolean measureAgain = false;
if (lp.horizontalWeight > 0.0f) {
width += (int) ((mWidth - width) * lp.horizontalWeight);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
MeasureSpec.EXACTLY);
measureAgain = true;
}
if (lp.verticalWeight > 0.0f) {
height += (int) ((mHeight - height) * lp.verticalWeight);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
MeasureSpec.EXACTLY);
measureAgain = true;
}
//初次绘制的时候,我们根据窗口属性如weight属性等,重新进行一次测量,正式因为这里我们可以看见
//自定义view中onMeasure方法被执行了两次
if (measureAgain) {
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
//设置layoutRequested属性为true,表示可以进行layout方法
layoutRequested = true;
}
}
} else {
/*省略代码 */
}
final boolean didLayout = layoutRequested && !mStopped;
boolean triggerGlobalLayoutListener = didLayout
|| mAttachInfo.mRecomputeGlobalAttributes;
if (didLayout) {
//performLayout将调用host.layout方法,在layout方法中,将调用view的onLayout方法
//从而自定义view的onLayout方法将被回调
//这里需要注意下layout方法,在layout方法中,
//如果在上面的measeure()中是直接从缓存中拿的,没有走onMeasure()的话要去走一次onMeasure()
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
//省略代码
}
boolean skipDraw = false;
//期初mFirst为true,设置DecorView获取焦点
if (mFirst) {
// handle first focus request
if (DEBUG_INPUT_RESIZE) Log.v(TAG, "First: mView.hasFocus()="
+ mView.hasFocus());
if (mView != null) {
if (!mView.hasFocus()) {
mView.requestFocus(View.FOCUS_FORWARD);
if (DEBUG_INPUT_RESIZE) Log.v(TAG, "First: requested focused view="
+ mView.findFocus());
} else {
if (DEBUG_INPUT_RESIZE) Log.v(TAG, "First: existing focused view="
+ mView.findFocus());
}
}
if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_ANIMATING) != 0) {
// The first time we relayout the window, if the system is
// doing window animations, we want to hold of on any future
// draws until the animation is done.
mWindowsAnimating = true;
}
} else if (mWindowsAnimating) {
skipDraw = true;
}
mFirst = false;
mWillDrawSoon = false;
mNewSurfaceNeeded = false;
mViewVisibility = viewVisibility;
if (mAttachInfo.mHasWindowFocus && !isInLocalFocusMode()) {
final boolean imTarget = WindowManager.LayoutParams
.mayUseInputMethod(mWindowAttributes.flags);
if (imTarget != mLastWasImTarget) {
mLastWasImTarget = imTarget;
InputMethodManager imm = InputMethodManager.peekInstance();
if (imm != null && imTarget) {
imm.startGettingWindowFocus(mView);
imm.onWindowFocus(mView, mView.findFocus(),
mWindowAttributes.softInputMode,
!mHasHadWindowFocus, mWindowAttributes.flags);
}
}
}
// Remember if we must report the next draw.
if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
mReportNextDraw = true;
}
boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() ||
viewVisibility != View.VISIBLE;
if (!cancelDraw && !newSurface) {
if (!skipDraw || mReportNextDraw) {
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).startChangingAnimations();
}
mPendingTransitions.clear();
}
//调用View的performDraw方法完成绘制
performDraw();
}
} else {
if (viewVisibility == View.VISIBLE) {
// Try again
scheduleTraversals();
} else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).endChangingAnimations();
}
mPendingTransitions.clear();
}
}
mIsInTraversal = false;
}
在这里我们已经分析了,通过performTraversals方法,我们可以看到自定义view的两次onMeasure,两次onLayout和依次onDraw的过程
我们注意分析测量,布局和绘制三个步骤
测量
在测量的时候,ViewRootImpl会调用getRootMeasureSpec方法,计算childWidthMeasureSpec和childHeightMeasureSpec,并将这两个方法传递给
performMeasure,从容传递给OnMeasure,这样自定义view的onMeasure方法便有了回调
//计算测量模式,该方法返回一个32位的int值,同时MeasureSpec提供getMode和getSize的方法返回mode和zise
//windowSzie表示窗体大小, rootDimension表示layout的width和heigth的属性
//在这里坐下说明,当rootDimension为match或者具体数值的时候,返回的int是正数
//当rootDimension为wrap时候,返回的int是负数, 这点我一直没搞清楚,当是wrap的时候,为什么是负数
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.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
makeMeasureSpec中size是测量的大小,mode是下面三种的一种
android.view.View.MeasureSpec#UNSPECIFIED
android.view.View.MeasureSpec#EXACTLY
android.view.View.MeasureSpec#AT_MOST
public static int makeMeasureSpec(int size, int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
我们可以知道,mode只能是如下三种模式的一种,<<表示有符号左移符号,先将十进制0转换为二进制,然后向左边移动30位。
然后负数的左移过程,首先先求反码,在就补码,在进行移位操作
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
public static final int EXACTLY = 1 << MODE_SHIFT;
public static final int AT_MOST = 2 << MODE_SHIFT;
现在分析下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);
}
}
其调用了View的measure方法,完成onMeasure的过程
//计算该view需要的大小,同时该view的大小受到父布局layoutparam的影响
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
// 通常没有设置layoutMode,这里为false
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);
}
// Suppress sign extension for the low bytes
long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
final boolean isExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY &&
MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
final boolean matchingSize = isExactly &&
getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec) &&
getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
if (forceLayout || !matchingSize &&
(widthMeasureSpec != mOldWidthMeasureSpec ||
heightMeasureSpec != mOldHeightMeasureSpec)) {
// first clears the measured dimension flag
// 清除flag的PFLAG_MEASURED_DIMENSION_SET位,表明view当前没有设置尺寸
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
resolveRtlPropertiesIfNeeded();
// 查询保存在缓存中是否有当前测量值的key,有的话同时flag的PFLAG_FORCE_LAYOUT位没有强制设置的话则不需要重新onMeasure()测量
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
// 这里去具体设置该view的测量宽mMeasuredWidth和高mMeasuredHeight
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
long value = mMeasureCache.valueAt(cacheIndex);
// Casting a long to int drops the high 32 bits, no mask needed
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
// 如果是直接拿缓存没有走onMeasure()则在这里置该位,在layout()中再去onMeasure()
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
// flag not set, setMeasuredDimension() was not invoked, we raise
// an exception to warn the developer
if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
throw new IllegalStateException("onMeasure() did not set the"
+ " measured dimension by calling"
+ " setMeasuredDimension()");
}
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
(long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
}
layout的过程
在ViewRootImpl的performTraversals方法中,会调用performLayout方法完成layout的过程
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
mLayoutRequested = false;
mScrollMayChange = true;
mInLayout = true;
final View host = mView;
try {
//调用View的layout方法完成onLayout的过程,从入参可以看出,layout的启动是在屏幕的0,0位置
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
mInLayout = false;
int numViewsRequestingLayout = mLayoutRequesters.size();
//此处numViewsRequestingLayout大于0
if (numViewsRequestingLayout > 0) {
// requestLayout() was called during layout.
// If no layout-request flags are set on the requesting views, there is no problem.
// If some requests are still pending, then we need to clear those flags and do
// a full request/measure/layout pass to handle this situation.
ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,
false);
if (validLayoutRequesters != null) {
// Set this flag to indicate that any further requests are happening during
// the second pass, which may result in posting those requests to the next
// frame instead
mHandlingLayoutInLayoutRequest = true;
// Process fresh layout requests, then measure and layout
int numValidRequests = validLayoutRequesters.size();
for (int i = 0; i < numValidRequests; ++i) {
final View view = validLayoutRequesters.get(i);
Log.w("View", "requestLayout() improperly called by " + view +
" during layout: running second layout pass");
view.requestLayout();
}
measureHierarchy(host, lp, mView.getContext().getResources(),
desiredWindowWidth, desiredWindowHeight);
mInLayout = true;
//再次调用View的layout方法完成onLayout的过程,从入参可以看出,layout的启动是在屏幕的0,0位置
//可以看出这里便是两次onlayout的过程
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
mHandlingLayoutInLayoutRequest = false;
// Check the valid requests again, this time without checking/clearing the
// layout flags, since requests happening during the second pass get noop'd
validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true);
if (validLayoutRequesters != null) {
final ArrayList<View> finalRequesters = validLayoutRequesters;
// Post second-pass requests to the next frame
getRunQueue().post(new Runnable() {
@Override
public void run() {
int numValidRequests = finalRequesters.size();
for (int i = 0; i < numValidRequests; ++i) {
final View view = finalRequesters.get(i);
Log.w("View", "requestLayout() improperly called by " + view +
" during second layout pass: posting in next frame");
view.requestLayout();
}
}
});
}
}
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
mInLayout = false;
}
draw的过程
private void performDraw() {
if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) {
return;
}
final boolean fullRedrawNeeded = mFullRedrawNeeded;
mFullRedrawNeeded = false;
mIsDrawing = true;
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");
try {
//调用viewrootimpl的draw方法完成绘制过程
draw(fullRedrawNeeded);
} finally {
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
draw方法的详细就比做过多解释,有兴趣的直接看如下源码
private void draw(boolean fullRedrawNeeded) {
Surface surface = mSurface;
if (!surface.isValid()) {
return;
}
if (DEBUG_FPS) {
trackFPS();
}
if (!sFirstDrawComplete) {
synchronized (sFirstDrawHandlers) {
sFirstDrawComplete = true;
final int count = sFirstDrawHandlers.size();
for (int i = 0; i< count; i++) {
mHandler.post(sFirstDrawHandlers.get(i));
}
}
}
scrollToRectOrFocus(null, false);
if (mAttachInfo.mViewScrollChanged) {
mAttachInfo.mViewScrollChanged = false;
mAttachInfo.mTreeObserver.dispatchOnScrollChanged();
}
boolean animating = mScroller != null && mScroller.computeScrollOffset();
final int curScrollY;
if (animating) {
curScrollY = mScroller.getCurrY();
} else {
curScrollY = mScrollY;
}
if (mCurScrollY != curScrollY) {
mCurScrollY = curScrollY;
fullRedrawNeeded = true;
if (mView instanceof RootViewSurfaceTaker) {
((RootViewSurfaceTaker) mView).onRootViewScrollYChanged(mCurScrollY);
}
}
final float appScale = mAttachInfo.mApplicationScale;
final boolean scalingRequired = mAttachInfo.mScalingRequired;
int resizeAlpha = 0;
if (mResizeBuffer != null) {
long deltaTime = SystemClock.uptimeMillis() - mResizeBufferStartTime;
if (deltaTime < mResizeBufferDuration) {
float amt = deltaTime/(float) mResizeBufferDuration;
amt = mResizeInterpolator.getInterpolation(amt);
animating = true;
resizeAlpha = 255 - (int)(amt*255);
} else {
disposeResizeBuffer();
}
}
final Rect dirty = mDirty;
if (mSurfaceHolder != null) {
// The app owns the surface, we won't draw.
dirty.setEmpty();
if (animating) {
if (mScroller != null) {
mScroller.abortAnimation();
}
disposeResizeBuffer();
}
return;
}
if (fullRedrawNeeded) {
mAttachInfo.mIgnoreDirtyState = true;
dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
}
if (DEBUG_ORIENTATION || DEBUG_DRAW) {
Log.v(TAG, "Draw " + mView + "/"
+ mWindowAttributes.getTitle()
+ ": dirty={" + dirty.left + "," + dirty.top
+ "," + dirty.right + "," + dirty.bottom + "} surface="
+ surface + " surface.isValid()=" + surface.isValid() + ", appScale:" +
appScale + ", width=" + mWidth + ", height=" + mHeight);
}
mAttachInfo.mTreeObserver.dispatchOnDraw();
int xOffset = 0;
int yOffset = curScrollY;
final WindowManager.LayoutParams params = mWindowAttributes;
final Rect surfaceInsets = params != null ? params.surfaceInsets : null;
if (surfaceInsets != null) {
xOffset -= surfaceInsets.left;
yOffset -= surfaceInsets.top;
// Offset dirty rect for surface insets.
dirty.offset(surfaceInsets.left, surfaceInsets.right);
}
boolean accessibilityFocusDirty = false;
final Drawable drawable = mAttachInfo.mAccessibilityFocusDrawable;
if (drawable != null) {
final Rect bounds = mAttachInfo.mTmpInvalRect;
final boolean hasFocus = getAccessibilityFocusedRect(bounds);
if (!hasFocus) {
bounds.setEmpty();
}
if (!bounds.equals(drawable.getBounds())) {
accessibilityFocusDirty = true;
}
}
if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) {
// If accessibility focus moved, always invalidate the root.
boolean invalidateRoot = accessibilityFocusDirty;
// Draw with hardware renderer.
mIsAnimating = false;
if (mHardwareYOffset != yOffset || mHardwareXOffset != xOffset) {
mHardwareYOffset = yOffset;
mHardwareXOffset = xOffset;
invalidateRoot = true;
}
mResizeAlpha = resizeAlpha;
if (invalidateRoot) {
mAttachInfo.mHardwareRenderer.invalidateRoot();
}
dirty.setEmpty();
mBlockResizeBuffer = false;
mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this);
} else {
// If we get here with a disabled & requested hardware renderer, something went
// wrong (an invalidate posted right before we destroyed the hardware surface
// for instance) so we should just bail out. Locking the surface with software
// rendering at this point would lock it forever and prevent hardware renderer
// from doing its job when it comes back.
// Before we request a new frame we must however attempt to reinitiliaze the
// hardware renderer if it's in requested state. This would happen after an
// eglTerminate() for instance.
if (mAttachInfo.mHardwareRenderer != null &&
!mAttachInfo.mHardwareRenderer.isEnabled() &&
mAttachInfo.mHardwareRenderer.isRequested()) {
try {
mAttachInfo.mHardwareRenderer.initializeIfNeeded(
mWidth, mHeight, mSurface, surfaceInsets);
} catch (OutOfResourcesException e) {
handleOutOfResourcesException(e);
return;
}
mFullRedrawNeeded = true;
scheduleTraversals();
return;
}
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
return;
}
}
}
if (animating) {
mFullRedrawNeeded = true;
scheduleTraversals();
}
}