1 /* <lambda>null2 * Copyright (C) 2023 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.quickstep.util.unfold 17 18 import android.graphics.Point 19 import android.view.ViewGroup 20 import com.android.app.animation.Interpolators.LINEAR 21 import com.android.app.animation.Interpolators.clampToProgress 22 import com.android.launcher3.CellLayout 23 import com.android.launcher3.LauncherAnimUtils.HOTSEAT_SCALE_PROPERTY_FACTORY 24 import com.android.launcher3.LauncherAnimUtils.SCALE_INDEX_UNFOLD_ANIMATION 25 import com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X 26 import com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y 27 import com.android.launcher3.LauncherAnimUtils.WORKSPACE_SCALE_PROPERTY_FACTORY 28 import com.android.launcher3.Workspace 29 import com.android.launcher3.anim.PendingAnimation 30 import com.android.launcher3.uioverrides.QuickstepLauncher 31 import com.android.launcher3.util.HorizontalInsettableView 32 33 private typealias ViewGroupAction = (ViewGroup, Boolean) -> Unit 34 35 object UnfoldAnimationBuilder { 36 37 private val CLIP_CHILDREN: ViewGroupAction = ViewGroup::setClipChildren 38 private val CLIP_TO_PADDING: ViewGroupAction = ViewGroup::setClipToPadding 39 40 data class RestoreInfo(val action: ViewGroupAction, var target: ViewGroup, var value: Boolean) 41 42 // Percentage of the width of the quick search bar that will be reduced 43 // from the both sides of the bar when progress is 0 44 private const val MAX_WIDTH_INSET_FRACTION = 0.04f 45 46 // Scale factor for the whole workspace and hotseat 47 private const val SCALE_LAUNCHER_FROM = 0.92f 48 49 // Translation factor for all the items on the homescreen 50 private const val TRANSLATION_PERCENTAGE = 0.08f 51 52 private fun setClipChildren( 53 target: ViewGroup, 54 value: Boolean, 55 restoreList: MutableList<RestoreInfo> 56 ) { 57 val originalValue = target.clipChildren 58 if (originalValue != value) { 59 target.clipChildren = value 60 restoreList.add(RestoreInfo(CLIP_CHILDREN, target, originalValue)) 61 } 62 } 63 64 private fun setClipToPadding( 65 target: ViewGroup, 66 value: Boolean, 67 restoreList: MutableList<RestoreInfo> 68 ) { 69 val originalValue = target.clipToPadding 70 if (originalValue != value) { 71 target.clipToPadding = value 72 restoreList.add(RestoreInfo(CLIP_TO_PADDING, target, originalValue)) 73 } 74 } 75 76 private fun addChildrenAnimation( 77 itemsContainer: ViewGroup, 78 isVerticalFold: Boolean, 79 screenSize: Point, 80 anim: PendingAnimation 81 ) { 82 val tempLocation = IntArray(2) 83 for (i in 0 until itemsContainer.childCount) { 84 val child = itemsContainer.getChildAt(i) 85 86 child.getLocationOnScreen(tempLocation) 87 if (isVerticalFold) { 88 val viewCenterX = tempLocation[0] + child.width / 2 89 val distanceFromScreenCenterToViewCenter = screenSize.x / 2 - viewCenterX 90 anim.addFloat( 91 child, 92 VIEW_TRANSLATE_X, 93 distanceFromScreenCenterToViewCenter * TRANSLATION_PERCENTAGE, 94 0f, 95 LINEAR 96 ) 97 } else { 98 val viewCenterY = tempLocation[1] + child.height / 2 99 val distanceFromScreenCenterToViewCenter = screenSize.y / 2 - viewCenterY 100 anim.addFloat( 101 child, 102 VIEW_TRANSLATE_Y, 103 distanceFromScreenCenterToViewCenter * TRANSLATION_PERCENTAGE, 104 0f, 105 LINEAR 106 ) 107 } 108 } 109 } 110 111 /** 112 * Builds an animation for the unfold experience and adds it to the provided PendingAnimation 113 */ 114 fun buildUnfoldAnimation( 115 launcher: QuickstepLauncher, 116 isVerticalFold: Boolean, 117 screenSize: Point, 118 anim: PendingAnimation 119 ) { 120 val restoreList = ArrayList<RestoreInfo>() 121 val registerViews: (CellLayout) -> Unit = { cellLayout -> 122 setClipChildren(cellLayout, false, restoreList) 123 setClipToPadding(cellLayout, false, restoreList) 124 addChildrenAnimation(cellLayout.shortcutsAndWidgets, isVerticalFold, screenSize, anim) 125 } 126 127 val workspace: Workspace<*> = launcher.workspace 128 val hotseat = launcher.hotseat 129 130 // Animation icons from workspace for all orientations 131 workspace.forEachVisiblePage { registerViews(it as CellLayout) } 132 setClipChildren(workspace, false, restoreList) 133 setClipToPadding(workspace, true, restoreList) 134 135 // Workspace scale 136 launcher.workspace.setPivotToScaleWithSelf(launcher.hotseat) 137 val interpolator = clampToProgress(LINEAR, 0f, 1f) 138 anim.addFloat( 139 workspace, 140 WORKSPACE_SCALE_PROPERTY_FACTORY[SCALE_INDEX_UNFOLD_ANIMATION], 141 SCALE_LAUNCHER_FROM, 142 1f, 143 interpolator 144 ) 145 anim.addFloat( 146 hotseat, 147 HOTSEAT_SCALE_PROPERTY_FACTORY[SCALE_INDEX_UNFOLD_ANIMATION], 148 SCALE_LAUNCHER_FROM, 149 1f, 150 interpolator 151 ) 152 153 if (isVerticalFold) { 154 if (hotseat.qsb is HorizontalInsettableView) { 155 anim.addFloat( 156 hotseat.qsb as HorizontalInsettableView, 157 HorizontalInsettableView.HORIZONTAL_INSETS, 158 MAX_WIDTH_INSET_FRACTION, 159 0f, 160 LINEAR 161 ) 162 } 163 registerViews(hotseat) 164 } 165 anim.addEndListener { restoreList.forEach { it.action(it.target, it.value) } } 166 } 167 } 168