Skip to content

Commit 69fc9bc

Browse files
url() function support in background on android
1 parent 2b74f98 commit 69fc9bc

File tree

4 files changed

+154
-23
lines changed

4 files changed

+154
-23
lines changed

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/drawable/BackgroundImageDrawable.kt

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
package com.facebook.react.uimanager.drawable
99

1010
import android.content.Context
11+
import android.graphics.Bitmap
1112
import android.graphics.Canvas
1213
import android.graphics.ColorFilter
1314
import android.graphics.Paint
@@ -45,11 +46,13 @@ internal class BackgroundImageDrawable(
4546
private var backgroundImageClipPath: Path? = null
4647
private var backgroundPositioningArea: RectF? = null
4748
private var backgroundPaintingArea: RectF? = null
49+
private val urlImageLoader = BackgroundImageURLLoader()
4850

4951
var backgroundImageLayers: List<BackgroundImageLayer>? = null
5052
set(value) {
5153
if (field != value) {
5254
field = value
55+
loadUrlImages(value)
5356
invalidateSelf()
5457
}
5558
}
@@ -111,7 +114,7 @@ internal class BackgroundImageDrawable(
111114
}
112115

113116
override fun draw(canvas: Canvas) {
114-
if (backgroundImageLayers == null || backgroundImageLayers?.isEmpty() == true) {
117+
if (backgroundImageLayers.isNullOrEmpty()) {
115118
return
116119
}
117120

@@ -138,13 +141,29 @@ internal class BackgroundImageDrawable(
138141
val repeat = backgroundRepeat?.let { it.getOrNull(index % it.size) }
139142
val position = backgroundPosition?.let { it.getOrNull(index % it.size) }
140143

144+
val urlBitmap: Bitmap?
145+
val (intrinsicWidth, intrinsicHeight) = when (backgroundImageLayer) {
146+
is BackgroundImageLayer.GradientLayer -> {
147+
urlBitmap = null
148+
backgroundPositioningArea.width() to backgroundPositioningArea.height()
149+
}
150+
is BackgroundImageLayer.URLImageLayer -> {
151+
val bitmap = backgroundImageLayer.loadedBitmap
152+
if (bitmap == null) {
153+
continue
154+
}
155+
urlBitmap = bitmap
156+
bitmap.width.toFloat() to bitmap.height.toFloat()
157+
}
158+
}
159+
141160
// 2. Calculate the size of a single tile.
142161
val (tileWidth, tileHeight) =
143162
calculateBackgroundImageSize(
144163
backgroundPositioningArea.width(),
145164
backgroundPositioningArea.height(),
146-
backgroundPositioningArea.width(),
147-
backgroundPositioningArea.height(),
165+
intrinsicWidth,
166+
intrinsicHeight,
148167
size,
149168
repeat,
150169
)
@@ -153,8 +172,12 @@ internal class BackgroundImageDrawable(
153172
continue
154173
}
155174

156-
// 3. Set paint shader
157-
backgroundPaint.setShader(backgroundImageLayer.getShader(tileWidth, tileHeight))
175+
// 3. Set paint shader for gradients (URL images don't use shaders)
176+
if (backgroundImageLayer is BackgroundImageLayer.GradientLayer) {
177+
backgroundPaint.setShader(backgroundImageLayer.getShader(tileWidth, tileHeight))
178+
} else {
179+
backgroundPaint.setShader(null)
180+
}
158181

159182
// 4. Calculate spacing, x and y tiles count and position for tiles
160183
var (initialX, initialY) = calculateBackgroundPosition(tileWidth, tileHeight, position)
@@ -253,7 +276,13 @@ internal class BackgroundImageDrawable(
253276
repeat(yTilesCount) {
254277
canvas.save()
255278
canvas.translate(translateX, translateY)
256-
canvas.drawRect(0f, 0f, tileWidth, tileHeight, backgroundPaint)
279+
if (urlBitmap != null) {
280+
val srcRect = Rect(0, 0, urlBitmap.width, urlBitmap.height)
281+
val dstRect = RectF(0f, 0f, tileWidth, tileHeight)
282+
canvas.drawBitmap(urlBitmap, srcRect, dstRect, backgroundPaint)
283+
} else {
284+
canvas.drawRect(0f, 0f, tileWidth, tileHeight, backgroundPaint)
285+
}
257286
canvas.restore()
258287
translateY += tileHeight + ySpacing
259288
}
@@ -412,4 +441,14 @@ internal class BackgroundImageDrawable(
412441

413442
return translateX to translateY
414443
}
444+
445+
private fun loadUrlImages(layers: List<BackgroundImageLayer>?) {
446+
val urlLayers = layers?.filterIsInstance<BackgroundImageLayer.URLImageLayer>()
447+
if (urlLayers.isNullOrEmpty()) {
448+
urlImageLoader.cancelAllRequests()
449+
return
450+
}
451+
452+
urlImageLoader.loadImages(urlLayers) { invalidateSelf() }
453+
}
415454
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
package com.facebook.react.uimanager.drawable
9+
10+
import android.graphics.Bitmap
11+
import android.net.Uri
12+
import com.facebook.common.executors.UiThreadImmediateExecutorService
13+
import com.facebook.common.logging.FLog
14+
import com.facebook.common.references.CloseableReference
15+
import com.facebook.datasource.DataSource
16+
import com.facebook.drawee.backends.pipeline.Fresco
17+
import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber
18+
import com.facebook.imagepipeline.image.CloseableImage
19+
import com.facebook.imagepipeline.request.ImageRequestBuilder
20+
import com.facebook.react.uimanager.style.BackgroundImageLayer
21+
22+
public class BackgroundImageURLLoader {
23+
private companion object {
24+
private const val TAG = "BackgroundImageURLLoader"
25+
}
26+
27+
private val pendingRequests = mutableMapOf<String, DataSource<CloseableReference<CloseableImage>>>()
28+
private var onComplete: (() -> Unit)? = null
29+
30+
public fun loadImages(
31+
layers: List<BackgroundImageLayer.URLImageLayer>,
32+
onComplete: () -> Unit
33+
) {
34+
cancelAllRequests()
35+
36+
if (layers.isEmpty()) {
37+
onComplete()
38+
return
39+
}
40+
41+
this.onComplete = onComplete
42+
for (layer in layers) {
43+
val imageRequest = ImageRequestBuilder.newBuilderWithSource(Uri.parse(layer.uri)).build()
44+
val imagePipeline = Fresco.getImagePipeline()
45+
val dataSource = imagePipeline.fetchDecodedImage(imageRequest, null)
46+
47+
pendingRequests[layer.uri] = dataSource
48+
49+
dataSource.subscribe(
50+
object : BaseBitmapDataSubscriber() {
51+
override fun onNewResultImpl(bitmap: Bitmap?) {
52+
if (bitmap != null) {
53+
layer.loadedBitmap = bitmap.copy(bitmap.config ?: Bitmap.Config.ARGB_8888, false)
54+
}
55+
onRequestComplete(layer.uri)
56+
}
57+
58+
override fun onFailureImpl(dataSource: DataSource<CloseableReference<CloseableImage>>) {
59+
FLog.w(TAG, "Failed to load background image: ${layer.uri}-${dataSource.failureCause}")
60+
onRequestComplete(layer.uri)
61+
}
62+
},
63+
UiThreadImmediateExecutorService.getInstance()
64+
)
65+
}
66+
}
67+
68+
private fun onRequestComplete(uri: String) {
69+
pendingRequests.remove(uri)
70+
if (pendingRequests.isEmpty()) {
71+
onComplete?.invoke()
72+
}
73+
}
74+
75+
public fun cancelAllRequests() {
76+
for (dataSource in pendingRequests.values) {
77+
dataSource.close()
78+
}
79+
pendingRequests.clear()
80+
onComplete = null
81+
}
82+
}

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/style/BackgroundImageLayer.kt

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,38 +8,45 @@
88
package com.facebook.react.uimanager.style
99

1010
import android.content.Context
11+
import android.graphics.Bitmap
1112
import android.graphics.Shader
1213
import com.facebook.react.bridge.ReadableMap
1314
import com.facebook.react.bridge.ReadableType
1415

15-
public class BackgroundImageLayer() {
16-
private lateinit var gradient: Gradient
16+
public sealed class BackgroundImageLayer {
17+
public class GradientLayer internal constructor(private val gradient: Gradient) : BackgroundImageLayer() {
18+
public fun getShader(width: Float, height: Float): Shader = gradient.getShader(width, height)
19+
}
1720

18-
private constructor(gradient: Gradient) : this() {
19-
this.gradient = gradient
21+
public class URLImageLayer(public val uri: String) : BackgroundImageLayer() {
22+
public var loadedBitmap: Bitmap? = null
2023
}
2124

2225
public companion object {
23-
public fun parse(gradientMap: ReadableMap?, context: Context): BackgroundImageLayer? {
24-
if (gradientMap == null) {
26+
public fun parse(backgroundImageMap: ReadableMap?, context: Context): BackgroundImageLayer? {
27+
if (backgroundImageMap == null) {
2528
return null
2629
}
27-
val gradient = parseGradient(gradientMap, context) ?: return null
28-
return BackgroundImageLayer(gradient)
29-
}
3030

31-
private fun parseGradient(gradientMap: ReadableMap, context: Context): Gradient? {
32-
if (!gradientMap.hasKey("type") || gradientMap.getType("type") != ReadableType.String) {
31+
if (!backgroundImageMap.hasKey("type") || backgroundImageMap.getType("type") != ReadableType.String) {
3332
return null
3433
}
3534

36-
return when (gradientMap.getString("type")) {
37-
"linear-gradient" -> LinearGradient.parse(gradientMap, context)
38-
"radial-gradient" -> RadialGradient.parse(gradientMap, context)
35+
return when (backgroundImageMap.getString("type")) {
36+
"linear-gradient" -> {
37+
val gradient = LinearGradient.parse(backgroundImageMap, context) ?: return null
38+
GradientLayer(gradient)
39+
}
40+
"radial-gradient" -> {
41+
val gradient = RadialGradient.parse(backgroundImageMap, context) ?: return null
42+
GradientLayer(gradient)
43+
}
44+
"url" -> {
45+
val uri = backgroundImageMap.getString("uri") ?: return null
46+
URLImageLayer(uri)
47+
}
3948
else -> null
4049
}
4150
}
4251
}
43-
44-
public fun getShader(width: Float, height: Float): Shader = gradient.getShader(width, height)
4552
}

packages/react-native/ReactCommon/react/renderer/components/view/CMakeLists.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@ add_library(rrc_view OBJECT ${rrc_view_SRC})
2222
react_native_android_selector(platform_DIR
2323
${CMAKE_CURRENT_SOURCE_DIR}/platform/android/
2424
${CMAKE_CURRENT_SOURCE_DIR}/platform/cxx/)
25-
target_include_directories(rrc_view PUBLIC ${REACT_COMMON_DIR} ${platform_DIR})
25+
react_native_android_selector(imagemanager_platform_DIR
26+
${REACT_COMMON_DIR}/react/renderer/imagemanager/platform/android/
27+
${REACT_COMMON_DIR}/react/renderer/imagemanager/platform/cxx/)
28+
target_include_directories(rrc_view PUBLIC ${REACT_COMMON_DIR} ${platform_DIR} ${imagemanager_platform_DIR})
2629

2730
target_link_libraries(rrc_view
2831
folly_runtime

0 commit comments

Comments
 (0)