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