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 private Boolean mIsRegionDark; 51 StashedHandleView(Context context)52 public StashedHandleView(Context context) { 53 this(context, null); 54 } 55 StashedHandleView(Context context, AttributeSet attrs)56 public StashedHandleView(Context context, AttributeSet attrs) { 57 this(context, attrs, 0); 58 } 59 StashedHandleView(Context context, AttributeSet attrs, int defStyleAttr)60 public StashedHandleView(Context context, AttributeSet attrs, int defStyleAttr) { 61 this(context, attrs, defStyleAttr, 0); 62 } 63 StashedHandleView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)64 public StashedHandleView(Context context, AttributeSet attrs, int defStyleAttr, 65 int defStyleRes) { 66 super(context, attrs, defStyleAttr, defStyleRes); 67 68 mStashedHandleLightColor = ContextCompat.getColor(context, 69 R.color.taskbar_stashed_handle_light_color); 70 mStashedHandleDarkColor = ContextCompat.getColor(context, 71 R.color.taskbar_stashed_handle_dark_color); 72 } 73 74 /** 75 * Updates mSampledRegion to be the location of the stashedHandleBounds relative to the screen. 76 * @see #getSampledRegion() 77 */ updateSampledRegion(Rect stashedHandleBounds)78 public void updateSampledRegion(Rect stashedHandleBounds) { 79 getLocationOnScreen(mTmpArr); 80 // Translations are temporary due to animations, remove them for the purpose of determining 81 // the final region we want sampled. 82 mTmpArr[0] -= Math.round(getTranslationX()); 83 mTmpArr[1] -= Math.round(getTranslationY()); 84 mSampledRegion.set(stashedHandleBounds); 85 mSampledRegion.offset(mTmpArr[0], mTmpArr[1]); 86 } 87 getSampledRegion()88 public Rect getSampledRegion() { 89 return mSampledRegion; 90 } 91 92 /** 93 * Updates the handle color. 94 * @param isRegionDark Whether the background behind the handle is dark, and thus the handle 95 * should be light (and vice versa). 96 * @param animate Whether to animate the change, or apply it immediately. 97 */ updateHandleColor(boolean isRegionDark, boolean animate)98 public void updateHandleColor(boolean isRegionDark, boolean animate) { 99 if (mIsRegionDark != null && mIsRegionDark == isRegionDark) { 100 return; 101 } 102 int newColor = isRegionDark ? mStashedHandleLightColor : mStashedHandleDarkColor; 103 mIsRegionDark = isRegionDark; 104 if (mColorChangeAnim != null) { 105 mColorChangeAnim.cancel(); 106 } 107 if (animate) { 108 mColorChangeAnim = ObjectAnimator.ofArgb(this, 109 LauncherAnimUtils.VIEW_BACKGROUND_COLOR, newColor); 110 mColorChangeAnim.addListener(new AnimatorListenerAdapter() { 111 @Override 112 public void onAnimationEnd(Animator animation) { 113 mColorChangeAnim = null; 114 } 115 }); 116 mColorChangeAnim.setDuration(COLOR_CHANGE_DURATION); 117 mColorChangeAnim.start(); 118 } else { 119 setBackgroundColor(newColor); 120 } 121 } 122 123 /** 124 * Updates the handle scale. 125 * 126 * @param scale target scale to animate towards (starting from current scale) 127 * @param durationMs milliseconds for the animation to take 128 */ animateScale(float scale, long durationMs)129 public void animateScale(float scale, long durationMs) { 130 ObjectAnimator scaleAnim = ObjectAnimator.ofPropertyValuesHolder(this, 131 PropertyValuesHolder.ofFloat(SCALE_PROPERTY, scale)); 132 scaleAnim.setDuration(durationMs).setAutoCancel(true); 133 scaleAnim.start(); 134 } 135 } 136