不管是系统中窗口的动画,仍是应用中某一个View的动画,它们的原理都是同样的。当一个窗口打开的时候,为了看起来更缓和一点,系统都会给每个Activity窗口添加一个动画,关于动画的部分,我所想写的有四点。第1、动画有哪些类型;第二动画是怎么设置的,因为窗口动画和过分动画(Activity窗口动画)是不同的,就须要分开讲,在小米手机上,能够去开发者选项中将窗口动画的播放速度降慢5倍或者10倍,能够清楚的看到动画的过程。第三,动画设置完成以后,怎么触发垂直刷新信号一帧帧显示的,因为一个窗口可能存在多个动画,好比转屏动画、过分动画、窗口动画,自身动画等等,最终交给SurfaceFlinger绘制显示的时候,须要合成为一个动画,因此在谈一下动画的合成;第四,简单总结应用动画,用一个贝塞尔曲线绘制直播间点赞效果的例子讲解一下,总结而言,系统中的动画和应用中的动画原理是同样的,这篇文章是站在系统的角度上,搞清楚动画的原理,本文基于Android7.0源码。java
####1、动画类型
在Apptransition.java中定义了不少动画的类型,每一个类型以一个int值来表示。android
动画类型 | 值 | 含义 |
---|---|---|
TRANSIT_UNSET | -1 | 初始值,还没有设定 |
TRANSIT_NONE | 0 | 没有动画 |
TRANSIT_ACTIVITY_OPEN | 6 | 在同一task中在最顶端打开一个窗口 |
TRANSIT_ACTIVITY_CLOSE | 7 | 关闭当前活动窗口,恢复同一个task中的上一个窗口 |
TRANSIT_TASK_OPEN | 8 | 新建任务并建立窗口 |
TRANSIT_TASK_CLOSE | 9 | 关闭当前活动窗口,回到上一个任务 |
TRANSIT_TASK_TO_FRONT | 10 | 将任务移至最顶 |
TRANSIT_TASK_TO_BACK | 11 | 将当前任务移至最末 |
TRANSIT_WALLPAPER_CLOSE | 12 | 关闭到无墙纸的应用 |
TRANSIT_WALLPAPER_OPEN | 13 | 启动墙纸应用 |
TRANSIT_WALLPAPER_INTRA_OPEN | 14 | 有墙纸打开 |
TRANSIT_WALLPAPER_INTRA_CLOSE | 15 | 有墙纸关闭 |
默认是没有动画,即类型是TRANSIT_UNSET,拿Activity的启动来举例子,好比当一个Activity打开的时候,那么系统就会设置一个TRANSIT_ACTIVITY_OPEN的动画,若是你startActivity组件的时候,Intent对象带有FLAG_ACTIVITY_NO_ANIMATION这样的flag,那么系统就会给你设置一个TRANSIT_NONE,表示没有动画,不须要动画,若是你指定了lauchMode,跨Task栈新起了一个Actiivty,那么就会设置一个TRANSIT_TASK_OPEN类型,表示新建任务并建立窗口时候要用的动画,同理当Activity的关闭的时候,也相似,总之根据不一样的case,设置不一样的类型,后面根据这个设置好的类型,加载不一样的动画。windows
####2、动画设置
了解了动画类型了,咱们看一下Activity切换的时候,动画是怎么设置的,先简单看一下Activity的切换。bash
#####一、Activiy切换
app
什么是Activity的切换呢?
前一个Activity从resume状态变成pause状态,后一个Activity进入到resume状态,将前一个resume状态的窗口设置成不可见,后一个窗口设置成可见。less
切换的步骤ide
首先梳理一下prepareAppTransition方法。设置什么样的切换操做,其实由Activity的行为决定。函数
frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
@Override
public void prepareAppTransition(int transit, boolean alwaysKeepCurrent) {
if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
"prepareAppTransition()")) {
throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
}
synchronized(mWindowMap) {
boolean prepared = mAppTransition.prepareAppTransitionLocked(
transit, alwaysKeepCurrent);
//prepared为ture,说明已经被成功设置了切换操做,可是当前冻屏、熄屏、Display没有准备好的状况下,
//设置mSkipAppTransitionAnimation等于true,表示要跳过这切换操做对应动画的执行。
if (prepared && okToDisplay()) {
mSkipAppTransitionAnimation = false;
}
}
}复制代码
frameworks/base/services/core/java/com/android/server/wm/AppTransition.java
boolean prepareAppTransitionLocked(int transit, boolean alwaysKeepCurrent) {
.....
//isTransitionSet()表示已经设置了切换操做类型
if (!isTransitionSet() || mNextAppTransition == TRANSIT_NONE) {
setAppTransition(transit);
} else if (!alwaysKeepCurrent) {
//alwaysKeepCurrent若等于true,表示要维持上次设置的切换类型,本次新设置的不能覆盖它
if (transit == TRANSIT_TASK_OPEN && isTransitionEqual(TRANSIT_TASK_CLOSE)) {
// Opening a new task always supersedes a close for the anim.
setAppTransition(transit);
} else if (transit == TRANSIT_ACTIVITY_OPEN
&& isTransitionEqual(TRANSIT_ACTIVITY_CLOSE)) {
// Opening a new activity always supersedes a close for the anim.
setAppTransition(transit);
}
}
boolean prepared = prepare();
if (isTransitionSet()) {
//发送一个5秒的超时消息给WMS运行的线程(android.display线程),表示要在5秒时间类完成设置的切换操做。
mService.mH.removeMessages(H.APP_TRANSITION_TIMEOUT);
mService.mH.sendEmptyMessageDelayed(H.APP_TRANSITION_TIMEOUT, APP_TRANSITION_TIMEOUT_MS);
}
return prepared;
}复制代码
若是transit == TRANSIT_TASK_OPEN 而且isTransitionEqual(TRANSIT_TASK_CLOSE)返回true,表示上次(以前)给Activity设置的切换操做是TRANSIT_TASK_CLOSE,那么能够调用setAppTransition,由于打开的动画要比关闭的动画优先级要高。oop
若是transit == TRANSIT_ACTIVITY_OPEN 而且isTransitionEqual(TRANSIT_ACTIVITY_CLOSE)返回true,表示上次(以前)给Activity设置的切换操做是TRANSIT_ACTIVITY_CLOSE,那么能够调用setAppTransition,由于打开的动画要比关闭的动画优先级要高。post
frameworks/base/services/core/java/com/android/server/wm/AppTransition.java
private void setAppTransition(int transit) {
mNextAppTransition = transit;
setLastAppTransition(TRANSIT_UNSET, null, null);
}复制代码
setAppTransition执行事后,而且前一个激活的Activity组件进入到Paused状态了,而且客户端进程已经启动了,这个时候ActivityManagerService服务就会调用ActivityStack类的成员函数realStartActivityLocked来将正在启动的Activity组件加载起来,而且将它的状态设置为Resumed,首先看一下setAppVisibility,将窗口可见性的设置。
frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
@Override
public void setAppVisibility(IBinder token, boolean visible) {
.....
AppWindowToken wtoken;
synchronized(mWindowMap) {
//经过ActivityRecord:Token找到AppWindowToken,即找到这个token对应的Activity窗口
wtoken = findAppWindowToken(token);
if (wtoken == null) {
Slog.w(TAG_WM, "Attempted to set visibility of non-existing app token: " + token);
return;
}
.....
//mOpeningApps是WMS的成员,里面存放全部打开的窗口的AppWindowToken,首先移除,后面根据visible添加
mOpeningApps.remove(wtoken);
//mClosingApps是WMS的成员,里面存放全部关闭的窗口的AppWindowToken,首先移除,后面根据visible添加
mClosingApps.remove(wtoken);
//表示等待着去显示
wtoken.waitingToShow = false;
wtoken.hiddenRequested = !visible;
if (!visible) {
// If the app is dead while it was visible, we kept its dead window on screen.
// Now that the app is going invisible, we can remove it. It will be restarted
// if made visible again.
wtoken.removeAllDeadWindows();
wtoken.setVisibleBeforeClientHidden();
} else if (visible) {
if (!mAppTransition.isTransitionSet() && mAppTransition.isReady()) {
// Add the app mOpeningApps if transition is unset but ready. This means
// we're doing a screen freeze, and the unfreeze will wait for all opening // apps to be ready. mOpeningApps.add(wtoken); } wtoken.startingMoved = false; // If the token is currently hidden (should be the common case), or has been // stopped, then we need to set up to wait for its windows to be ready. if (wtoken.hidden || wtoken.mAppStopped) { wtoken.clearAllDrawn(); // If the app was already visible, don't reset the waitingToShow state.
//若是hidden的值等于false,说明Activity组件当前是不可见的。又因为上面visible为true,表示Activity将要被设置成可见的,
//所以,这时候就须要将AppWindowToken对象wtoken的成员变量waitingToShow的值设置为true。
if (wtoken.hidden) {
wtoken.waitingToShow = true;
}
if (wtoken.clientHidden) {
// In the case where we are making an app visible
// but holding off for a transition, we still need
// to tell the client to make its windows visible so
// they get drawn. Otherwise, we will wait on
// performing the transition until all windows have
// been drawn, they never will be, and we are sad.
wtoken.clientHidden = false;
//通知应用程序进程将参数token所描述的Activity组件设置为true
wtoken.sendAppVisibilityToClients();
}
}
wtoken.requestUpdateWallpaperIfNeeded();
if (DEBUG_ADD_REMOVE) Slog.v(
TAG_WM, "No longer Stopped: " + wtoken);
wtoken.mAppStopped = false;
}
//这个if分之在动画设置完成而且屏幕不冻屏,亮屏、Display OK的状况下才会走
if (okToDisplay() && mAppTransition.isTransitionSet()) {
if (wtoken.mAppAnimator.usingTransferredAnimation
&& wtoken.mAppAnimator.animation == null) {
Slog.wtf(TAG_WM, "Will NOT set dummy animation on: " + wtoken
+ ", using null transfered animation!");
}
if (!wtoken.mAppAnimator.usingTransferredAnimation &&
(!wtoken.startingDisplayed || mSkipAppTransitionAnimation)) {
if (DEBUG_APP_TRANSITIONS) Slog.v(
TAG_WM, "Setting dummy animation on: " + wtoken);
//设置哑动画,能够理解是一个站位的做用,后面会对它设置真正的动画
wtoken.mAppAnimator.setDummyAnimation();
}
wtoken.inPendingTransaction = true;
if (visible) {
//可见,把wtoken加入到mOpeningApps
mOpeningApps.add(wtoken);
wtoken.mEnteringAnimation = true;
} else {
//不可见,把wtoken加入到mClosingApps
mClosingApps.add(wtoken);
wtoken.mEnteringAnimation = false;
}
if (mAppTransition.getAppTransition() == AppTransition.TRANSIT_TASK_OPEN_BEHIND) {
// We're launchingBehind, add the launching activity to mOpeningApps. final WindowState win = findFocusedWindowLocked(getDefaultDisplayContentLocked()); if (win != null) { final AppWindowToken focusedToken = win.mAppToken; if (focusedToken != null) { focusedToken.hidden = true; mOpeningApps.add(focusedToken); } } } return; } final long origId = Binder.clearCallingIdentity(); wtoken.inPendingTransaction = false; //将参数token所描述的Activity组件的可见性设置为参数visible所描述的值; setTokenVisibilityLocked(wtoken, null, visible, AppTransition.TRANSIT_UNSET,true, wtoken.voiceInteraction); //向WMS服务上报参数token所描述的Activity组件的可见性 wtoken.updateReportedVisibilityLocked(); Binder.restoreCallingIdentity(origId); } }复制代码
这个方法变量比较多,要所有弄明白,仍是要花费一些功夫的。setAppVisibility设置好以后,就能够通知客户端启动APP进程了,(因此这样看来,当一个Activity的实例还不存在的时候,它的窗口的token就已经被肯定了)接着往下走,completeResumeLocked方法主要是从上下到检查哪些Activity组件是须要设置为可见的,哪些Activity组件是须要设置为不可见的,找到栈顶部第一个全屏显示的Activity组件,调用setAppVisibility设置为true,这个全屏显示Activity组件下面的全部Activity组件的可见性设置为false。
最后通知WindowManagerService服务调用executeAppTransition方法执行前面所准备的切换操做,执行这个切换操做跟Activity窗口动画(过分动画)有关系,如今就开始第二节内容。
#####二、过分动画设置
粗略的看一共19个步骤,前面prepareAppTransition设置切换操做和sendAppVisibility方法设置哪一个Activity要隐藏,哪一个Activity的要显示,已经解释过了,如今从sendAppVisibilityToClient开始。sendAppVisibilityToClient/dispatchAppVibility 这两个函数就是通知上层应用窗口可见性发生变化。若是下一个Activity是冷启动,那么这个函数并不能通知下一个Activity的窗口变为可见,由于此时该函数调用时,下一个Activity的窗口还没加到到WMS中来,Activity的窗口添加是Activity 的onResume方法中添加的。而后到第四步finishDrawingWindow下一个被Resume起来后,添加窗口、measure、layout、draw等一系列操做完成后便会调用WMS.finishDrawingWindow()来通知WMS,该窗口已经绘制好了,能够开始作动画了。WMS.finishDrawingWindow()会调用WindowStateAnimator.finishDrawingLocked()更新窗口状态mDrawState为COMMIT_DRAW_PENDING。其次WindowSurfacePlacer的requestTraversal方法,WindowSurfacePlacer的requestTraversal方法只是向WMS的主线程发送了一个DO_TRAVERSAL消息,WMS收到这个消息后,performSurfacePlacement方法就会执行。
frameworks/base/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
final void performSurfacePlacement() {
if (mDeferDepth > 0) {
return;
}
int loopCount = 6;
do {
mTraversalScheduled = false;
performSurfacePlacementLoop();
mService.mH.removeMessages(DO_TRAVERSAL);
loopCount--;
} while (mTraversalScheduled && loopCount > 0);
mWallpaperActionPending = false;
}复制代码
序列图中performSurfacePlacement、performSurfacePlacementLoop、performSurfacePlacementInner三个方法都是跟渲染相关的。performSurfacePlacement中调用了performSurfacePlacementLoop,performSurfacePlacementLoop中调用了performSurfacePlacementInner。(todolist:梳理performSurfacePlacement方法)
第十步commitFinishDrawingLocked是applySurfaceChangesTransaction方法调用进来的,该函数将窗口状态为COMMIT_DRAW_PENDING或READY_TO_SHOW的窗口,所有更新到READY_TO_SHOW状态
第十一步updateAllDrawnLocked函数更新AppWindowToken.allDrawn值。只有属于该AppWindowToken的全部窗口都是绘制完成状态(通常状况下只有一个窗口,有时候会有父窗口、子窗口,这时属于该AppWindowToken的窗口数量就不止一个了),AppWindowToken.allDrawn才会置为true。AppWindowToken.allDrawn为true才会使得第十步中的WMS.handleAppTransitionReadyLocked()完整的执行。
第十二步handleAppTransitionReadyLocked主要作了如下几件事情。
private int handleAppTransitionReadyLocked(WindowList windows) {
//获取系统中全部打开的activity数量
int appsCount = mService.mOpeningApps.size();
//transitionGoodToGo会判断多种case状况下,不用执行动画的状况,
//好比正在作转屏动画,mOpeningApps中任何一个allDrawn不等于true等
if (!transitionGoodToGo(appsCount)) {
return 0;
}
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "AppTransitionReady");
if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "**** GOOD TO GO");
//获取前面设置好的切换操做
int transit = mService.mAppTransition.getAppTransition();
//若是由于动画没有成功设置好,或者由于冻屏等缘由,致使的WMS中mSkipAppTransitionAnimation为true的话,切换操做类型设置为TRANSIT_UNSET
if (mService.mSkipAppTransitionAnimation) {
transit = AppTransition.TRANSIT_UNSET;
}
mService.mSkipAppTransitionAnimation = false;
mService.mNoAnimationNotifyOnTransitionFinished.clear();
//这个时候能够移除prepareAppTransition中设置的超时消息
mService.mH.removeMessages(H.APP_TRANSITION_TIMEOUT);
//从新进行窗口的排序,防止乱序
mService.rebuildAppWindowListLocked();
mWallpaperMayChange = false;
// The top-most window will supply the layout params,
// and we will determine it below.
//用来保存窗口参数
LayoutParams animLp = null;
int bestAnimLayer = -1;
boolean fullscreenAnim = false;
//是否有语音交互
boolean voiceInteraction = false;
int i;
for (i = 0; i < appsCount; i++) {
final AppWindowToken wtoken = mService.mOpeningApps.valueAt(i);
// Clearing the mAnimatingExit flag before entering animation. It's set to // true if app window is removed, or window relayout to invisible. // This also affects window visibility. We need to clear it *before* // maybeUpdateTransitToWallpaper() as the transition selection depends on // wallpaper target visibility. wtoken.clearAnimatingFlags(); } // Adjust wallpaper before we pull the lower/upper target, since pending changes // (like the clearAnimatingFlags() above) might affect wallpaper target result. final DisplayContent displayContent = mService.getDefaultDisplayContentLocked(); if ((displayContent.pendingLayoutChanges & FINISH_LAYOUT_REDO_WALLPAPER) != 0 && mWallpaperControllerLocked.adjustWallpaperWindows()) { //上面执行了clearAnimatingFlags,会影响Z-order.这里从新调整一下 mService.mLayersController.assignLayersLocked(windows); displayContent.layoutNeeded = true; } //在调整壁纸窗口在窗口堆栈的位置的时候,若是碰到系统在执行两个Activity组件的切换操做, //而且这两个Activity组件都须要显示壁纸, //那么Z轴位置较低的窗口就会lowerWallpaperTarget中, //而Z轴位置较高的窗口就会保存在upperWallpaperTarget中。 final WindowState lowerWallpaperTarget = mWallpaperControllerLocked.getLowerWallpaperTarget(); final WindowState upperWallpaperTarget = mWallpaperControllerLocked.getUpperWallpaperTarget(); boolean openingAppHasWallpaper = false; boolean closingAppHasWallpaper = false; final AppWindowToken lowerWallpaperAppToken; final AppWindowToken upperWallpaperAppToken; if (lowerWallpaperTarget == null) { lowerWallpaperAppToken = upperWallpaperAppToken = null; } else { lowerWallpaperAppToken = lowerWallpaperTarget.mAppToken; upperWallpaperAppToken = upperWallpaperTarget.mAppToken; } // Do a first pass through the tokens for two // things: // (1) Determine if both the closing and opening // app token sets are wallpaper targets, in which // case special animations are needed // (since the wallpaper needs to stay static // behind them). // (2) Find the layout params of the top-most // application window in the tokens, which is // what will control the animation theme. //获取关闭的activiy数量 final int closingAppsCount = mService.mClosingApps.size(); //获取打开的activiy数量 appsCount = closingAppsCount + mService.mOpeningApps.size(); //这段代码的for循环就是要从参与切换操做的Activity组件的窗口的WindowManager.LayoutParams对象中挑选出一个来建立切换动画 //要求是主窗口,它是全部候选窗口中Z轴位置最高的 for (i = 0; i < appsCount; i++) { final AppWindowToken wtoken; if (i < closingAppsCount) { wtoken = mService.mClosingApps.valueAt(i); if (wtoken == lowerWallpaperAppToken || wtoken == upperWallpaperAppToken) { //Activity关闭的时候,要显示墙纸窗口 closingAppHasWallpaper = true; } } else { wtoken = mService.mOpeningApps.valueAt(i - closingAppsCount); if (wtoken == lowerWallpaperAppToken || wtoken == upperWallpaperAppToken) { //Activity打开的时候,要显示墙纸窗口 openingAppHasWallpaper = true; } } voiceInteraction |= wtoken.voiceInteraction; //是不是全屏 if (wtoken.appFullscreen) { //找到主窗口,类型为TYPE_BASE_APPLICATION或者TYPE_APPLICATION_STARTING类型的 WindowState ws = wtoken.findMainWindow(); if (ws != null) { animLp = ws.mAttrs; bestAnimLayer = ws.mLayer; fullscreenAnim = true; } } else if (!fullscreenAnim) { WindowState ws = wtoken.findMainWindow(); if (ws != null) { if (ws.mLayer > bestAnimLayer) { animLp = ws.mAttrs; bestAnimLayer = ws.mLayer; } } } } //判断切换操做跟墙纸类型是否相关,调整窗口的类型(mayUpdateTransitionToWallpaper) transit = maybeUpdateTransitToWallpaper(transit, openingAppHasWallpaper, closingAppHasWallpaper, lowerWallpaperTarget, upperWallpaperTarget); // If all closing windows are obscured, then there is // no need to do an animation. This is the case, for // example, when this transition is being done behind // the lock screen. if (!mService.mPolicy.allowAppAnimationsLw()) { if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Animations disallowed by keyguard or dream."); animLp = null; } processApplicationsAnimatingInPlace(transit); mTmpLayerAndToken.token = null; // MIUI ADD: mService.mAppTransition.updateAllowCustomAnimationIfNeeded(mService.mClosingApps); //处理关闭的Activity handleClosingApps(transit, animLp, voiceInteraction, mTmpLayerAndToken); final AppWindowToken topClosingApp = mTmpLayerAndToken.token; final int topClosingLayer = mTmpLayerAndToken.layer; //处理打开的Activity,下面会说 final AppWindowToken topOpeningApp = handleOpeningApps(transit, animLp, voiceInteraction, topClosingLayer); mService.mAppTransition.setLastAppTransition(transit, topOpeningApp, topClosingApp); final AppWindowAnimator openingAppAnimator = (topOpeningApp == null) ? null : topOpeningApp.mAppAnimator; final AppWindowAnimator closingAppAnimator = (topClosingApp == null) ? null : topClosingApp.mAppAnimator; mService.mAppTransition.goodToGo(openingAppAnimator, closingAppAnimator, mService.mOpeningApps, mService.mClosingApps); mService.mAppTransition.postAnimationCallback(); mService.mAppTransition.clear(); mService.mOpeningApps.clear(); mService.mClosingApps.clear(); ..... return FINISH_LAYOUT_REDO_LAYOUT | FINISH_LAYOUT_REDO_CONFIG; }复制代码
整体来讲大体有八个步骤
第十三步handleOpeningApps这个函数用来设置APPWindowToken.hidden的可见性、设置Activity切换动画(若是参数transit==AppTransition.TRANSIT_UNSET,那就是会设置窗口动画,不然就会设置Activity切换动画),若是存在Activity切换动画或属于该Activity的窗口正在作窗口动画,那么返回值为true,handleOpeningApps中调用了setTokenVisibilityLocked方法。
/frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
boolean setTokenVisibilityLocked(AppWindowToken wtoken, WindowManager.LayoutParams lp,
boolean visible, int transit, boolean performLayout, boolean isVoiceInteraction) {
boolean delayed = false;
if (wtoken.clientHidden == visible) {
wtoken.clientHidden = !visible;
//再次通知应用程序端设置窗口可见性
wtoken.sendAppVisibilityToClients();
}
boolean visibilityChanged = false;
if (wtoken.hidden == visible || (wtoken.hidden && wtoken.mIsExiting) ||
(visible && wtoken.waitingForReplacement())) {
boolean changed = false;
boolean runningAppAnimation = false;
if (transit != AppTransition.TRANSIT_UNSET) {
if (wtoken.mAppAnimator.animation == AppWindowAnimator.sDummyAnimation) {
//把前面的哑动画清空
wtoken.mAppAnimator.setNullAnimation();
}
//建立动画
if (applyAnimationLocked(wtoken, lp, transit, visible, isVoiceInteraction)) {
delayed = runningAppAnimation = true;
}
WindowState window = wtoken.findMainWindow();
if (window != null && mAccessibilityController != null
&& window.getDisplayId() == Display.DEFAULT_DISPLAY) {
mAccessibilityController.onAppWindowTransitionLocked(window, transit);
}
changed = true;
}
.......
}
.......
return delayed;
}复制代码
建立动画是applyAnimationLocked方法干的事情。
frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
private boolean applyAnimationLocked(AppWindowToken atoken, WindowManager.LayoutParams lp,
int transit, boolean enter, boolean isVoiceInteraction) {
// Only apply an animation if the display isn't frozen. If it is // frozen, there is no reason to animate and it can cause strange // artifacts when we unfreeze the display if some different animation // is running. Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "WM#applyAnimationLocked"); if (okToDisplay()) { .... //传入各类参数,用AppTransition的loadAnimation建立一个动画 Animation a = mAppTransition.loadAnimation(lp, transit, enter, mCurConfiguration.uiMode, mCurConfiguration.orientation, frame, displayFrame, insets, surfaceInsets, isVoiceInteraction, freeform, atoken.mTask.mTaskId, mIsInMultiWindowMode); .... if (a != null) { .... atoken.mAppAnimator.setAnimation(a, containingWidth, containingHeight, mAppTransition.canSkipFirstFrame(), mAppTransition.getAppStackClipMode()); .... } } else { atoken.mAppAnimator.clearAnimation(); } return atoken.mAppAnimator.animation != null; }复制代码
applyAnimationLocked内部实质上仍是调用loadAnimation。
frameworks/base/services/core/java/com/android/server/wm/AppTransition.java
Animation loadAnimation(WindowManager.LayoutParams lp, int transit, boolean enter, int uiMode,
int orientation, Rect frame, Rect displayFrame, Rect insets,
@Nullable Rect surfaceInsets, boolean isVoiceInteraction, boolean freeform,
int taskId, boolean isInMultiWindowMode) {
if (transit == TRANSIT_WALLPAPER_OPEN && mNextAppTransitionType == NEXT_TRANSIT_TYPE_CUSTOM
&& !mAllowCustomAnimation) {
mNextAppTransitionType = AppTransitionInjector.NEXT_TRANSIT_TYPE_BACK_HOME;
}
Animation a;
if (isVoiceInteraction && (transit == TRANSIT_ACTIVITY_OPEN
|| transit == TRANSIT_TASK_OPEN
|| transit == TRANSIT_TASK_TO_FRONT)) {
a = loadAnimationRes(lp, enter
? com.android.internal.R.anim.voice_activity_open_enter
: com.android.internal.R.anim.voice_activity_open_exit);
if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
"applyAnimation voice:"
+ " anim=" + a + " transit=" + appTransitionToString(transit)
+ " isEntrance=" + enter + " Callers=" + Debug.getCallers(3));
} else if (isVoiceInteraction && (transit == TRANSIT_ACTIVITY_CLOSE
|| transit == TRANSIT_TASK_CLOSE
|| transit == TRANSIT_TASK_TO_BACK)) {
a = loadAnimationRes(lp, enter
? com.android.internal.R.anim.voice_activity_close_enter
: com.android.internal.R.anim.voice_activity_close_exit);
if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
"applyAnimation voice:"
+ " anim=" + a + " transit=" + appTransitionToString(transit)
+ " isEntrance=" + enter + " Callers=" + Debug.getCallers(3));
} else if (transit == TRANSIT_ACTIVITY_RELAUNCH) {
a = createRelaunchAnimation(frame, insets);
if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
"applyAnimation:"
+ " anim=" + a + " nextAppTransition=" + mNextAppTransition
+ " transit=" + appTransitionToString(transit)
+ " Callers=" + Debug.getCallers(3));
} else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CUSTOM) {
a = loadAnimationRes(mNextAppTransitionPackage, enter ?
mNextAppTransitionEnter : mNextAppTransitionExit);
if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
"applyAnimation:"
+ " anim=" + a + " nextAppTransition=ANIM_CUSTOM"
+ " transit=" + appTransitionToString(transit) + " isEntrance=" + enter
+ " Callers=" + Debug.getCallers(3));
} else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CUSTOM_IN_PLACE) {
a = loadAnimationRes(mNextAppTransitionPackage, mNextAppTransitionInPlace);
if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
"applyAnimation:"
+ " anim=" + a + " nextAppTransition=ANIM_CUSTOM_IN_PLACE"
+ " transit=" + appTransitionToString(transit)
+ " Callers=" + Debug.getCallers(3));
} else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CLIP_REVEAL) {
a = createClipRevealAnimationLocked(transit, enter, frame, displayFrame);
mLauncherAnimationRect.setEmpty();
if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
"applyAnimation:"
+ " anim=" + a + " nextAppTransition=ANIM_CLIP_REVEAL"
+ " transit=" + appTransitionToString(transit)
+ " Callers=" + Debug.getCallers(3));
} else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_SCALE_UP) {
a = createScaleUpAnimationLocked(transit, enter, frame);
if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
"applyAnimation:"
+ " anim=" + a + " nextAppTransition=ANIM_SCALE_UP"
+ " transit=" + appTransitionToString(transit) + " isEntrance=" + enter
+ " Callers=" + Debug.getCallers(3));
} else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP ||
mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN) {
mNextAppTransitionScaleUp =
(mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP);
a = createThumbnailEnterExitAnimationLocked(getThumbnailTransitionState(enter),
frame, transit, taskId);
if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) {
String animName = mNextAppTransitionScaleUp ?
"ANIM_THUMBNAIL_SCALE_UP" : "ANIM_THUMBNAIL_SCALE_DOWN";
Slog.v(TAG, "applyAnimation:"
+ " anim=" + a + " nextAppTransition=" + animName
+ " transit=" + appTransitionToString(transit) + " isEntrance=" + enter
+ " Callers=" + Debug.getCallers(3));
}
} else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP ||
mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN) {
mNextAppTransitionScaleUp =
(mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP);
a = createAspectScaledThumbnailEnterExitAnimationLocked(
getThumbnailTransitionState(enter), uiMode, orientation, transit, frame,
insets, surfaceInsets, freeform, taskId);
if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) {
String animName = mNextAppTransitionScaleUp ?
"ANIM_THUMBNAIL_ASPECT_SCALE_UP" : "ANIM_THUMBNAIL_ASPECT_SCALE_DOWN";
Slog.v(TAG, "applyAnimation:"
+ " anim=" + a + " nextAppTransition=" + animName
+ " transit=" + appTransitionToString(transit) + " isEntrance=" + enter
+ " Callers=" + Debug.getCallers(3));
}
} else {
int animAttr = 0;
switch (transit) {
case TRANSIT_ACTIVITY_OPEN:
animAttr = enter
? WindowAnimation_activityOpenEnterAnimation
: WindowAnimation_activityOpenExitAnimation;
break;
case TRANSIT_ACTIVITY_CLOSE:
animAttr = enter
? WindowAnimation_activityCloseEnterAnimation
: WindowAnimation_activityCloseExitAnimation;
break;
case TRANSIT_DOCK_TASK_FROM_RECENTS:
case TRANSIT_TASK_OPEN:
animAttr = enter
? WindowAnimation_taskOpenEnterAnimation
: WindowAnimation_taskOpenExitAnimation;
break;
case TRANSIT_TASK_CLOSE:
animAttr = enter
? WindowAnimation_taskCloseEnterAnimation
: WindowAnimation_taskCloseExitAnimation;
break;
case TRANSIT_TASK_TO_FRONT:
animAttr = enter
? WindowAnimation_taskToFrontEnterAnimation
: WindowAnimation_taskToFrontExitAnimation;
break;
case TRANSIT_TASK_TO_BACK:
animAttr = enter
? WindowAnimation_taskToBackEnterAnimation
: WindowAnimation_taskToBackExitAnimation;
break;
case TRANSIT_WALLPAPER_OPEN:
animAttr = enter
? WindowAnimation_wallpaperOpenEnterAnimation
: WindowAnimation_wallpaperOpenExitAnimation;
break;
case TRANSIT_WALLPAPER_CLOSE:
animAttr = enter
? WindowAnimation_wallpaperCloseEnterAnimation
: WindowAnimation_wallpaperCloseExitAnimation;
break;
case TRANSIT_WALLPAPER_INTRA_OPEN:
animAttr = enter
? WindowAnimation_wallpaperIntraOpenEnterAnimation
: WindowAnimation_wallpaperIntraOpenExitAnimation;
break;
case TRANSIT_WALLPAPER_INTRA_CLOSE:
animAttr = enter
? WindowAnimation_wallpaperIntraCloseEnterAnimation
: WindowAnimation_wallpaperIntraCloseExitAnimation;
break;
case TRANSIT_TASK_OPEN_BEHIND:
animAttr = enter
? WindowAnimation_launchTaskBehindSourceAnimation
: WindowAnimation_launchTaskBehindTargetAnimation;
}
a = animAttr != 0 ? loadAnimationAttr(lp, animAttr) : null;
if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
"applyAnimation:"
+ " anim=" + a
+ " animAttr=0x" + Integer.toHexString(animAttr)
+ " transit=" + appTransitionToString(transit) + " isEntrance=" + enter
+ " Callers=" + Debug.getCallers(3));
}
return a;
}复制代码
关于loadAnimation这个方法代码也是不少,可是逻辑很是简单了,就是根据设置的操做类型, 根据参数,使用loadAnimationRes()或loadAnimationAttr()或其余建立Animation接口来加载一个Animation出来。加载动画的时候须要注意一个优先级的问题。
好比Activity关闭的时候,加载的动画资源是下面这样
<set xmlns:android="http://schemas.android.com/apk/res/android" android:zAdjustment="normal">
<alpha android:fromAlpha="0.7" android:toAlpha="1.0"
android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
android:interpolator="@interpolator/linear_out_slow_in"
android:duration="250"/>
</set>复制代码
动画设置好了以后,就会经过setAnimation方法将动画anim保存在AppWindowAnimator的成员变量animation中,动画的执行时候,就会来取这个animation。
frameworks/base/services/core/java/com/android/server/wm/AppWindowAnimator.java
public void setAnimation(Animation anim, int width, int height, boolean skipFirstFrame,
int stackClip) {
animation = anim;
animating = false;
if (!anim.isInitialized()) {
anim.initialize(width, height, width, height);
}
....
}复制代码
刚刚在说动画的优先级的时候,说过若是设置了某种mNextAppTransitionType类型,就跟以这个mNextAppTransitionType类型做为返回,优先级高于第一种。这个过程的时序图以下。
frameworks/base/services/core/java/com/android/server/wm/AppTransition.java
void overridePendingAppTransition(String packageName, int enterAnim, int exitAnim,
IRemoteCallback startedCallback) {
if (isTransitionSet()) {
clear();
mNextAppTransitionType = NEXT_TRANSIT_TYPE_CUSTOM;
mNextAppTransitionPackage = packageName;
mNextAppTransitionEnter = enterAnim;
mNextAppTransitionExit = exitAnim;
postAnimationCallback();
mNextAppTransitionCallback = startedCallback;
} else {
postAnimationCallback();
}
}复制代码
mNextAppTransitionType被覆盖了以后,建立动画的时候就会优先返回设置了这种类型的动画。
#####三、窗口动画设置
boolean commitFinishDrawingLocked() {
.....
mDrawState = READY_TO_SHOW;
boolean result = false;
final AppWindowToken atoken = mWin.mAppToken;
//去取出atoken,若是atoken等于null,那么说明不是Activity窗口,就能够调用performShowLocked,进行窗口动画的设置
if (atoken == null || atoken.allDrawn || mWin.mAttrs.type == TYPE_APPLICATION_STARTING) {
result = performShowLocked();
}
return result;
}复制代码
performShowLocked中主要是调用了applyEnterAnimationLocked方法进行建立动画。
void applyEnterAnimationLocked() {
// If we are the new part of a window replacement transition and we have requested
// not to animate, we instead want to make it seamless, so we don't want to apply // an enter transition. if (mWin.mSkipEnterAnimationForSeamlessReplacement) { return; } final int transit; if (mEnterAnimationPending) { mEnterAnimationPending = false; transit = WindowManagerPolicy.TRANSIT_ENTER; } else { transit = WindowManagerPolicy.TRANSIT_SHOW; } applyAnimationLocked(transit, true); ..... }复制代码
mEnterAnimationPending的值等于true,说明当前所描述的窗口正在等待显示,也就是正处于不可见到可见状态的过程当中,那WindowManagerService类的成员函数applyEnterAnimationLocked就会对该窗口设置一个类型为WindowManagerPolicy.TRANSIT_ENTER的动画,不然的话,就会对该窗口设置一个类型为WindowManagerPolicy.TRANSIT_SHOW的动画。后面会根据这个类型,肯定styleable, 将参数transit的值转化为一个对应的动画样式名称。
frameworks/base/services/core/java/com/android/server/wm/WindowStateAnimator.java
boolean applyAnimationLocked(int transit, boolean isEntrance) {
......
if (mService.okToDisplay()) {
int anim = mPolicy.selectAnimationLw(mWin, transit);
int attr = -1;
Animation a = null;
if (anim != 0) {
a = anim != -1 ? AnimationUtils.loadAnimation(mContext, anim) : null;
} else {
switch (transit) {
case WindowManagerPolicy.TRANSIT_ENTER:
attr = com.android.internal.R.styleable.WindowAnimation_windowEnterAnimation;
break;
case WindowManagerPolicy.TRANSIT_EXIT:
attr = com.android.internal.R.styleable.WindowAnimation_windowExitAnimation;
break;
case WindowManagerPolicy.TRANSIT_SHOW:
attr = com.android.internal.R.styleable.WindowAnimation_windowShowAnimation;
break;
case WindowManagerPolicy.TRANSIT_HIDE:
attr = com.android.internal.R.styleable.WindowAnimation_windowHideAnimation;
break;
}
if (attr >= 0) {
a = mService.mAppTransition.loadAnimationAttr(mWin.mAttrs, attr);
}
}
if (DEBUG_ANIM) Slog.v(TAG,
"applyAnimation: win=" + this
+ " anim=" + anim + " attr=0x" + Integer.toHexString(attr)
+ " a=" + a
+ " transit=" + transit
+ " isEntrance=" + isEntrance + " Callers " + Debug.getCallers(3));
if (a != null) {
if (DEBUG_ANIM) logWithStack(TAG, "Loaded animation " + a + " for " + this);
setAnimation(a);
mAnimationIsEntrance = isEntrance;
}
} else {
clearAnimation();
}
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
if (mWin.mAttrs.type == TYPE_INPUT_METHOD) {
mService.adjustForImeIfNeeded(mWin.mDisplayContent);
if (isEntrance) {
mWin.setDisplayLayoutNeeded();
mService.mWindowPlacerLocked.requestTraversal();
}
}
return mAnimation != null;
}复制代码
首先会调用PhoneWindowManager的selectAnimationLw方法去查找特殊窗口的动画类型,这里特殊窗口主要是StatusBar、NavigationBar或者窗口的类型是TYPE_DOCK_DIVIDER(分屏)等,若是是这些窗口的话,就会直接返回一个动画类型(transit)保存在anim中,接下来会判断anim是否为-1,由于selectAnimationLw在窗口是Keyguard或者DREAM类型的时候会返回-1,若是不是-1,说明查找到了,返回到WindowStateAnimator中使用AnimationUtils的loadAnimation方法去查找出一个动画a保存在Animation所描述的变量a中。
若是上面都不是,那么就根据transit类型,肯定attr,调用AppTransition的loadAnimationAttr方法加载一个动画
frameworks/base/services/core/java/com/android/server/wm/AppTransition.java
Animation loadAnimationAttr(WindowManager.LayoutParams lp, int animAttr) {
int anim = 0;
Context context = mContext;
if (animAttr >= 0) {
AttributeCache.Entry ent = getCachedAnimations(lp);
if (ent != null) {
context = ent.context;
anim = ent.array.getResourceId(animAttr, 0);
}
}
if (anim != 0) {
return AnimationUtils.loadAnimation(context, anim);
}
return null;
}复制代码
frameworks/base/services/core/java/com/android/server/wm/WindowStateAnimator.java
public void setAnimation(Animation anim, long startTime, int stackClip) {
if (localLOGV) Slog.v(TAG, "Setting animation in " + this + ": " + anim);
mAnimating = false;
mLocalAnimating = false;
mAnimation = anim;
...
}复制代码
最后动画被保存在WindowStateAnimator的成员变量mAnimation中。对比前面的过分动画,最后是经过setAnimation方法将动画anim保存在AppWindowAnimator的成员变量animation中。当动画的执行时候,就会来取这个animation,动画的执行,放在接下来一节中更新。