Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

looking to implement a wall (GridView?) that goes off-screen and user can touch to move it around

Tags:

android

Desired effect I have a bunch of small images that I'd like to show on a "wall" and then let the user fling this wall in any direction and select an image.

Initial Idea as a possible implementation I was thinking a GridView that is larger than the screen can show - but all examples of using this widget indicate that the Gallery doesn't extend beyond the size of the screen.

Question What is the best widget to use to implement the desired effect ? A code sample would be especially beneficial.

EDIT... if someone has example code that will let me put about 30 images on a "wall" (table would be good) then I will accept that as the answer. Note that the "wall" should look like it extends beyond the edges of the display and allow a user to use the finger to drag the "wall" up down left right. Dragging should be in "free-form" mode. A single tap on an image selects it and a callback shall be detectable. I have created a bounty for this solution.

like image 843
Someone Somewhere Avatar asked May 07 '11 00:05

Someone Somewhere


1 Answers

The solution is actually quite simple, but a pain. I had to implement exactly this within a program I recently did. There are a few tricksy things you have to get around though. It must be noted that different OS versions have different experiences. Certain expertise or cludging around is required to make this work as drawing is sometimes adversely affected by this method. While I have a working copy, the code I provide is not for everyone, and is subject to whatever other changes or customizations have been made in your code.

  • set android:layout_width to wrap_content
  • set android:layout_height to wrap_content
  • In your code:
    • determine how many rows you will have.
    • divide number of items by the number of rows.
    • add gridView.setStretchMode(NO_STRETCH);
    • add gridView.setNumColumns( number of Columns );
    • add gridView.setColumnWidth( an explicit width in pixels );
  • To Scroll:
    • You may simply use gridView.scrollBy() in your onTouchEvent()

These are the steps. All are required in order to get it to work. The key is the NO_STRETCH with an explicit number of columns at a specific width. Further details can be provided, if you need clarification. You may even use a VelocityTracker or onFling to handle flinging. I have snappingToPage enabled in mine. Using this solution, there is not even a requirement to override onDraw() or onLayout(). Those three lines of code get rid of the need for a WebView or any other embedding or nesting.

Bi-directional scrolling is also quite easy to implement. Here is a simple code solution:

  1. First, in your class begin tracking X and Y positions by making two class members. Also add State tracking;

    // Holds the last position of the touch event (including movement)
    int myLastX;
    int myLastY;
    
    // Tracks the state of the Touch handling
    final static private int TOUCH_STATE_REST = 0;
    final static private int TOUCH_STATE_SCROLLING = 1;
    int myState = TOUCH_STATE_REST;
    
  2. Second, make sure to check for Scrolling, that way you can still click or longclick the images themselves.

    @Override public boolean onInterceptTouchEvent(final MotionEvent ev)
    {//User is already scrolling something. If we haven't interrupted this already,
    // then something else is handling its own scrolling and we should let this be.
    // Once we return TRUE, this event no longer fires and instead all goes to 
    // onTouch() until the next TouchEvent begins (often beginning with ACTION_DOWN).
        if ((ev.getAction() == MotionEvent.ACTION_MOVE)
        &&  (myState != TOUCH_STATE_REST))
            return false;
    
    // Grab the X and Y positions of the MotionEvent
        final float _x = ev.getX();
        final float _y = ev.getY();
        switch (ev.getAction())
        {   case MotionEvent.ACTION_MOVE:
                final int _diffX = (int) Math.abs(_x - myLastX);
                final int _diffY = (int) Math.abs(_y - myLastY);
    
                final boolean xMoved = _diffX > 0;
                final boolean yMoved = _diffY > 0;
                if (xMoved || yMoved)
                   myState = TOUCH_STATE_SCROLLING;
                break;
            case MotionEvent.ACTION_DOWN:
            // Remember location of down touch
                myLastX = _x;
                myLastY = _y;
                break;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                if (myState == TOUCH_STATE_SCROLLING)
                // Release the drag
                    myState = TOUCH_STATE_REST;
    
        }
        //If we are not At Rest, start handling in our own onTouch()
        return myState != TOUCH_STATE_REST;
    }
    
  3. After the GridView knows that you are Scrolling, it will send all Touch Events to onTouch. Do your Scrolling here.

    @Override public boolean onTouchEvent(final MotionEvent ev)
    {
        final int action = ev.getAction();
        final float x = ev.getX();
        final float y = ev.getY();
        final View child;
    
        switch (action) 
        {
            case MotionEvent.ACTION_DOWN:
                //Supplemental code, if needed
                break;
            case MotionEvent.ACTION_MOVE:
                // This handles Scrolling only.
                if (myState == TOUCH_STATE_SCROLLING)
                {
                    // Scroll to follow the motion event
                    // This will update the vars as long as your finger is down.
                    final int deltaX = (int) (myLastX - x);
                    final int deltaY = (int) (myLastY - y);
                    myLastX = x;
                    myLastY = y;
                    scrollBy(deltaX, deltaY);
                }
                break;
            case MotionEvent.ACTION_UP:
            // If Scrolling, stop the scroll so we can scroll later.
                if (myState == TOUCH_STATE_SCROLLING)
                    myState = TOUCH_STATE_REST;
                break
            case MotionEvent.ACTION_CANCEL:
            // This is just a failsafe. I don't even know how to cancel a touch event
                 myState = TOUCH_STATE_REST;
        }
    
    return true;
    }
    

If course, this solution moves at X and Y at the same time. If you want to move just one direction at a time, you can differentiate easily by checking the greater of the X and Y differences. (i.e. Math.abs(deltaX) > Math.abs(deltaY)) Below is a partial sample for one directional scrolling, but can switch between X or Y direction.

3b. Change this in your OnTouch() if you want to handle one direction at a time:

            case MotionEvent.ACTION_MOVE:
                if (myState == TOUCH_STATE_SCROLLING)
                {
                    //This will update the vars as long as your finger is down.
                    final int deltaX = (int) (myLastX - x);
                    final int deltaY = (int) (myLastY - y);
                    myLastX = x;
                    myLastY = y;

                    // Check which direction is the bigger of the two
                    if (Math.abs(deltaX) > Math.abs(deltaY))
                        scrollBy(deltaX, 0);
                    else if (Math.abs(deltaY) > Math.abs(deltaX))
                        scrollBy(0, deltaY);
                    // We do nothing if they are equal.
                }
                break;

FuzzicalLogic

like image 142
Fuzzical Logic Avatar answered Nov 01 '22 00:11

Fuzzical Logic