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

相关文章

Security OAuth2 单点登录流程

单点登录(英语:Single sign-on,缩写为 SSO),又译为单一签入,一种对于许多相互关连,但是又是各自独立的软件系统,提供访问控制的属性。当拥有这项属性时,当用户登录时,就可以获取所有系统的访问权限,不用对每个单一系统都逐一登录。这项功能通常是以轻型目录访问协议(LDAP)来实现,在服务器上会将用户信息存储到LDAP数据库中。相同的,单一注销(single sign-off)就是指

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

第10章 中断和动态时钟显示

第10章 中断和动态时钟显示 从本章开始,按照书籍的划分,第10章开始就进入保护模式(Protected Mode)部分了,感觉从这里开始难度突然就增加了。 书中介绍了为什么有中断(Interrupt)的设计,中断的几种方式:外部硬件中断、内部中断和软中断。通过中断做了一个会走的时钟和屏幕上输入字符的程序。 我自己理解中断的一些作用: 为了更好的利用处理器的性能。协同快速和慢速设备一起工作

MySQL数据库宕机,启动不起来,教你一招搞定!

作者介绍:老苏,10余年DBA工作运维经验,擅长Oracle、MySQL、PG、Mongodb数据库运维(如安装迁移,性能优化、故障应急处理等)公众号:老苏畅谈运维欢迎关注本人公众号,更多精彩与您分享。 MySQL数据库宕机,数据页损坏问题,启动不起来,该如何排查和解决,本文将为你说明具体的排查过程。 查看MySQL error日志 查看 MySQL error日志,排查哪个表(表空间

poj3468(线段树成段更新模板题)

题意:包括两个操作:1、将[a.b]上的数字加上v;2、查询区间[a,b]上的和 下面的介绍是下解题思路: 首先介绍  lazy-tag思想:用一个变量记录每一个线段树节点的变化值,当这部分线段的一致性被破坏我们就将这个变化值传递给子区间,大大增加了线段树的效率。 比如现在需要对[a,b]区间值进行加c操作,那么就从根节点[1,n]开始调用update函数进行操作,如果刚好执行到一个子节点,

hdu1394(线段树点更新的应用)

题意:求一个序列经过一定的操作得到的序列的最小逆序数 这题会用到逆序数的一个性质,在0到n-1这些数字组成的乱序排列,将第一个数字A移到最后一位,得到的逆序数为res-a+(n-a-1) 知道上面的知识点后,可以用暴力来解 代码如下: #include<iostream>#include<algorithm>#include<cstring>#include<stack>#in

hdu1689(线段树成段更新)

两种操作:1、set区间[a,b]上数字为v;2、查询[ 1 , n ]上的sum 代码如下: #include<iostream>#include<algorithm>#include<cstring>#include<stack>#include<queue>#include<set>#include<map>#include<stdio.h>#include<stdl

springboot3打包成war包,用tomcat8启动

1、在pom中,将打包类型改为war <packaging>war</packaging> 2、pom中排除SpringBoot内置的Tomcat容器并添加Tomcat依赖,用于编译和测试,         *依赖时一定设置 scope 为 provided (相当于 tomcat 依赖只在本地运行和测试的时候有效,         打包的时候会排除这个依赖)<scope>provided

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo