1 /* 2 * Copyright (C) 2014 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 17 package com.android.systemui.statusbar; 18 19 import static java.lang.Float.isNaN; 20 21 import android.annotation.NonNull; 22 import android.content.Context; 23 import android.graphics.Canvas; 24 import android.graphics.Color; 25 import android.graphics.PorterDuff; 26 import android.graphics.PorterDuff.Mode; 27 import android.graphics.PorterDuffColorFilter; 28 import android.graphics.drawable.Drawable; 29 import android.util.AttributeSet; 30 import android.view.View; 31 32 import androidx.core.graphics.ColorUtils; 33 34 import com.android.internal.annotations.VisibleForTesting; 35 import com.android.internal.colorextraction.ColorExtractor; 36 import com.android.internal.colorextraction.drawable.ScrimDrawable; 37 38 /** 39 * A view which can draw a scrim 40 */ 41 public class ScrimView extends View { 42 private final ColorExtractor.GradientColors mColors; 43 private float mViewAlpha = 1.0f; 44 private Drawable mDrawable; 45 private PorterDuffColorFilter mColorFilter; 46 private int mTintColor; 47 private Runnable mChangeRunnable; 48 ScrimView(Context context)49 public ScrimView(Context context) { 50 this(context, null); 51 } 52 ScrimView(Context context, AttributeSet attrs)53 public ScrimView(Context context, AttributeSet attrs) { 54 this(context, attrs, 0); 55 } 56 ScrimView(Context context, AttributeSet attrs, int defStyleAttr)57 public ScrimView(Context context, AttributeSet attrs, int defStyleAttr) { 58 this(context, attrs, defStyleAttr, 0); 59 } 60 ScrimView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)61 public ScrimView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 62 super(context, attrs, defStyleAttr, defStyleRes); 63 64 mDrawable = new ScrimDrawable(); 65 mDrawable.setCallback(this); 66 mColors = new ColorExtractor.GradientColors(); 67 updateColorWithTint(false); 68 } 69 70 @Override onDraw(Canvas canvas)71 protected void onDraw(Canvas canvas) { 72 if (mDrawable.getAlpha() > 0) { 73 mDrawable.draw(canvas); 74 } 75 } 76 setDrawable(Drawable drawable)77 public void setDrawable(Drawable drawable) { 78 mDrawable = drawable; 79 mDrawable.setCallback(this); 80 mDrawable.setBounds(getLeft(), getTop(), getRight(), getBottom()); 81 mDrawable.setAlpha((int) (255 * mViewAlpha)); 82 invalidate(); 83 } 84 85 @Override invalidateDrawable(@onNull Drawable drawable)86 public void invalidateDrawable(@NonNull Drawable drawable) { 87 super.invalidateDrawable(drawable); 88 if (drawable == mDrawable) { 89 invalidate(); 90 } 91 } 92 93 @Override onLayout(boolean changed, int left, int top, int right, int bottom)94 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 95 super.onLayout(changed, left, top, right, bottom); 96 if (changed) { 97 mDrawable.setBounds(left, top, right, bottom); 98 invalidate(); 99 } 100 } 101 setColors(@onNull ColorExtractor.GradientColors colors)102 public void setColors(@NonNull ColorExtractor.GradientColors colors) { 103 setColors(colors, false); 104 } 105 setColors(@onNull ColorExtractor.GradientColors colors, boolean animated)106 public void setColors(@NonNull ColorExtractor.GradientColors colors, boolean animated) { 107 if (colors == null) { 108 throw new IllegalArgumentException("Colors cannot be null"); 109 } 110 if (mColors.equals(colors)) { 111 return; 112 } 113 mColors.set(colors); 114 updateColorWithTint(animated); 115 } 116 117 @VisibleForTesting getDrawable()118 Drawable getDrawable() { 119 return mDrawable; 120 } 121 getColors()122 public ColorExtractor.GradientColors getColors() { 123 return mColors; 124 } 125 setTint(int color)126 public void setTint(int color) { 127 setTint(color, false); 128 } 129 setTint(int color, boolean animated)130 public void setTint(int color, boolean animated) { 131 if (mTintColor == color) { 132 return; 133 } 134 mTintColor = color; 135 updateColorWithTint(animated); 136 } 137 updateColorWithTint(boolean animated)138 private void updateColorWithTint(boolean animated) { 139 if (mDrawable instanceof ScrimDrawable) { 140 // Optimization to blend colors and avoid a color filter 141 ScrimDrawable drawable = (ScrimDrawable) mDrawable; 142 float tintAmount = Color.alpha(mTintColor) / 255f; 143 int mainTinted = ColorUtils.blendARGB(mColors.getMainColor(), mTintColor, 144 tintAmount); 145 drawable.setColor(mainTinted, animated); 146 } else { 147 boolean hasAlpha = Color.alpha(mTintColor) != 0; 148 if (hasAlpha) { 149 PorterDuff.Mode targetMode = mColorFilter == null ? Mode.SRC_OVER : 150 mColorFilter.getMode(); 151 if (mColorFilter == null || mColorFilter.getColor() != mTintColor) { 152 mColorFilter = new PorterDuffColorFilter(mTintColor, targetMode); 153 } 154 } else { 155 mColorFilter = null; 156 } 157 158 mDrawable.setColorFilter(mColorFilter); 159 mDrawable.invalidateSelf(); 160 } 161 162 if (mChangeRunnable != null) { 163 mChangeRunnable.run(); 164 } 165 } 166 getTint()167 public int getTint() { 168 return mTintColor; 169 } 170 171 @Override hasOverlappingRendering()172 public boolean hasOverlappingRendering() { 173 return false; 174 } 175 176 /** 177 * It might look counterintuitive to have another method to set the alpha instead of 178 * only using {@link #setAlpha(float)}. In this case we're in a hardware layer 179 * optimizing blend modes, so it makes sense. 180 * 181 * @param alpha Gradient alpha from 0 to 1. 182 */ setViewAlpha(float alpha)183 public void setViewAlpha(float alpha) { 184 if (isNaN(alpha)) { 185 throw new IllegalArgumentException("alpha cannot be NaN"); 186 } 187 if (alpha != mViewAlpha) { 188 mViewAlpha = alpha; 189 190 mDrawable.setAlpha((int) (255 * alpha)); 191 if (mChangeRunnable != null) { 192 mChangeRunnable.run(); 193 } 194 } 195 } 196 getViewAlpha()197 public float getViewAlpha() { 198 return mViewAlpha; 199 } 200 setChangeRunnable(Runnable changeRunnable)201 public void setChangeRunnable(Runnable changeRunnable) { 202 mChangeRunnable = changeRunnable; 203 } 204 205 @Override canReceivePointerEvents()206 protected boolean canReceivePointerEvents() { 207 return false; 208 } 209 } 210