Tuesday, September 14, 2010

Android: Use RealViewSwitcher to switch between views like the home screen does

Hi to all Android developers out there!
Did you ever want to integrate a feature into your app that allows users to switch between multiple views in the same way as one can switch between multiple screens on the Android home screen? There are some classes in the official SDK, namely Gallery, ViewFlipper and ViewAnimator, which one could use, but none of them works the same way the Launcher does.

Because I want to use this functionality in a new app, I stripped down the Launcher's source code and created a class called RealViewSwitcher - which I open to the public again under the same license conditions as the SDK. Please download it and tell me what you think - I appreciate all ideas and bugfixes. :-)

The RealViewSwitcher class itself is available here:
    http://marcreichelt.de/pub/realviewswitcher/RealViewSwitcher.java
A really simple example of how to use it can be found here:
    http://marcreichelt.de/pub/realviewswitcher/realviewswitcher_example.zip

I hope this supports you in building your own Android applications - keep up the great work, and please drop me a mail if you like it. :-)

Marc

46 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. People on SO are looking for this kind of functionality:

    http://stackoverflow.com/questions/4296175/android-nytimes-swipe-animation-gesture

    ReplyDelete
  3. how can you use it in a layout xml file?

    ReplyDelete
  4. @ferdy182:
    Just add it as the root layout (or a sub layout) like a normal layout view, and then add your views to it - like this:

    <de.marcreichelt.android.RealViewSwitcher
      android:layout_width="fill_parent"
      android:layout_height="fill_parent"
      ... />
      <!-- your sub views go here, each view is one "screen" - you might also want to add layouts here -->
    </de.marcreichelt.android.RealViewSwitcher>

    ReplyDelete
  5. Thanks for doing this! I've made a few modifications and posted my version to github: https://github.com/ysamlan/horizontalpager

    ReplyDelete
  6. Thanks a lot Marc Reichelt .

    ReplyDelete
  7. Works great! However i came across a situation where i wanted to be able to scroll through the multiple pages infinitely. In other words, when on the first page, be able to flip previous to the last page. Also when on the last page, be able to flip next to the first page. Then i came across a situation where i did not know how many pages i wanted to flip through. Ie: I dynamically generate the number of pages and be able to flip through. Here is my solution to both of these problems:


    I'm not sure if its the 100% proper way to do this but this is what i did:
    I knew i needed 5 pages for one use of this horizontal pager. What i did was put 5 layouts wrapped in the HorizontalPager tag (which can include images, buttons, etc) in the xml like this: [0, 1, 2, 3, 4], but this only allows scrolling from one end to the other, so i did this in the xml:
    [4, 0, 1, 2, 3, 4, 0]
    Then in the onScreenSwitched i say:
    if (screen == (numPages+1))
    {
    realViewSwitcher.setCurrentScreen(1, false);
    }
    else if (screen == 0)
    {
    realViewSwitcher.setCurrentScreen(numPages, false);
    }
    Now it allows for infinitely scrolling through the pages. I also came across a situation where i did not know how many pages i wanted to be able to flip through, so I did something like this:
    The xml had 3 layouts wrapped in the HorizontalPager like this: [prev, current, next]. Again, each of these layouts included multiple buttons, images, textViews, etc.
    When the user is on the first page (page 0 of N), you set the current screen to 0. Similarly when on the last page (page N) the current screen is set to 2 (the next screen position). Any page in between 0 and N (not including 0 and N) will be set to 1 (the current screen position). When the user swipes or flicks to the next or previous page an update is triggered which will set the current screen back to 1 (current) and update the previous and next values.

    Hope this helps others trying to mimic this behavior.

    ReplyDelete
  8. I was having problems with this when the orientation changed. In your example it snaps to the first screen when orientation changes, but i wanted it to snap to the current screen you are on. So what i did was took the snapToScreen code and rather than grabbing the view's getWidth() i substituted the screens width in its place and called the startScroll with duration zero. here is the code to accomplish this:

    public void orientationSnapToScreen(Context ctx)
    {
    Display display = ((WindowManager) ctx.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
    int displayWidth = display.getWidth();

    mNextScreen = Math.max(0, Math.min(getCurrentScreen(), getChildCount() - 1));
    final int newX = mNextScreen * displayWidth;
    final int delta = newX - getScrollX();

    mScroller.startScroll(getScrollX(), 0, delta, 0, 0);
    }

    and then you just call it like this in your code:

    @Override
    public void onConfigurationChanged(Configuration newConfig)
    {
    super.onConfigurationChanged(newConfig);

    // realViewSwitcher is a HorizontalPager
    realViewSwitcher.orientationSnapToScreen(this);
    }

    Hope this helps :)

    ReplyDelete
  9. David,

    Could you please post some code showing the dynamic paging with the three views? I have a situation where I'd like to use the pager, but have potentially hundreds of images to show, and need to keep memory at bay.

    ReplyDelete
  10. @David: Wow, this is great! About your second solution what is happening when the orientation changes: The view switcher currently does not save its state. That yet has to be implemented. Furthermore, scrollable views are not yet possible in the solution I posted here. I have a working version of this at home. I will upload it to GitHub later and post the link here. Hopefully then the "orientation change" problem will be solved, too.

    @Anonymous: Dynamic paging is yet not implemented, and I would say it is not that easy. In fact, I implemented it specifically for - but also limited to - a certain project which will go into the Android Market the next days. IMHO it is not yet ready for productional use other than in my application. The best method would be to do something like the Android AdapterView (used by GridView, ListView etc.) does: Create views on demand, re-use views that are not visible and delete views if they are no longer needed. I guess this will not be trivial to implement - but when it's done, it would be a really nice contribution to existing Android widgets.

    I will post an update soon - hang tight! :-D
    Marc

    ReplyDelete
  11. Thanks Marc.

    I would love to have the dynamic paging functionality. I'm banging my head on it right now by holding 3 variables and changing their imageUrls on scroll. But when I scroll back to screen1, I don't get a true snap (scroller scrolls thru the list of 3, even though I have animate set to false).

    I'll think about the reusable view solution. If you feel especially generous in the next day or so, I would also love to see the code you used to get this working.

    Thanks for a great switcher!

    ReplyDelete
  12. Hi @Anonymous,
    just drop me a mail. My e-mail address can be found on: http://marcreichelt.de/
    I will then send you some code that does what you primarily want. :-)

    Marc

    ReplyDelete
  13. Email sent. Thanks!

    ReplyDelete
  14. hey guys, i just finished putting a simple example on github that uses ImageViews and the Horizontal Pager. Again, I'm not 100% sure whats going on with memory in my example but please advise me if you know.

    here's my demo:
    https://github.com/aveyD/Dynamic-Paging-Demo

    Thanks!

    ReplyDelete
  15. hi! if i am changing the device orientation, the view is always set to element 0. how can i stay at the current selected position?

    thx!

    ReplyDelete
  16. hi alex. Please see my comment here:

    http://marcreichelt.blogspot.com/2010/09/android-use-realviewswitcher-to-switch.html?showComment=1300730138393#c3142836062621886772

    or see line 428 in:

    https://github.com/aveyD/Dynamic-Paging-Demo/blob/master/src/com/HorizontalPager/dynamicPaging/HorizontalPager.java#L428

    (orientationSnapToScreen)

    and line 141 in:

    https://github.com/aveyD/Dynamic-Paging-Demo/blob/master/src/com/HorizontalPager/dynamicPaging/Main.java#L141

    (onConfigurationChanged)

    Hope that helps :)

    ReplyDelete
  17. Hi, Marc,

    In my activity, I implemented OnDoubleTapListener. However, none of the OnDoubleTapListener methods were called. It seems that Realviewswitcher blocks those gestures. Do you have any idea to solve it ?

    ReplyDelete
  18. Marc,

    I'm trying to convert your view switcher into a view switcher backed up with an adapter.
    I've got quite far (although I don't re-use Views yet) but I have one odd thing going on. Switching through items in the middle of the array (not from/to the first or last item) will have a flickering effect where the item I just scrolled FROM is being flashed a few milliseconds.

    Please, have a look at it if you want: https://github.com/pakerfeldt/ViewSwitcher

    You should be able to clone it and run as-is to see the strange effect I was talking about.

    Any ideas on how to solve that?

    ReplyDelete
  19. I created a basic adapter impl. It's nothing so special, but I believe it could be useful for someone.
    http://code.google.com/p/android-view-slider/

    ReplyDelete
  20. Hi Marc,

    How do I capture a moment when the screen changes?

    I know there's a screen change listener, but where do I connect that listener to my code? I am a bit of a junior in android dev, but still know a bunch to understand.

    Thanks in advance,

    Gix

    ReplyDelete
  21. Can i use it with ListView inside? it doesnt work for me

    Thanks!

    ReplyDelete
  22. Hi all,

    I finally have a version using an Adapter as data source finished. Its based on the work from Marc. Please feel free to check it out: https://github.com/pakerfeldt/android-viewflow

    ReplyDelete
  23. Thanks, its a great thing to have!!

    ReplyDelete
  24. I can't get this working. Can someone help please. Here's how i did added views:

    <2LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    > <2de.marcreichelt.android.RealViewSwitcher
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:id="@+id/real_view_switcher">
    <2GridView android:id="@+id/gridView1" android:layout_width="match_parent" android:layout_height="match_parent">
    <2GridView android:id="@+id/gridView2" android:layout_width="match_parent" android:layout_height="match_parent">
    <2/de.marcreichelt.android.RealViewSwitcher>
    <2/LinearLayout4>

    In the .java class i did:

    public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    setContentView(R.layout.main);
    RealViewSwitcher realViewSwitcher = (RealViewSwitcher)findViewById(R.id.real_view_switcher);
    GridView gridview = (GridView) findViewById(R.id.gridView1);
    GridView gridview2 = (GridView) findViewById(R.id.gridView2);
    gridview.setAdapter(new ImageAdapter(this));
    gridview2.setAdapter(new ImageAdapter(this))

    The problem is, that i can't scroll to my second GridView, it simply doesn't do anything when i drag my main screen. Did I made some mistake here?

    PS:added number 2 in the tags, because blogger didn't realy want's xml tags in the comments.

    ReplyDelete
  25. João OliveiraMay 23, 2011 05:42 PM

    Hello. I´m currently using the RealViewSwitcher. It switches beetween 3 different layout. The problem is that one of them contains a listview and the another two contains gridviews. How can I make the program to swith between layouts by swapping my finger inside the listviews and gridviews area?

    ReplyDelete
  26. João, you could try the android-viewflow library out. I believe it should be possible, but I haven't tried it.

    https://github.com/pakerfeldt/android-viewflow

    At the moment, you will need to backup the viewflow library with an Adapter containing your three elements. I guess we will implement a basic adapter thingy for those who have a static number of elements.

    ReplyDelete
  27. It does not work well in a scrollview. Can it be due to some gesture intereferences?

    Thanks anyway, apart from that it's great!

    Jul

    ReplyDelete
  28. Hi Marc, I tried your example and sometime it gets stuck at the current and next screen like this (http://i1178.photobucket.com/albums/x370/BinhNguyen84/device-1.png). Do you know why it happens?

    Thanks,

    ReplyDelete
  29. Hi Marc, Its really a great sample,thanks for such a Image swipe sample.I am having one problem in that. The default image is appearing in the center of the Layout. How can i align it to top of the Layout? Please help me.

    Thanks Venkatesh Raghavan.

    ReplyDelete
  30. This is a very nice, 100% working clean code. Thank you.
    For those who want to use this class for a popup dialog, comment out the lines
    throw new IllegalStateException("ViewSwitcher can only be used in EXACTLY mode.");
    from the RealViewSwitcher.onMeasure.

    ReplyDelete
  31. its awesome. Sweet Simple but effective :)
    Thanks.

    ReplyDelete
  32. Great job, for sure!! ;)

    ReplyDelete
  33. hi im getting ViewSwitcher can only be used in EXACTLY mode exeption. when i comment that in the code, it doesn't draw anything. just blank. im using this inside a map activity. please help

    ReplyDelete
  34. First post here.

    I tried to use buttons or images that can be clicked, the scrolled didn't work, I'm just looking for that, any idea on how do this?

    But the way, nice work!

    ReplyDelete
  35. The ViewPager in the Android compatibility library is a good fit for this, and it's more of a standard (since it's written by Google). It's an AdapterView, so it's easy on your memory, and it's fairly simple to implement.

    ReplyDelete
  36. It looks great and in my requirement i have 5 viewes and i want to show 1.5 view in the screen every time up to last. Is it possible...

    ReplyDelete
  37. Hey Man it awesome ,it really helped me a lot!!!!

    Thanks!!!!

    ReplyDelete
  38. Would be great if this worked with ListViews!

    ReplyDelete
  39. Working great, thank you so much.

    ReplyDelete
  40. Hi,

    I m using the class and its working great!

    Onething, i m not getting if i can get a long press event? if user click and hold his finger on the view under realViewSwitcher?

    I m working on an existing application that uses the above class, so i may not be known of all functionality the class provides.. but the thing is i can get a click event, but not long press event for my view activity.

    any help will be really appreciated.

    ReplyDelete
  41. Hi, for my perpose, i tune your logic a litle bit,

    this what i do...

    i add,
    public static interface OnScreenMovementListener {
    void onScreenMovement(int screen);
    }
    to listen movement on

    case MotionEvent.ACTION_MOVE:
    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;
    }

    if (mTouchState == TOUCH_STATE_SCROLLING) {
    // Scroll to follow the motion event
    final int deltaX = (int) (mLastMotionX - x);
    mLastMotionX = x;

    final int scrollX = getScrollX();
    if (deltaX < 0) {
    if (scrollX > 0) {
    scrollBy(Math.max(-scrollX, deltaX), 0);

    if (mOnScreenMovementListener != null)
    mOnScreenMovementListener
    .onScreenMovement(mCurrentScreen);
    }
    } else if (deltaX > 0) {
    final int availableToScroll = getChildAt(
    getChildCount() - 1).getRight()
    - scrollX - getWidth();
    if (availableToScroll > 0) {
    scrollBy(Math.min(availableToScroll, deltaX), 0);

    if (mOnScreenMovementListener != null)
    mOnScreenMovementListener
    .onScreenMovement(mCurrentScreen);
    }
    }
    }

    break;


    and change your on onScreenSwitched location and tune it to
    private void snapToScreen(int whichScreen) {
    if (!mScroller.isFinished())
    return;

    whichScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1));

    mNextScreen = whichScreen;

    final int newX = whichScreen * getWidth();
    final int delta = newX - getScrollX();
    mScroller.startScroll(getScrollX(), 0, delta, 0, Math.abs(delta) * 2);
    invalidate();

    if (mOnScreenSwitchListener != null)
    mOnScreenSwitchListener.onScreenSwitched(whichScreen,
    mCurrentScreen);
    }

    to detect screen changing beter (from screen 1 to screen 2)

    ReplyDelete
  42. Hi,

    Your example works like a charm, but I have a question. How can I put some buttons above the 'RealViewSwitcher'?

    Thanks,

    ReplyDelete
  43. great job,
    thanks Marc Reichelt for RealViewSwitcher
    and David for Dynamic Paging

    I need a code or an idea...

    I want to set two views (images) side by side in landscape format - two pages on the screen
    like a magazine

    Thanks,
    and wish everybody a prosperous new year

    ReplyDelete