2

I've been trying to implement four directional swipe navigation. And I have some questions on how to put things together.

  1. Thinking about using fragments. Will it workout?
  2. Another idea is to create a big relative layout and arrange views as shown on the picture. But possibly will have issues when rotating device portait/landscape.
  3. Somehow emulate swipe effect

How all these points will workout? Can anyone help with it. I already found questions regarding to TouchMove feature

  1. Touch movement in only four directions?
  2. Fling gesture detection on grid layout
  3. https://github.com/JakeWharton/Android-DirectionalViewPager/

Any advice and help is appreciated, the reason is that I'm relatively new to android development.

enter image description here

Community
  • 1
  • 1
sultan
  • 5,978
  • 14
  • 59
  • 103
  • `Another idea is to create a big relative layout and arrange views as shown on the picture. But possibly will have issues when rotating device portait/landscape.` If you implement a layout for landscape mode also, you should have no issues. – Dzhuneyt Oct 09 '13 at 09:19

3 Answers3

4

not so hard task, thanks to Scroller its almost easy cake

if you want to put some interactive Views like Button etc (and i bet you will) you need to override onInterceptTouchEvent(MotionEvent ev)

public class FourDirectionLayout extends ViewGroup {
    private GestureDetector mDetector;
    private Scroller mScroller;
    private final String[] TEXTS = {
            "left view, left swipe only",
            "right view, right swipe only",
            "top view, top swipe only",
            "bottom view, bottom swipe only",
            "central view, swipe to the left, right, top or bottom",
    };
    private final int[] COLORS = {
            0xaa0000ff, 0xaa0000ff, 0xaaff0000, 0xaaff0000, 0xaa00ff00
    };
    private final int[] PACKED_OFFSETS = {
            -1, 0, 1, 0, 0, -1, 0, 1, 0, 0
    };

    public FourDirectionLayout(Context context) {
        super(context);
        for (int i = 0; i < TEXTS.length; i++) {
            TextView tv = new TextView(context);
            tv.setTag(i);
            tv.setTextSize(32);
            tv.setTypeface(Typeface.DEFAULT_BOLD);
            tv.setTextColor(0xffeeeeee);
            tv.setText(TEXTS[i]);
            tv.setBackgroundColor(COLORS[i]);
            addView(tv);
        }
        mDetector = new GestureDetector(context, mListener);
        mScroller = new Scroller(context);
    }

    private OnGestureListener mListener = new SimpleOnGestureListener() {
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            if (!mScroller.isFinished()) {
                return false;
            }
            int sx = getScrollX();
            int sy = getScrollY();
            int w = getWidth();
            int h = getHeight();
            int DURATION = 500;
            // check if horizontal/vertical fling
            if (Math.abs(velocityX) > Math.abs(velocityY)) {
                if (sy != 0 || velocityX * sx < 0) {
                    return false;
                }
//                DURATION = (int) (1000 * w / Math.abs(velocityX));
                int distance = velocityX < 0? w : -w;
                mScroller.startScroll(sx, sy, distance, 0, DURATION);
            } else {
                if (sx != 0 || velocityY * sy < 0) {
                    return false;
                }
//                DURATION = (int) (1000 * h / Math.abs(velocityY));
                int distance = velocityY < 0? h : -h;
                mScroller.startScroll(sx, sy, 0, distance, DURATION);
            }
            invalidate();
            return true;
        }
    };

    @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            invalidate();
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mDetector.onTouchEvent(event);
        return true;
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int cnt = getChildCount();
        for (int i = 0; i < cnt ; i++) {
            View child = getChildAt(i);
            int idx = (Integer) child.getTag() << 1;
            int xOffset = (r - l) * PACKED_OFFSETS[idx];
            int yOffset = (b - t) * PACKED_OFFSETS[idx + 1];
            child.layout(l + xOffset, t + yOffset, r + xOffset, b + yOffset);
        }
    }
}

to test it add this in your Activity.onCreate:

ViewGroup layout = new FourDirectionLayout(this);
setContentView(layout);
pskink
  • 23,874
  • 6
  • 66
  • 77
  • Neat. The reason I suggested Fragments is because I assumed the views would be heavy and I hate having heavy View trees that are not even visible. (and in your solution they would all be walked *and* drawn offscreen). But, yes, I was overcomplicating mainly because I was thinking in terms of animations and not classic scrolling (I've suffered from dealing with nested scrolling before..). – Delyan Oct 09 '13 at 17:33
  • @sultan updated to optimized implementation which reduced the class by ~30 lines of cide – pskink Oct 10 '13 at 07:09
1

Not an easy task and I'm afraid there isn't a do-this-and-don't-do-that answer.

Let's get the easy part out of the way - the way you detect swipes is using GestureDetector. You'll get an onFling (or onScroll) event and then you can update a Scroller to get actual coordinates. The way you store/display the screens is more difficult.

Off the top of my head, RelativeLayout would be kinda hard to animate. Scrolling would be a pain to do, though flings can be easier to animate by changing the screen in the middle, letting it lay out and using ViewTreeObserver.onPreDraw to animate to the new positions (before they are first drawn). Scrolling would be a bit harder, I suspect (though, if you extend RelativeLayout, you can just change the children's position, without relayout or remeasure, so maybe it's not such a bad idea).

Personally, I would extend FrameLayout, detect flings before the children can react (see onInterceptTouchEvent) and put the right fragment in there, with the appropriate transition animations. Seems to be the neatest way to think about it.

P.S. I was hoping you could just use DirectionalViewPager and change the direction at run time but the problem is the middle screen - it has to respond to both directions. Maybe extending from that class isn't a bad idea...

Delyan
  • 8,881
  • 4
  • 37
  • 42
  • Thank you @Delyan. I'll try it out with `FrameLayout` so as soon as results will come, will post a screen shot. – sultan Oct 09 '13 at 10:44
  • @Delyan i think you overcomplicated with your solution: see my answer, now after some optimizations my class takes exactly 99 lines of code :) – pskink Oct 09 '13 at 16:54
1

The closest I can think of, is Google Maps and the way they let the user navigate (scroll, fling and wipe) thru the map.

What you see there, when scrolling around with a slow network, is that they seem to work with a 4x5 grid. So 20 single images that are placed on a grid. And probably one more in each direction off screen.

You now need to do two things:

  • get touch events to detect, what the user wants to see at which position.

  • place your content on the grid. You can either build the grid as ImageView's on a FrameLayout or as Bitmap's on a SurfaceView. With the latter you have more control over drawing and placement. With the first putting text, buttons and such is easier. Depending on what you want to show you can go with one these approaches.

jboi
  • 11,324
  • 4
  • 36
  • 43