1 /* <lambda>null2 * Copyright (C) 2021 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 package com.android.systemui.shared.animation 17 18 import android.graphics.Point 19 import android.view.Surface 20 import android.view.Surface.Rotation 21 import android.view.View 22 import android.view.WindowManager 23 import com.android.systemui.unfold.UnfoldTransitionProgressProvider 24 import java.lang.ref.WeakReference 25 26 /** 27 * Creates an animation where all registered views are moved into their final location 28 * by moving from the center of the screen to the sides 29 */ 30 class UnfoldMoveFromCenterAnimator @JvmOverloads constructor( 31 private val windowManager: WindowManager, 32 /** 33 * Allows to set custom translation applier 34 * Could be useful when a view could be translated from 35 * several sources and we want to set the translation 36 * using custom methods instead of [View.setTranslationX] or 37 * [View.setTranslationY] 38 */ 39 private val translationApplier: TranslationApplier = object : TranslationApplier {}, 40 /** 41 * Allows to set custom implementation for getting 42 * view location. Could be useful if logical view bounds 43 * are different than actual bounds (e.g. view container may 44 * have larger width than width of the items in the container) 45 */ 46 private val viewCenterProvider: ViewCenterProvider = object : ViewCenterProvider {}, 47 /** Allows to set the alpha based on the progress. */ 48 private val alphaProvider: AlphaProvider? = null 49 ) : UnfoldTransitionProgressProvider.TransitionProgressListener { 50 51 private val screenSize = Point() 52 private var isVerticalFold = false 53 54 private val animatedViews: MutableList<AnimatedView> = arrayListOf() 55 56 private var lastAnimationProgress: Float = 1f 57 58 /** 59 * Updates display properties in order to calculate the initial position for the views 60 * Must be called before [registerViewForAnimation] 61 */ 62 @JvmOverloads updateDisplayPropertiesnull63 fun updateDisplayProperties(@Rotation rotation: Int = windowManager.defaultDisplay.rotation) { 64 windowManager.defaultDisplay.getSize(screenSize) 65 66 // Simple implementation to get current fold orientation, 67 // this might not be correct on all devices 68 // TODO: use JetPack WindowManager library to get the fold orientation 69 isVerticalFold = rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180 70 } 71 72 /** 73 * If target view positions have changed (e.g. because of layout changes) call this method 74 * to re-query view positions and update the translations 75 */ updateViewPositionsnull76 fun updateViewPositions() { 77 animatedViews.forEach { animatedView -> 78 animatedView.view.get()?.let { 79 animatedView.updateAnimatedView(it) 80 } 81 } 82 onTransitionProgress(lastAnimationProgress) 83 } 84 85 /** 86 * Registers a view to be animated, the view should be measured and layouted 87 * After finishing the animation it is necessary to clear 88 * the views using [clearRegisteredViews] 89 */ registerViewForAnimationnull90 fun registerViewForAnimation(view: View) { 91 val animatedView = createAnimatedView(view) 92 animatedViews.add(animatedView) 93 } 94 95 /** 96 * Unregisters all registered views and resets their translation 97 */ clearRegisteredViewsnull98 fun clearRegisteredViews() { 99 onTransitionProgress(1f) 100 animatedViews.clear() 101 } 102 onTransitionProgressnull103 override fun onTransitionProgress(progress: Float) { 104 animatedViews.forEach { 105 it.applyTransition(progress) 106 it.applyAlpha(progress) 107 } 108 lastAnimationProgress = progress 109 } 110 AnimatedViewnull111 private fun AnimatedView.applyTransition(progress: Float) { 112 view.get()?.let { view -> 113 translationApplier.apply( 114 view = view, 115 x = startTranslationX * (1 - progress), 116 y = startTranslationY * (1 - progress) 117 ) 118 } 119 } 120 AnimatedViewnull121 private fun AnimatedView.applyAlpha(progress: Float) { 122 if (alphaProvider == null) return 123 view.get()?.alpha = alphaProvider.getAlpha(progress) 124 } 125 createAnimatedViewnull126 private fun createAnimatedView(view: View): AnimatedView = 127 AnimatedView(view = WeakReference(view)).updateAnimatedView(view) 128 129 private fun AnimatedView.updateAnimatedView(view: View): AnimatedView { 130 val viewCenter = Point() 131 viewCenterProvider.getViewCenter(view, viewCenter) 132 133 val viewCenterX = viewCenter.x 134 val viewCenterY = viewCenter.y 135 136 if (isVerticalFold) { 137 val distanceFromScreenCenterToViewCenter = screenSize.x / 2 - viewCenterX 138 startTranslationX = distanceFromScreenCenterToViewCenter * TRANSLATION_PERCENTAGE 139 startTranslationY = 0f 140 } else { 141 val distanceFromScreenCenterToViewCenter = screenSize.y / 2 - viewCenterY 142 startTranslationX = 0f 143 startTranslationY = distanceFromScreenCenterToViewCenter * TRANSLATION_PERCENTAGE 144 } 145 146 return this 147 } 148 149 /** 150 * Interface that allows to use custom logic to apply translation to view 151 */ 152 interface TranslationApplier { 153 /** 154 * Called when we need to apply [x] and [y] translation to [view] 155 */ applynull156 fun apply(view: View, x: Float, y: Float) { 157 view.translationX = x 158 view.translationY = y 159 } 160 } 161 162 /** Allows to set a custom alpha based on the progress. */ 163 interface AlphaProvider { 164 165 /** Returns the alpha views should have at a given progress. */ getAlphanull166 fun getAlpha(progress: Float): Float 167 } 168 169 /** 170 * Interface that allows to use custom logic to get the center of the view 171 */ 172 interface ViewCenterProvider { 173 /** 174 * Called when we need to get the center of the view 175 */ 176 fun getViewCenter(view: View, outPoint: Point) { 177 val viewLocation = IntArray(2) 178 view.getLocationOnScreen(viewLocation) 179 180 val viewX = viewLocation[0] 181 val viewY = viewLocation[1] 182 183 outPoint.x = viewX + view.width / 2 184 outPoint.y = viewY + view.height / 2 185 } 186 } 187 188 private class AnimatedView( 189 val view: WeakReference<View>, 190 var startTranslationX: Float = 0f, 191 var startTranslationY: Float = 0f 192 ) 193 } 194 195 private const val TRANSLATION_PERCENTAGE = 0.08f 196