Skip to content

Commit

Permalink
Support loading vector drawables in ImageView (facebook#45354)
Browse files Browse the repository at this point in the history
Summary:
Pull Request resolved: facebook#45354

Fresco has indicated that they have no plans to support loading vector assets and similar drawable types in Drawee-backed views ([issue](facebook/fresco#329), [issue](facebook/fresco#1463), [issue](facebook/fresco#2463)). Guidance has been to instead load the vector drawable onto the backing image view ourselves. On the React Native side, having the ability to load vector drawables has been requested many times ([issue](facebook#16651), [issue](facebook#27502)).

I went this route over using a custom Fresco decoder for XML assets because vector drawables are compiled down into binary XML and I couldn't find a trivial, performant way to parse those files in a context-aware manner. This change only accounts for vector drawables, not any of the other XML-based drawable types (layer lists, level lists, state lists, 9-patch, etc.). Support could be added easily in the future by expanding the `getDrawableIfUnsupported` function.

## Changelog

[Android] [Added] - Added support for rendering XML assets provided to `Image`

Differential Revision: D59530172
  • Loading branch information
Abbondanzo authored and facebook-github-bot committed Jul 10, 2024
1 parent 0fd4a94 commit 2d01b5c
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import android.graphics.Shader;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.VectorDrawable;
import android.net.Uri;
import androidx.annotation.Nullable;
import com.facebook.common.internal.Objects;
Expand Down Expand Up @@ -478,6 +479,17 @@ public void maybeUpdateView() {
? mFadeDurationMs
: mImageSource.isResource() ? 0 : REMOTE_IMAGE_FADE_DURATION_MS);

Drawable drawable = getDrawableIfUnsupported(mImageSource);
if (drawable != null) {
maybeUpdateViewFromDrawable(drawable);
} else {
maybeUpdateViewFromRequest(doResize);
}

mIsDirty = false;
}

private void maybeUpdateViewFromRequest(boolean doResize) {
List<Postprocessor> postprocessors = new LinkedList<>();
if (mIterativeBoxBlurPostProcessor != null) {
postprocessors.add(mIterativeBoxBlurPostProcessor);
Expand Down Expand Up @@ -535,17 +547,43 @@ public void maybeUpdateView() {
}

if (mDownloadListener != null) {
hierarchy.setProgressBarImage(mDownloadListener);
getHierarchy().setProgressBarImage(mDownloadListener);
}

setController(mDraweeControllerBuilder.build());
mIsDirty = false;

// Reset again so the DraweeControllerBuilder clears all it's references. Otherwise, this causes
// a memory leak.
mDraweeControllerBuilder.reset();
}

private void maybeUpdateViewFromDrawable(Drawable drawable) {
final EventDispatcher mEventDispatcher =
UIManagerHelper.getEventDispatcherForReactTag((ReactContext) getContext(), getId());
final boolean shouldNotify = mDownloadListener != null;

if (shouldNotify && mEventDispatcher != null) {
mEventDispatcher.dispatchEvent(
ImageLoadEvent.createLoadStartEvent(
UIManagerHelper.getSurfaceId(ReactImageView.this), getId()));
}

getHierarchy().setImage(drawable, 1, false);

if (shouldNotify && mEventDispatcher != null) {
mEventDispatcher.dispatchEvent(
ImageLoadEvent.createLoadEvent(
UIManagerHelper.getSurfaceId(ReactImageView.this),
getId(),
mImageSource.getSource(),
getWidth(),
getHeight()));
mEventDispatcher.dispatchEvent(
ImageLoadEvent.createLoadEndEvent(
UIManagerHelper.getSurfaceId(ReactImageView.this), getId()));
}
}

// VisibleForTesting
public void setControllerListener(ControllerListener controllerListener) {
mControllerForTesting = controllerListener;
Expand Down Expand Up @@ -609,6 +647,28 @@ private boolean shouldResize(ImageSource imageSource) {
}
}

/**
* Checks if the provided ImageSource should not be requested through Fresco and instead loaded
* directly from the resources table. Fresco explicitly does not support a number of drawable
* types like VectorDrawable but they can still be mounted in the image hierarchy.
*
* @param imageSource
* @return drawable resource if Fresco cannot load the image, null otherwise
*/
private @Nullable Drawable getDrawableIfUnsupported(ImageSource imageSource) {
if (!imageSource.isResource()) {
return null;
}
Drawable drawable =
ResourceDrawableIdHelper.getInstance()
.getResourceDrawable(getContext(), imageSource.getSource());
if (drawable != null && drawable instanceof VectorDrawable) {
return drawable;
} else {
return null;
}
}

@Nullable
private ResizeOptions getResizeOptions() {
int width = Math.round((float) getWidth() * mResizeMultiplier);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="#a4c639"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#000"
android:pathData="M17.6,11.48 L19.44,8.3a0.63,0.63 0,0 0,-1.09 -0.63l-1.88,3.24a11.43,11.43 0,0 0,-8.94 0L5.65,7.67a0.63,0.63 0,0 0,-1.09 0.63L6.4,11.48A10.81,10.81 0,0 0,1 20L23,20A10.81,10.81 0,0 0,17.6 11.48ZM7,17.25A1.25,1.25 0,1 1,8.25 16,1.25 1.25,0 0,1 7,17.25ZM17,17.25A1.25,1.25 0,1 1,18.25 16,1.25 1.25,0 0,1 17,17.25Z" />
</vector>
26 changes: 26 additions & 0 deletions packages/rn-tester/js/examples/Image/ImageExample.js
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,23 @@ class OnPartialLoadExample extends React.Component<
}
}

type VectorDrawableExampleState = {||};

type VectorDrawableExampleProps = $ReadOnly<{||}>;

class VectorDrawableExample extends React.Component<
VectorDrawableExampleProps,
VectorDrawableExampleState,
> {
render(): React.Node {
return (
<View style={styles.flex}>
<Image source={{uri: 'ic_android'}} style={{height: 64, width: 64}} />
</View>
);
}
}

const fullImage: ImageSource = {
uri: IMAGE2,
};
Expand Down Expand Up @@ -1511,4 +1528,13 @@ exports.examples = [
},
platform: 'ios',
},
{
title: 'Vector Drawable',
description:
'Demonstrating an example of loading a vector drawable asset by name',
render: function (): React.Node {
return <VectorDrawableExample />;
},
platform: 'android',
},
];

0 comments on commit 2d01b5c

Please sign in to comment.