Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 90ee0590 authored by Matt Casey's avatar Matt Casey
Browse files

Prevent CropView from getting confused by multiple pointers.

Not really implementing full multitouch, but ensuring that letting up on
the first finger doesn't just continue the event with another one.

Also refactor some of the MagnifierView's event listening to decouple it
from CropView a bit more.

Bug: 183240525
Test: Touch one edge with one finger, then put another one down and lift
      the first, ensure that movement doesn't continue with second finger.
Change-Id: Ia9f0293a914fb85f80a746f1093a3b23b23eab73
parent 8ccd7eb5
Loading
Loading
Loading
Loading
+64 −22
Original line number Diff line number Diff line
@@ -71,6 +71,7 @@ public class CropView extends View {
    private int mImageWidth;

    private CropBoundary mCurrentDraggingBoundary = CropBoundary.NONE;
    private int mActivePointerId;
    // The starting value of mCurrentDraggingBoundary's crop, used to compute touch deltas.
    private float mMovementStartValue;
    private float mStartingY;  // y coordinate of ACTION_DOWN
@@ -138,35 +139,60 @@ public class CropView extends View {
    public boolean onTouchEvent(MotionEvent event) {
        int topPx = fractionToVerticalPixels(mCrop.top);
        int bottomPx = fractionToVerticalPixels(mCrop.bottom);
        switch (event.getAction()) {
        switch (event.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:
                mCurrentDraggingBoundary = nearestBoundary(event, topPx, bottomPx,
                        fractionToHorizontalPixels(mCrop.left),
                        fractionToHorizontalPixels(mCrop.right));
                if (mCurrentDraggingBoundary != CropBoundary.NONE) {
                    mActivePointerId = event.getPointerId(0);
                    mStartingY = event.getY();
                    mStartingX = event.getX();
                    mMovementStartValue = getBoundaryPosition(mCurrentDraggingBoundary);
                    updateListener(event);
                    updateListener(MotionEvent.ACTION_DOWN, event.getX());
                    mMotionRange = getAllowedValues(mCurrentDraggingBoundary);
                }
                return true;
            case MotionEvent.ACTION_MOVE:
                if (mCurrentDraggingBoundary != CropBoundary.NONE) {
                    float deltaPx = isVertical(mCurrentDraggingBoundary) ? event.getY() - mStartingY
                            : event.getX() - mStartingX;
                    float delta = pixelDistanceToFraction((int) deltaPx, mCurrentDraggingBoundary);
                    int pointerIndex = event.findPointerIndex(mActivePointerId);
                    if (pointerIndex >= 0) {
                        // Original pointer still active, do the move.
                        float deltaPx = isVertical(mCurrentDraggingBoundary)
                                ? event.getY(pointerIndex) - mStartingY
                                : event.getX(pointerIndex) - mStartingX;
                        float delta = pixelDistanceToFraction((int) deltaPx,
                                mCurrentDraggingBoundary);
                        setBoundaryPosition(mCurrentDraggingBoundary,
                                mMotionRange.clamp(mMovementStartValue + delta));
                    updateListener(event);
                        updateListener(MotionEvent.ACTION_MOVE, event.getX(pointerIndex));
                        invalidate();
                    }
                    return true;
                }
                break;
            case MotionEvent.ACTION_POINTER_DOWN:
                if (mActivePointerId == event.getPointerId(event.getActionIndex())
                        && mCurrentDraggingBoundary != CropBoundary.NONE) {
                    updateListener(MotionEvent.ACTION_DOWN, event.getX(event.getActionIndex()));
                    return true;
                }
                break;
            case MotionEvent.ACTION_POINTER_UP:
                if (mActivePointerId == event.getPointerId(event.getActionIndex())
                        && mCurrentDraggingBoundary != CropBoundary.NONE) {
                    updateListener(MotionEvent.ACTION_UP, event.getX(event.getActionIndex()));
                    return true;
                }
                break;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                if (mCurrentDraggingBoundary != CropBoundary.NONE) {
                    updateListener(event);
                if (mCurrentDraggingBoundary != CropBoundary.NONE
                        && mActivePointerId == event.getPointerId(mActivePointerId)) {
                    updateListener(MotionEvent.ACTION_UP, event.getX(0));
                    return true;
                }
                break;
        }
        return super.onTouchEvent(event);
    }
@@ -308,12 +334,29 @@ public class CropView extends View {
        return null;
    }

    private void updateListener(MotionEvent event) {
        if (mCropInteractionListener != null && (isVertical(mCurrentDraggingBoundary))) {
    /**
     * @param action either ACTION_DOWN, ACTION_UP or ACTION_MOVE.
     * @param x coordinate of the relevant pointer.
     */
    private void updateListener(int action, float x) {
        if (mCropInteractionListener != null && isVertical(mCurrentDraggingBoundary)) {
            float boundaryPosition = getBoundaryPosition(mCurrentDraggingBoundary);
            mCropInteractionListener.onCropMotionEvent(event, mCurrentDraggingBoundary,
            switch (action) {
                case MotionEvent.ACTION_DOWN:
                    mCropInteractionListener.onCropDragStarted(mCurrentDraggingBoundary,
                            boundaryPosition, fractionToVerticalPixels(boundaryPosition),
                            (mCrop.left + mCrop.right) / 2, x);
                    break;
                case MotionEvent.ACTION_MOVE:
                    mCropInteractionListener.onCropDragMoved(mCurrentDraggingBoundary,
                            boundaryPosition, fractionToVerticalPixels(boundaryPosition),
                    (mCrop.left + mCrop.right) / 2);
                            (mCrop.left + mCrop.right) / 2, x);
                    break;
                case MotionEvent.ACTION_UP:
                    mCropInteractionListener.onCropDragComplete();
                    break;

            }
        }
    }

@@ -545,12 +588,11 @@ public class CropView extends View {
     * Listen for crop motion events and state.
     */
    public interface CropInteractionListener {
        /**
         * Called whenever CropView has a MotionEvent that can impact the position of the crop
         * boundaries.
         */
        void onCropMotionEvent(MotionEvent event, CropBoundary boundary, float boundaryPosition,
                int boundaryPositionPx, float horizontalCenter);
        void onCropDragStarted(CropBoundary boundary, float boundaryPosition,
                int boundaryPositionPx, float horizontalCenter, float x);
        void onCropDragMoved(CropBoundary boundary, float boundaryPosition,
                int boundaryPositionPx, float horizontalCenter, float x);
        void onCropDragComplete();
    }

    static class SavedState extends BaseSavedState {
+41 −40
Original line number Diff line number Diff line
@@ -28,7 +28,6 @@ import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewPropertyAnimator;

@@ -148,16 +147,14 @@ public class MagnifierView extends View implements CropView.CropInteractionListe
    }

    @Override
    public void onCropMotionEvent(MotionEvent event, CropView.CropBoundary boundary,
            float cropPosition, int cropPositionPx, float horizontalCenter) {
    public void onCropDragStarted(CropView.CropBoundary boundary, float boundaryPosition,
            int boundaryPositionPx, float horizontalCenter, float x) {
        mCropBoundary = boundary;
        mLastCenter = horizontalCenter;
        boolean touchOnRight = event.getX() > getParentWidth() / 2;
        boolean touchOnRight = x > getParentWidth() / 2;
        float translateXTarget = touchOnRight ? 0 : getParentWidth() - getWidth();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mLastCropPosition = cropPosition;
                setTranslationY(cropPositionPx - getHeight() / 2);
        mLastCropPosition = boundaryPosition;
        setTranslationY(boundaryPositionPx - getHeight() / 2);
        setPivotX(getWidth() / 2);
        setPivotY(getHeight() / 2);
        setScaleX(0.2f);
@@ -169,11 +166,16 @@ public class MagnifierView extends View implements CropView.CropInteractionListe
                animate().alpha(1f).translationX(translateXTarget).scaleX(1f).scaleY(1f);
        mTranslationAnimator.setListener(mTranslationAnimatorListener);
        mTranslationAnimator.start();
                break;
            case MotionEvent.ACTION_MOVE:
    }

    @Override
    public void onCropDragMoved(CropView.CropBoundary boundary, float boundaryPosition,
            int boundaryPositionPx, float horizontalCenter, float x) {
        boolean touchOnRight = x > getParentWidth() / 2;
        float translateXTarget = touchOnRight ? 0 : getParentWidth() - getWidth();
        // The touch is near the middle if it's within 10% of the center point.
        // We don't want to animate horizontally if the touch is near the middle.
                boolean nearMiddle = Math.abs(event.getX() - getParentWidth() / 2)
        boolean nearMiddle = Math.abs(x - getParentWidth() / 2)
                < getParentWidth() / 10f;
        boolean viewOnLeft = getTranslationX() < (getParentWidth() - getWidth()) / 2;
        if (!nearMiddle && viewOnLeft != touchOnRight && mTranslationAnimator == null) {
@@ -181,16 +183,15 @@ public class MagnifierView extends View implements CropView.CropInteractionListe
            mTranslationAnimator.setListener(mTranslationAnimatorListener);
            mTranslationAnimator.start();
        }
                mLastCropPosition = cropPosition;
                setTranslationY(cropPositionPx - getHeight() / 2);
        mLastCropPosition = boundaryPosition;
        setTranslationY(boundaryPositionPx - getHeight() / 2);
        invalidate();
                break;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
    }

    @Override
    public void onCropDragComplete() {
        animate().alpha(0).translationX((getParentWidth() - getWidth()) / 2).scaleX(0.2f)
                .scaleY(0.2f).withEndAction(() -> setVisibility(View.INVISIBLE)).start();
                break;
        }
    }

    private Path generateCheckerboard() {