/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.launcher3.views; import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.drawable.Drawable; import android.os.Build; import android.text.Spannable; import android.text.SpannableString; import android.text.TextUtils; import android.text.style.ImageSpan; import android.util.AttributeSet; import android.util.Log; import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; import androidx.annotation.RequiresApi; import com.android.launcher3.BubbleTextView; import com.android.launcher3.R; /** * Extension of {@link BubbleTextView} which draws two shadows on the text (ambient and key shadows} */ public class DoubleShadowBubbleTextView extends BubbleTextView { private final ShadowInfo mShadowInfo; public DoubleShadowBubbleTextView(Context context) { this(context, null); } public DoubleShadowBubbleTextView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public DoubleShadowBubbleTextView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); mShadowInfo = ShadowInfo.Companion.fromContext(context, attrs, defStyle); setShadowLayer( mShadowInfo.getAmbientShadowBlur(), 0, 0, mShadowInfo.getAmbientShadowColor() ); } @Override public void setTextWithStartIcon(CharSequence text, @DrawableRes int drawableId) { Drawable drawable = getContext().getDrawable(drawableId); if (drawable == null) { setText(text); Log.w(TAG, "setTextWithStartIcon: start icon Drawable not found from resources" + ", will just set text instead."); return; } drawable.setTint(getCurrentTextColor()); int textSize = Math.round(getTextSize()); ImageSpan imageSpan; if (!skipDoubleShadow() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { drawable = getDoubleShadowDrawable(drawable, textSize); } drawable.setBounds(0, 0, textSize, textSize); imageSpan = new ImageSpan(drawable, ImageSpan.ALIGN_CENTER); // First space will be replaced with Drawable, second space is for space before text. SpannableString spannable = new SpannableString(" " + text); spannable.setSpan(imageSpan, 0, 1, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); setText(spannable); } @RequiresApi(Build.VERSION_CODES.S) private DoubleShadowIconDrawable getDoubleShadowDrawable( @NonNull Drawable drawable, int textSize ) { // add some padding via inset to avoid shadow clipping int iconInsetSize = getContext().getResources() .getDimensionPixelSize(R.dimen.app_title_icon_shadow_inset); return new DoubleShadowIconDrawable( mShadowInfo, drawable, textSize, iconInsetSize ); } @Override public void onDraw(Canvas canvas) { if (shouldDrawAppContrastTile() && !TextUtils.isEmpty(getText())) { drawAppContrastTile(canvas); } // If text is transparent or shadow alpha is 0, don't draw any shadow if (skipDoubleShadow()) { super.onDraw(canvas); return; } int alpha = Color.alpha(getCurrentTextColor()); // We enhance the shadow by drawing the shadow twice getPaint().setShadowLayer(mShadowInfo.getAmbientShadowBlur(), 0, 0, getTextShadowColor(mShadowInfo.getAmbientShadowColor(), alpha)); drawWithoutDot(canvas); canvas.save(); canvas.clipRect(getScrollX(), getScrollY() + getExtendedPaddingTop(), getScrollX() + getWidth(), getScrollY() + getHeight()); getPaint().setShadowLayer( mShadowInfo.getKeyShadowBlur(), mShadowInfo.getKeyShadowOffsetX(), mShadowInfo.getKeyShadowOffsetY(), getTextShadowColor(mShadowInfo.getKeyShadowColor(), alpha)); drawWithoutDot(canvas); canvas.restore(); drawDotIfNecessary(canvas); drawRunningAppIndicatorIfNecessary(canvas); } private boolean skipDoubleShadow() { int textAlpha = Color.alpha(getCurrentTextColor()); int keyShadowAlpha = Color.alpha(mShadowInfo.getKeyShadowColor()); int ambientShadowAlpha = Color.alpha(mShadowInfo.getAmbientShadowColor()); if (textAlpha == 0 || (keyShadowAlpha == 0 && ambientShadowAlpha == 0)) { getPaint().clearShadowLayer(); return true; } else if (ambientShadowAlpha > 0 && keyShadowAlpha == 0) { getPaint().setShadowLayer(mShadowInfo.getAmbientShadowBlur(), 0, 0, getTextShadowColor(mShadowInfo.getAmbientShadowColor(), textAlpha)); return true; } else if (keyShadowAlpha > 0 && ambientShadowAlpha == 0) { getPaint().setShadowLayer( mShadowInfo.getKeyShadowBlur(), mShadowInfo.getKeyShadowOffsetX(), mShadowInfo.getKeyShadowOffsetY(), getTextShadowColor(mShadowInfo.getKeyShadowColor(), textAlpha)); return true; } else { return false; } } // Multiplies the alpha of shadowColor by textAlpha. private static int getTextShadowColor(int shadowColor, int textAlpha) { return setColorAlphaBound(shadowColor, Math.round(Color.alpha(shadowColor) * textAlpha / 255f)); } }