11

I have to make an application in which the user moves around a screen, which is like our android home screen.

There is a list of images that come, and we can scroll through the images in a horizontal scroll er.

The user cannot change the location of images, it's just like a few thumbnails arranged over a horizontal screen

This is like the paging control in iPhone app development.

I have tried to find ways to do this, but I am fairly new to android and I wanted to know the best way to achieve the above?

I have heard of the gallery control, but I am not sure if it will suit my purposes.

Also if you can give links to the answers you suggest.. it would be great as if there is a new controller involved , I will be able to understand it better as I am still a fresher. Thank you in advance.

Edit: For those who are unfamiliar with the iPhone paging view,here is a video example.

Phil
  • 35,852
  • 23
  • 123
  • 164
user590849
  • 11,655
  • 27
  • 84
  • 125
  • If there are only images then, Gallery View will work. – Jaydeep Khamar May 30 '11 at 07:02
  • but like it is available in iphone, i want the pages to appear also at the bottom. – user590849 May 30 '11 at 08:55
  • also i want a scroll control which is as smooth as the scroll in the home view of android phones.. like we have 5 screens available and then we can move between them.. i want to emulate that in my application without using gesture listener.. – user590849 May 30 '11 at 08:56
  • the iphone paging control provides all that i am saying by default. i was wondering if android can do it for us without us having to do all the work. if it is not possible, then ofcourse i will have to code..hence the question " best way to implement horizontal scrolling"... – user590849 May 31 '11 at 05:03
  • @user590849 check my answer. there is an opensource widget that exactly does what you are looking for. – Varun Jun 02 '11 at 06:16
  • @User590849 you should remember to award this bounty. – jcolebrand Jun 05 '11 at 22:15

10 Answers10

8

In my search to implement this, I found many implementation like below

  1. http://www.codeshogun.com/blog/2009/04/16/how-to-implement-swipe-action-in-android/
  2. https://github.com/ysamlan/horizontalpager
  3. http://code.google.com/p/deezapps-widgets/
ingsaurabh
  • 15,249
  • 7
  • 52
  • 81
6

Well, you could look at the source code for the android homescreen, since it's open source. Maybe you can get some ideas from there.

dmon
  • 30,048
  • 8
  • 87
  • 96
6

The GreenDroid library (a collection of useful Android components) recently added a PagedView class that does exactly what you're looking for. It also includes a PageIndicator that works like the iOS dots.

It uses a Adapter system, similar to ListView (with efficient View re-use, etc), which I haven't seen in any of the other implementations of this pattern.


Source code: https://github.com/cyrilmottier/GreenDroid

Demo application: https://market.android.com/details?id=com.cyrilmottier.android.gdcatalog

Christopher Souvey
  • 2,890
  • 21
  • 21
5

You could use a HorizontalScrollView to hold a LinearLayout containing all your image views.

Ted Hopp
  • 232,168
  • 48
  • 399
  • 521
  • but then the scrolling is hard to handle.. i mean, the scrollview by default is not a smooth scroller. i will have to implement gesture listener and then methods like smoothscrollto - to implement smooth horizontal scrolling.. – user590849 May 25 '11 at 20:15
  • I have implemented something like that... and it was the best solution i've managed – neteinstein May 30 '11 at 18:04
  • Actually HorizontalScrollView **does** have smooth scrolling. This plus a `LinearLayout` is the simplest solution to your problem, and doesn't require any subclassing. – Sam May 31 '11 at 17:41
  • ok. i just want that when the user scrolls from one set of images to the other,and stops in between , he should be automatically scrolled to the screeen which is visible more than 50%. could you explain what you are saying in more detail so that i can achieve what i am looking for. – user590849 Jun 01 '11 at 00:29
5

I think you want like the paging control in iphone app development, in android this functionality is available by using ViewFlipper. It may help you that you want to implement.

Datta Kunde
  • 467
  • 6
  • 14
  • it is a good idea, but the client wants the scroll feature..if i do what i want via viewflipper then i would have to input a gesture listener to see what view to flip and at what rate to flip. all of this is done by default in ios. – user590849 May 31 '11 at 13:30
  • Then use the Horizontal scroll view inside that use the Linear Layout and set your images in that Linear Layout(orientation is horizontal). and for smooth scrolling use the various properties of scroll view like smoothscroll etc. – Datta Kunde May 31 '11 at 14:39
5

Like dmon said you can try something with the android homescreen app OR try this http://code.google.com/p/deezapps-widgets/

This seems to me like a custom implementation of the homescreen from android.. and has the page control as well jus like the iphone.. I think this is what you are looking for...

Varun
  • 33,833
  • 4
  • 49
  • 42
4

I have hacked around with items posted here: Horizontal "tab"ish scroll between views

I had good luck replicating a horizontal scrolling type navigation that your describing.

Community
  • 1
  • 1
sgarman
  • 6,152
  • 5
  • 40
  • 44
4

Here is a solution to this problem with a lot of helpful code.

Community
  • 1
  • 1
Phil
  • 35,852
  • 23
  • 123
  • 164
4

I posted code to do something like the Android homescreen before : Developing an Android Homescreen

Note that will make the whole screen flip between pages, it will not work if you want to flip only part of the screen like shown on the video you linked to.

First the source code

package com.matthieu.launcher;

import android.content.Context;
import android.util.Log;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewConfiguration;
import android.widget.Scroller;

public class DragableSpace extends ViewGroup {
    private Scroller mScroller;
    private VelocityTracker mVelocityTracker;

    private int mScrollX = 0;
    private int mCurrentScreen = 0;

    private float mLastMotionX;

    private static final String LOG_TAG = "DragableSpace";

    private static final int SNAP_VELOCITY = 1000;

    private final static int TOUCH_STATE_REST = 0;
    private final static int TOUCH_STATE_SCROLLING = 1;

    private int mTouchState = TOUCH_STATE_REST;

    private int mTouchSlop = 0;

    public DragableSpace(Context context) {
        super(context);
        mScroller = new Scroller(context);

        mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();

        this.setLayoutParams(new ViewGroup.LayoutParams(
                    ViewGroup.LayoutParams.WRAP_CONTENT,
                    ViewGroup.LayoutParams.FILL_PARENT));
    }

    public DragableSpace(Context context, AttributeSet attrs) {
        super(context, attrs);
        mScroller = new Scroller(context);

        mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();

        this.setLayoutParams(new ViewGroup.LayoutParams(
                    ViewGroup.LayoutParams.WRAP_CONTENT ,
                    ViewGroup.LayoutParams.FILL_PARENT));

        TypedArray a=getContext().obtainStyledAttributes(attrs,R.styleable.DragableSpace);
        mCurrentScreen = a.getInteger(R.styleable.DragableSpace_default_screen, 0);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        /*
         * This method JUST determines whether we want to intercept the motion.
         * If we return true, onTouchEvent will be called and we do the actual
         * scrolling there.
         */

        /*
         * Shortcut the most recurring case: the user is in the dragging state
         * and he is moving his finger. We want to intercept this motion.
         */
        final int action = ev.getAction();
        if ((action == MotionEvent.ACTION_MOVE)
                && (mTouchState != TOUCH_STATE_REST)) {
            return true;
                }

        final float x = ev.getX();

        switch (action) {
            case MotionEvent.ACTION_MOVE:
                /*
                 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
                 * whether the user has moved far enough from his original down touch.
                 */

                /*
                 * Locally do absolute value. mLastMotionX is set to the y value
                 * of the down event.
                 */
                final int xDiff = (int) Math.abs(x - mLastMotionX);

                boolean xMoved = xDiff > mTouchSlop;

                if (xMoved) {
                    // Scroll if the user moved far enough along the X axis
                    mTouchState = TOUCH_STATE_SCROLLING;
                }
                break;

            case MotionEvent.ACTION_DOWN:
                // Remember location of down touch
                mLastMotionX = x;

                /*
                 * If being flinged and user touches the screen, initiate drag;
                 * otherwise don't.  mScroller.isFinished should be false when
                 * being flinged.
                 */
                mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING;
                break;

            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                // Release the drag
                mTouchState = TOUCH_STATE_REST;
                break;
        }

        /*
         * The only time we want to intercept motion events is if we are in the
         * drag mode.
         */
        return mTouchState != TOUCH_STATE_REST;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain();
        }
        mVelocityTracker.addMovement(event);

        final int action = event.getAction();
        final float x = event.getX();

        switch (action) {
            case MotionEvent.ACTION_DOWN:
                Log.i(LOG_TAG, "event : down");
                /*
                 * If being flinged and user touches, stop the fling. isFinished
                 * will be false if being flinged.
                 */
                if (!mScroller.isFinished()) {
                    mScroller.abortAnimation();
                }

                // Remember where the motion event started
                mLastMotionX = x;
                break;
            case MotionEvent.ACTION_MOVE:
                // Log.i(LOG_TAG,"event : move");
                // if (mTouchState == TOUCH_STATE_SCROLLING) {
                // Scroll to follow the motion event
                final int deltaX = (int) (mLastMotionX - x);
                mLastMotionX = x;

                //Log.i(LOG_TAG, "event : move, deltaX " + deltaX + ", mScrollX " + mScrollX);

                if (deltaX < 0) {
                    if (mScrollX > 0) {
                        scrollBy(Math.max(-mScrollX, deltaX), 0);
                    }
                } else if (deltaX > 0) {
                    final int availableToScroll = getChildAt(getChildCount() - 1)
                        .getRight()
                        - mScrollX - getWidth();
                    if (availableToScroll > 0) {
                        scrollBy(Math.min(availableToScroll, deltaX), 0);
                    }
                }
                // }
                break;
            case MotionEvent.ACTION_UP:
                Log.i(LOG_TAG, "event : up");
                // if (mTouchState == TOUCH_STATE_SCROLLING) {
                final VelocityTracker velocityTracker = mVelocityTracker;
                velocityTracker.computeCurrentVelocity(1000);
                int velocityX = (int) velocityTracker.getXVelocity();

                if (velocityX > SNAP_VELOCITY && mCurrentScreen > 0) {
                    // Fling hard enough to move left
                    snapToScreen(mCurrentScreen - 1);
                } else if (velocityX < -SNAP_VELOCITY
                        && mCurrentScreen < getChildCount() - 1) {
                    // Fling hard enough to move right
                    snapToScreen(mCurrentScreen + 1);
                } else {
                    snapToDestination();
                }

                if (mVelocityTracker != null) {
                    mVelocityTracker.recycle();
                    mVelocityTracker = null;
                }
                // }
                mTouchState = TOUCH_STATE_REST;
                break;
            case MotionEvent.ACTION_CANCEL:
                Log.i(LOG_TAG, "event : cancel");
                mTouchState = TOUCH_STATE_REST;
        }
        mScrollX = this.getScrollX();

        return true;
    }

    private void snapToDestination() {
        final int screenWidth = getWidth();
        final int whichScreen = (mScrollX + (screenWidth / 2)) / screenWidth;
        Log.i(LOG_TAG, "from des");
        snapToScreen(whichScreen);
    }

    public void snapToScreen(int whichScreen) {         
        Log.i(LOG_TAG, "snap To Screen " + whichScreen);
        mCurrentScreen = whichScreen;
        final int newX = whichScreen * getWidth();
        final int delta = newX - mScrollX;
        mScroller.startScroll(mScrollX, 0, delta, 0, Math.abs(delta) * 2);             
        invalidate();
    }

    public void setToScreen(int whichScreen) {
        Log.i(LOG_TAG, "set To Screen " + whichScreen);
        mCurrentScreen = whichScreen;
        final int newX = whichScreen * getWidth();
        mScroller.startScroll(newX, 0, 0, 0, 10);             
        invalidate();
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int childLeft = 0;

        final int count = getChildCount();
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (child.getVisibility() != View.GONE) {
                final int childWidth = child.getMeasuredWidth();
                child.layout(childLeft, 0, childLeft + childWidth, child
                        .getMeasuredHeight());
                childLeft += childWidth;
            }
        }

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        final int width = MeasureSpec.getSize(widthMeasureSpec);
        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        if (widthMode != MeasureSpec.EXACTLY) {
            throw new IllegalStateException("error mode.");
        }

        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        if (heightMode != MeasureSpec.EXACTLY) {
            throw new IllegalStateException("error mode.");
        }

        // The children are given the same width and height as the workspace
        final int count = getChildCount();
        for (int i = 0; i < count; i++) {
            getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
        }
        Log.i(LOG_TAG, "moving to screen "+mCurrentScreen);
        scrollTo(mCurrentScreen * width, 0);      
    }  

    @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            mScrollX = mScroller.getCurrX();
            scrollTo(mScrollX, 0);
            postInvalidate();
        }
    }
}

And the layout file :

<?xml version="1.0" encoding="utf-8"?>
<com.matthieu.launcher.DragableSpace xmlns:app="http://schemas.android.com/apk/res/com.matthieu.launcher"
    xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/space"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
app:default_screen="1"
>
<include android:id="@+id/left"  layout="@layout/left_screen" />
<include android:id="@+id/center"  layout="@layout/initial_screen" />
<include android:id="@+id/right"  layout="@layout/right_screen" />
</com.matthieu.launcher.DragableSpace>

To be able to have the extra attribute in the xml file, you want to save this in res/values/attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="DragableSpace">
        <attr name="default_screen" format="integer"/>
    </declare-styleable>
</resources>

The original SO post also has a few comments to make it work correctly...

Community
  • 1
  • 1
Matthieu
  • 16,103
  • 10
  • 59
  • 86
4

If you're specifically interested in duplicating the home screen-style paging control, take a look at this, with source at Github. It even includes code to do the little page dots on the bottom.

Femi
  • 64,273
  • 8
  • 118
  • 148