1 /* 2 * Copyright (C) 2023 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.wm.shell.bubbles.bar; 17 18 import android.animation.Animator; 19 import android.animation.AnimatorListenerAdapter; 20 import android.animation.ArgbEvaluator; 21 import android.animation.ObjectAnimator; 22 import android.annotation.Nullable; 23 import android.content.Context; 24 import android.graphics.Canvas; 25 import android.graphics.Paint; 26 import android.util.AttributeSet; 27 import android.view.View; 28 29 import androidx.annotation.ColorInt; 30 import androidx.annotation.VisibleForTesting; 31 import androidx.core.animation.IntProperty; 32 import androidx.core.content.ContextCompat; 33 34 import com.android.wm.shell.R; 35 36 /** 37 * Handle view to show at the top of a bubble bar expanded view. 38 */ 39 public class BubbleBarHandleView extends View { 40 private static final long COLOR_CHANGE_DURATION = 120; 41 42 /** Custom property to set handle color. */ 43 private static final IntProperty<BubbleBarHandleView> HANDLE_COLOR = new IntProperty<>( 44 "handleColor") { 45 @Override 46 public void setValue(BubbleBarHandleView bubbleBarHandleView, int color) { 47 bubbleBarHandleView.setHandleColor(color); 48 } 49 50 @Override 51 public Integer get(BubbleBarHandleView bubbleBarHandleView) { 52 return bubbleBarHandleView.getHandleColor(); 53 } 54 }; 55 56 @VisibleForTesting 57 final Paint mHandlePaint = new Paint(); 58 private final @ColorInt int mHandleLightColor; 59 private final @ColorInt int mHandleDarkColor; 60 private final ArgbEvaluator mArgbEvaluator = ArgbEvaluator.getInstance(); 61 private final float mHandleHeight; 62 private final float mHandleWidth; 63 private float mCurrentHandleHeight; 64 private float mCurrentHandleWidth; 65 @Nullable 66 private ObjectAnimator mColorChangeAnim; 67 private @ColorInt int mRegionSamplerColor; 68 private boolean mHasSampledColor; 69 BubbleBarHandleView(Context context)70 public BubbleBarHandleView(Context context) { 71 this(context, null /* attrs */); 72 } 73 BubbleBarHandleView(Context context, AttributeSet attrs)74 public BubbleBarHandleView(Context context, AttributeSet attrs) { 75 this(context, attrs, 0 /* defStyleAttr */); 76 } 77 BubbleBarHandleView(Context context, AttributeSet attrs, int defStyleAttr)78 public BubbleBarHandleView(Context context, AttributeSet attrs, int defStyleAttr) { 79 this(context, attrs, defStyleAttr, 0 /* defStyleRes */); 80 } 81 BubbleBarHandleView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)82 public BubbleBarHandleView(Context context, AttributeSet attrs, int defStyleAttr, 83 int defStyleRes) { 84 super(context, attrs, defStyleAttr, defStyleRes); 85 mHandlePaint.setFlags(Paint.ANTI_ALIAS_FLAG); 86 mHandlePaint.setStyle(Paint.Style.FILL); 87 mHandlePaint.setColor(0); 88 mHandleHeight = getResources().getDimensionPixelSize( 89 R.dimen.bubble_bar_expanded_view_handle_height); 90 mHandleWidth = getResources().getDimensionPixelSize( 91 R.dimen.bubble_bar_expanded_view_caption_width); 92 mHandleLightColor = ContextCompat.getColor(getContext(), 93 R.color.bubble_bar_expanded_view_handle_light); 94 mHandleDarkColor = ContextCompat.getColor(getContext(), 95 R.color.bubble_bar_expanded_view_handle_dark); 96 mCurrentHandleHeight = mHandleHeight; 97 mCurrentHandleWidth = mHandleWidth; 98 setContentDescription(getResources().getString(R.string.handle_text)); 99 } 100 setHandleColor(int color)101 private void setHandleColor(int color) { 102 mHandlePaint.setColor(color); 103 invalidate(); 104 } 105 106 /** 107 * Get current color value for the handle 108 */ 109 @ColorInt getHandleColor()110 public int getHandleColor() { 111 return mHandlePaint.getColor(); 112 } 113 114 @Override onDraw(Canvas canvas)115 protected void onDraw(Canvas canvas) { 116 super.onDraw(canvas); 117 float handleLeft = (getWidth() - mCurrentHandleWidth) / 2; 118 float handleRight = handleLeft + mCurrentHandleWidth; 119 float handleCenterY = (float) getHeight() / 2; 120 float handleTop = (int) (handleCenterY - mCurrentHandleHeight / 2); 121 float handleBottom = handleTop + mCurrentHandleHeight; 122 float cornerRadius = mCurrentHandleHeight / 2; 123 canvas.drawRoundRect(handleLeft, handleTop, handleRight, handleBottom, cornerRadius, 124 cornerRadius, mHandlePaint); 125 } 126 127 /** Sets handle width, height and color. Does not change the layout properties */ setHandleProperties(float width, float height, int color)128 private void setHandleProperties(float width, float height, int color) { 129 mCurrentHandleHeight = height; 130 mCurrentHandleWidth = width; 131 mHandlePaint.setColor(color); 132 invalidate(); 133 } 134 135 /** 136 * Set initial color for the handle. Takes effect if the 137 * {@link #updateHandleColor(boolean, boolean)} has not been called. 138 */ setHandleInitialColor(@olorInt int color)139 public void setHandleInitialColor(@ColorInt int color) { 140 if (!mHasSampledColor) { 141 setHandleColor(color); 142 } 143 } 144 145 /** 146 * Updates the handle color. 147 * 148 * @param isRegionDark Whether the background behind the handle is dark, and thus the handle 149 * should be light (and vice versa). 150 * @param animated Whether to animate the change, or apply it immediately. 151 */ updateHandleColor(boolean isRegionDark, boolean animated)152 public void updateHandleColor(boolean isRegionDark, boolean animated) { 153 int newColor = isRegionDark ? mHandleLightColor : mHandleDarkColor; 154 if (newColor == mRegionSamplerColor) { 155 return; 156 } 157 mHasSampledColor = true; 158 mRegionSamplerColor = newColor; 159 if (mColorChangeAnim != null) { 160 mColorChangeAnim.cancel(); 161 } 162 if (animated) { 163 mColorChangeAnim = ObjectAnimator.ofArgb(this, HANDLE_COLOR, newColor); 164 mColorChangeAnim.addListener(new AnimatorListenerAdapter() { 165 @Override 166 public void onAnimationEnd(Animator animation) { 167 mColorChangeAnim = null; 168 } 169 }); 170 mColorChangeAnim.setDuration(COLOR_CHANGE_DURATION); 171 mColorChangeAnim.start(); 172 } else { 173 setHandleColor(newColor); 174 } 175 } 176 177 /** Returns handle padding top. */ getHandlePaddingTop()178 public int getHandlePaddingTop() { 179 return (getHeight() - getResources().getDimensionPixelSize( 180 R.dimen.bubble_bar_expanded_view_handle_height)) / 2; 181 } 182 183 /** Animates handle for the bubble menu. */ animateHandleForMenu(float progress, float widthDelta, float heightDelta, int menuColor)184 public void animateHandleForMenu(float progress, float widthDelta, float heightDelta, 185 int menuColor) { 186 float currentWidth = mHandleWidth + widthDelta * progress; 187 float currentHeight = mHandleHeight + heightDelta * progress; 188 int color = (int) mArgbEvaluator.evaluate(progress, mRegionSamplerColor, menuColor); 189 setHandleProperties(currentWidth, currentHeight, color); 190 setTranslationY(heightDelta * progress / 2); 191 } 192 193 /** Restores all the properties that were animated to the default values. */ restoreAnimationDefaults()194 public void restoreAnimationDefaults() { 195 setHandleProperties(mHandleWidth, mHandleHeight, mRegionSamplerColor); 196 setTranslationY(0); 197 } 198 199 /** Returns the handle height. */ getHandleHeight()200 public int getHandleHeight() { 201 return (int) mHandleHeight; 202 } 203 204 /** Returns the handle width. */ getHandleWidth()205 public int getHandleWidth() { 206 return (int) mHandleWidth; 207 } 208 } 209