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