1 /* 2 * Copyright (C) 2024 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 android.content.res.ColorStateList 20 import android.graphics.BlendMode 21 import android.graphics.Canvas 22 import android.graphics.Color 23 import android.graphics.ColorFilter 24 import android.graphics.PixelFormat 25 import android.graphics.PorterDuff 26 import android.graphics.PorterDuffColorFilter 27 import android.graphics.RenderEffect 28 import android.graphics.RenderNode 29 import android.graphics.Shader 30 import android.graphics.drawable.Drawable 31 import android.graphics.drawable.InsetDrawable 32 import android.os.Build.VERSION_CODES 33 import androidx.annotation.RequiresApi 34 import androidx.annotation.VisibleForTesting 35 36 /** 37 * Launcher wrapper for Drawables to provide a double shadow effect. Currently for use with 38 * [DoubleShadowBubbleTextView] to provide a similar shadow to inline icons. 39 */ 40 @RequiresApi(VERSION_CODES.S) 41 class DoubleShadowIconDrawable( 42 private val shadowInfo: ShadowInfo, 43 iconDrawable: Drawable, 44 private val iconSize: Int, 45 iconInsetSize: Int 46 ) : Drawable() { 47 private val mIconDrawable: InsetDrawable 48 private val mDoubleShadowNode: RenderNode? 49 50 init { 51 mIconDrawable = InsetDrawable(iconDrawable, iconInsetSize) 52 mIconDrawable.setBounds(0, 0, iconSize, iconSize) 53 mDoubleShadowNode = createShadowRenderNode() 54 } 55 56 @VisibleForTesting createShadowRenderNodenull57 fun createShadowRenderNode(): RenderNode { 58 val renderNode = RenderNode("DoubleShadowNode") 59 renderNode.setPosition(0, 0, iconSize, iconSize) 60 // Create render effects 61 val ambientShadow = 62 createShadowRenderEffect( 63 shadowInfo.ambientShadowBlur, 64 0f, 65 0f, 66 Color.alpha(shadowInfo.ambientShadowColor).toFloat() 67 ) 68 val keyShadow = 69 createShadowRenderEffect( 70 shadowInfo.keyShadowBlur, 71 shadowInfo.keyShadowOffsetX, 72 shadowInfo.keyShadowOffsetY, 73 Color.alpha(shadowInfo.keyShadowColor).toFloat() 74 ) 75 val blend = RenderEffect.createBlendModeEffect(ambientShadow, keyShadow, BlendMode.DST_ATOP) 76 renderNode.setRenderEffect(blend) 77 return renderNode 78 } 79 80 @VisibleForTesting createShadowRenderEffectnull81 fun createShadowRenderEffect( 82 radius: Float, 83 offsetX: Float, 84 offsetY: Float, 85 alpha: Float 86 ): RenderEffect { 87 return RenderEffect.createColorFilterEffect( 88 PorterDuffColorFilter(Color.argb(alpha, 0f, 0f, 0f), PorterDuff.Mode.MULTIPLY), 89 RenderEffect.createOffsetEffect( 90 offsetX, 91 offsetY, 92 RenderEffect.createBlurEffect(radius, radius, Shader.TileMode.CLAMP) 93 ) 94 ) 95 } 96 drawnull97 override fun draw(canvas: Canvas) { 98 if (canvas.isHardwareAccelerated && mDoubleShadowNode != null) { 99 if (!mDoubleShadowNode.hasDisplayList()) { 100 // Record render node if its display list is not recorded or discarded 101 // (which happens when it's no longer drawn by anything). 102 val recordingCanvas = mDoubleShadowNode.beginRecording() 103 mIconDrawable.draw(recordingCanvas) 104 mDoubleShadowNode.endRecording() 105 } 106 canvas.drawRenderNode(mDoubleShadowNode) 107 } 108 mIconDrawable.draw(canvas) 109 } 110 getIntrinsicHeightnull111 override fun getIntrinsicHeight() = iconSize 112 113 override fun getIntrinsicWidth() = iconSize 114 115 override fun getOpacity() = PixelFormat.TRANSPARENT 116 117 override fun setAlpha(alpha: Int) { 118 mIconDrawable.alpha = alpha 119 } 120 setColorFilternull121 override fun setColorFilter(colorFilter: ColorFilter?) { 122 mIconDrawable.colorFilter = colorFilter 123 } 124 setTintnull125 override fun setTint(color: Int) { 126 mIconDrawable.setTint(color) 127 } 128 setTintListnull129 override fun setTintList(tint: ColorStateList?) { 130 mIconDrawable.setTintList(tint) 131 } 132 } 133