• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.wm.shell.shared.animation
18 
19 import android.animation.PointFEvaluator
20 import android.animation.ValueAnimator
21 import android.graphics.PointF
22 import android.graphics.Rect
23 import android.util.DisplayMetrics
24 import android.util.TypedValue
25 import android.view.Choreographer
26 import android.view.SurfaceControl
27 import android.view.animation.Interpolator
28 import android.window.TransitionInfo
29 
30 /** Creates animations that can be applied to windows/surfaces. */
31 object WindowAnimator {
32 
33     /** Parameters defining a window bounds animation. */
34     data class BoundsAnimationParams(
35         val durationMs: Long,
36         val startOffsetYDp: Float = 0f,
37         val endOffsetYDp: Float = 0f,
38         val startScale: Float = 1f,
39         val endScale: Float = 1f,
40         val interpolator: Interpolator,
41     )
42 
43     /**
44      * Creates an animator to reposition and scale the bounds of the leash of the given change.
45      *
46      * @param displayMetrics the metrics of the display where the animation plays in
47      * @param boundsAnimDef the parameters for the animation itself (duration, scale, position)
48      * @param change the change to which the animation should be applied
49      * @param transaction the transaction to apply the animation to
50      */
51     fun createBoundsAnimator(
52         displayMetrics: DisplayMetrics,
53         boundsAnimDef: BoundsAnimationParams,
54         change: TransitionInfo.Change,
55         transaction: SurfaceControl.Transaction,
56     ): ValueAnimator {
57         val startPos =
58             getPosition(
59                 displayMetrics,
60                 change.endAbsBounds,
61                 boundsAnimDef.startScale,
62                 boundsAnimDef.startOffsetYDp,
63             )
64         val leash = change.leash
65         val endPos =
66             getPosition(
67                 displayMetrics,
68                 change.endAbsBounds,
69                 boundsAnimDef.endScale,
70                 boundsAnimDef.endOffsetYDp,
71             )
72         return ValueAnimator.ofObject(PointFEvaluator(), startPos, endPos).apply {
73             duration = boundsAnimDef.durationMs
74             interpolator = boundsAnimDef.interpolator
75             addUpdateListener { animation ->
76                 val animPos = animation.animatedValue as PointF
77                 val animScale =
78                     interpolate(
79                         boundsAnimDef.startScale,
80                         boundsAnimDef.endScale,
81                         animation.animatedFraction
82                     )
83                 transaction
84                     .setPosition(leash, animPos.x, animPos.y)
85                     .setScale(leash, animScale, animScale)
86                     .setFrameTimeline(Choreographer.getInstance().vsyncId)
87                     .apply()
88             }
89         }
90     }
91 
92     private fun interpolate(startVal: Float, endVal: Float, fraction: Float): Float {
93         require(fraction in 0.0f..1.0f)
94         return startVal + (endVal - startVal) * fraction
95     }
96 
97     private fun getPosition(
98         displayMetrics: DisplayMetrics,
99         bounds: Rect,
100         scale: Float,
101         offsetYDp: Float
102     ) = PointF(bounds.left.toFloat(), bounds.top.toFloat()).apply {
103             check(scale in 0.0f..1.0f)
104             // Scale the bounds down with an anchor in the center
105             offset(
106                 (bounds.width().toFloat() * (1 - scale) / 2),
107                 (bounds.height().toFloat() * (1 - scale) / 2),
108             )
109             val offsetYPx =
110                 TypedValue.applyDimension(
111                         TypedValue.COMPLEX_UNIT_DIP,
112                         offsetYDp,
113                         displayMetrics,
114                     )
115                     .toInt()
116             offset(/* dx= */ 0f, offsetYPx.toFloat())
117         }
118 }
119