masked-view: maskElement cannot be animated on Android

In MaskedElementIOS, maskElement can be nicely animated, however it doesn’t seem to work on Android with this component.

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 14
  • Comments: 23

Most upvoted comments

I have another use case for an animated maskElement that doesn’t work on Android, let me know if this is helpful to provide it.

Right now, the best workaround to this is to use ClipPath from RN SVG but this has serious limitations.

Hardware Accelerated MaskView Animations in React Native Android

This is a workaround for the performance issues with MaskView animations on Android devices in React Native. While the MaskView animations work well on iOS, they pose some performance issues on Android.

The Issue:

When androidRenderingMode is set to software, the animations work but the performance is poor with barely 10 fps. However, with androidRenderingMode set to hardware, the animations work as long as the MaskView element is in motion at initialization. Once the element stops, it freezes and cannot be animated anymore. The performance, however, is far better with 45 - 60 fps.

The Workaround:

The workaround involves keeping the MaskView element always in motion. Trying this from the JavaScript code results in performance issues, with the framerate dropping to around 15-30 fps.

A more efficient solution is to modify the native code to keep the UI thread constantly moving the element. This results in a performance increase of up to 60 fps.

Here’s the patch for the changes that you can use with patch-package:

@react-native-masked-view+masked-view+0.2.9.patch

diff --git a/node_modules/@react-native-masked-view/masked-view/android/src/main/java/org/reactnative/maskedview/RNCMaskedView.java b/node_modules/@react-native-masked-view/masked-view/android/src/main/java/org/reactnative/maskedview/RNCMaskedView.java
index 4615d21..dfa80a8 100644
--- a/node_modules/@react-native-masked-view/masked-view/android/src/main/java/org/reactnative/maskedview/RNCMaskedView.java
+++ b/node_modules/@react-native-masked-view/masked-view/android/src/main/java/org/reactnative/maskedview/RNCMaskedView.java
@@ -7,6 +7,7 @@ import android.graphics.Paint;
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffXfermode;
 import android.view.View;
+import android.animation.ValueAnimator;
 
 import com.facebook.react.views.view.ReactViewGroup;
 
@@ -17,6 +18,7 @@ public class RNCMaskedView extends ReactViewGroup {
   private boolean mBitmapMaskInvalidated = false;
   private Paint mPaint;
   private PorterDuffXfermode mPorterDuffXferMode;
+  private ValueAnimator animator;
 
   public RNCMaskedView(Context context) {
     super(context);
@@ -26,6 +28,18 @@ public class RNCMaskedView extends ReactViewGroup {
 
     mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
     mPorterDuffXferMode = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);
+
+    animator = ValueAnimator.ofFloat(0, 1);
+    animator.setDuration(60000); // Change duration as needed
+    animator.setRepeatCount(ValueAnimator.INFINITE);
+    animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+        @Override
+        public void onAnimationUpdate(ValueAnimator animation) {
+    // Force a refresh on each animation frame
+    invalidate();
+        }
+    });
+    animator.start();
   }
 
   @Override
@@ -112,4 +126,13 @@ public class RNCMaskedView extends ReactViewGroup {
       setLayerType(LAYER_TYPE_HARDWARE, null);
     }
   }
+
+  @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        // Stop the animator when view is detached
+        if (animator != null) {
+            animator.end();
+        }
+    }
 }

This may not be the most elegant solution, but it works and hopefully can inspire a more efficient solution from a more experienced Android engineer.

Thanks to @wcandillon for the Liquid Swipe example that inspired this workaround.

And here’s a video showing how the Liquid Swipe example looks on my Android 10 device with androidRenderingMode="hardware":

https://github.com/react-native-masked-view/masked-view/assets/7797467/d1cf2faa-4079-453a-9515-7d72511013bb

I read in some other issues that it has been fixed because of hardware mode in the new versions, but I installed it and it didn’t work on my android 7.

Pretty sure this shouldn’t be closed.

Hi @wcandillon, still not able to make your liquid-swipe example working with the latest react-native-masked-view version. Any suggestion?

UPDATE: I was missing react-native-gesture-handler android setup. Now it’s working like a charm 😃

Has there been any progress on solving this?

I can’t display an image inside the mask view with another image as the maskElement.

<MaskedView maskElement={( <Image source={require('../assets/image_mask.png')} /> )} > <image source={image} /> </MaskedView>

It does not render anything. If I set a backgroundcolor on maskelement it render the image (expected).

Reloading the code using fast reload render the image until the next reread of the view. Unfortunately I know too little about android development to attempt solving this myself

animatable element in maskedElement not working here.

I don’t get it. maskedElement is still not animate-able on android using the latest version of this library on android. Is it just me or everyone else too?

@wcandillon for my usage, the performance has been impeccable. You can see in the package I’ve built that the animations are fluid, even with a somewhat complex animation:

https://github.com/crutchcorn/react-native-button-toggle-group

If anyone has a workaround for this 👍