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