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.Outline; 28 import android.graphics.Rect; 29 import android.util.AttributeSet; 30 import android.view.View; 31 import android.view.ViewOutlineProvider; 32 33 import androidx.annotation.NonNull; 34 import androidx.annotation.Nullable; 35 import androidx.constraintlayout.widget.ConstraintLayout; 36 37 import com.android.launcher3.R; 38 39 import java.util.ArrayList; 40 41 /** 42 * Helper View for the gesture tutorial mock previous app task view. 43 * 44 * This helper class allows animating from a single-row layout to a two-row layout as seen in 45 * large screen devices. 46 */ 47 public class AnimatedTaskView extends ConstraintLayout { 48 49 private View mFullTaskView; 50 private View mTopTaskView; 51 private View mBottomTaskView; 52 53 private ViewOutlineProvider mTaskViewOutlineProvider = null; 54 private final Rect mTaskViewAnimatedRect = new Rect(); 55 private float mTaskViewAnimatedRadius; 56 AnimatedTaskView(@onNull Context context)57 public AnimatedTaskView(@NonNull Context context) { 58 this(context, null); 59 } 60 AnimatedTaskView(@onNull Context context, @Nullable AttributeSet attrs)61 public AnimatedTaskView(@NonNull Context context, @Nullable AttributeSet attrs) { 62 this(context, attrs, 0); 63 } 64 AnimatedTaskView( @onNull Context context, @Nullable AttributeSet attrs, int defStyleAttr)65 public AnimatedTaskView( 66 @NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 67 this(context, attrs, defStyleAttr, 0); 68 } 69 AnimatedTaskView( @onNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes)70 public AnimatedTaskView( 71 @NonNull Context context, 72 @Nullable AttributeSet attrs, 73 int defStyleAttr, 74 int defStyleRes) { 75 super(context, attrs, defStyleAttr, defStyleRes); 76 } 77 78 @Override onFinishInflate()79 protected void onFinishInflate() { 80 super.onFinishInflate(); 81 82 mFullTaskView = findViewById(R.id.full_task_view); 83 mTopTaskView = findViewById(R.id.top_task_view); 84 mBottomTaskView = findViewById(R.id.bottom_task_view); 85 86 setToSingleRowLayout(false); 87 } 88 createAnimationToMultiRowLayout()89 AnimatorSet createAnimationToMultiRowLayout() { 90 if (mTaskViewOutlineProvider == null) { 91 // This is an illegal state. 92 return null; 93 } 94 Outline startOutline = new Outline(); 95 mTaskViewOutlineProvider.getOutline(this, startOutline); 96 Rect outlineStartRect = new Rect(); 97 startOutline.getRect(outlineStartRect); 98 int endRectBottom = mTopTaskView.getHeight(); 99 float outlineStartRadius = startOutline.getRadius(); 100 float outlineEndRadius = getContext().getResources().getDimensionPixelSize( 101 R.dimen.gesture_tutorial_small_task_view_corner_radius); 102 103 ValueAnimator outlineAnimator = ValueAnimator.ofFloat(0f, 1f); 104 outlineAnimator.addUpdateListener(valueAnimator -> { 105 float progress = (float) valueAnimator.getAnimatedValue(); 106 mTaskViewAnimatedRect.bottom = (int) (outlineStartRect.bottom 107 + progress * (endRectBottom - outlineStartRect.bottom)); 108 mTaskViewAnimatedRadius = outlineStartRadius 109 + progress * (outlineEndRadius - outlineStartRadius); 110 mFullTaskView.invalidateOutline(); 111 }); 112 outlineAnimator.addListener(new AnimatorListenerAdapter() { 113 @Override 114 public void onAnimationStart(Animator animation) { 115 super.onAnimationStart(animation); 116 addAnimatedOutlineProvider(mFullTaskView, outlineStartRect, outlineStartRadius); 117 } 118 119 @Override 120 public void onAnimationEnd(Animator animation) { 121 super.onAnimationEnd(animation); 122 mFullTaskView.setOutlineProvider(mTaskViewOutlineProvider); 123 } 124 125 @Override 126 public void onAnimationCancel(Animator animation) { 127 super.onAnimationCancel(animation); 128 mFullTaskView.setOutlineProvider(mTaskViewOutlineProvider); 129 } 130 }); 131 132 ArrayList<Animator> animations = new ArrayList<>(); 133 animations.add(ObjectAnimator.ofFloat( 134 mBottomTaskView, View.TRANSLATION_X, -mBottomTaskView.getWidth(), 0)); 135 animations.add(outlineAnimator); 136 137 AnimatorSet animatorSet = new AnimatorSet(); 138 animatorSet.playTogether(animations); 139 animatorSet.addListener(new AnimatorListenerAdapter() { 140 @Override 141 public void onAnimationStart(Animator animation) { 142 super.onAnimationStart(animation); 143 setToSingleRowLayout(true); 144 145 setPadding(0, outlineStartRect.top, 0, getHeight() - outlineStartRect.bottom); 146 } 147 148 @Override 149 public void onAnimationEnd(Animator animation) { 150 super.onAnimationEnd(animation); 151 setToMultiRowLayout(); 152 } 153 154 @Override 155 public void onAnimationCancel(Animator animation) { 156 super.onAnimationCancel(animation); 157 setToMultiRowLayout(); 158 } 159 }); 160 161 return animatorSet; 162 } 163 setToSingleRowLayout(boolean forAnimation)164 void setToSingleRowLayout(boolean forAnimation) { 165 mFullTaskView.setVisibility(VISIBLE); 166 mTopTaskView.setVisibility(INVISIBLE); 167 mBottomTaskView.setVisibility(forAnimation ? VISIBLE : INVISIBLE); 168 } 169 setToMultiRowLayout()170 void setToMultiRowLayout() { 171 mFullTaskView.setVisibility(INVISIBLE); 172 mTopTaskView.setVisibility(VISIBLE); 173 mBottomTaskView.setVisibility(VISIBLE); 174 } 175 setFakeTaskViewFillColor(@olorInt int colorResId)176 void setFakeTaskViewFillColor(@ColorInt int colorResId) { 177 mFullTaskView.setBackgroundColor(colorResId); 178 179 if (ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()){ 180 mTopTaskView.getBackground().setTint(colorResId); 181 mBottomTaskView.getBackground().setTint(colorResId); 182 } 183 } 184 185 @Override setClipToOutline(boolean clipToOutline)186 public void setClipToOutline(boolean clipToOutline) { 187 mFullTaskView.setClipToOutline(clipToOutline); 188 } 189 190 @Override setOutlineProvider(ViewOutlineProvider provider)191 public void setOutlineProvider(ViewOutlineProvider provider) { 192 mTaskViewOutlineProvider = provider; 193 mFullTaskView.setOutlineProvider(mTaskViewOutlineProvider); 194 } 195 addAnimatedOutlineProvider(View view, Rect outlineStartRect, float outlineStartRadius)196 private void addAnimatedOutlineProvider(View view, 197 Rect outlineStartRect, float outlineStartRadius){ 198 mTaskViewAnimatedRect.set(outlineStartRect); 199 mTaskViewAnimatedRadius = outlineStartRadius; 200 view.setClipToOutline(true); 201 view.setOutlineProvider(new ViewOutlineProvider() { 202 @Override 203 public void getOutline(View view, Outline outline) { 204 outline.setRoundRect(mTaskViewAnimatedRect, mTaskViewAnimatedRadius); 205 } 206 }); 207 } 208 } 209