OnTouchListener prevents visual feedback
Asked Answered
D

2

11

I have a ListView which has an OnItemClickListener. On top of that, each row (CardView) has an OnTouchListener in order to implement a swiping gesture.

The OnTouchListener reacts to ACTION_DOWN, ACTION_CANCEL, ACTION_MOVE and ACTION_UP. I use performItemClick() when ACTION_UP happens, so that the OnItemClickListener is called when required.

If I return true in the OnTouchListener, the swiping movement I implemented via ACTION_MOVE works perfectly, clicking the item works as well. However, there is zero visual feedback. Usually, there would be a ripple on Lollipop or a background change on ICS.

If I return False (meaning I don't want to intercept the event), then there is a visual feedback and the clicking works... But my OnTouchListener never intercepts any ACTION_MOVE event. This prevents any swiping.

I tried a variety of solutions, such as using v.setpressed() but it had no effect.

I'm curious to know how I could preserve the ripple (or visual feedback in general) that would happen if my OnTouchListener wasn't intercepting the event.

Here's my OnTouchListener, if you're curious.

private final View.OnTouchListener mTouchListener = new View.OnTouchListener() {
    float mDownX;
    private int mSwipeSlop = -1;
    private boolean mItemPressed;
    private VelocityTracker mVelocityTracker = null;
    private HashMap<Long, Integer> mItemIdTopMap = new HashMap<>();

    @Override
    public boolean onTouch(final View v, MotionEvent event) {
        int index = event.getActionIndex();
        int pointerId = event.getPointerId(index);

        if (mSwipeSlop < 0) {
            mSwipeSlop = ViewConfiguration.get(getActivity())
                    .getScaledTouchSlop();
        }

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                if (mItemPressed) {
                    // Multi-item swipes not handled
                    return false;
                }
                mItemPressed = true;
                mDownX = event.getX();
                if (mVelocityTracker == null) {
                    // Retrieve a new VelocityTracker object to watch the velocity of a motion.
                    mVelocityTracker = VelocityTracker.obtain();
                } else {
                    // Reset the velocity tracker back to its initial state.
                    mVelocityTracker.clear();
                }
                mVelocityTracker.addMovement(event);
                break;
            case MotionEvent.ACTION_CANCEL:
                v.setAlpha(1);
                v.setTranslationX(0);
                mItemPressed = false;
                mVelocityTracker.recycle();
                mVelocityTracker = null;
                break;
            case MotionEvent.ACTION_MOVE: {
                mVelocityTracker.addMovement(event);
                float x = event.getX() + v.getTranslationX();
                float deltaX = x - mDownX;
                float deltaXAbs = Math.abs(deltaX);
                if (!mSwiping) {
                    if (deltaXAbs > mSwipeSlop) {
                        mSwiping = true;
                        getListView().requestDisallowInterceptTouchEvent(true);
                        mBackgroundContainer.showBackground(v.getTop(), v.getHeight());
                    }
                }
                if (mSwiping) {
                    v.setTranslationX((x - mDownX));
                    v.setAlpha(1 - deltaXAbs / v.getWidth());
                }
            }
            break;
            case MotionEvent.ACTION_UP: {
                // User let go - figure out whether to animate the view out, or back into place
                if (mSwiping) {
                    float x = event.getX() + v.getTranslationX();
                    float deltaX = x - mDownX;
                    float deltaXAbs = Math.abs(deltaX);
                    float fractionCovered;
                    float endX;
                    float endAlpha;
                    final boolean remove;
                    mVelocityTracker.computeCurrentVelocity(1000);
                    float velocityX = Math.abs(VelocityTrackerCompat.getXVelocity(mVelocityTracker, pointerId));
                    if (velocityX > 700 || deltaXAbs > v.getWidth() / 4) {
                        // fixme
                        fractionCovered = deltaXAbs / v.getWidth();
                        endX = deltaX < 0 ? -v.getWidth() : v.getWidth();
                        endAlpha = 0;
                        remove = true;
                    } else {
                        // Not far enough - animate it back
                        fractionCovered = 1 - (deltaXAbs / v.getWidth());
                        endX = 0;
                        endAlpha = 1;
                        remove = false;
                    }
                    mVelocityTracker.clear();

                    int SWIPE_DURATION = 600;
                    long duration = (int) ((1 - fractionCovered) * SWIPE_DURATION);
                    getListView().setEnabled(false);
                    v.animate().setDuration(duration).
                            alpha(endAlpha).translationX(endX).
                            withEndAction(new Runnable() { // fixme replace with AnimationListener
                                @Override
                                public void run() {
                                    // Restore animated values
                                    v.setAlpha(1);
                                    v.setTranslationX(0);
                                    if (remove) {
                                        animateRemoval(getListView(), v);
                                    } else {
                                        mBackgroundContainer.hideBackground();
                                        mSwiping = false;
                                        getListView().setEnabled(true);
                                    }
                                }
                            });
                } else {
                    int position = getListView().getPositionForView(v);
                    if (position != ListView.INVALID_POSITION)
                        getListView().performItemClick(v, position, getListView().getItemIdAtPosition(position));
                }
            }
            mItemPressed = false;
            break;
            default:
                return false;
        }
        return true;
    }

    private void animateRemoval(final ListView listview, View viewToRemove) {
        // irrelevant
    }
};

It's based on Chet Haase's demo from a couple of years back.

I really can't figure it out. Help?

Thanks!

Dyak answered 26/7, 2015 at 14:38 Comment(6)
Maybe https://mcmap.net/q/842835/-gesture-in-listview-android?Manymanya
@TudorLuca The gesture detection (swiping) works fine. The problem is that touching a row doesn't give me any visual feedback.Dyak
Have you tried to return true when it's on ACTION_MOVE and false when the swiping has finished?Manymanya
In your switch case Action_Move instead of using break; remove that line and use return false , Try itTachyphylaxis
@TudorLuca I just tried it. Things get weird: At first, it's only possible to swipe (no clicking, no ripples), and after swiping it once, it becomes possible to click (ripples are visible) and impossible to swipe.Dyak
@L-X No difference. Swiping is possible, clicking works but there is no visual feedback.Dyak
P
15

to me, v.setPressed did the trick, where v is the view receiving the touch event. Meaning that on ACTION_DOWN I invoked v.setPressed(true) and on ACTION_UP v.setPressed(false)

Praxis answered 22/10, 2015 at 13:47 Comment(1)
view.setPress() works perfectly, remember to set it to false when ACTION_UPCafard
A
0

I found that a click that causes a new fragment to appear, prevents the ripple. Adding a short delay in the click solved my issue. That is, this

      columnViewHolder.rippleLayout.setOnClickListener((v) -> {

            dataManager.setProducts(id, response.getProducts());
            dataManager.setProductListPosition(id, position);
            dataManager.clearCurrentProductItem();
            Log.d(TAG, "product click " + product.toString());
            EventBus.getDefault().post(new ProductDetailEvent(id, product));
            tagManager.hubSpokeClick(product.getCode());

        });

doesn't work, but this does:

           columnViewHolder.rippleLayout.setOnClickListener((v) -> {

                handler.postDelayed(() -> {
                    dataManager.setProducts(id, response.getProducts());
                    dataManager.setProductListPosition(id, position);
                    dataManager.clearCurrentProductItem();
                    Log.d(TAG, "product click " + product.toString());
                    EventBus.getDefault().post(new ProductDetailEvent(id, product));
                    tagManager.hubSpokeClick(product.getCode());
                }, 200l);
         });
Alcoholicity answered 16/6, 2017 at 13:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.