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.keyguard; 17 18 import static com.android.settingslib.Utils.getColorAttrDefaultColor; 19 import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.ColorId.NUM_PAD_BACKGROUND; 20 import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.ColorId.NUM_PAD_BACKGROUND_PRESSED; 21 import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.ColorId.NUM_PAD_BUTTON; 22 import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.ColorId.NUM_PAD_KEY; 23 import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.ColorId.NUM_PAD_PRESSED; 24 import static com.android.systemui.util.ColorUtilKt.getPrivateAttrColorIfUnset; 25 26 import android.animation.AnimatorSet; 27 import android.animation.ArgbEvaluator; 28 import android.animation.ValueAnimator; 29 import android.annotation.Nullable; 30 import android.annotation.SuppressLint; 31 import android.content.Context; 32 import android.content.res.TypedArray; 33 import android.graphics.drawable.Drawable; 34 import android.graphics.drawable.GradientDrawable; 35 import android.view.ContextThemeWrapper; 36 import android.widget.TextView; 37 38 import androidx.annotation.StyleRes; 39 40 import com.android.app.animation.Interpolators; 41 42 /** 43 * Provides background color and radius animations for key pad buttons. 44 */ 45 class NumPadAnimator { 46 private ValueAnimator mExpandAnimator; 47 private AnimatorSet mExpandAnimatorSet; 48 private ValueAnimator mContractAnimator; 49 private AnimatorSet mContractAnimatorSet; 50 private GradientDrawable mBackground; 51 private Drawable mImageButton; 52 private TextView mDigitTextView; 53 private int mNormalBackgroundColor; 54 private int mPressedBackgroundColor; 55 private int mTextColorPrimary; 56 private int mTextColorPressed; 57 private int mStyle; 58 private float mStartRadius; 59 private float mEndRadius; 60 private int mHeight; 61 private int mWidth; 62 63 private static final int EXPAND_ANIMATION_MS = 100; 64 private static final int EXPAND_COLOR_ANIMATION_MS = 50; 65 private static final int CONTRACT_ANIMATION_DELAY_MS = 33; 66 private static final int CONTRACT_ANIMATION_MS = 417; 67 NumPadAnimator(Context context, final Drawable drawable, @StyleRes int style, Drawable buttonImage)68 NumPadAnimator(Context context, final Drawable drawable, 69 @StyleRes int style, Drawable buttonImage) { 70 this(context, drawable, style, null, buttonImage); 71 } 72 NumPadAnimator(Context context, final Drawable drawable, @StyleRes int style, @Nullable TextView digitTextView, @Nullable Drawable buttonImage)73 NumPadAnimator(Context context, final Drawable drawable, @StyleRes int style, 74 @Nullable TextView digitTextView, @Nullable Drawable buttonImage) { 75 mStyle = style; 76 mBackground = (GradientDrawable) drawable; 77 mDigitTextView = digitTextView; 78 mImageButton = buttonImage; 79 80 reloadColors(context); 81 } 82 expand()83 public void expand() { 84 mExpandAnimatorSet.cancel(); 85 mContractAnimatorSet.cancel(); 86 mExpandAnimatorSet.start(); 87 } 88 contract()89 public void contract() { 90 mExpandAnimatorSet.cancel(); 91 mContractAnimatorSet.cancel(); 92 mContractAnimatorSet.start(); 93 } 94 setProgress(float progress)95 public void setProgress(float progress) { 96 mBackground.setCornerRadius(mEndRadius + (mStartRadius - mEndRadius) * progress); 97 int height = (int) (mHeight * 0.7f + mHeight * 0.3 * progress); 98 int difference = mHeight - height; 99 100 int left = 0; 101 int top = difference / 2; 102 int right = mWidth; 103 int bottom = mHeight - difference / 2; 104 mBackground.setBounds(left, top, right, bottom); 105 } 106 onLayout(int width, int height)107 void onLayout(int width, int height) { 108 boolean shouldUpdateHeight = height != mHeight; 109 mWidth = width; 110 mHeight = height; 111 mStartRadius = height / 2f; 112 mEndRadius = height / 4f; 113 mExpandAnimator.setFloatValues(mStartRadius, mEndRadius); 114 mContractAnimator.setFloatValues(mEndRadius, mStartRadius); 115 // Set initial corner radius. 116 if (shouldUpdateHeight) { 117 mBackground.setCornerRadius(mStartRadius); 118 } 119 } 120 121 /** 122 * Reload colors from resources. 123 **/ reloadColors(Context context)124 void reloadColors(Context context) { 125 boolean isNumPadKey = mImageButton == null; 126 127 int[] customAttrs = {android.R.attr.colorControlNormal}; 128 ContextThemeWrapper ctw = new ContextThemeWrapper(context, mStyle); 129 @SuppressLint("ResourceType") TypedArray a = ctw.obtainStyledAttributes(customAttrs); 130 mNormalBackgroundColor = getPrivateAttrColorIfUnset(ctw, a, 0, 0, 131 NUM_PAD_BACKGROUND); 132 a.recycle(); 133 134 mPressedBackgroundColor = getColorAttrDefaultColor(context, NUM_PAD_BACKGROUND_PRESSED); 135 mTextColorPressed = getColorAttrDefaultColor(context, NUM_PAD_PRESSED); 136 137 mBackground.setColor(mNormalBackgroundColor); 138 mTextColorPrimary = isNumPadKey 139 ? getColorAttrDefaultColor(context, NUM_PAD_KEY) 140 : getColorAttrDefaultColor(context, NUM_PAD_BUTTON); 141 createAnimators(); 142 } 143 createAnimators()144 private void createAnimators() { 145 // Actual values will be updated later, usually during an onLayout() call 146 mExpandAnimator = ValueAnimator.ofFloat(0f, 1f); 147 mExpandAnimator.setDuration(EXPAND_ANIMATION_MS); 148 mExpandAnimator.setInterpolator(Interpolators.LINEAR); 149 mExpandAnimator.addUpdateListener( 150 anim -> mBackground.setCornerRadius((float) anim.getAnimatedValue())); 151 152 ValueAnimator expandBackgroundColorAnimator = ValueAnimator.ofObject(new ArgbEvaluator(), 153 mNormalBackgroundColor, mPressedBackgroundColor); 154 expandBackgroundColorAnimator.setDuration(EXPAND_COLOR_ANIMATION_MS); 155 expandBackgroundColorAnimator.setInterpolator(Interpolators.LINEAR); 156 expandBackgroundColorAnimator.addUpdateListener( 157 animator -> mBackground.setColor((int) animator.getAnimatedValue())); 158 159 ValueAnimator expandTextColorAnimator = 160 ValueAnimator.ofObject(new ArgbEvaluator(), 161 mTextColorPrimary, mTextColorPressed); 162 expandTextColorAnimator.setInterpolator(Interpolators.LINEAR); 163 expandTextColorAnimator.setDuration(EXPAND_COLOR_ANIMATION_MS); 164 expandTextColorAnimator.addUpdateListener(valueAnimator -> { 165 if (mDigitTextView != null) { 166 mDigitTextView.setTextColor((int) valueAnimator.getAnimatedValue()); 167 } 168 if (mImageButton != null) { 169 mImageButton.setTint((int) valueAnimator.getAnimatedValue()); 170 } 171 }); 172 173 mExpandAnimatorSet = new AnimatorSet(); 174 mExpandAnimatorSet.playTogether(mExpandAnimator, 175 expandBackgroundColorAnimator, expandTextColorAnimator); 176 177 mContractAnimator = ValueAnimator.ofFloat(1f, 0f); 178 mContractAnimator.setStartDelay(CONTRACT_ANIMATION_DELAY_MS); 179 mContractAnimator.setDuration(CONTRACT_ANIMATION_MS); 180 mContractAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); 181 mContractAnimator.addUpdateListener( 182 anim -> mBackground.setCornerRadius((float) anim.getAnimatedValue())); 183 ValueAnimator contractBackgroundColorAnimator = ValueAnimator.ofObject(new ArgbEvaluator(), 184 mPressedBackgroundColor, mNormalBackgroundColor); 185 contractBackgroundColorAnimator.setInterpolator(Interpolators.LINEAR); 186 contractBackgroundColorAnimator.setStartDelay(CONTRACT_ANIMATION_DELAY_MS); 187 contractBackgroundColorAnimator.setDuration(CONTRACT_ANIMATION_MS); 188 contractBackgroundColorAnimator.addUpdateListener( 189 animator -> mBackground.setColor((int) animator.getAnimatedValue())); 190 191 ValueAnimator contractTextColorAnimator = 192 ValueAnimator.ofObject(new ArgbEvaluator(), mTextColorPressed, 193 mTextColorPrimary); 194 contractTextColorAnimator.setInterpolator(Interpolators.LINEAR); 195 contractTextColorAnimator.setStartDelay(CONTRACT_ANIMATION_DELAY_MS); 196 contractTextColorAnimator.setDuration(CONTRACT_ANIMATION_MS); 197 contractTextColorAnimator.addUpdateListener(valueAnimator -> { 198 if (mDigitTextView != null) { 199 mDigitTextView.setTextColor((int) valueAnimator.getAnimatedValue()); 200 } 201 if (mImageButton != null) { 202 mImageButton.setTint((int) valueAnimator.getAnimatedValue()); 203 } 204 }); 205 206 mContractAnimatorSet = new AnimatorSet(); 207 mContractAnimatorSet.playTogether(mContractAnimator, 208 contractBackgroundColorAnimator, contractTextColorAnimator); 209 } 210 } 211 212