1 /* 2 * Copyright (C) 2017 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.launcher3.views; 18 19 import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound; 20 21 import android.content.Context; 22 import android.graphics.Canvas; 23 import android.graphics.Color; 24 import android.graphics.drawable.Drawable; 25 import android.os.Build; 26 import android.text.Spannable; 27 import android.text.SpannableString; 28 import android.text.TextUtils; 29 import android.text.style.ImageSpan; 30 import android.util.AttributeSet; 31 import android.util.Log; 32 33 import androidx.annotation.DrawableRes; 34 import androidx.annotation.NonNull; 35 import androidx.annotation.RequiresApi; 36 37 import com.android.launcher3.BubbleTextView; 38 import com.android.launcher3.R; 39 40 /** 41 * Extension of {@link BubbleTextView} which draws two shadows on the text (ambient and key shadows} 42 */ 43 public class DoubleShadowBubbleTextView extends BubbleTextView { 44 45 private final ShadowInfo mShadowInfo; 46 DoubleShadowBubbleTextView(Context context)47 public DoubleShadowBubbleTextView(Context context) { 48 this(context, null); 49 } 50 DoubleShadowBubbleTextView(Context context, AttributeSet attrs)51 public DoubleShadowBubbleTextView(Context context, AttributeSet attrs) { 52 this(context, attrs, 0); 53 } 54 DoubleShadowBubbleTextView(Context context, AttributeSet attrs, int defStyle)55 public DoubleShadowBubbleTextView(Context context, AttributeSet attrs, int defStyle) { 56 super(context, attrs, defStyle); 57 mShadowInfo = ShadowInfo.Companion.fromContext(context, attrs, defStyle); 58 setShadowLayer( 59 mShadowInfo.getAmbientShadowBlur(), 60 0, 61 0, 62 mShadowInfo.getAmbientShadowColor() 63 ); 64 } 65 66 @Override setTextWithStartIcon(CharSequence text, @DrawableRes int drawableId)67 public void setTextWithStartIcon(CharSequence text, @DrawableRes int drawableId) { 68 Drawable drawable = getContext().getDrawable(drawableId); 69 if (drawable == null) { 70 setText(text); 71 Log.w(TAG, "setTextWithStartIcon: start icon Drawable not found from resources" 72 + ", will just set text instead."); 73 return; 74 } 75 drawable.setTint(getCurrentTextColor()); 76 int textSize = Math.round(getTextSize()); 77 ImageSpan imageSpan; 78 if (!skipDoubleShadow() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { 79 drawable = getDoubleShadowDrawable(drawable, textSize); 80 } 81 drawable.setBounds(0, 0, textSize, textSize); 82 imageSpan = new ImageSpan(drawable, ImageSpan.ALIGN_CENTER); 83 // First space will be replaced with Drawable, second space is for space before text. 84 SpannableString spannable = new SpannableString(" " + text); 85 spannable.setSpan(imageSpan, 0, 1, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); 86 setText(spannable); 87 } 88 89 @RequiresApi(Build.VERSION_CODES.S) getDoubleShadowDrawable( @onNull Drawable drawable, int textSize )90 private DoubleShadowIconDrawable getDoubleShadowDrawable( 91 @NonNull Drawable drawable, int textSize 92 ) { 93 // add some padding via inset to avoid shadow clipping 94 int iconInsetSize = getContext().getResources() 95 .getDimensionPixelSize(R.dimen.app_title_icon_shadow_inset); 96 return new DoubleShadowIconDrawable( 97 mShadowInfo, 98 drawable, 99 textSize, 100 iconInsetSize 101 ); 102 } 103 104 @Override onDraw(Canvas canvas)105 public void onDraw(Canvas canvas) { 106 if (shouldDrawAppContrastTile() && !TextUtils.isEmpty(getText())) { 107 drawAppContrastTile(canvas); 108 } 109 // If text is transparent or shadow alpha is 0, don't draw any shadow 110 if (skipDoubleShadow()) { 111 super.onDraw(canvas); 112 return; 113 } 114 int alpha = Color.alpha(getCurrentTextColor()); 115 116 // We enhance the shadow by drawing the shadow twice 117 getPaint().setShadowLayer(mShadowInfo.getAmbientShadowBlur(), 0, 0, 118 getTextShadowColor(mShadowInfo.getAmbientShadowColor(), alpha)); 119 120 drawWithoutDot(canvas); 121 canvas.save(); 122 canvas.clipRect(getScrollX(), getScrollY() + getExtendedPaddingTop(), 123 getScrollX() + getWidth(), 124 getScrollY() + getHeight()); 125 126 getPaint().setShadowLayer( 127 mShadowInfo.getKeyShadowBlur(), 128 mShadowInfo.getKeyShadowOffsetX(), 129 mShadowInfo.getKeyShadowOffsetY(), 130 getTextShadowColor(mShadowInfo.getKeyShadowColor(), alpha)); 131 drawWithoutDot(canvas); 132 canvas.restore(); 133 134 drawDotIfNecessary(canvas); 135 drawRunningAppIndicatorIfNecessary(canvas); 136 } 137 skipDoubleShadow()138 private boolean skipDoubleShadow() { 139 int textAlpha = Color.alpha(getCurrentTextColor()); 140 int keyShadowAlpha = Color.alpha(mShadowInfo.getKeyShadowColor()); 141 int ambientShadowAlpha = Color.alpha(mShadowInfo.getAmbientShadowColor()); 142 if (textAlpha == 0 || (keyShadowAlpha == 0 && ambientShadowAlpha == 0)) { 143 getPaint().clearShadowLayer(); 144 return true; 145 } else if (ambientShadowAlpha > 0 && keyShadowAlpha == 0) { 146 getPaint().setShadowLayer(mShadowInfo.getAmbientShadowBlur(), 0, 0, 147 getTextShadowColor(mShadowInfo.getAmbientShadowColor(), textAlpha)); 148 return true; 149 } else if (keyShadowAlpha > 0 && ambientShadowAlpha == 0) { 150 getPaint().setShadowLayer( 151 mShadowInfo.getKeyShadowBlur(), 152 mShadowInfo.getKeyShadowOffsetX(), 153 mShadowInfo.getKeyShadowOffsetY(), 154 getTextShadowColor(mShadowInfo.getKeyShadowColor(), textAlpha)); 155 return true; 156 } else { 157 return false; 158 } 159 } 160 161 162 // Multiplies the alpha of shadowColor by textAlpha. getTextShadowColor(int shadowColor, int textAlpha)163 private static int getTextShadowColor(int shadowColor, int textAlpha) { 164 return setColorAlphaBound(shadowColor, 165 Math.round(Color.alpha(shadowColor) * textAlpha / 255f)); 166 } 167 } 168