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.launcher3.taskbar;
17 
18 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
19 
20 import android.animation.Animator;
21 import android.animation.AnimatorListenerAdapter;
22 import android.animation.ObjectAnimator;
23 import android.animation.PropertyValuesHolder;
24 import android.annotation.Nullable;
25 import android.content.Context;
26 import android.graphics.Rect;
27 import android.util.AttributeSet;
28 import android.view.View;
29 
30 import androidx.annotation.ColorInt;
31 import androidx.core.content.ContextCompat;
32 
33 import com.android.launcher3.LauncherAnimUtils;
34 import com.android.launcher3.R;
35 
36 /**
37  * View to render a handle that changes color based on the background to ensure contrast. Used for
38  * the taskbar when stashed as well as the bubble bar when stashed.
39  */
40 public class StashedHandleView extends View {
41 
42     private static final long COLOR_CHANGE_DURATION = 120;
43 
44     private final @ColorInt int mStashedHandleLightColor;
45     private final @ColorInt int mStashedHandleDarkColor;
46     private final Rect mSampledRegion = new Rect();
47     private final int[] mTmpArr = new int[2];
48 
49     private @Nullable ObjectAnimator mColorChangeAnim;
50 
StashedHandleView(Context context)51     public StashedHandleView(Context context) {
52         this(context, null);
53     }
54 
StashedHandleView(Context context, AttributeSet attrs)55     public StashedHandleView(Context context, AttributeSet attrs) {
56         this(context, attrs, 0);
57     }
58 
StashedHandleView(Context context, AttributeSet attrs, int defStyleAttr)59     public StashedHandleView(Context context, AttributeSet attrs, int defStyleAttr) {
60         this(context, attrs, defStyleAttr, 0);
61     }
62 
StashedHandleView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)63     public StashedHandleView(Context context, AttributeSet attrs, int defStyleAttr,
64             int defStyleRes) {
65         super(context, attrs, defStyleAttr, defStyleRes);
66 
67         mStashedHandleLightColor = ContextCompat.getColor(context,
68                 R.color.taskbar_stashed_handle_light_color);
69         mStashedHandleDarkColor = ContextCompat.getColor(context,
70                 R.color.taskbar_stashed_handle_dark_color);
71     }
72 
73     /**
74      * Updates mSampledRegion to be the location of the stashedHandleBounds relative to the screen.
75      * @see #getSampledRegion()
76      */
updateSampledRegion(Rect stashedHandleBounds)77     public void updateSampledRegion(Rect stashedHandleBounds) {
78         getLocationOnScreen(mTmpArr);
79         // Translations are temporary due to animations, remove them for the purpose of determining
80         // the final region we want sampled.
81         mTmpArr[0] -= Math.round(getTranslationX());
82         mTmpArr[1] -= Math.round(getTranslationY());
83         mSampledRegion.set(stashedHandleBounds);
84         mSampledRegion.offset(mTmpArr[0], mTmpArr[1]);
85     }
86 
getSampledRegion()87     public Rect getSampledRegion() {
88         return mSampledRegion;
89     }
90 
91     /**
92      * Updates the handle color.
93      * @param isRegionDark Whether the background behind the handle is dark, and thus the handle
94      *                     should be light (and vice versa).
95      * @param animate Whether to animate the change, or apply it immediately.
96      */
updateHandleColor(boolean isRegionDark, boolean animate)97     public void updateHandleColor(boolean isRegionDark, boolean animate) {
98         int newColor = isRegionDark ? mStashedHandleLightColor : mStashedHandleDarkColor;
99         if (mColorChangeAnim != null) {
100             mColorChangeAnim.cancel();
101         }
102         if (animate) {
103             mColorChangeAnim = ObjectAnimator.ofArgb(this,
104                     LauncherAnimUtils.VIEW_BACKGROUND_COLOR, newColor);
105             mColorChangeAnim.addListener(new AnimatorListenerAdapter() {
106                 @Override
107                 public void onAnimationEnd(Animator animation) {
108                     mColorChangeAnim = null;
109                 }
110             });
111             mColorChangeAnim.setDuration(COLOR_CHANGE_DURATION);
112             mColorChangeAnim.start();
113         } else {
114             setBackgroundColor(newColor);
115         }
116     }
117 
118     /**
119      * Updates the handle scale.
120      *
121      * @param scale target scale to animate towards (starting from current scale)
122      * @param durationMs milliseconds for the animation to take
123      */
animateScale(float scale, long durationMs)124     public void animateScale(float scale, long durationMs) {
125         ObjectAnimator scaleAnim = ObjectAnimator.ofPropertyValuesHolder(this,
126                 PropertyValuesHolder.ofFloat(SCALE_PROPERTY, scale));
127         scaleAnim.setDuration(durationMs).setAutoCancel(true);
128         scaleAnim.start();
129     }
130 }
131