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.quickstep.interaction; 17 18 import static com.android.launcher3.config.FeatureFlags.ENABLE_NEW_GESTURE_NAV_TUTORIAL; 19 20 import android.animation.Animator; 21 import android.animation.AnimatorListenerAdapter; 22 import android.animation.AnimatorSet; 23 import android.animation.ObjectAnimator; 24 import android.animation.ValueAnimator; 25 import android.annotation.ColorInt; 26 import android.content.Context; 27 import android.graphics.Matrix; 28 import android.graphics.Outline; 29 import android.graphics.Rect; 30 import android.util.AttributeSet; 31 import android.util.Log; 32 import android.view.View; 33 import android.view.ViewOutlineProvider; 34 import android.view.animation.ScaleAnimation; 35 import android.view.animation.TranslateAnimation; 36 import android.widget.ViewAnimator; 37 38 import androidx.annotation.NonNull; 39 import androidx.annotation.Nullable; 40 import androidx.constraintlayout.widget.ConstraintLayout; 41 42 import com.android.launcher3.R; 43 44 import java.util.ArrayList; 45 46 /** 47 * Helper View for the gesture tutorial mock previous app task view. 48 * 49 * This helper class allows animating from a single-row layout to a two-row layout as seen in 50 * large screen devices. 51 */ 52 public class AnimatedTaskView extends ConstraintLayout { 53 54 private View mFullTaskView; 55 private View mTopTaskView; 56 private View mBottomTaskView; 57 58 private ViewOutlineProvider mTaskViewOutlineProvider = null; 59 private final Rect mTaskViewAnimatedRect = new Rect(); 60 private float mTaskViewAnimatedRadius; 61 AnimatedTaskView(@onNull Context context)62 public AnimatedTaskView(@NonNull Context context) { 63 super(context); 64 } 65 AnimatedTaskView(@onNull Context context, @Nullable AttributeSet attrs)66 public AnimatedTaskView(@NonNull Context context, 67 @Nullable AttributeSet attrs) { 68 super(context, attrs); 69 } 70 AnimatedTaskView( @onNull Context context, @Nullable AttributeSet attrs, int defStyleAttr)71 public AnimatedTaskView( 72 @NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 73 super(context, attrs, defStyleAttr); 74 } 75 AnimatedTaskView( @onNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes)76 public AnimatedTaskView( 77 @NonNull Context context, 78 @Nullable AttributeSet attrs, 79 int defStyleAttr, 80 int defStyleRes) { 81 super(context, attrs, defStyleAttr, defStyleRes); 82 } 83 84 @Override onFinishInflate()85 protected void onFinishInflate() { 86 super.onFinishInflate(); 87 88 mFullTaskView = findViewById(R.id.full_task_view); 89 mTopTaskView = findViewById(R.id.top_task_view); 90 mBottomTaskView = findViewById(R.id.bottom_task_view); 91 92 setToSingleRowLayout(false); 93 } 94 animateToFillScreen(@ullable Runnable onAnimationEndCallback)95 void animateToFillScreen(@Nullable Runnable onAnimationEndCallback) { 96 97 AnimatorSet set = new AnimatorSet(); 98 ArrayList<Animator> animations = new ArrayList<>(); 99 100 // center view 101 animations.add(ObjectAnimator.ofFloat(this, TRANSLATION_X, 0)); 102 103 // calculate full screen scaling, scale should be 1:1 for x and y 104 Matrix matrix = getAnimationMatrix(); 105 float[] matrixValues = new float[9]; 106 matrix.getValues(matrixValues); 107 float scaleX = matrixValues[Matrix.MSCALE_X]; 108 float scaleToFullScreen = 1 / scaleX; 109 110 // scale view to full screen 111 ValueAnimator scale = ValueAnimator.ofFloat(1f, scaleToFullScreen); 112 scale.addUpdateListener(animation -> { 113 float value = (float) animation.getAnimatedValue(); 114 mFullTaskView.setScaleX(value); 115 mFullTaskView.setScaleY(value); 116 }); 117 118 animations.add(scale); 119 set.playSequentially(animations); 120 121 set.addListener(new AnimatorListenerAdapter() { 122 @Override 123 public void onAnimationEnd(Animator animation) { 124 super.onAnimationEnd(animation); 125 if (onAnimationEndCallback != null) { 126 onAnimationEndCallback.run(); 127 } 128 } 129 }); 130 131 set.start(); 132 } 133 createAnimationToMultiRowLayout()134 AnimatorSet createAnimationToMultiRowLayout() { 135 if (mTaskViewOutlineProvider == null) { 136 // This is an illegal state. 137 return null; 138 } 139 Outline startOutline = new Outline(); 140 mTaskViewOutlineProvider.getOutline(this, startOutline); 141 Rect outlineStartRect = new Rect(); 142 startOutline.getRect(outlineStartRect); 143 int endRectBottom = mTopTaskView.getHeight(); 144 float outlineStartRadius = startOutline.getRadius(); 145 float outlineEndRadius = getContext().getResources().getDimensionPixelSize( 146 R.dimen.gesture_tutorial_small_task_view_corner_radius); 147 148 ValueAnimator outlineAnimator = ValueAnimator.ofFloat(0f, 1f); 149 outlineAnimator.addUpdateListener(valueAnimator -> { 150 float progress = (float) valueAnimator.getAnimatedValue(); 151 mTaskViewAnimatedRect.bottom = (int) (outlineStartRect.bottom 152 + progress * (endRectBottom - outlineStartRect.bottom)); 153 mTaskViewAnimatedRadius = outlineStartRadius 154 + progress * (outlineEndRadius - outlineStartRadius); 155 mFullTaskView.invalidateOutline(); 156 }); 157 outlineAnimator.addListener(new AnimatorListenerAdapter() { 158 @Override 159 public void onAnimationStart(Animator animation) { 160 super.onAnimationStart(animation); 161 162 mTaskViewAnimatedRect.set(outlineStartRect); 163 mTaskViewAnimatedRadius = outlineStartRadius; 164 165 mFullTaskView.setClipToOutline(true); 166 mFullTaskView.setOutlineProvider(new ViewOutlineProvider() { 167 @Override 168 public void getOutline(View view, Outline outline) { 169 outline.setRoundRect(mTaskViewAnimatedRect, mTaskViewAnimatedRadius); 170 } 171 }); 172 } 173 174 @Override 175 public void onAnimationEnd(Animator animation) { 176 super.onAnimationEnd(animation); 177 mFullTaskView.setOutlineProvider(mTaskViewOutlineProvider); 178 } 179 180 @Override 181 public void onAnimationCancel(Animator animation) { 182 super.onAnimationCancel(animation); 183 mFullTaskView.setOutlineProvider(mTaskViewOutlineProvider); 184 } 185 }); 186 187 ArrayList<Animator> animations = new ArrayList<>(); 188 animations.add(ObjectAnimator.ofFloat( 189 mBottomTaskView, View.TRANSLATION_X, -mBottomTaskView.getWidth(), 0)); 190 animations.add(outlineAnimator); 191 192 AnimatorSet animatorSet = new AnimatorSet(); 193 animatorSet.playTogether(animations); 194 animatorSet.addListener(new AnimatorListenerAdapter() { 195 @Override 196 public void onAnimationStart(Animator animation) { 197 super.onAnimationStart(animation); 198 setToSingleRowLayout(true); 199 200 setPadding(0, outlineStartRect.top, 0, getHeight() - outlineStartRect.bottom); 201 } 202 203 @Override 204 public void onAnimationEnd(Animator animation) { 205 super.onAnimationEnd(animation); 206 setToMultiRowLayout(); 207 } 208 209 @Override 210 public void onAnimationCancel(Animator animation) { 211 super.onAnimationCancel(animation); 212 setToMultiRowLayout(); 213 } 214 }); 215 216 return animatorSet; 217 } 218 setToSingleRowLayout(boolean forAnimation)219 void setToSingleRowLayout(boolean forAnimation) { 220 mFullTaskView.setVisibility(VISIBLE); 221 mTopTaskView.setVisibility(INVISIBLE); 222 mBottomTaskView.setVisibility(forAnimation ? VISIBLE : INVISIBLE); 223 } 224 setToMultiRowLayout()225 void setToMultiRowLayout() { 226 mFullTaskView.setVisibility(INVISIBLE); 227 mTopTaskView.setVisibility(VISIBLE); 228 mBottomTaskView.setVisibility(VISIBLE); 229 } 230 setFakeTaskViewFillColor(@olorInt int colorResId)231 void setFakeTaskViewFillColor(@ColorInt int colorResId) { 232 mFullTaskView.setBackgroundColor(colorResId); 233 234 if (ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()){ 235 mTopTaskView.getBackground().setTint(colorResId); 236 mBottomTaskView.getBackground().setTint(colorResId); 237 } 238 } 239 240 @Override setClipToOutline(boolean clipToOutline)241 public void setClipToOutline(boolean clipToOutline) { 242 mFullTaskView.setClipToOutline(clipToOutline); 243 } 244 245 @Override setOutlineProvider(ViewOutlineProvider provider)246 public void setOutlineProvider(ViewOutlineProvider provider) { 247 mTaskViewOutlineProvider = provider; 248 mFullTaskView.setOutlineProvider(mTaskViewOutlineProvider); 249 } 250 } 251