Rudolf Gröhling
Rudolf Gröhling

Reputation: 4825

Combine multiple onTouchListeners()

I have an Activity, inside is ViewPager and inside is ListFragment with ListView items. I want define touch gesture for the items (drag, fling etc.).

I'm able to attach onTouchListener() to each ListView item View by overriding getView() of adapter.

Adapter adapter = new Adapter(blah, blah, blah) {
        @Override
        public View getView(final int position, View convertView, ViewGroup parent) {
            View itemView = super.getView(position, convertView, parent);
            itemView.setOnTouchListener(new View.OnTouchListener() {
                blah, blah, blah
            });
            return itemView;
        }
     };

But I only receive MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE and MotionEvent.ACTION_UP if it occurs inside triggering item View boundary. For example I'm not able to catch MotionEvent.ACTION_OUTSIDE.

My guess is, that my custom onTouchListener compete with listener of ViewPager and of Activity. I want to trigger these other listeners in some occasions, i.e. if user move to side, I want to slide the whole View inside ViewPager but if he push on the ListView item and move vertically, I want startDrag() of the item.

How to implement that?


Edit/ Currently, my custom listener on ListView items works along with ViewPager, which is great. I'm able to catch ListView item events as long as I don't move outside its View and I'm able to slide the whole View in ViewPager as well.


Edit/ I've rewrote my ListView into RecycledView and realized that if I attach listener to item View, the ViewPager catches almost all move events, while allowing only the click events to go through. I've also realized, that if I attach OnTouchListener to the RecycledList it is able to catch move events along with the ViewPager so it depends on which level I attach the Listener. Problem is, that in item View level I have available full reference to item, while when working with the list, I need to guess item position from event coordinates and then gather data from Adapter and RecycledView, which is extra work.


Edit/ it wasn't that hard after all. Luckily, RecycledView has findChildViewUnder(int xPos, int yPos) method, so getting the View was piece of cake.

Here's my implementation, if someone is interested

    mRecycledView.setOnTouchListener(new View.OnTouchListener() {
        int LONG_PRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout();
        float mItemViewHeight, mInitialY;
        boolean mIsResolved;
        final Handler mHandler = new Handler(Looper.getMainLooper());
        final Runnable mLongPress = new Runnable() {
            @Override
            public void run() {
                mIsResolved = true;
                onLongClick();
            }
        };
        @Override
        public boolean onTouch(View v, MotionEvent me) {
            switch (MotionEventCompat.getActionMasked(me)) {
                case MotionEvent.ACTION_DOWN:
                    mInitialY = me.getY();
                    mActiveUserView = mRecycledView.findChildViewUnder(me.getX(), mInitialY);
                    if (mActiveUserView == null) { // clicked to RecycledView where's no item
                        mIsResolved = true;
                    } else {
                        mIsResolved = false;
                        mItemViewHeight = (float) mActiveUserView.getHeight();
                        mHandler.postDelayed(mLongPress, LONG_PRESS_TIMEOUT);
                    }
                    break;
                case MotionEvent.ACTION_MOVE:
                    if (!mIsResolved) {
                        stopLongClickHandler();
                        // check for vertical upward move
                        if (mInitialY - me.getY() > mItemViewHeight) {
                            mIsResolved = true;
                            onVerticalDrag();
                        }
                    }
                    break;
                case MotionEvent.ACTION_UP:
                    if (!mIsResolved) {
                        stopLongClickHandler();
                        mIsResolved = true;
                        onClick();
                    }
            }
            return !mIsResolved;
        }

        void stopLongClickHandler() {
            mHandler.removeCallbacksAndMessages(null);
        }

        void onVerticalDrag() {
            Log.e("DEBUG", "start drag");
        }

        void onLongClick() {
            Log.e("DEBUG", "long click");
        }

        void onClick() {                
            Log.e("DEBUG", "short click");
        }
    });

Upvotes: 0

Views: 111

Answers (1)

Evgeniy Mishustin
Evgeniy Mishustin

Reputation: 3794

I can suggest you replacing ListView with RecyclerView. Then read this tutorial: it contains almost every drag/swipe/touch implementations on RecyclerView clearly explained.

Upvotes: 2

Related Questions