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