Android T 远程动画显示流程其三——桌面侧动画启动流程(更新中)

2024-02-28 20:44

本文主要是介绍Android T 远程动画显示流程其三——桌面侧动画启动流程(更新中),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

接着前文分析Android T 远程动画显示流程其二
我们通过IRemoteAnimationRunner跨进程通信从系统进程来到了桌面进程,这里是真正动画播放的逻辑。

进入桌面进程启动动画

跨进程通信,实现IRemoteAnimationRunner

代码路径:frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java

public abstract class RemoteAnimationRunnerCompat extends IRemoteAnimationRunner.Stub {public abstract void onAnimationStart(@WindowManager.TransitionOldType int transit,RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers,RemoteAnimationTarget[] nonApps, Runnable finishedCallback);@Overridepublic final void onAnimationStart(@TransitionOldType int transit,RemoteAnimationTarget[] apps,RemoteAnimationTarget[] wallpapers,RemoteAnimationTarget[] nonApps,final IRemoteAnimationFinishedCallback finishedCallback) {//调用自身抽象方法onAnimationStartonAnimationStart(transit, apps, wallpapers,nonApps, () -> {try {finishedCallback.onAnimationFinished();} catch (RemoteException e) {Log.e("ActivityOptionsCompat", "Failed to call app controlled animation"+ " finished callback", e);}});}......
}

这里传递的参数都是前面RemoteAnimationController.goodToGo方法中获取的值。
transit的值是TRANSIT_OLD_WALLPAPER_CLOSE(12);
app指的是桌面和应用的RemoteAnimationTarget;
wallpapers壁纸的RemoteAnimationTarget;
nonApp非APP类型的RemoteAnimationTarget;
finishedCallback是FinishedCallback对象,这里传递的是调用了其onAnimationFinished()方法。

这方方法调用了自身抽象方法调用自身抽象方法onAnimationStart,onAnimationStart方法真正的实现在LauncherAnimationRunner类中

@TargetApi(Build.VERSION_CODES.P)
public class LauncherAnimationRunner extends RemoteAnimationRunnerCompat {......@BinderThreadpublic void onAnimationStart(int transit,RemoteAnimationTarget[] appTargets,RemoteAnimationTarget[] wallpaperTargets,RemoteAnimationTarget[] nonAppTargets,Runnable runnable) {Runnable r = () -> {//退出动画的流程,此时mAnimationResult为空,尚未进入该流程finishExistingAnimation();//创建AnimationResult,传递了两个runnable//() -> mAnimationResult = null,把AnimationResult对象置空//runnable,就是前面传递的IRemoteAnimationFinishedCallback.onAnimationFinishedmAnimationResult = new AnimationResult(() -> mAnimationResult = null, runnable);//传递从系统侧调用过来的参数创建动画getFactory().onCreateAnimation(transit, appTargets, wallpaperTargets, nonAppTargets,mAnimationResult);};//根据mStartAtFrontOfQueue的值,执行线程 rif (mStartAtFrontOfQueue) {//将Runnable插入到消息队列的前面,以确保它尽快被执行postAtFrontOfQueueAsynchronously(mHandler, r);} else {//将Runnable异步地插入到消息队列中,它将在队列中的其他消息之后执行。postAsyncCallback(mHandler, r);}}......
}
  • 退出动画的流程
    finishExistingAnimation();

        @UiThreadprivate void finishExistingAnimation() {if (mAnimationResult != null) {mAnimationResult.finish();mAnimationResult = null;}}
    

    根据mAnimationResult是否为空执行finish方法,主要就是执行mASyncFinishRunnable,后续会在动画退出流程中细讲。

  • 创建AnimationResult
    mAnimationResult = new AnimationResult(() -> mAnimationResult = null, runnable);

        public static final class AnimationResult {......private AnimationResult(Runnable syncFinishRunnable, Runnable asyncFinishRunnable) {mSyncFinishRunnable = syncFinishRunnable;mASyncFinishRunnable = asyncFinishRunnable;}......}
    

    AnimationResult主要用来返回当前动画播放结果,以便后续执行动画播放完成时的回调(mASyncFinishRunnable)。
    () -> mAnimationResult = null,一个把AnimationResult对象置空的Runnable,保存到mSyncFinishRunnable中;
    runnable,就是前面传递的IRemoteAnimationFinishedCallback.onAnimationFinished,保存到mASyncFinishRunnable中。

  • 传递从系统侧创建的参数创建动画

    getFactory().onCreateAnimation(transit, appTargets, wallpaperTargets, nonAppTargets,mAnimationResult);
    

    传递了从系统侧创建的参数,并传递了mAnimationResult对象。这里调用的是RemoteAnimationFactory接口中的onCreateAnimation方法。

        /*** Used with LauncherAnimationRunner as an interface for the runner to call back to the* implementation.*/@FunctionalInterfacepublic interface RemoteAnimationFactory {/*** Called on the UI thread when the animation targets are received. The implementation must* call {@link AnimationResult#setAnimation} with the target animation to be run.*/void onCreateAnimation(int transit,RemoteAnimationTarget[] appTargets,RemoteAnimationTarget[] wallpaperTargets,RemoteAnimationTarget[] nonAppTargets,LauncherAnimationRunner.AnimationResult result);......}
    

    在最开始Launcher.startActivitySafely流程中,QuickstepTransitionManager.getActivityLaunchOptions方法中创建了AppLaunchAnimationRunner对象,并作为RemoteAnimationFactory对象传递到了。

            mAppLaunchRunner = new AppLaunchAnimationRunner(v, onEndCallback);RemoteAnimationRunnerCompat runner = new LauncherAnimationRunner(mHandler, mAppLaunchRunner, true /* startAtFrontOfQueue */);
    

    因此我们这里RemoteAnimationFactory的实现,就是在QuickstepTransitionManager.AppLaunchAnimationRunner中。

传递从系统侧创建的参数创建动画

代码路径:packages/apps/Launcher3/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java

    private class AppLaunchAnimationRunner implements RemoteAnimationFactory {private final View mV;private final RunnableList mOnEndCallback;AppLaunchAnimationRunner(View v, RunnableList onEndCallback) {mV = v;mOnEndCallback = onEndCallback;}@Overridepublic void onCreateAnimation(int transit,RemoteAnimationTarget[] appTargets,RemoteAnimationTarget[] wallpaperTargets,RemoteAnimationTarget[] nonAppTargets,LauncherAnimationRunner.AnimationResult result) {//创建AnimatorSetAnimatorSet anim = new AnimatorSet();//判断桌面的是否已经不在前台boolean launcherClosing =launcherIsATargetWithMode(appTargets, MODE_CLOSING);//检查是否从桌面小部件启动应用final boolean launchingFromWidget = mV instanceof LauncherAppWidgetHostView;//检查是否从最近应用列表启动应用final boolean launchingFromRecents = isLaunchingFromRecents(mV, appTargets);//决定是否跳过动画的第一帧final boolean skipFirstFrame;if (launchingFromWidget) {//从桌面小部件启动应用的动画composeWidgetLaunchAnimator(anim, (LauncherAppWidgetHostView) mV, appTargets,wallpaperTargets, nonAppTargets, launcherClosing);addCujInstrumentation(anim, InteractionJankMonitorWrapper.CUJ_APP_LAUNCH_FROM_WIDGET);skipFirstFrame = true;} else if (launchingFromRecents) {//从最近任务启动应用的动画composeRecentsLaunchAnimator(anim, mV, appTargets, wallpaperTargets, nonAppTargets,launcherClosing);addCujInstrumentation(anim, InteractionJankMonitorWrapper.CUJ_APP_LAUNCH_FROM_RECENTS);skipFirstFrame = true;} else {//点击桌面图标启动应用的动画composeIconLaunchAnimator(anim, mV, appTargets, wallpaperTargets, nonAppTargets,launcherClosing);addCujInstrumentation(anim, InteractionJankMonitorWrapper.CUJ_APP_LAUNCH_FROM_ICON);skipFirstFrame = false;}//桌面不在前台给动画添加一个监听器if (launcherClosing) {anim.addListener(mForceInvisibleListener);}//设置动画和回调result.setAnimation(anim, mLauncher, mOnEndCallback::executeAllAndDestroy,skipFirstFrame);}@Overridepublic void onAnimationCancelled() {mOnEndCallback.executeAllAndDestroy();}}

这里我们主要关注点击桌面图标启动应用的动画逻辑

点击桌面图标启动应用的动画

composeIconLaunchAnimator(anim, mV, appTargets, wallpaperTargets, nonAppTargets,launcherClosing);

anim一个AnimatorSet对象;
mV这里指的是启动的应用图标,比如com.android.launcher3.BubbleTextView{bace738 VFED..CL. ........ 582,525-859,945 #7f09016a app:id/icon}
appTargets指的是桌面和应用的RemoteAnimationTarget;
wallpaperTargets壁纸的RemoteAnimationTarget;
nonAppTargets非APP类型的RemoteAnimationTarget;
launcherClosing此时桌面的是否已经不在前台,因此值为true

    /*** Compose the animations for a launch from the app icon.** @param anim            the animation to add to* @param v               the launching view with the icon* @param appTargets      the list of opening/closing apps* @param launcherClosing true if launcher is closing*/private void composeIconLaunchAnimator(@NonNull AnimatorSet anim, @NonNull View v,@NonNull RemoteAnimationTarget[] appTargets,@NonNull RemoteAnimationTarget[] wallpaperTargets,@NonNull RemoteAnimationTarget[] nonAppTargets,boolean launcherClosing) {// Set the state animation first so that any state listeners are called// before our internal listeners.mLauncher.getStateManager().setCurrentAnimation(anim);// Note: the targetBounds are relative to the launcherint startDelay = getSingleFrameMs(mLauncher);Animator windowAnimator = getOpeningWindowAnimators(v, appTargets, wallpaperTargets, nonAppTargets, launcherClosing);windowAnimator.setStartDelay(startDelay);anim.play(windowAnimator);if (launcherClosing) {// Delay animation by a frame to avoid jank.Pair<AnimatorSet, Runnable> launcherContentAnimator =getLauncherContentAnimator(true /* isAppOpening */, startDelay, false);anim.play(launcherContentAnimator.first);anim.addListener(new AnimatorListenerAdapter() {@Overridepublic void onAnimationEnd(Animator animation) {launcherContentAnimator.second.run();}});}}

之前最为关键的就是getOpeningWindowAnimators方法

Animator windowAnimator = getOpeningWindowAnimators(v, appTargets, wallpaperTargets, nonAppTargets, launcherClosing);

这个方法是动画真正的设置部分

    /*** @return Animator that controls the window of the opening targets from app icons.*/private Animator getOpeningWindowAnimators(View v,RemoteAnimationTarget[] appTargets,RemoteAnimationTarget[] wallpaperTargets,RemoteAnimationTarget[] nonAppTargets,boolean launcherClosing) {//获取应用方向int rotationChange = getRotationChange(appTargets);//获取启动应用的窗口边界Rect windowTargetBounds = getWindowTargetBounds(appTargets, rotationChange);//检查appTargets中所有应用目标是否透明boolean appTargetsAreTranslucent = areAllTargetsTranslucent(appTargets);RectF launcherIconBounds = new RectF();//获取一个浮动图标视图FloatingIconView floatingView = FloatingIconView.getFloatingIconView(mLauncher, v,!appTargetsAreTranslucent, launcherIconBounds, true /* isOpening */);Rect crop = new Rect();Matrix matrix = new Matrix();//创建mMode为MODE_OPENING的RemoteAnimationTargets对象//把app、壁纸和非app类型的RemoteAnimationTarget对象保存到RemoteAnimationTargets中RemoteAnimationTargets openingTargets = new RemoteAnimationTargets(appTargets,wallpaperTargets, nonAppTargets, MODE_OPENING);//创建SurfaceTransactionApplier对象SurfaceTransactionApplier surfaceApplier =new SurfaceTransactionApplier(floatingView);//为了确保动画完成时,释放相关资源openingTargets.addReleaseCheck(surfaceApplier);//获取导航栏的RemoteAnimationTarget对象RemoteAnimationTarget navBarTarget = openingTargets.getNavBarRemoteAnimationTarget();int[] dragLayerBounds = new int[2];mDragLayer.getLocationOnScreen(dragLayerBounds);//检查是否支持冷启动窗口Splash Screenfinal boolean hasSplashScreen;if (supportsSSplashScreen()) {int taskId = openingTargets.getFirstAppTargetTaskId();Pair<Integer, Integer> defaultParams = Pair.create(STARTING_WINDOW_TYPE_NONE, 0);Pair<Integer, Integer> taskParams =mTaskStartParams.getOrDefault(taskId, defaultParams);mTaskStartParams.remove(taskId);hasSplashScreen = taskParams.first == STARTING_WINDOW_TYPE_SPLASH_SCREEN;} else {hasSplashScreen = false;}//创建AnimOpenProperties对象,设置应用启动时的动画属性AnimOpenProperties prop = new AnimOpenProperties(mLauncher.getResources(), mDeviceProfile,windowTargetBounds, launcherIconBounds, v, dragLayerBounds[0], dragLayerBounds[1],hasSplashScreen, floatingView.isDifferentFromAppIcon());//计算裁剪区域的边界int left = prop.cropCenterXStart - prop.cropWidthStart / 2;int top = prop.cropCenterYStart - prop.cropHeightStart / 2;int right = left + prop.cropWidthStart;int bottom = top + prop.cropHeightStart;// Set the crop here so we can calculate the corner radius below.crop.set(left, top, right, bottom);//创建临时矩形和点对象RectF floatingIconBounds = new RectF();RectF tmpRectF = new RectF();Point tmpPos = new Point();//设置动画的一些参数和监听AnimatorSet animatorSet = new AnimatorSet();ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1);appAnimator.setDuration(APP_LAUNCH_DURATION);//设置动画的插值器为LINEAR。插值器决定了动画的速度曲线。LINEAR意味着动画将匀速进行appAnimator.setInterpolator(LINEAR);//为appAnimator添加一个动画监听器floatingView。//当动画开始、结束、取消或重复时,floatingView上的相应方法将被调用。appAnimator.addListener(floatingView);appAnimator.addListener(new AnimatorListenerAdapter() {@Override//监听动开始public void onAnimationStart(Animator animation) {//获取LauncherTaskbarUIController的实例LauncherTaskbarUIController taskbarController = mLauncher.getTaskbarUIController();//检查是否应该调用shouldShowEdu()if (taskbarController != null && taskbarController.shouldShowEdu()) {// LAUNCHER_TASKBAR_EDUCATION_SHOWING is set to true here, when the education// flow is about to start, to avoid a race condition with other components// that would show something else to the user as soon as the app is opened.//将LAUNCHER_TASKBAR_EDUCATION_SHOWING设置为true,以避免与其他组件发生竞争Settings.Secure.putInt(mLauncher.getContentResolver(),LAUNCHER_TASKBAR_EDUCATION_SHOWING, 1);}}@Override//监听动结束public void onAnimationEnd(Animator animation) {if (v instanceof BubbleTextView) {//我们这里v是BubbleTextView类型//设置控件v保持按下的状态为false((BubbleTextView) v).setStayPressed(false);}//获取LauncherTaskbarUIController的实例LauncherTaskbarUIController taskbarController = mLauncher.getTaskbarUIController();if (taskbarController != null) {//调用shouldShowEdu()taskbarController.showEdu();}//释放所有类型的RemoteAnimationTarget对象//包含壁纸、app和非app类型的RemoteAnimationTarget对象openingTargets.release();}});//initialWindowRadius用于设置动画开始时的窗口圆角半径//supportsRoundedCornersOnWindows(mLauncher.getResources()判断桌面是否支持窗口圆角final float initialWindowRadius = supportsRoundedCornersOnWindows(mLauncher.getResources())? Math.max(crop.width(), crop.height()) / 2f: 0f;//finalWindowRadius用于设置动画结束时的窗口圆角半径//mDeviceProfile.isMultiWindowMode检查是否处于多窗口模式//getWindowCornerRadius(mLauncher)获取桌面窗口的圆角半径final float finalWindowRadius = mDeviceProfile.isMultiWindowMode? 0 : getWindowCornerRadius(mLauncher);//inalShadowRadius用于设置动画结束时的阴影半径//appTargetsAreTranslucent表示应用目标是否半透明//mMaxShadowRadius最大阴影半径值final float finalShadowRadius = appTargetsAreTranslucent ? 0 : mMaxShadowRadius;MultiValueUpdateListener listener = new MultiValueUpdateListener() {//mDx:这个属性表示在动画过程中,X轴上的位移变化。//它从0开始,到prop.dX结束,动画时长为APP_LAUNCH_DURATION,使用mOpeningXInterpolator作为插值器。FloatProp mDx = new FloatProp(0, prop.dX, 0, APP_LAUNCH_DURATION,mOpeningXInterpolator);//这个属性表示在动画过程中,Y轴上的位移变化。//它从0开始,到prop.dY结束,动画时长为APP_LAUNCH_DURATION,使用mOpeningInterpolator作为插值器。FloatProp mDy = new FloatProp(0, prop.dY, 0, APP_LAUNCH_DURATION,mOpeningInterpolator);//mIconScaleToFitScreen:这个属性表示应用图标在屏幕上的缩放变化。//它从prop.initialAppIconScale开始,到prop.finalAppIconScale结束,//动画时长为APP_LAUNCH_DURATION,使用mOpeningInterpolator作为插值器。FloatProp mIconScaleToFitScreen = new FloatProp(prop.initialAppIconScale,prop.finalAppIconScale, 0, APP_LAUNCH_DURATION, mOpeningInterpolator);//mIconAlpha:这个属性表示应用图标的透明度变化。//它从prop.iconAlphaStart开始,到0结束,//动画的开始延迟为APP_LAUNCH_ALPHA_START_DELAY,时长为APP_LAUNCH_ALPHA_DURATION,//使用线性插值器(LINEAR)。FloatProp mIconAlpha = new FloatProp(prop.iconAlphaStart, 0f,APP_LAUNCH_ALPHA_START_DELAY, APP_LAUNCH_ALPHA_DURATION, LINEAR);//mWindowRadius:这个属性表示窗口圆角的半径变化。//它从initialWindowRadius开始,到finalWindowRadius结束,动画时长为APP_LAUNCH_DURATION,//使用mOpeningInterpolator作为插值器。FloatProp mWindowRadius = new FloatProp(initialWindowRadius, finalWindowRadius, 0,APP_LAUNCH_DURATION, mOpeningInterpolator);//mShadowRadius:这个属性表示阴影的半径变化。//它从0开始,到finalShadowRadius结束,动画时长为APP_LAUNCH_DURATION,//使用mOpeningInterpolator作为插值器。FloatProp mShadowRadius = new FloatProp(0, finalShadowRadius, 0,APP_LAUNCH_DURATION, mOpeningInterpolator);//mCropRectCenterX、mCropRectCenterY、mCropRectWidth、mCropRectHeight//这些属性分别表示裁剪矩形的中心X坐标、中心Y坐标、宽度和高度的变化。//它们都有各自的起始值和结束值,动画时长为APP_LAUNCH_DURATION,使用mOpeningInterpolator作为插值器。FloatProp mCropRectCenterX = new FloatProp(prop.cropCenterXStart, prop.cropCenterXEnd,0, APP_LAUNCH_DURATION, mOpeningInterpolator);FloatProp mCropRectCenterY = new FloatProp(prop.cropCenterYStart, prop.cropCenterYEnd,0, APP_LAUNCH_DURATION, mOpeningInterpolator);FloatProp mCropRectWidth = new FloatProp(prop.cropWidthStart, prop.cropWidthEnd, 0,APP_LAUNCH_DURATION, mOpeningInterpolator);FloatProp mCropRectHeight = new FloatProp(prop.cropHeightStart, prop.cropHeightEnd, 0,APP_LAUNCH_DURATION, mOpeningInterpolator);//这个属性表示导航栏的淡出效果。//它从1开始,到0结束,动画时长为ANIMATION_NAV_FADE_OUT_DURATION,//使用NAV_FADE_OUT_INTERPOLATOR作为插值器。FloatProp mNavFadeOut = new FloatProp(1f, 0f, 0, ANIMATION_NAV_FADE_OUT_DURATION,NAV_FADE_OUT_INTERPOLATOR);//mNavFadeIn:这个属性表示导航栏的淡入效果。它从0开始,到1结束,//动画的开始延迟为ANIMATION_DELAY_NAV_FADE_IN,时长为ANIMATION_NAV_FADE_IN_DURATION,//使用NAV_FADE_IN_INTERPOLATOR作为插值器。FloatProp mNavFadeIn = new FloatProp(0f, 1f, ANIMATION_DELAY_NAV_FADE_IN,ANIMATION_NAV_FADE_IN_DURATION, NAV_FADE_IN_INTERPOLATOR);//动画的更新@Overridepublic void onUpdate(float percent, boolean initOnly) {// Calculate the size of the scaled icon.//计算缩放图标的大小float iconWidth = launcherIconBounds.width() * mIconScaleToFitScreen.value;float iconHeight = launcherIconBounds.height() * mIconScaleToFitScreen.value;int left = (int) (mCropRectCenterX.value - mCropRectWidth.value / 2);int top = (int) (mCropRectCenterY.value - mCropRectHeight.value / 2);int right = (int) (left + mCropRectWidth.value);int bottom = (int) (top + mCropRectHeight.value);crop.set(left, top, right, bottom);final int windowCropWidth = crop.width();final int windowCropHeight = crop.height();if (rotationChange != 0) {Utilities.rotateBounds(crop, mDeviceProfile.widthPx,mDeviceProfile.heightPx, rotationChange);}// Scale the size of the icon to match the size of the window crop.//缩放图标的大小以匹配窗口裁剪的大小。float scaleX = iconWidth / windowCropWidth;float scaleY = iconHeight / windowCropHeight;float scale = Math.min(1f, Math.max(scaleX, scaleY));float scaledCropWidth = windowCropWidth * scale;float scaledCropHeight = windowCropHeight * scale;float offsetX = (scaledCropWidth - iconWidth) / 2;float offsetY = (scaledCropHeight - iconHeight) / 2;// Calculate the window position to match the icon position.//计算窗口位置以匹配图标位置。tmpRectF.set(launcherIconBounds);tmpRectF.offset(dragLayerBounds[0], dragLayerBounds[1]);tmpRectF.offset(mDx.value, mDy.value);Utilities.scaleRectFAboutCenter(tmpRectF, mIconScaleToFitScreen.value);float windowTransX0 = tmpRectF.left - offsetX - crop.left * scale;float windowTransY0 = tmpRectF.top - offsetY - crop.top * scale;// Calculate the icon position.//计算图标位置floatingIconBounds.set(launcherIconBounds);floatingIconBounds.offset(mDx.value, mDy.value);Utilities.scaleRectFAboutCenter(floatingIconBounds, mIconScaleToFitScreen.value);floatingIconBounds.left -= offsetX;floatingIconBounds.top -= offsetY;floatingIconBounds.right += offsetX;floatingIconBounds.bottom += offsetY;if (initOnly) {// For the init pass, we want full alpha since the window is not yet ready.//使用floatingView.update方法更新浮动视图的属性,包括透明度、边界、半径等。  floatingView.update(1f, 255, floatingIconBounds, percent, 0f,mWindowRadius.value * scale, true /* isOpening */);return;}SurfaceTransaction transaction = new SurfaceTransaction();//遍历桌面和启动应用的RemoteAnimationTarget,获取其leash,分别做处理for (int i = appTargets.length - 1; i >= 0; i--) {RemoteAnimationTarget target = appTargets[i];SurfaceProperties builder = transaction.forSurface(target.leash);if (target.mode == MODE_OPENING) {/*** 如果目标模式是MODE_OPENING(打开模式),代码会设置一个矩阵(matrix)来进行缩放和平移操作。* 根据rotationChange的值(可能是表示屏幕旋转的变量),代码会决定如何平移窗口。  * 然后,使用floatingView.update方法更新浮动视图的属性,包括透明度、边界、半径等。  * 接着,通过builder.setMatrix等方法设置窗口的矩阵、裁剪区域、透明度、圆角半径和阴影半径。*/matrix.setScale(scale, scale);if (rotationChange == 1) {matrix.postTranslate(windowTransY0,mDeviceProfile.widthPx - (windowTransX0 + scaledCropWidth));} else if (rotationChange == 2) {matrix.postTranslate(mDeviceProfile.widthPx - (windowTransX0 + scaledCropWidth),mDeviceProfile.heightPx - (windowTransY0 + scaledCropHeight));} else if (rotationChange == 3) {matrix.postTranslate(mDeviceProfile.heightPx - (windowTransY0 + scaledCropHeight),windowTransX0);} else {matrix.postTranslate(windowTransX0, windowTransY0);}floatingView.update(mIconAlpha.value, 255, floatingIconBounds, percent, 0f,mWindowRadius.value * scale, true /* isOpening */);builder.setMatrix(matrix).setWindowCrop(crop).setAlpha(1f - mIconAlpha.value).setCornerRadius(mWindowRadius.value).setShadowRadius(mShadowRadius.value);} else if (target.mode == MODE_CLOSING) {/*** 如果目标模式是MODE_CLOSING(关闭模式),代码会处理关闭动画。* 首先,根据目标的本地边界或位置设置临时位置(tmpPos)。* 然后,根据rotationChange的值,可能需要对裁剪区域(crop)和临时位置进行旋转调整。* 最后,设置窗口的矩阵和裁剪区域,并将透明度设置为1(完全不透明)。*/if (target.localBounds != null) {tmpPos.set(target.localBounds.left, target.localBounds.top);} else {tmpPos.set(target.position.x, target.position.y);}final Rect crop = new Rect(target.screenSpaceBounds);crop.offsetTo(0, 0);if ((rotationChange % 2) == 1) {int tmp = crop.right;crop.right = crop.bottom;crop.bottom = tmp;tmp = tmpPos.x;tmpPos.x = tmpPos.y;tmpPos.y = tmp;}matrix.setTranslate(tmpPos.x, tmpPos.y);builder.setMatrix(matrix).setWindowCrop(crop).setAlpha(1f);}}/*** 如果navBarTarget不为空(即存在导航栏目标),代码会为其设置动画和视图属性。 * 根据`mNavFadeIn.value`的值,决定是淡入还是淡出导航栏。如果淡入值大于起始值,则应用淡入动画;*/if (navBarTarget != null) {SurfaceProperties navBuilder =transaction.forSurface(navBarTarget.leash);if (mNavFadeIn.value > mNavFadeIn.getStartValue()) {matrix.setScale(scale, scale);matrix.postTranslate(windowTransX0, windowTransY0);navBuilder.setMatrix(matrix).setWindowCrop(crop).setAlpha(mNavFadeIn.value);} else {navBuilder.setAlpha(mNavFadeOut.value);}}surfaceApplier.scheduleApply(transaction);}};appAnimator.addUpdateListener(listener);// Since we added a start delay, call update here to init the FloatingIconView properly.listener.onUpdate(0, true /* initOnly */);// If app targets are translucent, do not animate the background as it causes a visible// flicker when it resets itself at the end of its animation.if (appTargetsAreTranslucent || !launcherClosing) {animatorSet.play(appAnimator);} else {animatorSet.playTogether(appAnimator, getBackgroundAnimator());}return animatorSet;}

设置并启动动画

回到QuickstepTransitionManager.AppLaunchAnimationRunner.onCreateAnimation方法中

result.setAnimation(anim, mLauncher, mOnEndCallback::executeAllAndDestroy,skipFirstFrame);

代码路径:packages/apps/Launcher3/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java

        @UiThreadpublic void setAnimation(AnimatorSet animation, Context context) {setAnimation(animation, context, null, true);}/*** Sets the animation to play for this app launch* @param skipFirstFrame Iff true, we skip the first frame of the animation.*                       We set to false when skipping first frame causes jank.*/@UiThreadpublic void setAnimation(AnimatorSet animation, Context context,@Nullable Runnable onCompleteCallback, boolean skipFirstFrame) {if (mInitialized) {throw new IllegalStateException("Animation already initialized");}mInitialized = true;mAnimator = animation;mOnCompleteCallback = onCompleteCallback;if (mAnimator == null) {finish();} else if (mFinished) {// Animation callback was already finished, skip the animation.mAnimator.start();mAnimator.end();if (mOnCompleteCallback != null) {mOnCompleteCallback.run();}} else {// Start the animationmAnimator.addListener(new AnimatorListenerAdapter() {@Overridepublic void onAnimationEnd(Animator animation) {finish();}});mAnimator.start();if (skipFirstFrame) {// Because t=0 has the app icon in its original spot, we can skip the// first frame and have the same movement one frame earlier.mAnimator.setCurrentPlayTime(Math.min(getSingleFrameMs(context), mAnimator.getTotalDuration()));}}}}

动画移除流程

处理和响应动画完成的逻辑

代码路径:frameworks/base/services/core/java/com/android/server/wm/WindowContainer.java

		private void doAnimationFinished(@AnimationType int type, AnimationAdapter anim) {for (int i = 0; i < mSurfaceAnimationSources.size(); ++i) {//mSurfaceAnimationSources中每个容器,做对应的onAnimationFinishedmSurfaceAnimationSources.valueAt(i).onAnimationFinished(type, anim);}//清除动画源列表mSurfaceAnimationSources.clear();if (mDisplayContent != null) {//调用DisplayContent的onWindowAnimationFinished方法//从当前源码上看,主要是针对输入法相关做了一些操作mDisplayContent.onWindowAnimationFinished(this, type);}}/*** Called when an animation has finished running.*/protected void onAnimationFinished(@AnimationType int type, AnimationAdapter anim) {//主要用于 清空 mSurfaceAnimationSources 列表doAnimationFinished(type, anim);//WindowManagerService中实现onAnimationFinished()//用于唤醒所有等待mGlobalLock对象的线程,确保多个线程能够正确地执行任务mWmService.onAnimationFinished();//将 mNeedsZBoost 设置为 false,表示不再需要Z轴增强mNeedsZBoost = false;}

我们这里mSurfaceAnimationSources是保存的是需要做动画的ActivityRecord,mSurfaceAnimationSources的值是在applyAnimationUnchecked方法中添加的。
mSurfaceAnimationSources.valueAt(i).onAnimationFinished(type, anim);调用了不同容器onAnimationFinished方法,在ActivityRecord和WindowState中都重写了这个方法。我们这里是远程动画,主要调用的就是ActivityRecord中重写的onAnimationFinished方法。
代码路径:frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java

    @Overrideprotected void onAnimationFinished(@AnimationType int type, AnimationAdapter anim) {super.onAnimationFinished(type, anim);Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "AR#onAnimationFinished");mTransit = TRANSIT_OLD_UNSET;mTransitFlags = 0;setAppLayoutChanges(FINISH_LAYOUT_REDO_ANIM | FINISH_LAYOUT_REDO_WALLPAPER,"ActivityRecord");clearThumbnail();setClientVisible(isVisible() || mVisibleRequested);getDisplayContent().computeImeTargetIfNeeded(this);ProtoLog.v(WM_DEBUG_ANIM, "Animation done in %s"+ ": reportedVisible=%b okToDisplay=%b okToAnimate=%b startingDisplayed=%b",this, reportedVisible, okToDisplay(), okToAnimate(),isStartingWindowDisplayed());// clean up thumbnail windowif (mThumbnail != null) {mThumbnail.destroy();mThumbnail = null;}// WindowState.onExitAnimationDone might modify the children list, so make a copy and then// traverse the copy.final ArrayList<WindowState> children = new ArrayList<>(mChildren);children.forEach(WindowState::onExitAnimationDone);// The starting window could transfer to another activity after app transition started, in// that case the latest top activity might not receive exit animation done callback if the// starting window didn't applied exit animation success. Notify animation finish to the// starting window if needed.if (task != null && startingMoved) {final WindowState transferredStarting = task.getWindow(w ->w.mAttrs.type == TYPE_APPLICATION_STARTING);if (transferredStarting != null && transferredStarting.mAnimatingExit&& !transferredStarting.isSelfAnimating(0 /* flags */,ANIMATION_TYPE_WINDOW_ANIMATION)) {transferredStarting.onExitAnimationDone();}}getDisplayContent().mAppTransition.notifyAppTransitionFinishedLocked(token);scheduleAnimation();// Schedule to handle the stopping and finishing activities which the animation is done// because the activities which were animating have not been stopped yet.mTaskSupervisor.scheduleProcessStoppingAndFinishingActivitiesIfNeeded();Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);}

这篇关于Android T 远程动画显示流程其三——桌面侧动画启动流程(更新中)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/756570

相关文章

如何解决idea的Module:‘:app‘platform‘android-32‘not found.问题

《如何解决idea的Module:‘:app‘platform‘android-32‘notfound.问题》:本文主要介绍如何解决idea的Module:‘:app‘platform‘andr... 目录idea的Module:‘:app‘pwww.chinasem.cnlatform‘android-32

Android实现打开本地pdf文件的两种方式

《Android实现打开本地pdf文件的两种方式》在现代应用中,PDF格式因其跨平台、稳定性好、展示内容一致等特点,在Android平台上,如何高效地打开本地PDF文件,不仅关系到用户体验,也直接影响... 目录一、项目概述二、相关知识2.1 PDF文件基本概述2.2 android 文件访问与存储权限2.

Android Studio 配置国内镜像源的实现步骤

《AndroidStudio配置国内镜像源的实现步骤》本文主要介绍了AndroidStudio配置国内镜像源的实现步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,... 目录一、修改 hosts,解决 SDK 下载失败的问题二、修改 gradle 地址,解决 gradle

Redis在windows环境下如何启动

《Redis在windows环境下如何启动》:本文主要介绍Redis在windows环境下如何启动的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Redis在Windows环境下启动1.在redis的安装目录下2.输入·redis-server.exe

解决SpringBoot启动报错:Failed to load property source from location 'classpath:/application.yml'

《解决SpringBoot启动报错:Failedtoloadpropertysourcefromlocationclasspath:/application.yml问题》这篇文章主要介绍... 目录在启动SpringBoot项目时报如下错误原因可能是1.yml中语法错误2.yml文件格式是GBK总结在启动S

MySQL更新某个字段拼接固定字符串的实现

《MySQL更新某个字段拼接固定字符串的实现》在MySQL中,我们经常需要对数据库中的某个字段进行更新操作,本文就来介绍一下MySQL更新某个字段拼接固定字符串的实现,感兴趣的可以了解一下... 目录1. 查看字段当前值2. 更新字段拼接固定字符串3. 验证更新结果mysql更新某个字段拼接固定字符串 -

在Android平台上实现消息推送功能

《在Android平台上实现消息推送功能》随着移动互联网应用的飞速发展,消息推送已成为移动应用中不可或缺的功能,在Android平台上,实现消息推送涉及到服务端的消息发送、客户端的消息接收、通知渠道(... 目录一、项目概述二、相关知识介绍2.1 消息推送的基本原理2.2 Firebase Cloud Me

Ubuntu中远程连接Mysql数据库的详细图文教程

《Ubuntu中远程连接Mysql数据库的详细图文教程》Ubuntu是一个以桌面应用为主的Linux发行版操作系统,这篇文章主要为大家详细介绍了Ubuntu中远程连接Mysql数据库的详细图文教程,有... 目录1、版本2、检查有没有mysql2.1 查询是否安装了Mysql包2.2 查看Mysql版本2.

Android中Dialog的使用详解

《Android中Dialog的使用详解》Dialog(对话框)是Android中常用的UI组件,用于临时显示重要信息或获取用户输入,本文给大家介绍Android中Dialog的使用,感兴趣的朋友一起... 目录android中Dialog的使用详解1. 基本Dialog类型1.1 AlertDialog(

SpringBoot启动报错的11个高频问题排查与解决终极指南

《SpringBoot启动报错的11个高频问题排查与解决终极指南》这篇文章主要为大家详细介绍了SpringBoot启动报错的11个高频问题的排查与解决,文中的示例代码讲解详细,感兴趣的小伙伴可以了解一... 目录1. 依赖冲突:NoSuchMethodError 的终极解法2. Bean注入失败:No qu