Android窗口动画
android 窗口动画
Ardroid系统动画(一)窗口动画
什么是窗口动画?
Android窗口动画是Android系统动画的一种,当一个非Activity主窗口,非壁纸窗口添加或者移除的时候会触 发Android窗口动画的流程,举个例子 Toast的弹出或者移除的过程中就会触发窗口动画。
窗口动画流程
我把窗口动画的执行流程大概分为以下几步
- 触发添加窗口动画
- 加载对应的动画资源
- 创建leash,同时把执行动画的窗口容器reparent到 leash上
- 开始循环执行窗口动画
- 动画结束,把执行动画的窗口重新reparent到该容器原来的父节点上。
一. 触发添加窗口动画
在WMS中最重要的函数performSurfacePlacementNoTrace()后期,会遍历一块屏幕上所有的窗口(WindowState),如果该WindowState有surfece的话,会调用到WindowState.winAnimator.ommitFinishDrawingLocked()方法:
WindowStateAnimator.java
boolean commitFinishDrawingLocked() {
if (DEBUG_STARTING_WINDOW_VERBOSE &&
mWin.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING) {
Slog.i(TAG, "commitFinishDrawingLocked: " + mWin + " cur mDrawState="
+ drawStateToString());
}
if (mDrawState != COMMIT_DRAW_PENDING && mDrawState != READY_TO_SHOW) {
return false;
}
ProtoLog.i(WM_DEBUG_ANIM, "commitFinishDrawingLocked: mDrawState=READY_TO_SHOW %s",
mSurfaceController);
mDrawState = READY_TO_SHOW;
boolean result = false;
final ActivityRecord activity = mWin.mActivityRecord;
if (activity == null || activity.canShowWindows()
|| mWin.mAttrs.type == TYPE_APPLICATION_STARTING) {
//进一步到这里
result = mWin.performShowLocked();
}
return result;
}
如果是新添加的窗口则会通过mWinAnimator.applyEnterAnimationLocked();来触发窗口 进入动画。
WindowState.java
boolean performShowLocked() {
......
logPerformShow("Showing ");
mWmService.enableScreenIfNeededLocked();
// 如果新添加进来的窗口,则会在这里触发窗口动画。
mWinAnimator.applyEnterAnimationLocked();
// Force the show in the next prepareSurfaceLocked() call.
mWinAnimator.mLastAlpha = -1;
ProtoLog.v(WM_DEBUG_ANIM, "performShowLocked: mDrawState=HAS_DRAWN in %s", this);
mWinAnimator.mDrawState = HAS_DRAWN;
mWmService.scheduleAnimationLocked();
if (mHidden) {
mHidden = false;
final DisplayContent displayContent = getDisplayContent();
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowState c = mChildren.get(i);
if (c.mWinAnimator.mSurfaceController != null) {
c.performShowLocked();
// It hadn't been shown, which means layout not performed on it, so now we
// want to make sure to do a layout. If called from within the transaction
// loop, this will cause it to restart with a new layout.
if (displayContent != null) {
displayContent.setLayoutNeeded();
}
}
}
}
return true;
}
如下在窗口动画的处罚过程中if (mAttrType != TYPE_BASE_APPLICATION && !mIsWallpaper) {
applyAnimationLocked(transit, true);
} 就决定了只会给 非Activity主窗口和非壁纸类型的窗口添加窗口进入动画。
WindowStateAnimator.java
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;
// 在窗口的添加过程中 mEnterAnimationPending 已经被置为True,所以transit = WindowManagerPolicy.TRANSIT_ENTER;
if (mEnterAnimationPending) {
mEnterAnimationPending = false;
transit = WindowManagerPolicy.TRANSIT_ENTER;
} else {
transit = WindowManagerPolicy.TRANSIT_SHOW;
}
// We don't apply animation for application main window here since this window type
// should be controlled by ActivityRecord in general. Wallpaper is also excluded because
// WallpaperController should handle it.
//可以知道窗口进入动画不适用于 非activity的窗口 和 壁纸
if (mAttrType != TYPE_BASE_APPLICATION && !mIsWallpaper) {
applyAnimationLocked(transit, true);
}
if (mService.mAccessibilityController.hasCallbacks()) {
mService.mAccessibilityController.onWindowTransition(mWin, transit);
}
}
二. 加载对应的动画资源
接下来就是根据场景添加添加对应的窗口动画类型,有定制的话,则采用定制的窗口动画类型。
WindowStateAnimator.java
boolean
(int transit, boolean isEntrance) {
if (mWin.isAnimating() && mAnimationIsEntrance == isEntrance) {
// If we are trying to apply an animation, but already running
// an animation of the same type, then just leave that one alone.
return true;
}
final boolean isImeWindow = mWin.mAttrs.type == TYPE_INPUT_METHOD;
// 如果是输入法窗口 特殊处理
if (isEntrance && isImeWindow) {
mWin.getDisplayContent().adjustForImeIfNeeded();
mWin.setDisplayLayoutNeeded();
mService.mWindowPlacerLocked.requestTraversal();
}
// 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.
// 正常情况下 mWin.mToken.okToAnimate()) 为true
if (mWin.mToken.okToAnimate()) {
// 这个方法中 会对 StatusBar的窗口 和 NavigationBar窗口 进行特殊处理,如果是Toast 则返回DisplayPolicy.ANIMATION_STYLEABLE)
int anim = mWin.getDisplayContent().getDisplayPolicy().selectAnimation(mWin, transit);
int attr = -1;
Animation a = null;
if (anim != DisplayPolicy.ANIMATION_STYLEABLE) {
if (anim != DisplayPolicy.ANIMATION_NONE) {
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "WSA#loadAnimation");
a = AnimationUtils.loadAnimation(mContext, anim);
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
}
} else {
switch (transit) {
// Toast添加会进入到该分支
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) {
// 根据选择的动画参数 生成 Animation
a = mWin.getDisplayContent().mAppTransition.loadAnimationAttr(
mWin.mAttrs, attr, TRANSIT_OLD_NONE);
}
}
if (ProtoLogImpl.isEnabled(WM_DEBUG_ANIM)) {
ProtoLog.v(WM_DEBUG_ANIM, "applyAnimation: win=%s"
+ " anim=%d attr=0x%x a=%s transit=%d type=%d isEntrance=%b Callers %s",
this, anim, attr, a, transit, mAttrType, isEntrance, Debug.getCallers(20));
}
if (a != null) {
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "WSA#startAnimation");
// 窗口根据生成的对应类型的Animation ,来进一步触发动画
mWin.startAnimation(a);
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
mAnimationIsEntrance = isEntrance;
}
} else if (!isImeWindow) {
mWin.cancelAnimation();
}
if (!isEntrance && isImeWindow) {
mWin.getDisplayContent().adjustForImeIfNeeded();
}
return mWin.isAnimating(0 /* flags */, ANIMATION_TYPE_WINDOW_ANIMATION);
}
至此加载对应的动画资源的流程结束。接下来开始进一步触发,如下对动画的参数进行了进一步设置,同时把 WindowAnimationSpec(动画规格)和mWmService.mSurfaceAnimationRunner(用来运行动画)封装到了一个LocalAnimationAdapter对象里
WindowState.java
void startAnimation(Animation anim) {
// If we are an inset provider, all our animations are driven by the inset client.
if (mControllableInsetProvider != null) {
return;
}
final DisplayInfo displayInfo = getDisplayInfo();
// 对动画的一些参数进行初始化,一些标志位进行初始化
anim.initialize(mWindowFrames.mFrame.width(),
mWindowFrames.mFrame.height(),
displayInfo.appWidth, displayInfo.appHeight);
// 给动画设置duiration
anim.restrictDuration(MAX_ANIMATION_DURATION);
// 设置动画缩放比例
anim.scaleCurrentDuration(mWmService.getWindowAnimationScaleLocked());
// 在这里创建了一个 LocalAnimationAdapter 对象
final AnimationAdapter adapter = new LocalAnimationAdapter(
new WindowAnimationSpec(anim, mSurfacePosition, false /* canSkipFirstFrame */,
0 /* windowCornerRadius */),
mWmService.mSurfaceAnimationRunner);
// 进一步启动动画
startAnimation(getPendingTransaction(), adapter);
commitPendingTransaction();
}
private void startAnimation(Transaction t, AnimationAdapter adapter) {
startAnimation(t, adapter, mWinAnimator.mLastHidden, ANIMATION_TYPE_WINDOW_ANIMATION);
}
void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
@AnimationType int type) {
startAnimation(t, anim, hidden, type, null /* animationFinishedCallback */);
}
void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
@AnimationType int type) {
startAnimation(t, anim, hidden, type, null /* animationFinishedCallback */);
}
void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
@AnimationType int type,
@Nullable OnAnimationFinishedCallback animationFinishedCallback) {
startAnimation(t, anim, hidden, type, animationFinishedCallback,
null /* adapterAnimationCancelledCallback */, null /* snapshotAnim */);
}
三. 创建leash,同时把执行动画的窗口容器reparent到 leash上
如下调用到mSurfaceAnimator.startAnimation()来进一步启动动画。
但是需要注意的是 mSurfaceAnimator,为WindowContainer的一个属性, 类型为SurfaceAnimator,以下为SurfaceAnimator的类注释:
/**
* 一个可以运行动画在一些拥有子surfaces的 对象上,
* A class that can run animations on objects that have a set of child surfaces. We do this by
*
* reparenting all child surfaces of an object onto a new surface, called the "Leash". The Leash
* gets attached in the surface hierarchy where the the children were attached to. We then hand off
* the Leash to the component handling the animation, which is specified by the
* {@link AnimationAdapter}. When the animation is done animating, our callback to finish the
* animation will be invoked, at which we reparent the children back to the original parent.
*/
class SurfaceAnimator {
......
}
SurfaceAnimator引入了一个重要的概念:Leash,字面翻译为 “系带“,何为Leash?
WindowContainer.java
void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
@AnimationType int type,
@Nullable OnAnimationFinishedCallback animationFinishedCallback,
@Nullable Runnable animationCancelledCallback,
@Nullable AnimationAdapter snapshotAnim) {
ProtoLog.v(WM_DEBUG_ANIM, "Starting animation on %s: type=%d, anim=%s",
this, type, anim);
// TODO: This should use isVisible() but because isVisible has a really weird meaning at
// the moment this doesn't work for all animatable window containers.
mSurfaceAnimator.startAnimation(t, anim, hidden, type, animationFinishedCallback,
animationCancelledCallback, snapshotAnim, mSurfaceFreezer);
}
SurfaceAnimator.java
void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
@AnimationType int type,
@Nullable OnAnimationFinishedCallback animationFinishedCallback,
@Nullable Runnable animationCancelledCallback,
@Nullable AnimationAdapter snapshotAnim, @Nullable SurfaceFreezer freezer) {
cancelAnimation(t, true /* restarting */, true /* forwardCancel */);
mAnimation = anim;
mAnimationType = type;
mSurfaceAnimationFinishedCallback = animationFinishedCallback;
mAnimationCancelledCallback = animationCancelledCallback;
final SurfaceControl surface = mAnimatable.getSurfaceControl();
if (surface == null) {
Slog.w(TAG, "Unable to start animation, surface is null or no children.");
cancelAnimation();
return;
}
mLeash = freezer != null ? freezer.takeLeashForAnimation() : null;
// 在这里会创建leash
if (mLeash == null) {
mLeash = createAnimationLeash(mAnimatable, surface, t, type,
mAnimatable.getSurfaceWidth(), mAnimatable.getSurfaceHeight(), 0 /* x */,
0 /* y */, hidden, mService.mTransactionFactory);
mAnimatable.onAnimationLeashCreated(t, mLeash);
}
mAnimatable.onLeashAnimationStarting(t, mLeash);
if (mAnimationStartDelayed) {
ProtoLog.i(WM_DEBUG_ANIM, "Animation start delayed for %s", mAnimatable);
return;
}
// 创建完毕Leash 开始进一步的启动动画
mAnimation.startAnimation(mLeash, t, type, mInnerAnimationFinishedCallback);
if (ProtoLogImpl.isEnabled(WM_DEBUG_ANIM)) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
mAnimation.dump(pw, "");
ProtoLog.d(WM_DEBUG_ANIM, "Animation start for %s, anim=%s", mAnimatable, sw);
}
if (snapshotAnim != null) {
mSnapshot = freezer.takeSnapshotForAnimation();
if (mSnapshot == null) {
Slog.e(TAG, "No snapshot target to start animation on for " + mAnimatable);
return;
}
mSnapshot.startAnimation(t, snapshotAnim, type);
}
}
如上,当startAnimation的mLeash为空的时候会去创建一个Leash,重点关注createAnimationLeash(),了解Leash是如何创建的。
SurfaceAnimator.java
static SurfaceControl createAnimationLeash(Animatable animatable, SurfaceControl surface,
Transaction t, @AnimationType int type, int width, int height, int x, int y,
boolean hidden, Supplier<Transaction> transactionFactory) {
ProtoLog.i(WM_DEBUG_ANIM, "Reparenting to leash for %s", animatable);
final SurfaceControl.Builder builder = animatable.makeAnimationLeash()
// 设置Leash的父节点为运行窗口动画的目标对象的SurfaceCrol的父节点
.setParent(animatable.getAnimationLeashParent())
.setName(surface + " - animation-leash of " + animationTypeToString(type))
// TODO(b/151665759) Defer reparent calls
// We want the leash to be visible immediately because the transaction which shows
// the leash may be deferred but the reparent will not. This will cause the leashed
// surface to be invisible until the deferred transaction is applied. If this
// doesn't work, you will can see the 2/3 button nav bar flicker during seamless
// rotation.
.setHidden(hidden)
// 设置Leash的Surface类型为容器类型,即没有有用于渲染的Buffer。
.setEffectLayer()
.setCallsite("SurfaceAnimator.createAnimationLeash");
final SurfaceControl leash = builder.build();
t.setWindowCrop(leash, width, height);
t.setPosition(leash, x, y);
t.show(leash);
t.setAlpha(leash, hidden ? 0 : 1);
// 把需要执行动画的SurafaControl又挂在Leash上
t.reparent(surface, leash);
return leash;
}
以上为Leah的创过程,其中最重要的就是三步:
- 创建Leash(本质上就是个SurfaceControl),设置Leash的父节点为运行窗口动画的目标对象SurfaceCrol的父节点。
- 设置Leash的Surface类型为容器类型
- 把运行窗口动画的目标对象SurfaceCrol的父节点又设置为Leash。
简而言之,就是在执行动画的SuraceCntrol和它的父节点之间添加了一个类型为Leash的SurfaceControl,后面执行动画就直接操作这个Leash即可。
创建完毕Leash接下来通过 mAnimation.startAnimation(mLeash, t, type, mInnerAnimationFinishedCallback);来进一步启动动画。
LocalAnimationAdapter.java
@Override
public void startAnimation(SurfaceControl animationLeash, Transaction t,
@AnimationType int type, @NonNull OnAnimationFinishedCallback finishCallback) {
mAnimator.startAnimation(mSpec, animationLeash, t,
() -> finishCallback.onAnimationFinished(type, this));
}
四.开始循环执行窗口动画
void startAnimation(AnimationSpec a, SurfaceControl animationLeash, Transaction t,
Runnable finishCallback) {
synchronized (mLock) {
final RunningAnimation runningAnim = new RunningAnimation(a, animationLeash,
finishCallback);
boolean requiresEdgeExtension = requiresEdgeExtension(a);
... ...
if (!requiresEdgeExtension) {
mPendingAnimations.put(animationLeash, runningAnim);
if (!mAnimationStartDeferred && mPreProcessingAnimations.isEmpty()) {
mChoreographer.postFrameCallback(this::startAnimations);
}
// Some animations (e.g. move animations) require the initial transform to be
// applied immediately.
applyTransformation(runningAnim, t, 0 /* currentPlayTime */);
}
}
}
private void startAnimations(long frameTimeNanos) {
synchronized (mLock) {
if (!mPreProcessingAnimations.isEmpty()) {
// We only want to start running animations once all mPreProcessingAnimations have
// been processed to ensure preprocessed animations start in sync.
// NOTE: This means we might delay running animations that require preprocessing if
// new animations that also require preprocessing are requested before the previous
// ones have finished (see b/227449117).
return;
}
// 近一步启动
startPendingAnimationsLocked();
}
mPowerManagerInternal.setPowerBoost(Boost.INTERACTION, 0);
}
@GuardedBy("mLock")
private void startAnimationLocked(RunningAnimation a) {
// 注意在这里创建了一个ValueAnimator的对象,后续动画的运行就要又它来处理
final ValueAnimator anim = mAnimatorFactory.makeAnimator();
// Animation length is already expected to be scaled.
// 设置缩放比例
anim.overrideDurationScale(1.0f);
// 设置持续时间
anim.setDuration(a.mAnimSpec.getDuration());
// 注册更新的监听,将会由ValueAnimator 不断触发以实现动画的效果、
anim.addUpdateListener(animation -> {
synchronized (mCancelLock) {
if (!a.mCancelled) {
final long duration = anim.getDuration();
long currentPlayTime = anim.getCurrentPlayTime();
if (currentPlayTime > duration) {
currentPlayTime = duration;
}
// 在这里会根据duration来真正操作surafce的属性,以实现窗口动画的效果
applyTransformation(a, mFrameTransaction, currentPlayTime);
}
}
// Transaction will be applied in the commit phase.
// 对surface属性的修改并不能立马生效,需要合并成一个事务,然后提交给surafeflinger。
scheduleApplyTransaction();
});
anim.addListener(new AnimatorListenerAdapter() {
// 添加动画开始的监听
@Override
public void onAnimationStart(Animator animation) {
synchronized (mCancelLock) {
if (!a.mCancelled) {
// TODO: change this back to use show instead of alpha when b/138459974 is
// fixed.
mFrameTransaction.setAlpha(a.mLeash, 1);
}
}
}
// 添加动画结束的监听
@Override
public void onAnimationEnd(Animator animation) {
synchronized (mLock) {
mRunningAnimations.remove(a.mLeash);
synchronized (mCancelLock) {
if (!a.mCancelled) {
// Post on other thread that we can push final state without jank.
mAnimationThreadHandler.post(a.mFinishCallback);
}
}
}
}
});
a.mAnim = anim;
// 把正在运行的动画添加到mRunningAnimations中
mRunningAnimations.put(a.mLeash, a);
// 在这里会真正的开始启动动画。
anim.start();
if (a.mAnimSpec.canSkipFirstFrame()) {
// If we can skip the first frame, we start one frame later.
anim.setCurrentPlayTime(mChoreographer.getFrameIntervalNanos() / NANOS_PER_MS);
}
// Immediately start the animation by manually applying an animation frame. Otherwise, the
// start time would only be set in the next frame, leading to a delay.
anim.doAnimationFrame(mChoreographer.getFrameTime());
}
可见最后驱动窗口执行动画是属性动画,但是这个属性动画比较特的是他的编舞者是Wms中专门用于系统动画的一个编舞者。
属性动画的驱动原理,这篇文章不重点介绍,
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)