Android 应用的退场和入场动画分析

Android 应用的退场和入场动画分析

一、情景

在Android14上,由于新增了需求,需要定制自己的打开应用和退出应用动画。因此着手调查,应用打开和退出动画的出处。

二、任务

调查创建应用动画的创建流程和播放流程

三、行动

根据网上的的回答大多数都说应用的默认入场动画都在目录frameworks/base/core/res/res/anim这下面,分别是入场进入动画activity_open_enter.xml、入场退出动画activity_open_exit.xml、退场进入动画activity_close_enter.xml、退场退出动画activity_close_exit.xml。但是以为改这个就能改变就太想当然了,当然是不生效,既然是默认的,肯定是没人用的,最后还是得去撸代码,优先去看Launcher,应用的入场动画肯定是launcher侧创建的,需要ActivityOption去传递,基于这个猜测我找到launcher打开应用的操作:

应用打开动画

ActivityContext.java
 /**
     * Safely starts an activity.
     *
     * @param v View starting the activity.
     * @param intent Base intent being launched.
     * @param item Item associated with the view.
     * @return RunnableList for listening for animation finish if the activity was properly
     *         or started, {@code null} if the launch finished
     */
    default RunnableList startActivitySafely(
            View v, Intent intent, @Nullable ItemInfo item) {
        Preconditions.assertUIThread();
        Context context = (Context) this;
        if (isAppBlockedForSafeMode() && !PackageManagerHelper.isSystemApp(context, intent)) {
            Toast.makeText(context, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show();
            return null;
        }
        
		//此处开始创建打开桌面应用的一些属性和动作,很显然v不为null,此处调用的是getActivityLaunchOptions,开始引入一些动画。
        ActivityOptionsWrapper options = v != null ? getActivityLaunchOptions(v, item)
                : makeDefaultActivityOptions(item != null && item.animationType == DEFAULT_NO_ICON
                        ? SPLASH_SCREEN_STYLE_SOLID_COLOR : -1 /* SPLASH_SCREEN_STYLE_UNDEFINED */);
        UserHandle user = item == null ? null : item.user;
        Bundle optsBundle = options.toBundle();
        // Prepare intent
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        if (v != null) {
            intent.setSourceBounds(Utilities.getViewBounds(v));
        }
        try {
            boolean isShortcut = (item instanceof WorkspaceItemInfo)
                    && (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT
                    || item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT)
                    && !((WorkspaceItemInfo) item).isPromise();
            if (isShortcut) {
                // Shortcuts need some special checks due to legacy reasons.
                startShortcutIntentSafely(intent, optsBundle, item);
            } else if (user == null || user.equals(Process.myUserHandle())) {
                // Could be launching some bookkeeping activity
                context.startActivity(intent, optsBundle);
            } else {
                context.getSystemService(LauncherApps.class).startMainActivity(
                        intent.getComponent(), user, intent.getSourceBounds(), optsBundle);
            }
            if (item != null) {
                InstanceId instanceId = new InstanceIdSequence().newInstanceId();
                logAppLaunch(getStatsLogManager(), item, instanceId);
            }
            return options.onEndCallback;
        } catch (NullPointerException | ActivityNotFoundException | SecurityException e) {
            Toast.makeText(context, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
            Log.e(TAG, "Unable to launch. tag=" + item + " intent=" + intent, e);
        }
        return null;
    }

发现getActivityLaunchOptions继续往下看

ActivityContext.java
    /**
     * Returns launch options for an Activity.
     *
     * @param v View initiating a launch.
     * @param item Item associated with the view.
     */
    default ActivityOptionsWrapper getActivityLaunchOptions(View v, @Nullable ItemInfo item) {
        int left = 0, top = 0;
        int width = v.getMeasuredWidth(), height = v.getMeasuredHeight();
        if (v instanceof BubbleTextView) {
            // Launch from center of icon, not entire view
            Drawable icon = ((BubbleTextView) v).getIcon();
            if (icon != null) {
                Rect bounds = icon.getBounds();
                left = (width - bounds.width()) / 2;
                top = v.getPaddingTop();
                width = bounds.width();
                height = bounds.height();
            }
        }
		
		//发现此处开始制作剪辑显示动画了
        ActivityOptions options =
                ActivityOptions.makeClipRevealAnimation(v, left, top, width, height);

        options.setLaunchDisplayId(
                (v != null && v.getDisplay() != null) ? v.getDisplay().getDisplayId()
                        : Display.DEFAULT_DISPLAY);
        RunnableList callback = new RunnableList();
        return new ActivityOptionsWrapper(options, callback);
    }

看到ActivityOptions.makeClipRevealAnimation开始制作动画了,继续看makeClipRevealAnimation这个方法

	ActivityOptions.java
    /**
     * Create an ActivityOptions specifying an animation where the new
     * activity is revealed from a small originating area of the screen to
     * its final full representation.
     *
     * @param source The View that the new activity is animating from.  This
     * defines the coordinate space for <var>startX</var> and <var>startY</var>.
     * @param startX The x starting location of the new activity, relative to <var>source</var>.
     * @param startY The y starting location of the activity, relative to <var>source</var>.
     * @param width The initial width of the new activity.
     * @param height The initial height of the new activity.
     * @return Returns a new ActivityOptions object that you can use to
     * supply these options as the options Bundle when starting an activity.
     */
    public static ActivityOptions makeClipRevealAnimation(View source,
            int startX, int startY, int width, int height) {
        ActivityOptions opts = new ActivityOptions();
        opts.mAnimationType = ANIM_CLIP_REVEAL;
        int[] pts = new int[2];
        source.getLocationOnScreen(pts);
        opts.mStartX = pts[0] + startX;
        opts.mStartY = pts[1] + startY;
        opts.mWidth = width;
        opts.mHeight = height;
        return opts;
    }

可以看到设置动画的入场图标和位置以及大小。所以第一次打开动画会有个应用图标居中白底或者黑底的效果。
但是以上都是默认Launcher打开应用动画方式,现在由于QuickstepLauncher 继承Launcher,而Launcher继承ActivityContext.java,所以 getActivityLaunchOptions这个方法不是默认的,是由QuickstepLauncher实现的

所以继续往下看

QuickstepLauncher.java

    @Override
    public ActivityOptionsWrapper getActivityLaunchOptions(View v, @Nullable ItemInfo item) {
        ActivityOptionsWrapper activityOptions =
                mAppTransitionManager.hasControlRemoteAppTransitionPermission()
                        ? mAppTransitionManager.getActivityLaunchOptions(v)
                        : super.getActivityLaunchOptions(v, item);
        if (mLastTouchUpTime > 0) {
            activityOptions.options.setSourceInfo(ActivityOptions.SourceInfo.TYPE_LAUNCHER,
                    mLastTouchUpTime);
        }
        if (item != null && (item.animationType == DEFAULT_NO_ICON
                || item.animationType == VIEW_BACKGROUND)) {
            activityOptions.options.setSplashScreenStyle(
                    SplashScreen.SPLASH_SCREEN_STYLE_SOLID_COLOR);
        } else {
            activityOptions.options.setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_ICON);
        }
        activityOptions.options.setLaunchDisplayId(
                (v != null && v.getDisplay() != null) ? v.getDisplay().getDisplayId()
                        : Display.DEFAULT_DISPLAY);
        addLaunchCookie(item, activityOptions.options);
        return activityOptions;
    }

此处hasControlRemoteAppTransitionPermission是用来判断Launcher是否有远程过度动画控制权限,当然默认launcher都是有的,所以这里继续看mAppTransitionManager.getActivityLaunchOptions(v)这个方法。

QuickstepTransitionManager.java

300       public ActivityOptionsWrapper getActivityLaunchOptions(View v) {
301          boolean fromRecents = isLaunchingFromRecents(v, null /* targets */);
302          RunnableList onEndCallback = new RunnableList();
303  
304          // Handle the case where an already visible task is launched which results in no transition
305          TaskRestartedDuringLaunchListener restartedListener =
306                  new TaskRestartedDuringLaunchListener();
307          restartedListener.register(onEndCallback::executeAllAndDestroy);
308          onEndCallback.add(restartedListener::unregister);
309  
310          mAppLaunchRunner = new AppLaunchAnimationRunner(v, onEndCallback);
311          ItemInfo tag = (ItemInfo) v.getTag();
312          if (tag != null && tag.shouldUseBackgroundAnimation()) {
313              ContainerAnimationRunner containerAnimationRunner =
314                      ContainerAnimationRunner.from(v, mStartingWindowListener, onEndCallback);
315              if (containerAnimationRunner != null) {
316                  mAppLaunchRunner = containerAnimationRunner;
317              }
318          }
			//本地生成的一个动画执行的runner。
319          RemoteAnimationRunnerCompat runner = new LauncherAnimationRunner(
320                  mHandler, mAppLaunchRunner, true /* startAtFrontOfQueue */);
321  
322          // Note that this duration is a guess as we do not know if the animation will be a
323          // recents launch or not for sure until we know the opening app targets.
324          long duration = fromRecents
325                  ? RECENTS_LAUNCH_DURATION
326                  : APP_LAUNCH_DURATION;
327  
328          long statusBarTransitionDelay = duration - STATUS_BAR_TRANSITION_DURATION
329                  - STATUS_BAR_TRANSITION_PRE_DELAY;
			 //向远端注册这个runner,过度动画开始时会回掉runner。
330          ActivityOptions options = ActivityOptions.makeRemoteAnimation(
331                  new RemoteAnimationAdapter(runner, duration, statusBarTransitionDelay),
332                  new RemoteTransition(runner.toRemoteTransition(),
333                          mLauncher.getIApplicationThread(), "QuickstepLaunch"));
334          return new ActivityOptionsWrapper(options, onEndCallback);
335      }

这里向远端注册一个过度动画runner,过度动画开始执行后,就会调用LauncherAnimationRunner里的onAnimationStart方法,而这里就是应用执行动画和播放位置,后续不在详解,可以自己往下看。

应用关闭动画

这后续补充有点忙嘿嘿

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

何愁无路QAQ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值