Android 13 WMS-动画流程

news2024/10/22 16:19:57
 

动画的类型如下

    @IntDef(flag = true, prefix = { "ANIMATION_TYPE_" }, value = {
            ANIMATION_TYPE_NONE,
            ANIMATION_TYPE_APP_TRANSITION,
            ANIMATION_TYPE_SCREEN_ROTATION,
            ANIMATION_TYPE_DIMMER,
            ANIMATION_TYPE_RECENTS,
            ANIMATION_TYPE_WINDOW_ANIMATION,
            ANIMATION_TYPE_INSETS_CONTROL,
            ANIMATION_TYPE_TOKEN_TRANSFORM,
            ANIMATION_TYPE_STARTING_REVEAL
    })

ANIMATION_TYPE_APP_TRANSITION动画

当点击一个二级页面时, 会有如下调用栈

动画的type = ANIMATION_TYPE_APP_TRANSITION

03-05 14:55:37.752  3059  3163 D jinyanmeianimation: SurfaceAnimator startAnimation mAnimation:com.android.server.wm.LocalAnimationAdapter@71444ae
03-05 14:55:37.752  3059  3163 D jinyanmeianimation: SurfaceAnimator startAnimation mLeash:Surface(name=Surface(name=ActivityRecord{8b5f0f2 u0 com.android.settings/.MainSettings} t76})/@0xaa929f3 - animation-leash of app_transition)/@0x9c116b0
03-05 14:55:37.752  3059  3163 D jinyanmeianimation: SurfaceAnimator startAnimation t:android.view.SurfaceControl$Transaction@8d14824
03-05 14:55:37.752  3059  3163 D jinyanmeianimation: SurfaceAnimator startAnimation type:1
03-05 14:55:37.752  3059  3163 D jinyanmeianimation: SurfaceAnimator startAnimation mInnerAnimationFinishedCallback:com.android.server.wm.SurfaceAnimator$$ExternalSyntheticLambda0@d22724f
03-05 14:55:37.752  3059  3163 D jinyanmeianimation: java.lang.RuntimeException: jinyanmeianimation
03-05 14:55:37.752  3059  3163 D jinyanmeianimation: 	at com.android.server.wm.SurfaceAnimator.startAnimation(SurfaceAnimator.java:200)
03-05 14:55:37.752  3059  3163 D jinyanmeianimation: 	at com.android.server.wm.WindowContainer.startAnimation(WindowContainer.java:2912)
03-05 14:55:37.752  3059  3163 D jinyanmeianimation: 	at com.android.server.wm.WindowContainer$AnimationRunnerBuilder.lambda$build$4$com-android-server-wm-WindowContainer$AnimationRunnerBuilder(WindowContainer.java:4257)
03-05 14:55:37.752  3059  3163 D jinyanmeianimation: 	at com.android.server.wm.WindowContainer$AnimationRunnerBuilder$$ExternalSyntheticLambda4.startAnimation(Unknown Source:7)
03-05 14:55:37.752  3059  3163 D jinyanmeianimation: 	at com.android.server.wm.WindowContainer.applyAnimationUnchecked(WindowContainer.java:3384)
03-05 14:55:37.752  3059  3163 D jinyanmeianimation: 	at com.android.server.wm.WindowContainer.applyAnimation(WindowContainer.java:3072)
03-05 14:55:37.752  3059  3163 D jinyanmeianimation: 	at com.android.server.wm.ActivityRecord.applyAnimation(ActivityRecord.java:6074)
03-05 14:55:37.752  3059  3163 D jinyanmeianimation: 	at com.android.server.wm.AppTransitionController.applyAnimations(AppTransitionController.java:876)
03-05 14:55:37.752  3059  3163 D jinyanmeianimation: 	at com.android.server.wm.AppTransitionController.applyAnimations(AppTransitionController.java:1083)
03-05 14:55:37.752  3059  3163 D jinyanmeianimation: 	at com.android.server.wm.AppTransitionController.handleAppTransitionReady(AppTransitionController.java:293)
03-05 14:55:37.752  3059  3163 D jinyanmeianimation: 	at com.android.server.wm.RootWindowContainer.checkAppTransitionReady(RootWindowContainer.java:1070)
03-05 14:55:37.752  3059  3163 D jinyanmeianimation: 	at com.android.server.wm.RootWindowContainer.performSurfacePlacementNoTrace(RootWindowContainer.java:937)
03-05 14:55:37.752  3059  3163 D jinyanmeianimation: 	at com.android.server.wm.RootWindowContainer.performSurfacePlacement(RootWindowContainer.java:877)
03-05 14:55:37.752  3059  3163 D jinyanmeianimation: 	at com.android.server.wm.WindowSurfacePlacer.performSurfacePlacementLoop(WindowSurfacePlacer.java:199)
03-05 14:55:37.752  3059  3163 D jinyanmeianimation: 	at com.android.server.wm.WindowSurfacePlacer.performSurfacePlacement(WindowSurfacePlacer.java:148)
03-05 14:55:37.752  3059  3163 D jinyanmeianimation: 	at com.android.server.wm.WindowSurfacePlacer.performSurfacePlacement(WindowSurfacePlacer.java:137)
03-05 14:55:37.752  3059  3163 D jinyanmeianimation: 	at com.android.server.wm.WindowSurfacePlacer$Traverser.run(WindowSurfacePlacer.java:79)
03-05 14:55:37.752  3059  3163 D jinyanmeianimation: 	at android.os.Handler.handleCallback(Handler.java:942)
03-05 14:55:37.752  3059  3163 D jinyanmeianimation: 	at android.os.Handler.dispatchMessage(Handler.java:99)
03-05 14:55:37.752  3059  3163 D jinyanmeianimation: 	at android.os.Looper.loopOnce(Looper.java:211)
03-05 14:55:37.752  3059  3163 D jinyanmeianimation: 	at android.os.Looper.loop(Looper.java:300)
03-05 14:55:37.752  3059  3163 D jinyanmeianimation: 	at android.os.HandlerThread.run(HandlerThread.java:67)

每次循环都要检查是否开始一个动画

void handleAppTransitionReady() {
183          mTempTransitionReasons.clear();
//        //检查app transition是否已经准备好

184          if (!transitionGoodToGo(mDisplayContent.mOpeningApps, mTempTransitionReasons)
185                  || !transitionGoodToGo(mDisplayContent.mChangingContainers, mTempTransitionReasons)
186                  || !transitionGoodToGoForTaskFragments()) {
187              return;
188          }
189          Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "AppTransitionReady");
190  
191          ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "**** GOOD TO GO");
192          // TODO(b/205335975): Remove window which stuck in animatingExit status. Find actual cause.
193          mDisplayContent.forAllWindows(WindowState::cleanupAnimatingExitWindow,
194                  true /* traverseTopToBottom */);
195          // TODO(new-app-transition): Remove code using appTransition.getAppTransition()
196          final AppTransition appTransition = mDisplayContent.mAppTransition;
197  
198          mDisplayContent.mNoAnimationNotifyOnTransitionFinished.clear();
199  
200          appTransition.removeAppTransitionTimeoutCallbacks();
201  
202          mDisplayContent.mWallpaperMayChange = false;
203  
204          int appCount = mDisplayContent.mOpeningApps.size();
205          for (int i = 0; i < appCount; ++i) {
206              // Clearing the mAnimatingExit flag before entering animation. It's set to true if app
207              // window is removed, or window relayout to invisible. This also affects window
208              // visibility. We need to clear it *before* maybeUpdateTransitToWallpaper() as the
209              // transition selection depends on wallpaper target visibility.
210              mDisplayContent.mOpeningApps.valueAtUnchecked(i).clearAnimatingFlags();
211          }
212          appCount = mDisplayContent.mChangingContainers.size();
213          for (int i = 0; i < appCount; ++i) {
214              // Clearing for same reason as above.
215              final ActivityRecord activity = getAppFromContainer(
216                      mDisplayContent.mChangingContainers.valueAtUnchecked(i));
217              if (activity != null) {
218                  activity.clearAnimatingFlags();
219              }
220          }
221  
222          // Adjust wallpaper before we pull the lower/upper target, since pending changes
223          // (like the clearAnimatingFlags() above) might affect wallpaper target result.
224          // Or, the opening app window should be a wallpaper target.
225          mWallpaperControllerLocked.adjustWallpaperWindowsForAppTransitionIfNeeded(
226                  mDisplayContent.mOpeningApps);
227  
228          // Remove launcher from app transition animation while recents is running. Recents animation
229          // is managed outside of app transition framework, so we just need to commit visibility.
230          final boolean excludeLauncherFromAnimation =
231                  mDisplayContent.mOpeningApps.stream().anyMatch(
232                          (app) -> app.isAnimating(PARENTS, ANIMATION_TYPE_RECENTS))
233                  || mDisplayContent.mClosingApps.stream().anyMatch(
234                          (app) -> app.isAnimating(PARENTS, ANIMATION_TYPE_RECENTS));
235          final ArraySet<ActivityRecord> openingAppsForAnimation = getAppsForAnimation(
236                  mDisplayContent.mOpeningApps, excludeLauncherFromAnimation);
237          final ArraySet<ActivityRecord> closingAppsForAnimation = getAppsForAnimation(
238                  mDisplayContent.mClosingApps, excludeLauncherFromAnimation);
239  
240          @TransitionOldType final int transit = getTransitCompatType(
241                  mDisplayContent.mAppTransition, openingAppsForAnimation, closingAppsForAnimation,
242                  mDisplayContent.mChangingContainers,
243                  mWallpaperControllerLocked.getWallpaperTarget(), getOldWallpaper(),
244                  mDisplayContent.mSkipAppTransitionAnimation);
245          mDisplayContent.mSkipAppTransitionAnimation = false;
246  
247          ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
248                  "handleAppTransitionReady: displayId=%d appTransition={%s}"
249                  + " excludeLauncherFromAnimation=%b openingApps=[%s] closingApps=[%s] transit=%s",
250                  mDisplayContent.mDisplayId, appTransition.toString(), excludeLauncherFromAnimation,
251                  mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
252                  AppTransition.appTransitionOldToString(transit));
253  
254          // Find the layout params of the top-most application window in the tokens, which is
255          // what will control the animation theme. If all closing windows are obscured, then there is
256          // no need to do an animation. This is the case, for example, when this transition is being
257          // done behind a dream window.
258          final ArraySet<Integer> activityTypes = collectActivityTypes(openingAppsForAnimation,
259                  closingAppsForAnimation, mDisplayContent.mChangingContainers);
//首先会获取当前需要opening和closing的app window列表(ActivityRecord类型)
260          final ActivityRecord animLpActivity = findAnimLayoutParamsToken(transit, activityTypes,
261                  openingAppsForAnimation, closingAppsForAnimation,
262                  mDisplayContent.mChangingContainers);
263          final ActivityRecord topOpeningApp =
264                  getTopApp(openingAppsForAnimation, false /* ignoreHidden */);
265          final ActivityRecord topClosingApp =
266                  getTopApp(closingAppsForAnimation, false /* ignoreHidden */);
267          final ActivityRecord topChangingApp =
268                  getTopApp(mDisplayContent.mChangingContainers, false /* ignoreHidden */);
269          final WindowManager.LayoutParams animLp = getAnimLp(animLpActivity);
270  
271          // Check if there is any override
272          if (!overrideWithTaskFragmentRemoteAnimation(transit, activityTypes)) {
273              // Unfreeze the windows that were previously frozen for TaskFragment animation.
274              unfreezeEmbeddedChangingWindows();
275              overrideWithRemoteAnimationIfSet(animLpActivity, transit, activityTypes);
276          }
277  
278          final boolean voiceInteraction = containsVoiceInteraction(closingAppsForAnimation)
279                  || containsVoiceInteraction(openingAppsForAnimation);
280  
281          final int layoutRedo;
282          mService.mSurfaceAnimationRunner.deferStartingAnimations();
283          try {
//然后在applyAnimations方法里面对window列表进行遍历WindowContainer的动画applyAnimation方法的调用
284              applyAnimations(openingAppsForAnimation, closingAppsForAnimation, transit, animLp,
285                      voiceInteraction);
286              handleClosingApps();
287              handleOpeningApps();
288              handleChangingApps(transit);
289  
290              appTransition.setLastAppTransition(transit, topOpeningApp,
291                      topClosingApp, topChangingApp);
292  
293              final int flags = appTransition.getTransitFlags();
294              layoutRedo = appTransition.goodToGo(transit, topOpeningApp);
295              handleNonAppWindowsInTransition(transit, flags);
296              appTransition.postAnimationCallback();
297              appTransition.clear();
298          } finally {
299              mService.mSurfaceAnimationRunner.continueStartingAnimations();
300          }
301  
302          mService.mTaskSnapshotController.onTransitionStarting(mDisplayContent);
303  
304          mDisplayContent.mOpeningApps.clear();
305          mDisplayContent.mClosingApps.clear();
306          mDisplayContent.mChangingContainers.clear();
307          mDisplayContent.mUnknownAppVisibilityController.clear();
308  
309          // This has changed the visibility of windows, so perform
310          // a new layout to get them all up-to-date.
311          mDisplayContent.setLayoutNeeded();
312  
313          mDisplayContent.computeImeTarget(true /* updateImeTarget */);
314  
315          mService.mAtmService.mTaskSupervisor.getActivityMetricsLogger().notifyTransitionStarting(
316                  mTempTransitionReasons);
317  
318          Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
319  
320          mDisplayContent.pendingLayoutChanges |=
321                  layoutRedo | FINISH_LAYOUT_REDO_LAYOUT | FINISH_LAYOUT_REDO_CONFIG;
322      }

applyAnimationUnchecked包含两部分, 

第一先通过getAnimationAdapter加载合适的动画

如果是普通的窗口动画,比如app内部activity的切换,当前的场景是设置主菜单跳转子菜单,根据当前场景获取到具体的transit,transit=TRANSIT_OLD_ACTIVITY_OPEN,然后再结合enter为true或者false,可以最终可以找到设置主菜单的动画xml资源是activity_open_exit.xml,设置子菜单的动画xml资源是activity_open_enter.xml,在获取到具体xml资源名字后,通过AnimationUtils.loadAnimation方法把xml资源转成Animation对象。
之后就会创建一个WindowAnimationSpec对象,并把Animation对象作为构造方法的第一个参数传给了WindowAnimationSpec

第二开始动画 

protected void applyAnimationUnchecked(WindowManager.LayoutParams lp, boolean enter,
2994              @TransitionOldType int transit, boolean isVoiceInteraction,
2995              @Nullable ArrayList<WindowContainer> sources) {
2996          final Task task = asTask();
2997          if (task != null && !enter && !task.isActivityTypeHomeOrRecents()) {
2998              final InsetsControlTarget imeTarget = mDisplayContent.getImeTarget(IME_TARGET_LAYERING);
2999              final boolean isImeLayeringTarget = imeTarget != null && imeTarget.getWindow() != null
3000                      && imeTarget.getWindow().getTask() == task;
3001              // Attach and show the IME screenshot when the task is the IME target and performing
3002              // task closing transition to the next task.
3003              if (isImeLayeringTarget && AppTransition.isTaskCloseTransitOld(transit)) {
3004                  mDisplayContent.showImeScreenshot();
3005              }
3006          }
3007          final Pair<AnimationAdapter, AnimationAdapter> adapters = getAnimationAdapter(lp,
3008                  transit, enter, isVoiceInteraction);
3009          AnimationAdapter adapter = adapters.first;
3010          AnimationAdapter thumbnailAdapter = adapters.second;
3011          if (adapter != null) {
3012              if (sources != null) {
3013                  mSurfaceAnimationSources.addAll(sources);
3014              }
3015  
3016              AnimationRunnerBuilder animationRunnerBuilder = new AnimationRunnerBuilder();
3017  
3018              if (isTaskTransitOld(transit)) {
3019                  animationRunnerBuilder.setTaskBackgroundColor(getTaskAnimationBackgroundColor());
3020                  // TODO: Remove when we migrate to shell (b/202383002)
3021                  if (mWmService.mTaskTransitionSpec != null) {
3022                      animationRunnerBuilder.hideInsetSourceViewOverflows(
3023                              mWmService.mTaskTransitionSpec.animationBoundInsets);
3024                  }
3025              }
3026  
3027              final ActivityRecord activityRecord = asActivityRecord();
3028              if (activityRecord != null && isActivityTransitOld(transit)
3029                      && adapter.getShowBackground()) {
3030                  final @ColorInt int backgroundColorForTransition;
3031                  if (adapter.getBackgroundColor() != 0) {
3032                      // If available use the background color provided through getBackgroundColor
3033                      // which if set originates from a call to overridePendingAppTransition.
3034                      backgroundColorForTransition = adapter.getBackgroundColor();
3035                  } else {
3036                      // Otherwise default to the window's background color if provided through
3037                      // the theme as the background color for the animation - the top most window
3038                      // with a valid background color and showBackground set takes precedence.
3039                      final Task arTask = activityRecord.getTask();
3040                      backgroundColorForTransition = ColorUtils.setAlphaComponent(
3041                              arTask.getTaskDescription().getBackgroundColor(), 255);
3042                  }
3043                  animationRunnerBuilder.setTaskBackgroundColor(backgroundColorForTransition);
3044              }
3045  
3046              animationRunnerBuilder.build()
3047                      .startAnimation(getPendingTransaction(), adapter, !isVisible(),
3048                              ANIMATION_TYPE_APP_TRANSITION, thumbnailAdapter);
3049  
3050              if (adapter.getShowWallpaper()) {
3051                  getDisplayContent().pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
3052              }
3053          }
3054      }
3055  

 计算transition的类型

在getTransitCompatType中会对动画做转换,将动画转化为old类型的动画

 @TransitionOldType static int getTransitCompatType(AppTransition appTransition,
            ArraySet<ActivityRecord> openingApps, ArraySet<ActivityRecord> closingApps,
            ArraySet<WindowContainer> changingContainers, @Nullable WindowState wallpaperTarget,
            @Nullable WindowState oldWallpaper, boolean skipAppTransitionAnimation) {
        Slog.d("jinyanmeianimation","getTransitCompatType appTransition :" + appTransition );

        Slog.d("jinyanmeianimation","getTransitCompatType appTransition :" + appTransition , new RuntimeException("jinyanmeianimation"));


        // Determine if closing and opening app token sets are wallpaper targets, in which case
        // special animations are needed.
        final boolean openingAppHasWallpaper = canBeWallpaperTarget(openingApps)
                && wallpaperTarget != null;
        final boolean closingAppHasWallpaper = canBeWallpaperTarget(closingApps)
                && wallpaperTarget != null;

        // Keyguard transit has highest priority.
        switch (appTransition.getKeyguardTransition()) {
            case TRANSIT_KEYGUARD_GOING_AWAY:
                return openingAppHasWallpaper ? TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER
                        : TRANSIT_OLD_KEYGUARD_GOING_AWAY;
            case TRANSIT_KEYGUARD_OCCLUDE:
                // When there is a closing app, the keyguard has already been occluded by an
                // activity, and another activity has started on top of that activity, so normal
                // app transition animation should be used.
                return closingApps.isEmpty() ? TRANSIT_OLD_KEYGUARD_OCCLUDE
                        : TRANSIT_OLD_ACTIVITY_OPEN;
            case TRANSIT_KEYGUARD_UNOCCLUDE:
                return TRANSIT_OLD_KEYGUARD_UNOCCLUDE;
        }

        // This is not keyguard transition and one of the app has request to skip app transition.
        // MIUI MOD: START
        // For Stage Split Screen: Drag to enter split-screen feature.
        // if (skipAppTransitionAnimation) {
        if (skipAppTransitionAnimation ||
                ActivityTaskManagerServiceStub.get().removeSplitTaskShotIfNeed()) {
        // END
            return WindowManager.TRANSIT_OLD_UNSET;
        }
        @TransitionFlags final int flags = appTransition.getTransitFlags();
        @TransitionType final int firstTransit = appTransition.getFirstAppTransition();

        // Special transitions
        // TODO(new-app-transitions): Revisit if those can be rewritten by using flags.
        if (appTransition.containsTransitRequest(TRANSIT_CHANGE) && !changingContainers.isEmpty()) {
            // MIUI ADD: START Activity Embedding Resizing
            if (MiuiEmbeddingWindowServiceStub.get()
                    .skipAppTransitionAnimationForEmbeddingDivider()) {
                return WindowManager.TRANSIT_OLD_UNSET;
            }
            // END
            @TransitContainerType int changingType =
                    getTransitContainerType(changingContainers.valueAt(0));
            switch (changingType) {
                case TYPE_TASK:
                    return TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE;
                case TYPE_TASK_FRAGMENT:
                    return TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
                default:
                    throw new IllegalStateException(
                            "TRANSIT_CHANGE with unrecognized changing type=" + changingType);
            }
        }
        if ((flags & TRANSIT_FLAG_APP_CRASHED) != 0) {
            return TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE;
        }
        if (firstTransit == TRANSIT_NONE) {
            return TRANSIT_OLD_NONE;
        }

        /*
         * There are cases where we open/close a new task/activity, but in reality only a
         * translucent activity on top of existing activities is opening/closing. For that one, we
         * have a different animation because non of the task/activity animations actually work well
         * with translucent apps.
         */
        if (isNormalTransit(firstTransit)) {
            boolean allOpeningVisible = true;
            boolean allTranslucentOpeningApps = !openingApps.isEmpty();
            for (int i = openingApps.size() - 1; i >= 0; i--) {
                final ActivityRecord activity = openingApps.valueAt(i);
                if (!activity.isVisible()) {
                    allOpeningVisible = false;
                    if (activity.fillsParent()) {
                        allTranslucentOpeningApps = false;
                    }
                }
            }
            boolean allTranslucentClosingApps = !closingApps.isEmpty();
            for (int i = closingApps.size() - 1; i >= 0; i--) {
                if (closingApps.valueAt(i).fillsParent()) {
                    allTranslucentClosingApps = false;
                    break;
                }
            }

            if (allTranslucentClosingApps && allOpeningVisible) {
                return TRANSIT_OLD_TRANSLUCENT_ACTIVITY_CLOSE;
            }
            if (allTranslucentOpeningApps && closingApps.isEmpty()) {
                return TRANSIT_OLD_TRANSLUCENT_ACTIVITY_OPEN;
            }
        }

        final ActivityRecord topOpeningApp = getTopApp(openingApps,
                false /* ignoreHidden */);
        final ActivityRecord topClosingApp = getTopApp(closingApps,
                true /* ignoreHidden */);

        if (closingAppHasWallpaper && openingAppHasWallpaper) {
            ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Wallpaper animation!");
            switch (firstTransit) {
                case TRANSIT_OPEN:
                case TRANSIT_TO_FRONT:
                    return TRANSIT_OLD_WALLPAPER_INTRA_OPEN;
                case TRANSIT_CLOSE:
                case TRANSIT_TO_BACK:
                    return TRANSIT_OLD_WALLPAPER_INTRA_CLOSE;
            }
        } else if (oldWallpaper != null && !openingApps.isEmpty()
                && !openingApps.contains(oldWallpaper.mActivityRecord)
                && closingApps.contains(oldWallpaper.mActivityRecord)
                && topClosingApp == oldWallpaper.mActivityRecord) {
            // We are transitioning from an activity with a wallpaper to one without.
            return TRANSIT_OLD_WALLPAPER_CLOSE;
        } else if (wallpaperTarget != null && wallpaperTarget.isVisible()
                && openingApps.contains(wallpaperTarget.mActivityRecord)
                && topOpeningApp == wallpaperTarget.mActivityRecord
                /* && transit != TRANSIT_TRANSLUCENT_ACTIVITY_CLOSE */) {
            // We are transitioning from an activity without
            // a wallpaper to now showing the wallpaper
            return TRANSIT_OLD_WALLPAPER_OPEN;
        }

        final ArraySet<WindowContainer> openingWcs = getAnimationTargets(
                openingApps, closingApps, true /* visible */);
        final ArraySet<WindowContainer> closingWcs = getAnimationTargets(
                openingApps, closingApps, false /* visible */);
        final WindowContainer<?> openingContainer = !openingWcs.isEmpty()
                ? openingWcs.valueAt(0) : null;
        final WindowContainer<?> closingContainer = !closingWcs.isEmpty()
                ? closingWcs.valueAt(0) : null;
        @TransitContainerType int openingType = getTransitContainerType(openingContainer);
        @TransitContainerType int closingType = getTransitContainerType(closingContainer);
        if (appTransition.containsTransitRequest(TRANSIT_TO_FRONT) && openingType == TYPE_TASK) {
            return TRANSIT_OLD_TASK_TO_FRONT;
        }
        if (appTransition.containsTransitRequest(TRANSIT_TO_BACK) && closingType == TYPE_TASK) {
            return TRANSIT_OLD_TASK_TO_BACK;
        }
        if (appTransition.containsTransitRequest(TRANSIT_OPEN)) {
            if (openingType == TYPE_TASK) {
                return (appTransition.getTransitFlags() & TRANSIT_FLAG_OPEN_BEHIND) != 0
                        ? TRANSIT_OLD_TASK_OPEN_BEHIND : TRANSIT_OLD_TASK_OPEN;
            }
            if (openingType == TYPE_ACTIVITY) {
                return TRANSIT_OLD_ACTIVITY_OPEN;
            }
            if (openingType == TYPE_TASK_FRAGMENT) {
                return TRANSIT_OLD_TASK_FRAGMENT_OPEN;
            }
        }
        if (appTransition.containsTransitRequest(TRANSIT_CLOSE)) {
            if (closingType == TYPE_TASK) {
                return TRANSIT_OLD_TASK_CLOSE;
            }
            if (closingType == TYPE_TASK_FRAGMENT) {
                return TRANSIT_OLD_TASK_FRAGMENT_CLOSE;
            }
            if (closingType == TYPE_ACTIVITY) {
                for (int i = closingApps.size() - 1; i >= 0; i--) {
                    if (closingApps.valueAt(i).visibleIgnoringKeyguard) {
                        return TRANSIT_OLD_ACTIVITY_CLOSE;
                    }
                }
                // Skip close activity transition since no closing app can be visible
                return WindowManager.TRANSIT_OLD_UNSET;
            }
        }
        if (appTransition.containsTransitRequest(TRANSIT_RELAUNCH)
                && !openingWcs.isEmpty() && !openingApps.isEmpty()) {
            return TRANSIT_OLD_ACTIVITY_RELAUNCH;
        }
        return TRANSIT_OLD_NONE;
    }

加载和开始动画

getAnimationAdapter加载动画

 

首先遍历所有的windowContainer 调用applyAnimation

    private void applyAnimations(ArraySet<WindowContainer> wcs, ArraySet<ActivityRecord> apps,
            @TransitionOldType int transit, boolean visible, LayoutParams animLp,
            boolean voiceInteraction) {
        final int wcsCount = wcs.size();
        for (int i = 0; i < wcsCount; i++) {
            final WindowContainer wc = wcs.valueAt(i);
            // If app transition animation target is promoted to higher level, SurfaceAnimator
            // triggers WC#onAnimationFinished only on the promoted target. So we need to take care
            // of triggering AR#onAnimationFinished on each ActivityRecord which is a part of the
            // app transition.
            final ArrayList<ActivityRecord> transitioningDescendants = new ArrayList<>();
            for (int j = 0; j < apps.size(); ++j) {
                final ActivityRecord app = apps.valueAt(j);
                if (app.isDescendantOf(wc)) {
                    transitioningDescendants.add(app);
                }
            }
            wc.applyAnimation(animLp, transit, visible, voiceInteraction, transitioningDescendants);
        }
    }

然后调用getAnimationAdapter 根据窗口层次结构中给定的窗口布局属性获取动画适配器

startAnimation 创建leash开始动画

 

SurfaceAnimation.java
166      void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
167              @AnimationType int type,
168              @Nullable OnAnimationFinishedCallback animationFinishedCallback,
169              @Nullable Runnable animationCancelledCallback,
170              @Nullable AnimationAdapter snapshotAnim, @Nullable SurfaceFreezer freezer) {
171          cancelAnimation(t, true /* restarting */, true /* forwardCancel */);
172          mAnimation = anim;
173          mAnimationType = type;
174          mSurfaceAnimationFinishedCallback = animationFinishedCallback;
175          mAnimationCancelledCallback = animationCancelledCallback;
176          final SurfaceControl surface = mAnimatable.getSurfaceControl();
177          if (surface == null) {
178              Slog.w(TAG, "Unable to start animation, surface is null or no children.");
179              cancelAnimation();
180              return;
181          }
182          mLeash = freezer != null ? freezer.takeLeashForAnimation() : null;
183          if (mLeash == null) {
184              mLeash = createAnimationLeash(mAnimatable, surface, t, type,
185                      mAnimatable.getSurfaceWidth(), mAnimatable.getSurfaceHeight(), 0 /* x */,
186                      0 /* y */, hidden, mService.mTransactionFactory);
187              mAnimatable.onAnimationLeashCreated(t, mLeash);
188          }
189          mAnimatable.onLeashAnimationStarting(t, mLeash);
190          if (mAnimationStartDelayed) {
191              ProtoLog.i(WM_DEBUG_ANIM, "Animation start delayed for %s", mAnimatable);
192              return;
193          }
194          mAnimation.startAnimation(mLeash, t, type, mInnerAnimationFinishedCallback);
195          if (ProtoLogImpl.isEnabled(WM_DEBUG_ANIM)) {
196              StringWriter sw = new StringWriter();
197              PrintWriter pw = new PrintWriter(sw);
198              mAnimation.dump(pw, "");
199              ProtoLog.d(WM_DEBUG_ANIM, "Animation start for %s, anim=%s", mAnimatable, sw);
200          }
201          if (snapshotAnim != null) {
202              mSnapshot = freezer.takeSnapshotForAnimation();
203              if (mSnapshot == null) {
204                  Slog.e(TAG, "No snapshot target to start animation on for " + mAnimatable);
205                  return;
206              }
207              mSnapshot.startAnimation(t, snapshotAnim, type);
208          }
209      }

可见创建一个SuefaceControl, 然后把 WindowContainer的surface挂在了leash上面, 然后对leash做动画

455      static SurfaceControl createAnimationLeash(Animatable animatable, SurfaceControl surface,
456              Transaction t, @AnimationType int type, int width, int height, int x, int y,
457              boolean hidden, Supplier<Transaction> transactionFactory) {
458          ProtoLog.i(WM_DEBUG_ANIM, "Reparenting to leash for %s", animatable);
459          final SurfaceControl.Builder builder = animatable.makeAnimationLeash()
460                  .setParent(animatable.getAnimationLeashParent())
461                  .setName(surface + " - animation-leash of " + animationTypeToString(type))
462                  // TODO(b/151665759) Defer reparent calls
463                  // We want the leash to be visible immediately because the transaction which shows
464                  // the leash may be deferred but the reparent will not. This will cause the leashed
465                  // surface to be invisible until the deferred transaction is applied. If this
466                  // doesn't work, you will can see the 2/3 button nav bar flicker during seamless
467                  // rotation.
468                  .setHidden(hidden)
469                  .setEffectLayer()
470                  .setCallsite("SurfaceAnimator.createAnimationLeash");
471          final SurfaceControl leash = builder.build();
472          t.setWindowCrop(leash, width, height);
473          t.setPosition(leash, x, y);
474          t.show(leash);
475          t.setAlpha(leash, hidden ? 0 : 1);
476  
477          t.reparent(surface, leash);
478          return leash;
479      }

 

SurfaceAnimationRunner.java

171      void startAnimation(AnimationSpec a, SurfaceControl animationLeash, Transaction t,
172              Runnable finishCallback) {
173          synchronized (mLock) {
174              final RunningAnimation runningAnim = new RunningAnimation(a, animationLeash,
175                      finishCallback);
176              boolean requiresEdgeExtension = requiresEdgeExtension(a);
177  
178              if (requiresEdgeExtension) {
179                  final ArrayList<SurfaceControl> extensionSurfaces = new ArrayList<>();
180                  synchronized (mEdgeExtensionLock) {
181                      mEdgeExtensions.put(animationLeash, extensionSurfaces);
182                  }
183  
184                  mPreProcessingAnimations.put(animationLeash, runningAnim);
185  
186                  // We must wait for t to be committed since otherwise the leash doesn't have the
187                  // windows we want to screenshot and extend as children.
188                  t.addTransactionCommittedListener(mEdgeExtensionExecutor, () -> {
189                      final WindowAnimationSpec animationSpec = a.asWindowAnimationSpec();
190  
191                      final Transaction edgeExtensionCreationTransaction = new Transaction();
192                      edgeExtendWindow(animationLeash,
193                              animationSpec.getRootTaskBounds(), animationSpec.getAnimation(),
194                              edgeExtensionCreationTransaction);
195  
196                      synchronized (mLock) {
197                          // only run if animation is not yet canceled by this point
198                          if (mPreProcessingAnimations.get(animationLeash) == runningAnim) {
199                              // In the case the animation is cancelled, edge extensions are removed
200                              // onAnimationLeashLost which is called before onAnimationCancelled.
201                              // So we need to check if the edge extensions have already been removed
202                              // or not, and if so we don't want to apply the transaction.
203                              synchronized (mEdgeExtensionLock) {
204                                  if (!mEdgeExtensions.isEmpty()) {
205                                      edgeExtensionCreationTransaction.apply();
206                                  }
207                              }
208  
209                              mPreProcessingAnimations.remove(animationLeash);
210                              mPendingAnimations.put(animationLeash, runningAnim);
211                              if (!mAnimationStartDeferred && mPreProcessingAnimations.isEmpty()) {
212                                  mChoreographer.postFrameCallback(this::startAnimations);
213                              }
214                          }
215                      }
216                  });
217              }
218  
219              if (!requiresEdgeExtension) {
220                  mPendingAnimations.put(animationLeash, runningAnim);
221                  if (!mAnimationStartDeferred && mPreProcessingAnimations.isEmpty()) {
222                      mChoreographer.postFrameCallback(this::startAnimations);
223                  }
224              }
225  
226              // Some animations (e.g. move animations) require the initial transform to be
227              // applied immediately.
228              applyTransformation(runningAnim, t, 0 /* currentPlayTime */);
229          }
230      }

发送消息

scheduleFrameLocked:862, Choreographer (android.view)
postCallbackDelayedInternal:605, Choreographer (android.view)
postFrameCallbackDelayed:702, Choreographer (android.view)
postFrameCallback:682, Choreographer (android.view)
continueStartingAnimations:173, SurfaceAnimationRunner (com.android.server.wm)
handleAppTransitionReady:312, AppTransitionController (com.android.server.wm)
checkAppTransitionReady:1070, RootWindowContainer (com.android.server.wm)
performSurfacePlacementNoTrace:937, RootWindowContainer (com.android.server.wm)
performSurfacePlacement:877, RootWindowContainer (com.android.server.wm)
performSurfacePlacementLoop:199, WindowSurfacePlacer (com.android.server.wm)
performSurfacePlacement:148, WindowSurfacePlacer (com.android.server.wm)
performSurfacePlacement:137, WindowSurfacePlacer (com.android.server.wm)
handleAppTransitionTimeout:1814, AppTransition (com.android.server.wm)
lambda$new$0$com-android-server-wm-AppTransition:243, AppTransition (com.android.server.wm)
run:-1, AppTransition$$ExternalSyntheticLambda3 (com.android.server.wm)
handleCallback:942, Handler (android.os)
dispatchMessage:99, Handler (android.os)
loopOnce:211, Looper (android.os)
loop:300, Looper (android.os)
run:67, HandlerThread (android.os)
run:46, ServiceThread (com.android.server)

为动画添加listener

SurfaceAnimationRunner.java
 private void startAnimationLocked(RunningAnimation a) {
270          final ValueAnimator anim = mAnimatorFactory.makeAnimator();
271  
272          // Animation length is already expected to be scaled.
273          anim.overrideDurationScale(1.0f);
274          anim.setDuration(a.mAnimSpec.getDuration());
275          anim.addUpdateListener(animation -> {
276              synchronized (mCancelLock) {
277                  if (!a.mCancelled) {
278                      final long duration = anim.getDuration();
279                      long currentPlayTime = anim.getCurrentPlayTime();
280                      if (currentPlayTime > duration) {
281                          currentPlayTime = duration;
282                      }
283                      applyTransformation(a, mFrameTransaction, currentPlayTime);
284                  }
285              }
286  
287              // Transaction will be applied in the commit phase.
288              scheduleApplyTransaction();
289          });
290  
291          anim.addListener(new AnimatorListenerAdapter() {
292              @Override
293              public void onAnimationStart(Animator animation) {
294                  synchronized (mCancelLock) {
295                      if (!a.mCancelled) {
296                          // TODO: change this back to use show instead of alpha when b/138459974 is
297                          // fixed.
298                          mFrameTransaction.setAlpha(a.mLeash, 1);
299                      }
300                  }
301              }
302  
303              @Override
304              public void onAnimationEnd(Animator animation) {
305                  synchronized (mLock) {
306                      mRunningAnimations.remove(a.mLeash);
307                      synchronized (mCancelLock) {
308                          if (!a.mCancelled) {
309  
310                              // Post on other thread that we can push final state without jank.
311                              mAnimationThreadHandler.post(a.mFinishCallback);
312                          }
313                      }
314                  }
315              }
316          });
317          a.mAnim = anim;
318          mRunningAnimations.put(a.mLeash, a);
319  
320          anim.start();
321          if (a.mAnimSpec.canSkipFirstFrame()) {
322              // If we can skip the first frame, we start one frame later.
323              anim.setCurrentPlayTime(mChoreographer.getFrameIntervalNanos() / NANOS_PER_MS);
324          }
325  
326          // Immediately start the animation by manually applying an animation frame. Otherwise, the
327          // start time would only be set in the next frame, leading to a delay.
328          anim.doAnimationFrame(mChoreographer.getFrameTime());
329      }
330  

 

doFrame时更新动画位置

放vsync到来后, 执行onAnimationUpdate

    private void applyTransformation(RunningAnimation a, Transaction t, long currentPlayTime) {
        a.mAnimSpec.apply(t, a.mLeash, currentPlayTime);
    }

再来看下apply方法的具体实现,通过之前以具体xml资源创建的mAnimation对象,根据当前时间片currentPlayTime获取到当前的tmp.transformation,对leash对象实现了Matrix(大小,位置),Alpha,Crop等transformation变化,再通过Transaction 交给surfaceflinger显示,从而实现了动画当前时间片的显示效果。对比旧动画机制,这个transformation变化是在WindowStateAnimator类里面实现的。为什么要重点关注这个方法呢?因为如果窗口动画出bug了(位置大小不对?透明度异常?),就可以在这个方法里面打印window的相关参数来初步定位原因。 

119      @Override
120      public void apply(Transaction t, SurfaceControl leash, long currentPlayTime) {
121          final TmpValues tmp = mThreadLocalTmps.get();
122          tmp.transformation.clear();
123          mAnimation.getTransformation(currentPlayTime, tmp.transformation);
124          tmp.transformation.getMatrix().postTranslate(mPosition.x, mPosition.y);
125          t.setMatrix(leash, tmp.transformation.getMatrix(), tmp.floats);
126          t.setAlpha(leash, tmp.transformation.getAlpha());
127  
128          boolean cropSet = false;
129          if (mRootTaskClipMode == ROOT_TASK_CLIP_NONE) {
130              if (tmp.transformation.hasClipRect()) {
131                  final Rect clipRect = tmp.transformation.getClipRect();
132                  accountForExtension(tmp.transformation, clipRect);
133                  t.setWindowCrop(leash, clipRect);
134                  cropSet = true;
135              }
136          } else {
137              mTmpRect.set(mRootTaskBounds);
138              if (tmp.transformation.hasClipRect()) {
139                  mTmpRect.intersect(tmp.transformation.getClipRect());
140              }
141              accountForExtension(tmp.transformation, mTmpRect);
142              t.setWindowCrop(leash, mTmpRect);
143              cropSet = true;
144          }
145  
146          // We can only apply rounded corner if a crop is set, as otherwise the value is meaningless,
147          // since it doesn't have anything it's relative to.
148          if (cropSet && mAnimation.hasRoundedCorners() && mWindowCornerRadius > 0) {
149              t.setCornerRadius(leash, mWindowCornerRadius);
150          }
151      }

6、ValueAnimator类,从上面的介绍可以得知,窗口动画的最终本质就是一个ValueAnimator属性动画,理解了这一点,就相当于把窗口动画简单化了,最终的实现就类比于我们普通app的属性动画的实现(app属性动画的对象是view,窗口属性动画的对象是window),只不过整个流程比较复杂而已,但是最终的实现原理是一样的,殊途同归,这个才是android窗口动画机制的精髓所在

Rotation动画 ANIMATION_TYPE_SCREEN_ROTATION

1. 根据sensor导致的旋转动画

最终也会到SurfaceAnimation

 拿到动画适配器:

FadeAnimationController.java

    public void fadeWindowToken(boolean show, WindowToken windowToken, int animationType) {
        if (windowToken == null || windowToken.getParent() == null) {
            return;
        }

        final Animation animation = show ? getFadeInAnimation() : getFadeOutAnimation();
        final FadeAnimationAdapter animationAdapter = animation != null
                ? createAdapter(createAnimationSpec(animation), show, windowToken) : null;
        if (animationAdapter == null) {
            return;
        }

        windowToken.startAnimation(windowToken.getPendingTransaction(), animationAdapter,
                show /* hidden */, animationType, null /* finishedCallback */);
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1498635.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

以人为本的AI技术升级

我们需要以人为本的技术来提高生产力和投资回报率。 通过在数据标注流程中融合机器学习辅助技术&#xff0c;可以减少数据标注所需的时间、资金和人力。 有很多方法可以防止标注员被模型的预测误导。 在传统的机器学习&#xff08;Machine Learning&#xff09;方法下&#…

一篇长文教你进行全方位的使用appium【建议收藏】

随着移动应用的日益普及&#xff0c;移动应用的测试成为了软件开发的重要组成部分。Python&#xff0c;作为一种易于学习&#xff0c;功能强大的编程语言&#xff0c;特别适合进行这种测试。本文将详细介绍如何使用Python进行APP测试&#xff0c;并附带一个实例。 Python 和 A…

Docker快速入门和部署项目

1&#xff0c;Docker是一个&#xff0c;快速构建、运行、管理应用的工具 。 2&#xff0c;前面我们了解过在Linux操作系统的常见的命令以及如何在Linux中部署一个人单体的项目。感受如何呢&#xff1f;&#xff1f;&#xff1f; 命令太多了&#xff0c;记不住 软件安装包名字复…

网络学习:数据的封装与解封装

目录 一、数据的封装与解封装 1. 数据的封装过程 2. 数据的解封装过程 二、数据的传输过程 1. 相关概念 2. 网络传输过程中数据封装和解封装模拟 一、数据的封装与解封装 1. 数据的封装过程 数据封装过程&#xff0c;在这里我们举例说明&#xff0c;以两台主机的通信为…

【docker基础学习之】镜像构建

下面是在工作过遇到的一些实际例子&#xff0c;谨以此作为笔记参考 目录 1.背景2. 寻找方案3. 如何解决4.解决步骤4.1 DockerFile4.2 现在要做的 5. 镜像相关命令 1.背景 部署&#xff08;迁移&#xff09;项目时发现&#xff0c;项目的excel导出功能报错&#xff0c;错误如下…

ChatGPT数据分析应用——同期群分析

ChatGPT数据分析应用——同期群分析 ​ 同期群分析在一定程度上属于分组分析的一个变种。顾名思义&#xff0c;同期群就是相同时期的群体&#xff0c;同期群分析就是针对相同时期的群体展开分析。接下来我们让ChatGPT解释这个方法的概念并提供相应的案例。发送如下内容给ChatG…

chrome插件webRequest拦截请求并获取post请求体requestBody数据raw内容,解决中文乱码问题

详细使用说明可以看官方文档&#xff1a;https://developer.chrome.com/docs/extensions/reference/api/webRequest?hlzh-cn 拦截操作 想要通过浏览器插件拦截请求的话&#xff0c;需要在manifest.json里面添加webRequet权限&#xff1a; 拦截请求代码放在background.js里面…

力扣--从前序与中序遍历序列构造二叉树

题目&#xff1a; 思想&#xff1a; 首先先序遍历能确定根节点的值&#xff0c;此时查看该值在中序遍历中的位置&#xff08;如果索引为i&#xff09;&#xff0c;那么i左侧为左子树&#xff0c;i 右侧为右子树。从中序数组中即可看出左子树结点个数为 i&#xff0c;右子树节点…

王道机试C++第 3 章 排序与查找:排序问题 Day28(含二分查找)

查找 查找是另一类必须掌握的基础算法&#xff0c;它不仅会在机试中直接考查&#xff0c;而且是其他某些算法的基础。之所以将查找和排序放在一起讲&#xff0c;是因为二者有较强的联系。排序的重要意义之一便是帮助人们更加方便地进行查找。如果不对数据进行排序&#xff0c;…

ACM题解Day10|总结篇|进制转化,GCD ,LCM ,二分答案

&#x1f525;博客介绍&#xff1a; 27dCnc [Cstring中find_first_not_of()函数和find_last_not_of()函数-CSDN博客] 方差,期望 概率 今日打卡: 算法周总结 ACM题解Day3| To Crash or not To Crash,Integer Prefix ,I don’t want to pay for the Late Jar-CSDN博客 第3题:…

【LeetCode:2917. 找出数组中的 K-or 值 + 模拟+位运算】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

如何将 ONLYOFFICE 协作空间部署到 Kubernetes / OpenShift 集群中

需要 ONLYOFFICE 协作空间的可扩展实例吗&#xff1f;使用 Helm 轻松将其安装到 Kubernetes 或 OpenShift 集群中。阅读本文了解详情。 ONLYOFFICE 协作空间是什么 ONLYOFFICE 协作空间是一个协同办公平台&#xff0c;能够帮助用户更好地与客户、业务合作伙伴、承包商及第三方…

驱动调试第013期-G120XA驱动同步电机应用案例

概述 SINAMICS G120XA是西门子SINAMICS系列变频器的新成员&#xff0c; 功率范围覆盖0.75 kW~560 kW&#xff0c;内置风机和水泵行业应用功能&#xff0c;汇集了优异的高性能矢量控制算法&#xff0c;可以轻松的驱动风机、水泵及压缩机等负载。胜任各种应用场合&#xff0c;专…

【python基础学习11课_异常机制】

一、异常 1、异常的定义 异常&#xff1a;程序无法继续执行异常会中断程序执行异常处理&#xff0c;是为了不中断程序执行。而不是避免错误。有些代码是报错就是要暴露出来有了异常机制&#xff0c;错误的代码报错后抛出异常&#xff0c;代码从上到下&#xff0c;报错代码后面…

触发HTTP preflight预检及跨域的处理方法

最近在做需求的过程中&#xff0c;遇到了很多跨域和HTTP预检的问题。下面对我所遇到过的HTTP preflight和跨域的相关问题进行总结&#xff1a; 哪些情况会触发HTTP preflight preflight属于cors规范的一部分&#xff0c;在有跨域的时候&#xff0c;在一定情况下会触发preflig…

字节开启新一轮期权回购,价格又涨了(含算法原题)

字节期权 近日&#xff0c;字节跳动开启新一轮期权回购&#xff0c;价格微涨至 170 美元。 之前我们就写过 文章&#xff0c;分享历年来字节跳动的期权变化情况&#xff0c;这里再贴一下&#xff1a; 18年&#xff1a;10 19年&#xff1a;30 20年&#xff1a;60-70 21年&#x…

Linux系统编程(六)高级IO

目录 1. 阻塞和非阻塞 IO 2. IO 多路转接&#xff08;select、poll、epoll&#xff09; 3. 存储映射 IO&#xff08;mmap&#xff09; 4. 文件锁&#xff08;fcntl、lockf、flock&#xff09; 5. 管道实例 - 池类算法 1. 阻塞和非阻塞 IO 阻塞 IO&#xff1a;会等待操作的…

决定马里兰州地区版图的关键历史事件

1. 马里兰殖民地的建立&#xff1a; - 1632年&#xff0c;英国国王查理一世将一大片土地赐予塞西尔卡尔弗特男爵&#xff0c;这片土地是为了纪念国王的妻子亨丽埃塔玛丽亚而命名为“马里兰”。卡尔弗特和他的儿子随后建立了马里兰殖民地&#xff0c;这标志着马里兰作为一个独立…

车辆伤害VR安全教育培训复用性强

VR工地伤害虚拟体验是一种新兴的培训方式&#xff0c;它利用虚拟现实技术为参与者提供身临其境的体验。与传统的培训方式相比&#xff0c;VR工地伤害虚拟体验具有许多优势。 首先&#xff0c;VR工地伤害虚拟体验能够模拟真实的工作环境和事故场景&#xff0c;让参与者在安全的环…

hdu-2059(dp)

hdu-2059 龟兔赛跑 dp[i] 表示到第i个站所花费的最少时间&#xff0c;t[j][k]表示在第j个站充满电&#xff0c;直接开到第k个站所花的时间&#xff0c;那么状态转移为: dp[i] min(dp[i], dp[j] t[j][i]) 含义为&#xff0c;假设我们当前想知道到达第i个站的最少时间&#xff…