前言
在 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
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
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
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;
}
}