@@ -15,6 +15,7 @@ import android.graphics.ColorFilter
1515import android.graphics.Paint
1616import android.graphics.Path
1717import android.graphics.PixelFormat
18+ import android.graphics.Rect
1819import android.graphics.RectF
1920import android.graphics.drawable.Drawable
2021import androidx.annotation.RequiresApi
@@ -45,12 +46,8 @@ internal class InsetBoxShadowDrawable(
4546 private val offsetY : Float ,
4647 private val blurRadius : Float ,
4748 private val spread : Float ,
48- /*
49- * We assume borderRadius & borderInsets to be shared across multiple drawables
50- * therefore user should invalidate this drawable when changing either of them
51- */
52- var borderInsets : BorderInsets ? = null ,
53- var borderRadius : BorderRadiusStyle ? = null ,
49+ borderInsets : BorderInsets ? = null ,
50+ borderRadius : BorderRadiusStyle ? = null ,
5451) : Drawable() {
5552 private val shadowPaint =
5653 Paint ().apply {
@@ -61,6 +58,41 @@ internal class InsetBoxShadowDrawable(
6158 }
6259 }
6360
61+ private var needUpdatePath = true
62+ private val paddingBoxRect = RectF ()
63+ private val paddingBoxRadii = FloatArray (8 )
64+ private var hasPaddingBoxRadii = false
65+ private val paddingBoxPath = Path ()
66+ private val innerRect = RectF ()
67+ private val innerRadii = FloatArray (8 )
68+ private val outerRect = RectF ()
69+
70+ /*
71+ * We assume borderRadius & borderInsets to be shared across multiple drawables
72+ * therefore user should assign them to this drawable again when changing either of them
73+ */
74+ var borderInsets: BorderInsets ? = borderInsets
75+ set(value) {
76+ field = value
77+ needUpdatePath = true
78+ }
79+
80+ var borderRadius: BorderRadiusStyle ? = borderRadius
81+ set(value) {
82+ field = value
83+ needUpdatePath = true
84+ }
85+
86+ override fun onBoundsChange (bounds : Rect ) {
87+ super .onBoundsChange(bounds)
88+ needUpdatePath = true
89+ }
90+
91+ override fun onLayoutDirectionChanged (layoutDirection : Int ): Boolean {
92+ needUpdatePath = true
93+ return true
94+ }
95+
6496 override fun setAlpha (alpha : Int ) {
6597 shadowPaint.alpha = (((alpha / 255f ) * (Color .alpha(shadowColor) / 255f )) * 255f ).roundToInt()
6698 invalidateSelf()
@@ -82,69 +114,80 @@ internal class InsetBoxShadowDrawable(
82114 }
83115
84116 override fun draw (canvas : Canvas ) {
117+ updatePath()
118+
119+ canvas.save().let { saveCount ->
120+ if (hasPaddingBoxRadii) {
121+ canvas.clipPath(paddingBoxPath)
122+ canvas.drawDoubleRoundRect(outerRect, ZERO_RADII , innerRect, innerRadii, shadowPaint)
123+ } else {
124+ canvas.clipRect(paddingBoxRect)
125+ canvas.drawDoubleRoundRect(outerRect, ZERO_RADII , innerRect, ZERO_RADII , shadowPaint)
126+ }
127+
128+ canvas.restoreToCount(saveCount)
129+ }
130+ }
131+
132+ private fun updatePath () {
133+ if (! needUpdatePath) {
134+ return
135+ }
136+ needUpdatePath = false
137+
85138 val computedBorderRadii = computeBorderRadii()
86139 val computedBorderInsets = computeBorderInsets()
87140
88- val paddingBoxRect =
89- RectF (
90- bounds.left + (computedBorderInsets?.left ? : 0f ),
91- bounds.top + (computedBorderInsets?.top ? : 0f ),
92- bounds.right - (computedBorderInsets?.right ? : 0f ),
93- bounds.bottom - (computedBorderInsets?.bottom ? : 0f ),
94- )
95- val paddingBoxRadii =
96- computedBorderRadii?.let {
97- floatArrayOf(
98- innerRadius(it.topLeft.horizontal, computedBorderInsets?.left),
99- innerRadius(it.topLeft.vertical, computedBorderInsets?.top),
100- innerRadius(it.topRight.horizontal, computedBorderInsets?.right),
101- innerRadius(it.topRight.vertical, computedBorderInsets?.top),
102- innerRadius(it.bottomRight.horizontal, computedBorderInsets?.right),
103- innerRadius(it.bottomRight.vertical, computedBorderInsets?.bottom),
104- innerRadius(it.bottomLeft.horizontal, computedBorderInsets?.left),
105- innerRadius(it.bottomLeft.vertical, computedBorderInsets?.bottom),
106- )
107- }
141+ paddingBoxRect.set(
142+ bounds.left + (computedBorderInsets?.left ? : 0f ),
143+ bounds.top + (computedBorderInsets?.top ? : 0f ),
144+ bounds.right - (computedBorderInsets?.right ? : 0f ),
145+ bounds.bottom - (computedBorderInsets?.bottom ? : 0f ),
146+ )
147+ hasPaddingBoxRadii = computedBorderRadii != null
148+ if (computedBorderRadii != null ) {
149+ paddingBoxRadii[0 ] =
150+ innerRadius(computedBorderRadii.topLeft.horizontal, computedBorderInsets?.left)
151+ paddingBoxRadii[1 ] =
152+ innerRadius(computedBorderRadii.topLeft.vertical, computedBorderInsets?.top)
153+ paddingBoxRadii[2 ] =
154+ innerRadius(computedBorderRadii.topRight.horizontal, computedBorderInsets?.right)
155+ paddingBoxRadii[3 ] =
156+ innerRadius(computedBorderRadii.topRight.vertical, computedBorderInsets?.top)
157+ paddingBoxRadii[4 ] =
158+ innerRadius(computedBorderRadii.bottomRight.horizontal, computedBorderInsets?.right)
159+ paddingBoxRadii[5 ] =
160+ innerRadius(computedBorderRadii.bottomRight.vertical, computedBorderInsets?.bottom)
161+ paddingBoxRadii[6 ] =
162+ innerRadius(computedBorderRadii.bottomLeft.horizontal, computedBorderInsets?.left)
163+ paddingBoxRadii[7 ] =
164+ innerRadius(computedBorderRadii.bottomLeft.vertical, computedBorderInsets?.bottom)
165+ }
108166
109167 val x = offsetX.dpToPx()
110168 val y = offsetY.dpToPx()
111169 val spreadExtent = spread.dpToPx()
112- val innerRect =
113- RectF (paddingBoxRect).apply {
114- if (2 * spreadExtent > paddingBoxRect.width()) {
115- setEmpty()
116- } else {
117- inset(spreadExtent, spreadExtent)
118- }
119- offset(x, y)
120- }
170+ innerRect.set(paddingBoxRect)
171+ if (2 * spreadExtent > paddingBoxRect.width()) {
172+ innerRect.setEmpty()
173+ } else {
174+ innerRect.inset(spreadExtent, spreadExtent)
175+ }
176+ innerRect.offset(x, y)
121177
122178 // Graciously stolen from Blink
123179 // https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/paint/box_painter_base.cc;l=338;drc=0a301506035e13015ea5c8dd39164d0d5954fa60
124180 val blurExtent = FilterHelper .sigmaToRadius(blurRadius)
125- val outerRect =
126- RectF (innerRect).apply {
127- set(paddingBoxRect)
128- inset(- blurExtent, - blurExtent)
129- union(RectF (innerRect))
130- }
131-
132- canvas.save().let { saveCount ->
133- if (paddingBoxRadii != null ) {
134- canvas.clipPath(
135- Path ().apply { addRoundRect(paddingBoxRect, paddingBoxRadii, Path .Direction .CW ) },
136- )
137-
138- val innerRadii =
139- paddingBoxRadii.map { adjustRadiusForSpread(it, - spreadExtent) }.toFloatArray()
140-
141- canvas.drawDoubleRoundRect(outerRect, ZERO_RADII , innerRect, innerRadii, shadowPaint)
142- } else {
143- canvas.clipRect(paddingBoxRect)
144- canvas.drawDoubleRoundRect(outerRect, ZERO_RADII , innerRect, ZERO_RADII , shadowPaint)
181+ outerRect.set(paddingBoxRect)
182+ outerRect.inset(- blurExtent, - blurExtent)
183+ outerRect.union(innerRect)
184+
185+ if (hasPaddingBoxRadii) {
186+ paddingBoxPath.rewind()
187+ paddingBoxPath.addRoundRect(paddingBoxRect, paddingBoxRadii, Path .Direction .CW )
188+ for (index in innerRadii.indices) {
189+ innerRadii[index] = adjustRadiusForSpread(paddingBoxRadii[index], - spreadExtent)
145190 }
146-
147- canvas.restoreToCount(saveCount)
148191 }
149192 }
150193
0 commit comments