2

I've tried a lot of solutions but couldn't find anything that worked. Each element in my List view takes up the whole screen. I would like to navigate from one element to the next by using a vertical swipe gesture. My current setup right now has free scrolling, but I would like to disable this and allow only for vertical up and down swipes. Each swipe (depending on the direction) should bring me to the next consecutive element in the list view and snap into place.

Here is my current code:

@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
    // TODO Auto-generated method stub
    switch (scrollState) {
    case OnScrollListener.SCROLL_STATE_IDLE:

            if (scrolling){
                // get first visible item
                  RelativeLayout item = (RelativeLayout) view.getChildAt(0);
                  int top = Math.abs(item.getTop()); // top is a negative value
                  int botton = Math.abs(item.getBottom());
                  if (top >= botton){

                     //((ListView)view).setSelectionFromTop(view.getFirstVisiblePosition()+1, 0);
                     ((ListView)view).smoothScrollToPositionFromTop(view.getFirstVisiblePosition()+1, 0, 1);

                  } else {
                         ((ListView)view).smoothScrollToPositionFromTop(view.getFirstVisiblePosition(), 0, 1);

                         }
                }

            scrolling = false;
   break;
    case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:


    case OnScrollListener.SCROLL_STATE_FLING:   
          scrolling = true;
          break;

    }
}

How can I go about achieving this?

deadlock
  • 7,048
  • 14
  • 67
  • 115
  • To clarify: You want to swipe up/down to bring the next element into view instead of free-scrolling all the way to the top or bottom? – t0mm13b Jan 13 '14 at 21:23
  • @t0mm13b yes that is correct. I should only be able to scroll to the next element in the listview – deadlock Jan 13 '14 at 21:41
  • [This](http://stackoverflow.com/a/20540839/206367) might help you - for a start - you need to determine which direction are you scrolling, and if scrolling upwards, using the `setSelection`, and turn off the scrolling to prevent the flinging up/down, combine the two answers given below. :) – t0mm13b Jan 13 '14 at 22:32
  • +1 for a good question :) – t0mm13b Jan 13 '14 at 23:04

2 Answers2

4

If you want to disable built-in scrolling - you need to trick AbsListView a bit and hide ACTION_MOVE motion events from it. In this way you can get all the touch system coming along with ListView, but scrolling will be disabled.

What I'm doing in my example - is I send CANCEL event to the underlying AbsListView and process scrolling touch events manually.

Here is what I came up with. Not an optimal solution probably, but it proves the concept :)

/**
 * Created by paveld on 1/13/14.
 */
public class CustomListView extends ListView {

    private float touchSlop = 0;
    private float downY = 0;
    private boolean consumeTouchEvents = false;

    public CustomListView(Context context) {
        super(context);
        init();
    }

    public CustomListView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public CustomListView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    private void init() {
        touchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        boolean isHandled = true;
        switch (ev.getActionMasked()) {
            case MotionEvent.ACTION_MOVE:
                float distance = downY - ev.getY();
                if (!consumeTouchEvents && Math.abs(distance) > touchSlop) {

                    //send CANCEL event to the AbsListView so it doesn't scroll
                    ev.setAction(MotionEvent.ACTION_CANCEL);
                    isHandled = super.onTouchEvent(ev);
                    consumeTouchEvents = true;
                    handleScroll(distance);
                }
                break;
            case MotionEvent.ACTION_DOWN:
                consumeTouchEvents = false;
                downY = ev.getY();
                //fallthrough
            default:
                if (!consumeTouchEvents) {
                    isHandled = super.onTouchEvent(ev);
                }
                break;

        }
        return isHandled;
    }

    private void handleScroll(float distance) {
        if (distance > 0) {
            //scroll up
            if (getFirstVisiblePosition() < getAdapter().getCount() - 1) {
                smoothScrollToPositionFromTop(getFirstVisiblePosition() + 1, 0, 300);
            }
        } else {
            //scroll down
            if (getFirstVisiblePosition() > 0) {
                smoothScrollToPositionFromTop(getFirstVisiblePosition() - 1, 0, 300);
            }
        }
    }
}
Pavel Dudka
  • 20,754
  • 7
  • 70
  • 83
  • this works however whenever I use the @style/actionbaroverlay I'm unable to reach the last element in the listview. It only allows me to go as far as the second last element. This only happens on some devices not all. In this case, on the Nexus 5 it works perfectly but on the Samsung Galaxy ACE II X, I have this problem, any idea why? – deadlock Jan 17 '14 at 20:34
  • It's only scrolls at the first time. because, consumeTouchEvents needs to be false again somewhere. – hasan Apr 25 '14 at 14:58
1

You can always override the onTouchEvent() method of the listView (may require subclassing listView) so that you only perform your swipe detection.

Ex.

@Override
public boolean onTouchEvent(MotionEvent motionEvent) {
    //TODO: perform the swipe detection functionality here

    //always return true so that the scrolling isn't performed by the list view
    return true; 
}

I haven't tested this so there may be irregularities with how the listView detects scrolls. If onTouchEvent() doesn't work you should also look at onInterceptTouchEvent()

RocketSpock
  • 2,031
  • 16
  • 11