18

I'm building an Android Wear application which I would like to present as a few pages that can be swiped across horizontally.

For this I am using a GridViewPager and associated FragmentGridPagerAdapter which is hardwired to use a single row. This is working fine for swiping horizontally between tiles, however in one of my fragments I want to use a WearableListView to allow the user to select between several actions. Unfortunately this does not work as it seems the GridViewPager is prevent any swipes getting to the WearableListView. Does anyone know if there is a way this can be done using the components described?

I have also tried using a standard ViewPager and this allowed the WearableListView to scroll fine, but the horizontal swiping then becomes flakey and you often need to swipe a few times to move the view pager.

JamieH
  • 4,788
  • 6
  • 28
  • 29

3 Answers3

31

EDIT:
A while after posting my original answer below, I found the correct way to do this. Just set

    wearableListView.setGreedyTouchMode( true )

and the list view works perfectly inside a GridViewPager.

The original answer is still a good view to get scrolling working with a vertical scrolling GridViewPager nested inside a horizontal scrolling one.

ORIGINAL ANSWER:
More or less the same thing as the accepted answer can be implemented more succinctly by extending the GridViewPager like this:

public class HorizontalListPager extends GridViewPager {

    private final GestureDetector mGestureDetector;

    public HorizontalListPager( Context context, AttributeSet attrs ) {
        super( context, attrs );
        mGestureDetector = new GestureDetector( context, new HScrollDetector() );
    }

    @Override
    public boolean onInterceptTouchEvent( MotionEvent ev ) {
        // If we have more horizontal than vertical scrolling, intercept the event,
        // otherwise let the child handle it
        return super.onInterceptTouchEvent( ev ) && mGestureDetector.onTouchEvent( ev );
    }

    class HScrollDetector extends GestureDetector.SimpleOnGestureListener {

        @Override
        public boolean onScroll( MotionEvent e1, MotionEvent e2, float distanceX, float distanceY ) {
            // Returns true if scrolling horizontally
            return ( Math.abs( distanceX ) > Math.abs( distanceY ) );
        }
    }
}
Morne
  • 814
  • 6
  • 14
  • 6
    Gotta love this undocumented stuff! Makes me fill like Christopher Columbus – Ran Jan 08 '15 at 15:09
  • This updated answear should be accpeted one. Thanks for saving my time coding wired things with touch events. – Karol Żygłowicz Jun 19 '15 at 07:49
  • Does `setGreedyTouchMode` still allow horizontal paging? The docs make it sound like it wouldn't: "Controls whether `WearableListView` should intercept all touch events and also prevent the parent from receiving them." – stkent Sep 23 '15 at 20:21
  • WOW, This is the best solution! – Marcin Lagowski May 15 '16 at 17:34
10

I ran into the same thing. Just setting one row or overriding canscrollvertically() doesn't work. The only option I've found is to override the touch events like in Neevek's answer here: HorizontalScrollView within ScrollView Touch Handling

This is working for me:

private float xDistance, yDistance, lastX, lastY;

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    switch (ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
            setDownPosition(ev);
            break;
        case MotionEvent.ACTION_MOVE:
            if (isVerticalScroll(ev))
                return false;
    }

    return super.onInterceptTouchEvent(ev);
}

@Override
public boolean onTouchEvent(MotionEvent ev) {
    switch (ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
            setDownPosition(ev);
            break;
        case MotionEvent.ACTION_MOVE:
            if (isVerticalScroll(ev))
                return false;
    }

    return super.onTouchEvent(ev);
}

private void setDownPosition(MotionEvent ev){
    xDistance = yDistance = 0f;
    lastX = ev.getX();
    lastY = ev.getY();
}

private boolean isVerticalScroll(MotionEvent ev){
    final float curX = ev.getX();
    final float curY = ev.getY();
    xDistance += Math.abs(curX - lastX);
    yDistance += Math.abs(curY - lastY);
    lastX = curX;
    lastY = curY;
    if(xDistance < yDistance) {
        return true;
    }
    return false;
}
Community
  • 1
  • 1
Graydyn Young
  • 5,041
  • 1
  • 17
  • 19
  • Thanks never would have figured out this myself. I keep wondering why it is so hard to get anything done in Android sometimes. Really basic things. – powder366 Aug 19 '14 at 10:25
  • The CardFragments scrolling works seamlessly in GridViewPager. I'll try and see how it's done. – Gak2 Aug 20 '14 at 20:32
  • 1
    this should be the accepted answer: http://stackoverflow.com/a/25498711/271804 wearableListView.setGreedyTouchMode(true) is enough to make it work thanks @Mome – rubdottocom Aug 09 '15 at 14:17
1

Thanks a lot to Graydyn Young .. cause i took time to find how implements his solution that's the way i do :

  • create a class (FragmentViewPager) that extends GridViewPager with the previous code.
  • use this class instead of GridViewPager

    import android.content.Context;
    import android.support.wearable.view.GridViewPager;
    import android.util.AttributeSet;
    import android.view.MotionEvent;
    
    
    public class FragmentViewPager extends GridViewPager {
    
    
        public FragmentViewPager(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        public FragmentViewPager(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
        }
    
        public FragmentViewPager(Context context) {
            super(context);
        }
        private float xDistance, yDistance, lastX, lastY;
    
        @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {
            switch (ev.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    setDownPosition(ev);
                    break;
                case MotionEvent.ACTION_MOVE:
                    if (isVerticalScroll(ev))
                        return false;
            }
    
            return super.onInterceptTouchEvent(ev);
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent ev) {
            switch (ev.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    setDownPosition(ev);
                    break;
                case MotionEvent.ACTION_MOVE:
                    if (isVerticalScroll(ev))
                        return false;
            }
    
            return super.onTouchEvent(ev);
        }
    
        private void setDownPosition(MotionEvent ev){
            xDistance = yDistance = 0f;
            lastX = ev.getX();
            lastY = ev.getY();
        }
    
        private boolean isVerticalScroll(MotionEvent ev){
            final float curX = ev.getX();
            final float curY = ev.getY();
            xDistance += Math.abs(curX - lastX);
            yDistance += Math.abs(curY - lastY);
            lastX = curX;
            lastY = curY;
            if(xDistance < yDistance) {
                return true;
            }
            return false;
        }
    }
    

in your activity layout :

<com.example.FragmentViewPager
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent" />

in your activity onCreate() :

final FragmentViewPager pager = (FragmentViewPager) findViewById(R.id.pager);
eskan
  • 309
  • 1
  • 3
  • 13