Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Disable background touch events, until images gets settled to its original position. #5

Open
siddharth-kt opened this issue Oct 31, 2021 · 14 comments

Comments

@siddharth-kt
Copy link

siddharth-kt commented Oct 31, 2021

Great library !

It would be great if background touch/scroll etc.. events can be stopped until image gets settled down to its original position.
Because in few case I have found that user tries to scroll away fast (till that time image was not settled properly to its actual position) and since the actual position of that image gets lost, that image get stuck on the screen. Then user needs to restart the app.

Note : I also confirm this issue exists on android

@siddharth-kt
Copy link
Author

@oblador
Kindly sort this issue, its getting worse on pressing the key which navigates to another screen. Image gets stuck on the screen and app needs restart to be used again.

@JayantJoseph
Copy link

Facing the same issue.
Can confirm issue is present in android devices.

I have used the Pinchable component like the example below:

<FlatList
     data={[
       'https://thumbs.dreamstime.com/z/random-square-multicolor-pattern-24853666.jpg',
       'https://cdn4.vectorstock.com/i/1000x1000/51/38/random-square-pattern-seamless-background-vector-25695138.jpg',
       'https://c8.alamy.com/comp/K0D99R/square-background-random-black-and-white-colored-abstract-digital-K0D99R.jpg',
       'https://picsum.photos/800'
     ]}
     renderItem={({ item }) => (
      <Pinchable>
         <Image
           resizeMode="cover"
           source={{
             uri: item
           }}
           style={{
                height: dimension.screenWidth,
                width: dimension.screenWidth
}}
         />
       </Pinchable>
     )}
     ItemSeparatorComponent={() => (<View style={{ width: '100%', height: 10, backgroundColor: 'red' }} />)}
   />

Issue screen record given below

20211101_190841.mp4

From the moment the issue happens, the image will be present there even after navigating to other pages. Only by closing the app completely the image is dismissed.
@oblador

@siddharth-kt
Copy link
Author

Yeah same

@siddharth-kt
Copy link
Author

@oblador kindly reply!

@siddharth-kt
Copy link
Author

@Dein1 ??

@JayantJoseph
Copy link

@oblador is there any updates? is there any possibility to get a prop to know if pinch is happening, so we can disable the scroll looking into it, or something like that

@siddharth-kt
Copy link
Author

@oblador ?

@davidmrp
Copy link

davidmrp commented Feb 1, 2022

Same issue here!

@FelipeSSantos1
Copy link

Are you using the library with something like react-native-pager-view lib as showed here?

I am using it, and that issue happen just if I move line 10 to after the line 12 inside of component, if I declare it as the example shows, declaring AnimatedPagerView outside of component it works like a charm.

@hristowwe
Copy link

Are you using the library with something like react-native-pager-view lib as showed here?

I am using it, and that issue happen just if I move line 10 to after the line 12 inside of component, if I declare it as the example shows, declaring AnimatedPagerView outside of component it works like a charm.

Hello, can you show simple example?

@pierroo
Copy link

pierroo commented Nov 30, 2022

Has there been any progress on this issue?

@frodriguez-hu
Copy link

When the animation start I am trying to turn off the user interactions on the activity with:
ctx.getCurrentActivity().getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);

It works good in most cases but when I keep just one finger it starts to move things from the activity that should be blocked, I am trying to check what is going on

@frodriguez-hu
Copy link

frodriguez-hu commented Feb 27, 2023

This patch fix the issue:

diff --git a/node_modules/react-native-pinchable/android/src/main/java/com/oblador/pinchable/PinchableView.java b/node_modules/react-native-pinchable/android/src/main/java/com/oblador/pinchable/PinchableView.java
index 9f22074..841967c 100644
--- a/node_modules/react-native-pinchable/android/src/main/java/com/oblador/pinchable/PinchableView.java
+++ b/node_modules/react-native-pinchable/android/src/main/java/com/oblador/pinchable/PinchableView.java
@@ -8,6 +8,8 @@ import android.graphics.drawable.BitmapDrawable;
 import android.graphics.Color;
 import android.graphics.PointF;
 import android.graphics.Bitmap;
+import android.os.Handler;
+import android.os.Looper;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.View.OnTouchListener;
@@ -15,8 +17,11 @@ import android.view.View.OnTouchListener;
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
+import android.view.ViewParent;
+import android.view.WindowManager;
 import android.view.animation.DecelerateInterpolator;
 
+import com.facebook.react.uimanager.ThemedReactContext;
 import com.facebook.react.views.view.ReactViewGroup;
 
 public class PinchableView extends ReactViewGroup implements OnTouchListener {
@@ -33,9 +38,10 @@ public class PinchableView extends ReactViewGroup implements OnTouchListener {
     private ValueAnimator currentAnimator = null;
     private ColorDrawable backdrop = null;
     private BitmapDrawable clone = null;
-
+    private ThemedReactContext ctx = null;
     public PinchableView(Context context) {
         super(context);
+        this.ctx = (ThemedReactContext) context;
         this.setOnTouchListener(this);
     }
     
@@ -102,6 +108,8 @@ public class PinchableView extends ReactViewGroup implements OnTouchListener {
     }
 
     private void startGesture(View v, MotionEvent event) {
+        ctx.getCurrentActivity().getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,
+                WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
         if (currentAnimator != null) {
             currentAnimator.cancel();
         }
@@ -155,14 +163,12 @@ public class PinchableView extends ReactViewGroup implements OnTouchListener {
 
     private void endGesture(MotionEvent event) {
         active = false;
-        this.getParent().requestDisallowInterceptTouchEvent(false);
         if (currentAnimator != null) {
             currentAnimator.cancel();
         }
-
         ValueAnimator animator = ValueAnimator.ofFloat(1, 0);
         animator.setDuration(animationDuration);
-        animator.setInterpolator(new DecelerateInterpolator(1.5f));
+        animator.setInterpolator(new DecelerateInterpolator(5f));
         animator.addUpdateListener(updatedAnimation -> {
             float animatedValue = (float)updatedAnimation.getAnimatedValue();
             setCloneTransforms(translation.x * animatedValue, translation.y * animatedValue, 1 + (scale - 1) * animatedValue);
@@ -171,13 +177,22 @@ public class PinchableView extends ReactViewGroup implements OnTouchListener {
         animator.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
+                super.onAnimationEnd(animation);
                 endAnimation();
             }
 
             @Override
             public void onAnimationCancel(Animator animation) {
+                super.onAnimationCancel(animation);
                 endAnimation();
             }
+
+            @Override
+            public void onAnimationStart(Animator animation) {
+                super.onAnimationStart(animation);
+                ctx.getCurrentActivity().getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,
+                        WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
+            }
         });
         animator.start();
         currentAnimator = animator;
@@ -195,6 +210,15 @@ public class PinchableView extends ReactViewGroup implements OnTouchListener {
             clone = null;
         }
         setVisibility(View.VISIBLE);
+        ViewParent parent = this.getParent();
+        final Handler handler = new Handler(Looper.getMainLooper());
+        handler.postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                ctx.getCurrentActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
+                parent.requestDisallowInterceptTouchEvent(false);
+            }
+        }, 100);
     }
 
     public void setMinimumZoomScale(float minimumZoomScale) {

This is the entire PinchableView.java File:

package com.oblador.pinchable;

import java.lang.Math;

import android.content.Context;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.Color;
import android.graphics.PointF;
import android.graphics.Bitmap;
import android.os.Handler;
import android.os.Looper;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.view.ViewParent;
import android.view.WindowManager;
import android.view.animation.DecelerateInterpolator;

import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.views.view.ReactViewGroup;

public class PinchableView extends ReactViewGroup implements OnTouchListener {
    private final int animationDuration = 400;
    private float minScale = 1f;
    private float maxScale = 3f;
    private boolean active = false;
    private int sourceLocation[] = new int[2];
    private int sourceDimensions[] = new int[2];
    private PointF initialTouchPoint = new PointF();
    private float initialSpacing = 0f;
    private PointF translation = new PointF();
    private float scale = 1;
    private ValueAnimator currentAnimator = null;
    private ColorDrawable backdrop = null;
    private BitmapDrawable clone = null;
    private ThemedReactContext ctx = null;
    public PinchableView(Context context) {
        super(context);
        this.ctx = (ThemedReactContext) context;
        this.setOnTouchListener(this);
    }
    
    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        // Block touch events on children
        return true;
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        switch (event.getAction() & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_POINTER_DOWN:
                if (!active && event.getPointerCount() >= 2) {
                    startGesture(v, event);
                }
                break;
            case MotionEvent.ACTION_POINTER_UP:
                if (active && event.getPointerCount() < 2) {
                    endGesture(event);
                }
                break;
            case MotionEvent.ACTION_MOVE:
                if (active) {
                    if (event.getPointerCount() < 2) {
                        endGesture(event);
                    } else {
                        moveGesture(event);
                    }
                }
                break;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                if (active) {
                    endGesture(event);
                }
                break;
            default:
                break;
        }

        return true;
    }

    private float spacing(MotionEvent event) {
        float x = event.getX(0) - event.getX(1);
        float y = event.getY(0) - event.getY(1);
        return (float) Math.sqrt(x * x + y * y);
    }

    private void midPoint(PointF point, MotionEvent event)  {
        float x = event.getX(0) + event.getX(1);
        float y = event.getY(0) + event.getY(1);
        point.set(x / 2, y / 2);
    }

    private void setCloneTransforms(float translateX, float translateY, float scale) {
        int x = (int) (sourceLocation[0] + translateX - sourceDimensions[0] * (scale - 1) / 2);
        int y = (int) (sourceLocation[1] + translateY - sourceDimensions[1] * (scale - 1) / 2);
        int width = (int) (sourceDimensions[0] * scale);
        int height = (int) (sourceDimensions[1] * scale);
        clone.setBounds(x, y, x + width, y + height);
        backdrop.setAlpha((int) (255 * Math.min(scale - 1, 0.7)));
    }

    private void startGesture(View v, MotionEvent event) {
        ctx.getCurrentActivity().getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,
                WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
        if (currentAnimator != null) {
            currentAnimator.cancel();
        }
        View rootView = this.getRootView();
        if (clone != null) {
            rootView.getOverlay().remove(clone);
            clone = null;
        }
        Bitmap snapshot = null;
        v.setDrawingCacheEnabled(true);
        v.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_AUTO);
        try {
            snapshot = Bitmap.createBitmap(v.getDrawingCache(true));
            clone = new BitmapDrawable(this.getContext().getResources(), snapshot);
        } catch(Exception ex) {
            ex.printStackTrace();
            return;
        }

        active = true;

        if (backdrop == null) {
            backdrop = new ColorDrawable(Color.BLACK);
            backdrop.setAlpha(0);
            backdrop.setBounds(0, 0, rootView.getWidth(), rootView.getHeight());
            rootView.getOverlay().add(backdrop);
        }

        v.getLocationInWindow(sourceLocation);
        sourceDimensions[0] = v.getWidth();
        sourceDimensions[1] = v.getHeight();
        setCloneTransforms(0, 0, 1);
        rootView.getOverlay().add(clone);
        setVisibility(View.INVISIBLE);
        midPoint(initialTouchPoint, event);
        initialSpacing = spacing(event);
        translation.set(0, 0);
        scale = 1;
        this.getParent().requestDisallowInterceptTouchEvent(true);
    }

    private void moveGesture(MotionEvent event) {
        PointF current = new PointF();
        midPoint(current, event);
        float deltaX = current.x - initialTouchPoint.x;
        float deltaY = current.y - initialTouchPoint.y;
        scale = Math.min(maxScale, Math.max(minScale, spacing(event) / initialSpacing));
        setCloneTransforms(deltaX, deltaY, scale);
        translation.set(deltaX, deltaY);
    }

    private void endGesture(MotionEvent event) {
        active = false;
        if (currentAnimator != null) {
            currentAnimator.cancel();
        }
        ValueAnimator animator = ValueAnimator.ofFloat(1, 0);
        animator.setDuration(animationDuration);
        animator.setInterpolator(new DecelerateInterpolator(5f));
        animator.addUpdateListener(updatedAnimation -> {
            float animatedValue = (float)updatedAnimation.getAnimatedValue();
            setCloneTransforms(translation.x * animatedValue, translation.y * animatedValue, 1 + (scale - 1) * animatedValue);
        });

        animator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                endAnimation();
            }

            @Override
            public void onAnimationCancel(Animator animation) {
                super.onAnimationCancel(animation);
                endAnimation();
            }

            @Override
            public void onAnimationStart(Animator animation) {
                super.onAnimationStart(animation);
                ctx.getCurrentActivity().getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,
                        WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
            }
        });
        animator.start();
        currentAnimator = animator;
    }

    private void endAnimation() {
        currentAnimator = null;
        View rootView = this.getRootView();
        if (backdrop != null) {
            rootView.getOverlay().remove(backdrop);
            backdrop = null;
        }
        if (clone != null) {
            rootView.getOverlay().remove(clone);
            clone = null;
        }
        setVisibility(View.VISIBLE);
        ViewParent parent = this.getParent();
        final Handler handler = new Handler(Looper.getMainLooper());
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                ctx.getCurrentActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
                parent.requestDisallowInterceptTouchEvent(false);
            }
        }, 100);
    }

    public void setMinimumZoomScale(float minimumZoomScale) {
        minScale = minimumZoomScale;
    }

    public void setMaximumZoomScale(float maximumZoomScale) {
        maxScale = maximumZoomScale;
    }
}

I will create a pr when I have some time

@frodriguez-hu
Copy link

This pr fix the issue:

#14

@siddharth-kt @oblador @pierroo

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants