基于AndroidR源码分析
Android WMS动画系统初探(一)
Android WMS动画系统初探(二)
Android WMS动画系统初探(三)
Android WMS动画系统初探 完结篇
屏幕旋转动画
OrientationListener#onProposedRotationChanged ->
WindowManagerService#updateRotation
当屏幕需要旋转,WindowOrientationListener响应sensorchanged,会调用到WMS的updateRotation方法。
从这里开启屏幕旋转动画的入口。
WindowManagerService#updateRotation
/**
* 重新计算当前旋转
* 当系统状态发生变化时,如当前旋转可能需要更新、
* 例如当设备停靠或旋转到新的姿态时,调用WindowManagerPolicy
*/
@Override
public void updateRotation(boolean alwaysSendConfiguration, boolean forceRelayout) {
updateRotationUnchecked(alwaysSendConfiguration, forceRelayout);
}
private void updateRotationUnchecked(boolean alwaysSendConfiguration, boolean forceRelayout) {
ProtoLog.v(WM_DEBUG_ORIENTATION, "updateRotationUnchecked:"
+ " alwaysSendConfiguration=%b forceRelayout=%b",
alwaysSendConfiguration, forceRelayout);
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "updateRotation");
long origId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
boolean layoutNeeded = false;
final int displayCount = mRoot.mChildren.size();
for (int i = 0; i < displayCount; ++i) {
final DisplayContent displayContent = mRoot.mChildren.get(i);
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "updateRotation: display");
// 获取rotationChanged, 如果是true
// 则必须调用DisplayContent#sendNewConfiguration完成屏幕旋转或解冻
final boolean rotationChanged = displayContent.updateRotationUnchecked();
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
if (rotationChanged) {
mAtmService.getTaskChangeNotificationController()
.notifyOnActivityRotation(displayContent.mDisplayId);
}
if (!rotationChanged || forceRelayout) {
displayContent.setLayoutNeeded();
layoutNeeded = true;
}
if (rotationChanged || alwaysSendConfiguration) {
displayContent.sendNewConfiguration();
}
}
if (layoutNeeded) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
"updateRotation: performSurfacePlacement");
mWindowPlacerLocked.performSurfacePlacement();
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
}
} finally {
Binder.restoreCallingIdentity(origId);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
}
遍历mRoot.mChildren所有的DisplayContent,调用displayContent.updateRotationUnchecked()
-> DisplayRotation.updateRotationUnchecked
DisplayRotation#updateRotationUnchecked
/**
* 在这个方法中将通知container对于rotation的感知
* 然后根据顶部的activity开始冻屏动画或者无缝动画
* The display itself gets rotated in {@link DisplayContent#applyRotationLocked}
* during {@link DisplayContent#sendNewConfiguration}.
*
* @param forceUpdate 强制更新旋转。
* 有时在WM中,我们可能会跳过更新方向,因为我们正在等待旋转完成或显示解冻
* 这导致先前可见的活动的配置被应用到一个新可见的活动。
* 强制旋转更新可以解决这个问题。
* @return {@code true} 如果是true
* 则必须调用DisplayContent#sendNewConfiguration完成屏幕旋转或解冻
*/
boolean updateRotationUnchecked(boolean forceUpdate) {
final int displayId = mDisplayContent.getDisplayId();
if (!forceUpdate) {
if (mDeferredRotationPauseCount > 0) {
// rotation更新被暂停,推迟更新
ProtoLog.v(WM_DEBUG_ORIENTATION, "Deferring rotation, rotation is paused.");
return false;
}
final ScreenRotationAnimation screenRotationAnimation =
mDisplayContent.getRotationAnimation();
if (screenRotationAnimation != null && screenRotationAnimation.isAnimating()) {
// 当之前的旋转动画仍在进行时,不能执行旋转更新
// 将尝试在动画完成和显示解冻后再次更新。
ProtoLog.v(WM_DEBUG_ORIENTATION, "Deferring rotation, animation in progress.");
return false;
}
if (mService.mDisplayFrozen) {
// 即使屏幕旋转动画已经完成(例如isAnimating返回false)
// 但仍然有一段时间我们还没有解冻显示,我们也要停止旋转。
ProtoLog.v(WM_DEBUG_ORIENTATION,
"Deferring rotation, still finishing previous rotation");
return false;
}
if (mDisplayContent.mFixedRotationTransitionListener
.isTopFixedOrientationRecentsAnimating()) {
// 特殊场景:用户在Recents动画运行时旋转屏幕,忽略这次更新
return false;
}
}
if (!mService.mDisplayEnabled) {
// display不可用场景
ProtoLog.v(WM_DEBUG_ORIENTATION, "Deferring rotation, display is not enabled.");
return false;
}
final int oldRotation = mRotation;
final int lastOrientation = mLastOrientation;
final int rotation = rotationForOrientation(lastOrientation, oldRotation);
ProtoLog.v(WM_DEBUG_ORIENTATION,
"Computed rotation=%s (%d) for display id=%d based on lastOrientation=%s (%d) and "
+ "oldRotation=%s (%d)",
Surface.rotationToString(rotation), rotation,
displayId,
ActivityInfo.screenOrientationToString(lastOrientation), lastOrientation,
Surface.rotationToString(oldRotation), oldRotation);
ProtoLog.v(WM_DEBUG_ORIENTATION,
"Display id=%d selected orientation %s (%d), got rotation %s (%d)", displayId,
ActivityInfo.screenOrientationToString(lastOrientation), lastOrientation,
Surface.rotationToString(rotation), rotation);
if (oldRotation == rotation) {
// No change.
return false;
}
ProtoLog.v(WM_DEBUG_ORIENTATION,
"Display id=%d rotation changed to %d from %d, lastOrientation=%d",
displayId, rotation, oldRotation, lastOrientation);
if (DisplayContent.deltaRotation(rotation, oldRotation) != 2) {
mDisplayContent.mWaitingForConfig = true;
}
mRotation = rotation;
mService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_ACTIVE;
mService.mH.sendNewMessageDelayed(WindowManagerService.H.WINDOW_FREEZE_TIMEOUT,
mDisplayContent, WINDOW_FREEZE_TIMEOUT_DURATION);
mDisplayContent.setLayoutNeeded();
if (shouldRotateSeamlessly(oldRotation, rotation, forceUpdate)) {
// The screen rotation animation uses a screenshot to freeze the screen while windows
// resize underneath. When we are rotating seamlessly, we allow the elements to
// transition to their rotated state independently and without a freeze required.
prepareSeamlessRotation();
} else {
prepareNormalRotationAnimation();
}
// Give a remote handler (system ui) some time to reposition things.
startRemoteRotation(oldRotation, mRotation);
return true;
}
方法注释很清楚地解释了这个方法的工作:
- 在这个方法中将通知container对于rotation的感知,然后根据顶部的activity开始冻屏动画或者无缝动画。
- shouldRotateSeamlessly方法判断是否需要准备无缝动画。
- startRemoteRotation使得在动画开始之前systemui有机会响应方向变化
@DisplayContent.updateRotationUnchecked → @DisplayRotation.startRemoteRotation
SystemUI端处理查看DisplayChangeController.onRotateDisplay
DisplayRotation#shouldRotateSeamlessly
boolean shouldRotateSeamlessly(int oldRotation, int newRotation, boolean forceUpdate) {
// 如果应用(top activity)已经在正确的方向上启动那么不需要冻结显示
// 剩下的窗口可以使用无缝旋转
if (mDisplayContent.hasTopFixedRotationLaunchingApp()) {
return true;
}
final WindowState w = mDisplayPolicy.getTopFullscreenOpaqueWindow();
if (w == null || w != mDisplayContent.mCurrentFocus) {
return false;
}
// 只有当top window请求了无缝旋转并且处于全屏不透明状态时,我们才会启用无缝旋转。
// 无缝旋转需要冻结各种表面状态,不适合动画,所以我们现在在动画情况下禁用它
if (w.getAttrs().rotationAnimation != ROTATION_ANIMATION_SEAMLESS || w.isAnimatingLw()) {
return false;
}
// 对于上下旋转,我们不会随着导航条移动位置而无缝地旋转。
// 注意大多数应用程序(orientation:sensor|user,而不是fullSensor的应用)
// 不会进入reverse portarit(竖屏的反向)状态,所以实际上方向不会改变。
if (oldRotation == mUpsideDownRotation || newRotation == mUpsideDownRotation) {
return false;
}
// 如果导航条不能改变side,那么当我们改变方向时会发生跳跃,不能无缝地旋转
if (!mAllowSeamlessRotationDespiteNavBarMoving && !mDisplayPolicy.navigationBarCanMove()) {
return false;
}
// 如果Activity的bounds不同于它的父窗口,那么也不能无缝,因为窗口位置可能会在旋转后改变
if (w.mActivityRecord != null && !w.mActivityRecord.matchParentBounds()) {
return false;
}
// 在存在PinnedTask或System Alert窗口时,无法无缝旋转
if (mDisplayContent.getDefaultTaskDisplayArea().hasPinnedTask()
|| mDisplayContent.hasAlertWindowSurfaces()) {
return false;
}
// 在等待最后一次无缝旋转完成(即等待窗口重绘)时,不能旋转(无缝或不无缝)。
if (!forceUpdate && mDisplayContent.getWindow(win -> win.mSeamlesslyRotated) != null) {
return false;
}
return true;
}
可以看到进行无缝旋转的条件限制还是很多的,具体的判断逻辑我已经在代码上做了注释。
部分场景我目前还无法理解,可能要在之后的开发维护工作中逐步理解清楚。
无缝旋转
private void prepareSeamlessRotation() {
// We are careful to reset this in case a window was removed before it finished
// seamless rotation.
mSeamlessRotationCount = 0;
mRotatingSeamlessly = true;
}
额...看起来无缝旋转的prepare方法里并没有做什么太多的事情
先看一下冻屏旋转吧
冻屏旋转
void prepareNormalRotationAnimation() {
cancelSeamlessRotation();
final RotationAnimationPair anim = selectRotationAnimation();
mService.startFreezingDisplay(anim.mExit, anim.mEnter, mDisplayContent);
}
先是取消无缝旋转动画,然后进行冻屏旋转
private static class RotationAnimationPair {
@AnimRes
int mEnter;
@AnimRes
int mExit;
}
DisplayRotation#selectRotationAnimation方法挑选实际要运行的RotationAnimationPair动画效果
WindowManagerService#startFreezingDisplay
void startFreezingDisplay(int exitAnim, int enterAnim, DisplayContent displayContent,
int overrideOriginalRotation) {
// 如果当前已经处于冻屏或者无缝动画 return
if (mDisplayFrozen || displayContent.getDisplayRotation().isRotatingSeamlessly()) {
return;
}
// 判断是否满足冻屏条件
if (!displayContent.isReady() || !mPolicy.isScreenOn() || !displayContent.okToAnimate()) {
// No need to freeze the screen before the display is ready, if the screen is off,
// or we can't currently animate.
return;
}
ProtoLog.d(WM_DEBUG_ORIENTATION,
"startFreezingDisplayLocked: exitAnim=%d enterAnim=%d called by %s",
exitAnim, enterAnim, Debug.getCallers(8));
mScreenFrozenLock.acquire();
mDisplayFrozen = true;
mDisplayFreezeTime = SystemClock.elapsedRealtime();
mLastFinishedFreezeSource = null;
// {@link mDisplayFrozen} prevents us from freezing on multiple displays at the same time.
// As a result, we only track the display that has initially froze the screen.
mFrozenDisplayId = displayContent.getDisplayId();
// 冻结输入事件分发
mInputManagerCallback.freezeInputDispatchingLw();
if (displayContent.mAppTransition.isTransitionSet()) {
displayContent.mAppTransition.freeze();
}
if (PROFILE_ORIENTATION) {
File file = new File("/data/system/frozen");
Debug.startMethodTracing(file.toString(), 8 * 1024 * 1024);
}
mLatencyTracker.onActionStart(ACTION_ROTATE_SCREEN);
mExitAnimId = exitAnim;
mEnterAnimId = enterAnim;
displayContent.updateDisplayInfo();
// 这时候还未更新rotaton,获取的是旋转前的rotation
final int originalRotation = overrideOriginalRotation != ROTATION_UNDEFINED
? overrideOriginalRotation
: displayContent.getDisplayInfo().rotation;
// 创建ScreenRotationAnimation
displayContent.setRotationAnimation(new ScreenRotationAnimation(displayContent,
originalRotation));
}
startFreezingDisplay构建了ScreenRotationAnimation并设置到DisplayContent中
ScreenRotationAnimation
ScreenRotationAnimation(DisplayContent displayContent, @Surface.Rotation int originalRotation) {
mService = displayContent.mWmService;
mContext = mService.mContext;
mDisplayContent = displayContent;
displayContent.getBounds(mOriginalDisplayRect);
// Screenshot does NOT include rotation!
final DisplayInfo displayInfo = displayContent.getDisplayInfo();
// 重新从DisplayInfo中获取原方向值
final int realOriginalRotation = displayInfo.rotation;
final int originalWidth;
final int originalHeight;
// 这一段是获取旋转前的长和宽
if (displayContent.getDisplayRotation().isFixedToUserRotation()) {
// Emulated orientation.
mForceDefaultOrientation = true;
originalWidth = displayContent.mBaseDisplayWidth;
originalHeight = displayContent.mBaseDisplayHeight;
} else {
// Normal situation
originalWidth = displayInfo.logicalWidth;
originalHeight = displayInfo.logicalHeight;
}
if (realOriginalRotation == Surface.ROTATION_90
|| realOriginalRotation == Surface.ROTATION_270) {
mWidth = originalHeight;
mHeight = originalWidth;
} else {
mWidth = originalWidth;
mHeight = originalHeight;
}
mOriginalRotation = originalRotation;
// 计算旋转前后delta值 用于旋转动画效果
final int delta = DisplayContent.deltaRotation(originalRotation, realOriginalRotation);
final boolean flipped = delta == Surface.ROTATION_90 || delta == Surface.ROTATION_270;
mOriginalWidth = flipped ? originalHeight : originalWidth;
mOriginalHeight = flipped ? originalWidth : originalHeight;
mSurfaceRotationAnimationController = new SurfaceRotationAnimationController();
// Check whether the current screen contains any secure content.
final boolean isSecure = displayContent.hasSecureWindowOnScreen();
final SurfaceControl.Transaction t = mService.mTransactionFactory.get();
try {
// 创建动画要用的几个surface
mBackColorSurface = displayContent.makeChildSurface(null)
.setName("BackColorSurface")
.setColorLayer()
.setCallsite("ScreenRotationAnimation")
.build();
// mScreenshotLayer是真正用于旋转动画的layer
mScreenshotLayer = displayContent.makeOverlay()
.setName("RotationLayer")
.setBufferSize(mWidth, mHeight)
.setSecure(isSecure)
.setCallsite("ScreenRotationAnimation")
.build();
mEnterBlackFrameLayer = displayContent.makeOverlay()
.setName("EnterBlackFrameLayer")
.setContainerLayer()
.setCallsite("ScreenRotationAnimation")
.build();
// In case display bounds change, screenshot buffer and surface may mismatch so set a
// scaling mode.
SurfaceControl.Transaction t2 = mService.mTransactionFactory.get();
t2.setOverrideScalingMode(mScreenshotLayer, Surface.SCALING_MODE_SCALE_TO_WINDOW);
t2.apply(true /* sync */);
// 这一大段就是抓取截图绑定到mScreenshotLayer
final int displayId = displayContent.getDisplayId();
final Surface surface = mService.mSurfaceFactory.get();
surface.copyFrom(mScreenshotLayer);
SurfaceControl.ScreenshotGraphicBuffer gb =
mService.mDisplayManagerInternal.systemScreenshot(displayId);
if (gb != null) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
"ScreenRotationAnimation#getMedianBorderLuma");
mStartLuma = RotationAnimationUtils.getMedianBorderLuma(gb.getGraphicBuffer(),
gb.getColorSpace());
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
try {
surface.attachAndQueueBufferWithColorSpace(gb.getGraphicBuffer(),
gb.getColorSpace());
} catch (RuntimeException e) {
Slog.w(TAG, "Failed to attach screenshot - " + e.getMessage());
}
// If the screenshot contains secure layers, we have to make sure the
// screenshot surface we display it in also has FLAG_SECURE so that
// the user can not screenshot secure layers via the screenshot surface.
if (gb.containsSecureLayers()) {
t.setSecure(mScreenshotLayer, true);
}
t.setLayer(mScreenshotLayer, SCREEN_FREEZE_LAYER_BASE);
t.reparent(mBackColorSurface, displayContent.getSurfaceControl());
t.setLayer(mBackColorSurface, -1);
t.setColor(mBackColorSurface, new float[]{mStartLuma, mStartLuma, mStartLuma});
t.setAlpha(mBackColorSurface, 1);
t.show(mScreenshotLayer);
t.show(mBackColorSurface);
} else {
Slog.w(TAG, "Unable to take screenshot of display " + displayId);
}
surface.destroy();
} catch (OutOfResourcesException e) {
Slog.w(TAG, "Unable to allocate freeze surface", e);
}
ProtoLog.i(WM_SHOW_SURFACE_ALLOC,
" FREEZE %s: CREATE", mScreenshotLayer);
// 这里我的理解是对rotationLayer做默认的旋转变换
setRotation(t, realOriginalRotation);
t.apply();
}
接下来我们回到入口的 WindowManagerService#updateRotationUnchecked,接下来继续调用DisplayContent#sendNewConfiguration()
DisplayContent#sendNewConfiguration()
void sendNewConfiguration() {
...
if (mWaitingForConfig) {
mWaitingForConfig = false;
mWmService.mLastFinishedFreezeSource = "config-unchanged";
setLayoutNeeded();
mWmService.mWindowPlacerLocked.performSurfacePlacement();
}
}
这里再次调用我们熟悉的performSurfacePlacement方法,经典再现,进入窗口布局流程,
这个流程会像前文分析的那样调用到RootWindowContainer#performSurfacePlacementNoTrace方法
void performSurfacePlacementNoTrace() {
...
if (mOrientationChangeComplete) {
if (mWmService.mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_NONE) {
mWmService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_NONE;
mWmService.mLastFinishedFreezeSource = mLastWindowFreezeSource;
mWmService.mH.removeMessages(WINDOW_FREEZE_TIMEOUT);
}
mWmService.stopFreezingDisplayLocked();
}
}
mWmService#stopFreezingDisplayLocked()
ScreenRotationAnimation screenRotationAnimation = displayContent == null ? null
: displayContent.getRotationAnimation();
if (screenRotationAnimation != null && screenRotationAnimation.hasScreenshot()) {
ProtoLog.i(WM_DEBUG_ORIENTATION, "**** Dismissing screen rotation animation");
DisplayInfo displayInfo = displayContent.getDisplayInfo();
// Get rotation animation again, with new top window
if (!displayContent.getDisplayRotation().validateRotationAnimation(
mExitAnimId, mEnterAnimId, false /* forceDefault */)) {
mExitAnimId = mEnterAnimId = 0;
}
if (screenRotationAnimation.dismiss(mTransaction, MAX_ANIMATION_DURATION,
// MIUI ADD: START
// getTransitionAnimationScaleLocked(), displayInfo.logicalWidth,
mIsCastMode ? 0.0f : getTransitionAnimationScaleLocked(), displayInfo.logicalWidth,
// END
displayInfo.logicalHeight, mExitAnimId, mEnterAnimId)) {
mTransaction.apply();
} else {
screenRotationAnimation.kill();
displayContent.setRotationAnimation(null);
updateRotation = true;
}
} else {
if (screenRotationAnimation != null) {
screenRotationAnimation.kill();
displayContent.setRotationAnimation(null);
}
updateRotation = true;
}
displayContent.getRotationAnimation()获取了前面流程设置的ScreenRotationAnimation
screenRotationAnimation.dismiss方法将调用startAnimation启动动画
ScreenRotationAnimation#startAnimation
/**
* Returns true if animating.
*/
private boolean startAnimation(SurfaceControl.Transaction t, long maxAnimationDuration,
float animationScale, int finalWidth, int finalHeight, int exitAnim, int enterAnim) {
if (mScreenshotLayer == null) {
// Can't do animation.
return false;
}
if (mStarted) {
return true;
}
mStarted = true;
// Figure out how the screen has moved from the original rotation.
int delta = DisplayContent.deltaRotation(mCurRotation, mOriginalRotation);
// 这里的exitAnim和enterAnim来自前面流程的selectRotationAnimation方法
final boolean customAnim;
if (exitAnim != 0 && enterAnim != 0) {
customAnim = true;
mRotateExitAnimation = AnimationUtils.loadAnimation(mContext, exitAnim);
mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext, enterAnim);
mRotateAlphaAnimation = AnimationUtils.loadAnimation(mContext,
R.anim.screen_rotate_alpha);
} else {
// 如果selectRotationAnimation没有选择到custom动画效果
// 就加载默认的对应4种方向变换的动画效果
customAnim = false;
switch (delta) { /* Counter-Clockwise Rotations */
case Surface.ROTATION_0:
mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
R.anim.screen_rotate_0_exit);
mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
R.anim.screen_rotate_0_enter);
break;
case Surface.ROTATION_90:
mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
R.anim.screen_rotate_plus_90_exit);
mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
R.anim.screen_rotate_plus_90_enter);
break;
case Surface.ROTATION_180:
mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
R.anim.screen_rotate_180_exit);
mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
R.anim.screen_rotate_180_enter);
break;
case Surface.ROTATION_270:
mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
R.anim.screen_rotate_minus_90_exit);
mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
R.anim.screen_rotate_minus_90_enter);
break;
}
}
ProtoLog.d(WM_DEBUG_ORIENTATION, "Start rotation animation. customAnim=%s, "
+ "mCurRotation=%s, mOriginalRotation=%s",
customAnim, Surface.rotationToString(mCurRotation),
Surface.rotationToString(mOriginalRotation));
mRotateExitAnimation.initialize(finalWidth, finalHeight, mOriginalWidth, mOriginalHeight);
mRotateExitAnimation.restrictDuration(maxAnimationDuration);
mRotateExitAnimation.scaleCurrentDuration(animationScale);
mRotateEnterAnimation.initialize(finalWidth, finalHeight, mOriginalWidth, mOriginalHeight);
mRotateEnterAnimation.restrictDuration(maxAnimationDuration);
mRotateEnterAnimation.scaleCurrentDuration(animationScale);
mAnimRunning = false;
mFinishAnimReady = false;
mFinishAnimStartTime = -1;
if (customAnim) {
mRotateAlphaAnimation.restrictDuration(maxAnimationDuration);
mRotateAlphaAnimation.scaleCurrentDuration(animationScale);
}
if (customAnim && mEnteringBlackFrame == null) {
try {
Rect outer = new Rect(-finalWidth, -finalHeight,
finalWidth * 2, finalHeight * 2);
Rect inner = new Rect(0, 0, finalWidth, finalHeight);
mEnteringBlackFrame = new BlackFrame(mService.mTransactionFactory, t, outer, inner,
SCREEN_FREEZE_LAYER_ENTER, mDisplayContent, false, mEnterBlackFrameLayer);
} catch (OutOfResourcesException e) {
Slog.w(TAG, "Unable to allocate black surface", e);
}
}
if (customAnim) {
mSurfaceRotationAnimationController.startCustomAnimation();
} else {
mSurfaceRotationAnimationController.startScreenRotationAnimation();
}
return true;
}
- exitAnim和enterAnim来自前面流程的selectRotationAnimation方法
- 如果selectRotationAnimation方法没有获取到custom动画效果,就加载四种默认的效果
- 加载的Animation赋值给 mRotateEnterAnimation 和 mRotateExitAnimation
后面的startCustomAnimation或startScreenRotationAnimation方法流程将使用mRotateEnterAnimation及mRotateExitAnimation构建AnimationSpec,然后使用SurfaceAnimator启动并驱动动画,还是那个熟悉的味道。
关于无缝旋转的具体实现我后面会再做深入理解。
至此,WMS动画系统初探完结, 撒花 \ o /