1 /*
<lambda>null2 * Copyright (C) 2022 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.compose.ui.graphics.painter
18
19 import android.graphics.drawable.Animatable
20 import android.graphics.drawable.ColorDrawable
21 import android.graphics.drawable.Drawable
22 import android.os.Build
23 import android.os.Handler
24 import android.os.Looper
25 import android.view.View
26 import androidx.compose.runtime.Composable
27 import androidx.compose.runtime.RememberObserver
28 import androidx.compose.runtime.getValue
29 import androidx.compose.runtime.mutableStateOf
30 import androidx.compose.runtime.remember
31 import androidx.compose.runtime.setValue
32 import androidx.compose.ui.geometry.Size
33 import androidx.compose.ui.graphics.Color
34 import androidx.compose.ui.graphics.ColorFilter
35 import androidx.compose.ui.graphics.asAndroidColorFilter
36 import androidx.compose.ui.graphics.drawscope.DrawScope
37 import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
38 import androidx.compose.ui.graphics.nativeCanvas
39 import androidx.compose.ui.graphics.painter.ColorPainter
40 import androidx.compose.ui.graphics.painter.Painter
41 import androidx.compose.ui.graphics.withSave
42 import androidx.compose.ui.unit.LayoutDirection
43 import kotlin.math.roundToInt
44
45 /**
46 * *************************************************************************************************
47 * This file was forked from
48 * https://github.com/google/accompanist/blob/main/drawablepainter/src/main/java/com/google/accompanist/drawablepainter/DrawablePainter.kt
49 */
50 private val MAIN_HANDLER by lazy(LazyThreadSafetyMode.NONE) { Handler(Looper.getMainLooper()) }
51
52 /**
53 * A [Painter] which draws an Android [Drawable] and supports [Animatable] drawables. Instances
54 * should be remembered to be able to start and stop [Animatable] animations.
55 *
56 * Instances are usually retrieved from [rememberDrawablePainter].
57 */
58 public class DrawablePainter(public val drawable: Drawable) : Painter(), RememberObserver {
59 private var drawInvalidateTick by mutableStateOf(0)
60 private var drawableIntrinsicSize by mutableStateOf(drawable.intrinsicSize)
61
<lambda>null62 private val callback: Drawable.Callback by lazy {
63 object : Drawable.Callback {
64 override fun invalidateDrawable(d: Drawable) {
65 // Update the tick so that we get re-drawn
66 drawInvalidateTick++
67 // Update our intrinsic size too
68 drawableIntrinsicSize = drawable.intrinsicSize
69 }
70
71 override fun scheduleDrawable(d: Drawable, what: Runnable, time: Long) {
72 MAIN_HANDLER.postAtTime(what, time)
73 }
74
75 override fun unscheduleDrawable(d: Drawable, what: Runnable) {
76 MAIN_HANDLER.removeCallbacks(what)
77 }
78 }
79 }
80
81 init {
82 if (drawable.intrinsicWidth >= 0 && drawable.intrinsicHeight >= 0) {
83 // Update the drawable's bounds to match the intrinsic size
84 drawable.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
85 }
86 }
87
onRememberednull88 override fun onRemembered() {
89 drawable.callback = callback
90 drawable.setVisible(true, true)
91 if (drawable is Animatable) drawable.start()
92 }
93
onAbandonednull94 override fun onAbandoned(): Unit = onForgotten()
95
96 override fun onForgotten() {
97 if (drawable is Animatable) drawable.stop()
98 drawable.setVisible(false, false)
99 drawable.callback = null
100 }
101
applyAlphanull102 override fun applyAlpha(alpha: Float): Boolean {
103 drawable.alpha = (alpha * 255).roundToInt().coerceIn(0, 255)
104 return true
105 }
106
applyColorFilternull107 override fun applyColorFilter(colorFilter: ColorFilter?): Boolean {
108 drawable.colorFilter = colorFilter?.asAndroidColorFilter()
109 return true
110 }
111
applyLayoutDirectionnull112 override fun applyLayoutDirection(layoutDirection: LayoutDirection): Boolean {
113 if (Build.VERSION.SDK_INT >= 23) {
114 return drawable.setLayoutDirection(
115 when (layoutDirection) {
116 LayoutDirection.Ltr -> View.LAYOUT_DIRECTION_LTR
117 LayoutDirection.Rtl -> View.LAYOUT_DIRECTION_RTL
118 }
119 )
120 }
121 return false
122 }
123
124 override val intrinsicSize: Size
125 get() = drawableIntrinsicSize
126
onDrawnull127 override fun DrawScope.onDraw() {
128 drawIntoCanvas { canvas ->
129 // Reading this ensures that we invalidate when invalidateDrawable() is called
130 drawInvalidateTick
131
132 // Update the Drawable's bounds
133 drawable.setBounds(0, 0, size.width.roundToInt(), size.height.roundToInt())
134
135 canvas.withSave { drawable.draw(canvas.nativeCanvas) }
136 }
137 }
138 }
139
140 /**
141 * Remembers [Drawable] wrapped up as a [Painter]. This function attempts to un-wrap the drawable
142 * contents and use Compose primitives where possible.
143 *
144 * If the provided [drawable] is `null`, an empty no-op painter is returned.
145 *
146 * This function tries to dispatch lifecycle events to [drawable] as much as possible from within
147 * Compose.
148 *
149 * @sample com.google.accompanist.sample.drawablepainter.BasicSample
150 */
151 @Composable
rememberDrawablePainternull152 public fun rememberDrawablePainter(drawable: Drawable?): Painter =
153 remember(drawable) {
154 when (drawable) {
155 null -> EmptyPainter
156 is ColorDrawable -> ColorPainter(Color(drawable.color))
157 // Since the DrawablePainter will be remembered and it implements RememberObserver, it
158 // will receive the necessary events
159 else -> DrawablePainter(drawable.mutate())
160 }
161 }
162
163 private val Drawable.intrinsicSize: Size
164 get() =
165 when {
166 // Only return a finite size if the drawable has an intrinsic size
167 intrinsicWidth >= 0 && intrinsicHeight >= 0 -> {
168 Size(width = intrinsicWidth.toFloat(), height = intrinsicHeight.toFloat())
169 }
170 else -> Size.Unspecified
171 }
172
173 internal object EmptyPainter : Painter() {
174 override val intrinsicSize: Size
175 get() = Size.Unspecified
onDrawnull176 override fun DrawScope.onDraw() {}
177 }
178