前言
在 Android 系统中,系统主要是通过AppTransitionController和AppTransition这两个类是用于管理应用程序间切换动画的重要类。
一、简介
1、AppTransitionController
AppTransitionController 是一个系统级别的类,用于管理应用程序间切换时的动画效果。其作用包括:
- 动画类型管理:控制不同应用切换时的动画类型,如窗口动画、应用切换动画等。
- 动画资源管理:管理和加载与切换动画相关的资源,如动画文件、持续时间等。
- 调度动画:决定何时开始和结束应用程序切换的动画。
2、AppTransition
AppTransition 是 AppTransitionController 的一部分,负责实际执行应用切换动画。主要功能包括:
- 动画执行:根据 AppTransitionController 的设置,执行应用程序间的切换动画。
- 动画参数设置:配置和管理动画的参数,例如动画类型、持续时间、插值器等。
- 状态监控:监控动画执行的状态,以便在动画完成或被取消时进行适当的处理。
3、使用场景和重要性
这两个类在 Android 系统中的重要性体现在以下几个方面:
- 用户体验:应用程序切换时的流畅动画可以显著提升用户体验,这些动画通过 AppTransitionController 和 AppTransition 进行管理和优化。
- 系统一致性:Android 系统通过这些类确保应用程序切换时的动画表现一致性,使整个系统看起来更加统一。
- 定制性:开发者可以通过这些类定制应用切换时的动画效果,以满足特定应用或设备的需求。
二、对象的创建
1、AppTransition和AppTransitionController实例对象的创建。
AppTransition和AppTransitionController实例对象都是在DisplayContent对象的构造方法中被创建的。
frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java
class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowContainer> implements WindowManagerPolicy.DisplayContentInfo { DisplayContent(Display display, RootWindowContainer root) { super(root.mWindowManager); ...代码省略... mAppTransition = new AppTransition(mWmService.mContext, mWmService, this); mAppTransition.registerListenerLocked(mWmService.mActivityManagerAppTransitionNotifier); mAppTransition.registerListenerLocked(mFixedRotationTransitionListener); mAppTransitionController = new AppTransitionController(mWmService, this); ...代码省略... } }
2、关于DisplayContent对象
我们在Android 12系统源码_窗口管理(七)DisplayContent简介这篇文章有讲过,DisplayContent 用于管理屏幕,一块DisplayContent 对象实例代表一个屏幕设备,这样有多个屏幕的设备就可以创建多个DisplayContent 对象,虽然多数设备只有一个显示屏,但它们同样可以创建多个 DisplayContent 对象,如投屏的时候,可以创建一个虚拟的DisplayContent,DisplayContent会根据窗口的显示位置将窗口进行分组,隶属于同一个DisplayContent的窗口将会显示在同一个屏幕中,每一个DisplayContent都对应一个唯一的ID,在添加窗口时可以通过指定这个ID决定其将被显示在对应的个屏幕中。DisplayContent是一个非常具有隔离性的一个概念。处于不同DisplayContent的两个窗口在布局、显示顺序以及动画处理上不会产生任何耦合,而如果我们想让不同DisplayContent的窗口切换动画不同,就可以通过定制该DisplayContent对应的AppTransition和AppTransitionController来实现。
三、AppTransition
来看下AppTransition的相关的源码。
frameworks/base/services/core/java/com/android/server/wm/AppTransition.java
public class AppTransition implements Dump { static final int DEFAULT_APP_TRANSITION_DURATION = 336;//应用默认专场动画的执行时间 private final Context mContext; private final WindowManagerService mService; private final DisplayContent mDisplayContent; private final TransitionAnimation mTransitionAnimation;//应用间的切换动画 private final int mConfigShortAnimTime; private final Interpolator mDecelerateInterpolator; private final Interpolator mThumbnailFadeInInterpolator; private final Interpolator mThumbnailFadeOutInterpolator; private final Interpolator mLinearOutSlowInInterpolator; private final Interpolator mFastOutLinearInInterpolator; private final Interpolator mFastOutSlowInInterpolator; private final int mClipRevealTranslationY; private final boolean mGridLayoutRecentsEnabled; private final boolean mLowRamRecentsEnabled; private final int mDefaultWindowAnimationStyleResId; private RemoteAnimationController mRemoteAnimationController; final Handler mHandler; AppTransition(Context context, WindowManagerService service, DisplayContent displayContent) { mContext = context; mService = service; mHandler = new Handler(service.mH.getLooper()); mDisplayContent = displayContent; mTransitionAnimation = new TransitionAnimation(context, DEBUG_ANIM, TAG); mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context, com.android.internal.R.interpolator.linear_out_slow_in); mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context, com.android.internal.R.interpolator.fast_out_linear_in); mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context, com.android.internal.R.interpolator.fast_out_slow_in); mConfigShortAnimTime = context.getResources().getInteger( com.android.internal.R.integer.config_shortAnimTime); mDecelerateInterpolator = AnimationUtils.loadInterpolator(context, com.android.internal.R.interpolator.decelerate_cubic); mThumbnailFadeInInterpolator = new Interpolator() { @Override public float getInterpolation(float input) { // Linear response for first fraction, then complete after that. if (input < RECENTS_THUMBNAIL_FADEIN_FRACTION) { return 0f; } float t = (input - RECENTS_THUMBNAIL_FADEIN_FRACTION) / (1f - RECENTS_THUMBNAIL_FADEIN_FRACTION); return mFastOutLinearInInterpolator.getInterpolation(t); } }; mThumbnailFadeOutInterpolator = new Interpolator() { @Override public float getInterpolation(float input) { // Linear response for first fraction, then complete after that. if (input < RECENTS_THUMBNAIL_FADEOUT_FRACTION) { float t = input / RECENTS_THUMBNAIL_FADEOUT_FRACTION; return mLinearOutSlowInInterpolator.getInterpolation(t); } return 1f; } }; mClipRevealTranslationY = (int) (CLIP_REVEAL_TRANSLATION_Y_DP * mContext.getResources().getDisplayMetrics().density); mGridLayoutRecentsEnabled = SystemProperties.getBoolean("ro.recents.grid", false); mLowRamRecentsEnabled = ActivityManager.isLowRamDeviceStatic(); final TypedArray windowStyle = mContext.getTheme().obtainStyledAttributes( com.android.internal.R.styleable.Window); mDefaultWindowAnimationStyleResId = windowStyle.getResourceId( com.android.internal.R.styleable.Window_windowAnimationStyle, 0); windowStyle.recycle(); } /** * * @param lp 窗口参数 * @param transit 窗口切换动画类型 * @param enter 是否是进入 * @param frame 如果是进入动画,则是动画结束后窗口的位置,如果是退出动画,则是动画开始时窗口的位置 * @return 返回的就是最终的窗口转换动画 */ Animation loadAnimation(LayoutParams lp, int transit, boolean enter, int uiMode, int orientation, Rect frame, Rect displayFrame, Rect insets, @Nullable Rect surfaceInsets, @Nullable Rect stableInsets, boolean isVoiceInteraction, boolean freeform, WindowContainer container) { if (mNextAppTransitionOverrideRequested && (container.canCustomizeAppTransition() || mOverrideTaskTransition)) { mNextAppTransitionType = NEXT_TRANSIT_TYPE_CUSTOM; } Animation a; if (isKeyguardGoingAwayTransitOld(transit) && enter) { a = mTransitionAnimation.loadKeyguardExitAnimation(mNextAppTransitionFlags, transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER); } else if (transit == TRANSIT_OLD_KEYGUARD_OCCLUDE) { a = null; } else if (transit == TRANSIT_OLD_KEYGUARD_UNOCCLUDE && !enter) { a = mTransitionAnimation.loadKeyguardUnoccludeAnimation(); } else if (transit == TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE) { a = null; } else if (isVoiceInteraction && (transit == TRANSIT_OLD_ACTIVITY_OPEN || transit == TRANSIT_OLD_TASK_OPEN || transit == TRANSIT_OLD_TASK_TO_FRONT)) { a = mTransitionAnimation.loadVoiceActivityOpenAnimation(enter); ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM, "applyAnimation voice: anim=%s transit=%s isEntrance=%b Callers=%s", a, appTransitionOldToString(transit), enter, Debug.getCallers(3)); } else if (isVoiceInteraction && (transit == TRANSIT_OLD_ACTIVITY_CLOSE || transit == TRANSIT_OLD_TASK_CLOSE || transit == TRANSIT_OLD_TASK_TO_BACK)) { a = mTransitionAnimation.loadVoiceActivityExitAnimation(enter); ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM, "applyAnimation voice: anim=%s transit=%s isEntrance=%b Callers=%s", a, appTransitionOldToString(transit), enter, Debug.getCallers(3)); } else if (transit == TRANSIT_OLD_ACTIVITY_RELAUNCH) { a = mTransitionAnimation.createRelaunchAnimation(frame, insets, mDefaultNextAppTransitionAnimationSpec != null ? mDefaultNextAppTransitionAnimationSpec.rect : null); ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM, "applyAnimation: anim=%s transit=%s Callers=%s", a, appTransitionOldToString(transit), Debug.getCallers(3)); } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CUSTOM) { a = mTransitionAnimation.loadAppTransitionAnimation(mNextAppTransitionPackage, enter ? mNextAppTransitionEnter : mNextAppTransitionExit); ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM, "applyAnimation: anim=%s nextAppTransition=ANIM_CUSTOM transit=%s " + "isEntrance=%b Callers=%s", a, appTransitionOldToString(transit), enter, Debug.getCallers(3)); } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CUSTOM_IN_PLACE) { a = mTransitionAnimation.loadAppTransitionAnimation( mNextAppTransitionPackage, mNextAppTransitionInPlace); ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM, "applyAnimation: anim=%s nextAppTransition=ANIM_CUSTOM_IN_PLACE " + "transit=%s Callers=%s", a, appTransitionOldToString(transit), Debug.getCallers(3)); } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CLIP_REVEAL) { a = mTransitionAnimation.createClipRevealAnimationLockedCompat( transit, enter, frame, displayFrame, mDefaultNextAppTransitionAnimationSpec != null ? mDefaultNextAppTransitionAnimationSpec.rect : null); ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM, "applyAnimation: anim=%s nextAppTransition=ANIM_CLIP_REVEAL " + "transit=%s Callers=%s", a, appTransitionOldToString(transit), Debug.getCallers(3)); } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_SCALE_UP) { a = mTransitionAnimation.createScaleUpAnimationLockedCompat(transit, enter, frame, mDefaultNextAppTransitionAnimationSpec != null ? mDefaultNextAppTransitionAnimationSpec.rect : null); ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM, "applyAnimation: anim=%s nextAppTransition=ANIM_SCALE_UP transit=%s " + "isEntrance=%s Callers=%s", a, appTransitionOldToString(transit), enter, 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); final HardwareBuffer thumbnailHeader = getAppTransitionThumbnailHeader(container); a = mTransitionAnimation.createThumbnailEnterExitAnimationLockedCompat(enter, mNextAppTransitionScaleUp, frame, transit, thumbnailHeader, mDefaultNextAppTransitionAnimationSpec != null ? mDefaultNextAppTransitionAnimationSpec.rect : null); ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM, "applyAnimation: anim=%s nextAppTransition=%s transit=%s isEntrance=%b " + "Callers=%s", a, mNextAppTransitionScaleUp ? "ANIM_THUMBNAIL_SCALE_UP" : "ANIM_THUMBNAIL_SCALE_DOWN", appTransitionOldToString(transit), enter, 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); AppTransitionAnimationSpec spec = mNextAppTransitionAnimationsSpecs.get( container.hashCode()); a = mTransitionAnimation.createAspectScaledThumbnailEnterExitAnimationLocked(enter, mNextAppTransitionScaleUp, orientation, transit, frame, insets, surfaceInsets, stableInsets, freeform, spec != null ? spec.rect : null, mDefaultNextAppTransitionAnimationSpec != null ? mDefaultNextAppTransitionAnimationSpec.rect : null); ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM, "applyAnimation: anim=%s nextAppTransition=%s transit=%s isEntrance=%b " + "Callers=%s", a, mNextAppTransitionScaleUp ? "ANIM_THUMBNAIL_ASPECT_SCALE_UP" : "ANIM_THUMBNAIL_ASPECT_SCALE_DOWN", appTransitionOldToString(transit), enter, Debug.getCallers(3)); } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS && enter) { a = mTransitionAnimation.loadCrossProfileAppEnterAnimation(); ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM, "applyAnimation NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS: " + "anim=%s transit=%s isEntrance=true Callers=%s", a, appTransitionOldToString(transit), Debug.getCallers(3)); } else if (isChangeTransitOld(transit)) { // In the absence of a specific adapter, we just want to keep everything stationary. a = new AlphaAnimation(1.f, 1.f); a.setDuration(WindowChangeAnimationSpec.ANIMATION_DURATION); ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM, "applyAnimation: anim=%s transit=%s isEntrance=%b Callers=%s", a, appTransitionOldToString(transit), enter, Debug.getCallers(3)); } else { int animAttr = 0; switch (transit) { case TRANSIT_OLD_ACTIVITY_OPEN: case TRANSIT_OLD_TRANSLUCENT_ACTIVITY_OPEN: animAttr = enter ? WindowAnimation_activityOpenEnterAnimation : WindowAnimation_activityOpenExitAnimation; break; case TRANSIT_OLD_ACTIVITY_CLOSE: case TRANSIT_OLD_TRANSLUCENT_ACTIVITY_CLOSE: animAttr = enter ? WindowAnimation_activityCloseEnterAnimation : WindowAnimation_activityCloseExitAnimation; break; case TRANSIT_OLD_TASK_OPEN: animAttr = enter ? WindowAnimation_taskOpenEnterAnimation : WindowAnimation_taskOpenExitAnimation; break; case TRANSIT_OLD_TASK_CLOSE: animAttr = enter ? WindowAnimation_taskCloseEnterAnimation : WindowAnimation_taskCloseExitAnimation; break; case TRANSIT_OLD_TASK_TO_FRONT: animAttr = enter ? WindowAnimation_taskToFrontEnterAnimation : WindowAnimation_taskToFrontExitAnimation; break; case TRANSIT_OLD_TASK_TO_BACK: animAttr = enter ? WindowAnimation_taskToBackEnterAnimation : WindowAnimation_taskToBackExitAnimation; break; case TRANSIT_OLD_WALLPAPER_OPEN: animAttr = enter ? WindowAnimation_wallpaperOpenEnterAnimation : WindowAnimation_wallpaperOpenExitAnimation; break; case TRANSIT_OLD_WALLPAPER_CLOSE: animAttr = enter ? WindowAnimation_wallpaperCloseEnterAnimation : WindowAnimation_wallpaperCloseExitAnimation; break; case TRANSIT_OLD_WALLPAPER_INTRA_OPEN: animAttr = enter ? WindowAnimation_wallpaperIntraOpenEnterAnimation : WindowAnimation_wallpaperIntraOpenExitAnimation; break; case TRANSIT_OLD_WALLPAPER_INTRA_CLOSE: animAttr = enter ? WindowAnimation_wallpaperIntraCloseEnterAnimation : WindowAnimation_wallpaperIntraCloseExitAnimation; break; case TRANSIT_OLD_TASK_OPEN_BEHIND: animAttr = enter ? WindowAnimation_launchTaskBehindSourceAnimation : WindowAnimation_launchTaskBehindTargetAnimation; break; // TODO(b/189386466): Use activity transition as the fallback. Investigate if we // need new TaskFragment transition. case TRANSIT_OLD_TASK_FRAGMENT_OPEN: animAttr = enter ? WindowAnimation_activityOpenEnterAnimation : WindowAnimation_activityOpenExitAnimation; break; // TODO(b/189386466): Use activity transition as the fallback. Investigate if we // need new TaskFragment transition. case TRANSIT_OLD_TASK_FRAGMENT_CLOSE: animAttr = enter ? WindowAnimation_activityCloseEnterAnimation : WindowAnimation_activityCloseExitAnimation; break; } //动画 a = animAttr != 0 ? loadAnimationAttr(lp, animAttr, transit) : null; ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM, "applyAnimation: anim=%s animAttr=0x%x transit=%s isEntrance=%b " + "Callers=%s", a, animAttr, appTransitionOldToString(transit), enter, Debug.getCallers(3)); } setAppTransitionFinishedCallbackIfNeeded(a); return a; } /** * * @param lp 窗口参数 * @param animAttr 动画资源id * @param transit 窗口切换动画类型 * @return Animation动画 */ @Nullable Animation loadAnimationAttr(LayoutParams lp, int animAttr, int transit) { return mTransitionAnimation.loadAnimationAttr(lp, animAttr, transit); } }
frameworks/base/core/java/com/android/internal/policy/TransitionAnimation.java
public class TransitionAnimation { @Nullable public Animation loadAnimationAttr(LayoutParams lp, int animAttr, int transit) { int resId = Resources.ID_NULL; Context context = mContext; if (animAttr >= 0) { AttributeCache.Entry ent = getCachedAnimations(lp); if (ent != null) { context = ent.context; resId = ent.array.getResourceId(animAttr, 0); } } resId = updateToTranslucentAnimIfNeeded(resId, transit); if (ResourceId.isValid(resId)) { return loadAnimationSafely(context, resId, mTag); } return null; } private static int updateToTranslucentAnimIfNeeded(int anim, @TransitionOldType int transit) { if (transit == TRANSIT_OLD_TRANSLUCENT_ACTIVITY_OPEN && anim == R.anim.activity_open_enter) { return R.anim.activity_translucent_open_enter; } if (transit == TRANSIT_OLD_TRANSLUCENT_ACTIVITY_CLOSE && anim == R.anim.activity_close_exit) { return R.anim.activity_translucent_close_exit; } return anim; } }