Pages

Friday, August 5, 2011

Creating Custom Horizontal Scroll View With Snap or paging

I have faced various issues to develop a horizontal scroll view with paging effect. I googled it a lot but not able to find anything satisfactory. Some search results help me, using those i have created my own custom horizontal scrollview class which can give similar effect as effect on Tablet Home page.

I will be discussing it step by step so that you can use it where required and it becomes easy for you to understand.

Step 1) Layout file -  Simple layout file for demo


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_height="match_parent"
android:layout_width="match_parent" android:id="@+id/layer">
</LinearLayout>

Step 2) CustomHorizontalScrollView - Class file which extends HorizontalScrollView and give snapping or paging effect.


public class CustomHorizontalScrollView extends HorizontalScrollView implements
OnTouchListener, OnGestureListener {

        private static final int SWIPE_MIN_DISTANCE = 300;

private static final int SWIPE_THRESHOLD_VELOCITY = 300;
private static final int SWIPE_PAGE_ON_FACTOR = 10;


private GestureDetector gestureDetector;
private int scrollTo = 0;
private int maxItem = 0;
private int activeItem = 0;
private float prevScrollX = 0;
private boolean start = true;
private int itemWidth = 0;
private float currentScrollX;
private boolean flingDisable = true;

public CustomHorizontalScrollView(Context context) {
super(context);
setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,
LayoutParams.FILL_PARENT));
}

public CustomHorizontalScrollView(Context context, int maxItem,
int itemWidth) {
this(context);
this.maxItem = maxItem;
this.itemWidth = itemWidth;
gestureDetector = new GestureDetector(this);
this.setOnTouchListener(this);
}

@Override
public boolean onTouch(View v, MotionEvent event) {
if (gestureDetector.onTouchEvent(event)) {
return true;
}
Boolean returnValue = gestureDetector.onTouchEvent(event);

int x = (int) event.getRawX();

switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
if (start) {
this.prevScrollX = x;
start = false;
}
break;
case MotionEvent.ACTION_UP:
start = true;
this.currentScrollX = x;
int minFactor = itemWidth
/ ConfigurationParams.SWIPE_PAGE_ON_FACTOR;

if ((this.prevScrollX - this.currentScrollX) > minFactor) {
if (activeItem < maxItem - 1)
activeItem = activeItem + 1;

} else if ((this.currentScrollX - this.prevScrollX) > minFactor) {
if (activeItem > 0)
activeItem = activeItem - 1;
}
System.out.println("horizontal : " + activeItem);
scrollTo = activeItem * itemWidth;
this.smoothScrollTo(scrollTo, 0);
returnValue = true;
break;
}
return returnValue;
}

@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
if (flingDisable)
return false;
boolean returnValue = false;
float ptx1 = 0, ptx2 = 0;
if (e1 == null || e2 == null)
return false;
ptx1 = e1.getX();
ptx2 = e2.getX();
// right to left

if (ptx1 - ptx2 > ConfigurationParams.SWIPE_MIN_DISTANCE
&& Math.abs(velocityX) > ConfigurationParams.SWIPE_THRESHOLD_VELOCITY) {
if (activeItem < maxItem - 1)
activeItem = activeItem + 1;

returnValue = true;

} else if (ptx2 - ptx1 > ConfigurationParams.SWIPE_MIN_DISTANCE
&& Math.abs(velocityX) > ConfigurationParams.SWIPE_THRESHOLD_VELOCITY) {
if (activeItem > 0)
activeItem = activeItem - 1;

returnValue = true;
}
scrollTo = activeItem * itemWidth;
this.smoothScrollTo(0, scrollTo);
return returnValue;
}

@Override
public boolean onDown(MotionEvent e) {
return false;
}

@Override
public void onLongPress(MotionEvent e) {
}

@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
float distanceY) {
return false;
}

@Override
public void onShowPress(MotionEvent e) {
}

@Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
}



Step 3) TestHorizontalActivity- This class will be used as test class


public class TestHorizontalActivity extends Activity {

private LinearLayout linearLayout;
private CustomHorizontalScrollView horizontalScrollView;

protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

int width = activity.getWindowManager().getDefaultDisplay().getWidth();
int height = activity.getWindowManager().getDefaultDisplay().getHeight();
horizontalScrollView = new CustomHorizontalScrollView(this, 3,
width);
setContentView(R.layout.horizontal);
linearLayout = (LinearLayout) findViewById(R.id.layer);
linearLayout.addView(horizontalScrollView);

LinearLayout container = new LinearLayout(this);
container.setLayoutParams(new LayoutParams(width, height));
// container.setHeight(height);

TextView textView = new TextView(this);
textView.setWidth(width);
textView.setHeight(height);
textView.setGravity(Gravity.CENTER);
textView.setText("First  Screen");
textView.setBackgroundColor(Color.CYAN);
container.addView(textView);

textView = new TextView(this);
textView.setWidth(width);
textView.setHeight(height);
textView.setGravity(Gravity.CENTER);
textView.setText("Second  Screen");
textView.setBackgroundColor(Color.GREEN);
container.addView(textView);

textView = new TextView(this);
textView.setWidth(width);
textView.setHeight(height);
textView.setGravity(Gravity.CENTER);
textView.setText("Third  Screen");
textView.setBackgroundColor(Color.RED);
container.addView(textView);

horizontalScrollView.addView(container);
}

}


This is it, these 3 simple steps and you are ready to run a paging effect in your android application.

In next blog i will discuss how we can use combination of both horizontal scrolling and vertical scrolling.


I hope this post will be helpful. 

Please share your comments and and you can also contact me @ yash@iotasol.com or visit our site www.iotasol.com , www.iotadomains.com.

28 comments:

  1. Great post!!! The best way to create this kind of elements! And i've tried a few :D

    I'm trying to center the horizontalscrollview in one of my pages... but i can't do it correctly...
    Would you mind to help me?
    I show u my last try:

    public void centrarEnDia(int dia){
    if(activeItem==0 && dia<7 && dia>0){

    activeItem = dia-1;
    scrollTo = activeItem * itemWidth;
    this.smoothScrollTo(scrollTo, 0);
    //this.refreshDrawableState();
    System.out.println(activeItem+" jar"+scrollTo);

    }
    }

    ReplyDelete
  2. Thanks for your comments.

    You can try android:gravity of LinearLayout which is parent of horizontalscrollview set to center. I hope that help you.
    Or i misunderstood your problem

    ReplyDelete
  3. Thank you!
    But is not exactly what i need...

    I need to center the horizontalscrollview in the activeItem that i want in every moment.

    For example:
    imagine that we have 5 activeitems into the customHorizontalScrollView, well, i want to show the number 2, or 3, o the last one, whenever i want.

    Stting horizontalscrollview to center only works when i want to show the activeItem number 3, do u understand now?


    In the code i wrote i was trying to scroll to the activeitem that i indicated in the parameter "dia"... but it didn't work; i can change the activeItem variable, i can use "smoothScrollTo" method... but the horizontalscrollview does nothing...

    Can you figure out a way to do what i want?

    Sorry for my poor English!

    And thanks again!

    ReplyDelete
  4. Add these 2 methods too your class, i hope they will solve your problem

    /// pageNo starts from 1 whereas active item starts from 0
    public void scrollToPage(int pageNo) {
    activeItem = pageNo- 1;
    scrollTo = activeItem * itemWidth;
    this.smoothScrollTo(scrollTo, 0);
    }


    // these method is used to render to particular location at start
    @Override
    protected void onLayout (boolean changed, int l, int t, int r, int b) {
    super.onLayout(changed, l, t, r, b);
    this.scrollTo(scrollTo, 0);
    }

    ReplyDelete
  5. Thank you very much!!!!

    scrollToPage method is exactly the same that i was trying... but i didn't find a way to render (or draw or refresh or update...) the view to show the new start location.
    And your method "onLayout" solved it!!!
    Thanks again!! If someday the app gives me money (or a job :), be sure i will donate you or something!

    ReplyDelete
  6. Thank you lotasol.

    You helped me to solve one part of my problem, but another part is still remaining. In fact, when i'm focusing an item i ld like to have at the edge of it a preview of the previous and next item.

    Did you have any ideas to make it possible?

    ReplyDelete
  7. Hi,

    First to the excelent post, i was searching for something like this a while. But, i couldn´t run this application. The emulator acuss NullPointerException. I debug the code and the exception it´s launch in this method below. The attribute gestureDetector became null. Who I can resolve this ?

    public CustomHorizontalScrollView(Context context, int maxItem, int itemWidth) {
    this(context);
    this.maxItem = maxItem;
    this.itemWidth = itemWidth;
    gestureDetector = new GestureDetector((android.view.GestureDetector.OnGestureListener) this);
    this.setOnTouchListener(this);
    }

    Thanks
    Daniel Leite

    ReplyDelete
  8. Thanks for your comment Daniel.

    Just comment that line and everything related to gesturedetector its not required..

    Regards

    ReplyDelete
  9. Perfect !!!!!!!

    Very Good !!!!

    I will adapter to my problem.

    Do you have some example that i can applie page indicators ?

    Thanks
    Daniel Leite

    ReplyDelete
  10. Hi Lotasol !!!!

    I´m having problem. When I adapter to my context and do this (instead of creating textview):

    LinearLayout includeLinear = (LinearLayout) findViewById(R.id.layout_princial_busca);
    container.addView(includeLinear);

    They acuss null pointer. This is accepted, because this xml exist, but your father doesn´t is de layout that i set on setContentView.
    So, to resolve this problem, i put all of this on xml file. Like this:





    //Includes others linear layout to scroll them

    /LinearLayout>
    /CustomHorizontalScrollView>


    I read the comments and implement scrollToPage and change the call to smoothScrollTo for scrollToPage where the param is activeItem + 1. But didn´t work
    You have some suggest ?

    Thank
    Daniel Leite

    ReplyDelete
  11. This comment has been removed by the author.

    ReplyDelete
  12. The XML:

    package.CustomHorizontalScrollView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentRight="true"
    android:layout_alignParentTop="true">

    LinearLayout
    android:id="@+id/linearcroll"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="horizontal" >

    //Includes others linear layout to scroll them

    /LinearLayout>
    /CustomHorizontalScrollView>

    ReplyDelete
  13. Here is the code to solve the problem with the sliding...

    @Override
    public boolean onTouch(View v, MotionEvent event) {
    if (gestureDetector.onTouchEvent(event)) {
    return true;
    }
    Boolean returnValue = gestureDetector.onTouchEvent(event);

    int x = (int) event.getRawX();

    switch (event.getAction()) {
    case MotionEvent.ACTION_MOVE:
    if (start) {
    this.prevScrollX = x;
    start = false;
    }
    break;
    case MotionEvent.ACTION_UP:
    start = true;
    this.currentScrollX = x;

    if (this.prevScrollX - this.currentScrollX > 0) {
    if (activeItem < maxItem - 1) {
    activeItem = activeItem + 1;
    }

    } else if (this.prevScrollX - this.currentScrollX < 0) {
    if (activeItem > 0) {
    activeItem = activeItem - 1;
    }
    }

    scrollTo = activeItem * itemWidth;

    this.smoothScrollTo(scrollTo, 0);
    returnValue = true;
    break;
    }
    return returnValue;
    }

    ReplyDelete
  14. I tried using the above code but it shows a lot of error.Can anyone send me the code in a zip file please ?? my mail id is raamkumar20@gmail.com


    Thanks in advance

    ReplyDelete
  15. same.. Getting problem with "ConfigurationParams"... how to resolve this??

    ReplyDelete
  16. ConfigurationParams is a constant class, which holds static final constants for all configuratable parameters we are using
    for e.g
    ConfigurationParams.SWIPE_MIN_DISTANCE

    you can set it to ConfigurationParams.SWIPE_MIN_DISTANCE = 100
    or according to your need.

    Same is for other variables

    ReplyDelete
  17. Any ideas on a visual indicator to let the user know how many slides there are, which slide they are on, etc...

    ReplyDelete
  18. Hi! Very Good code, but i need an help
    i have to insert (inflating) a relative layout in to the horizontal scroll view, but it doesn't work...

    linearLayout.addView(horizontalScrollView);

    LinearLayout container = new LinearLayout(this);
    container.setLayoutParams(new LayoutParams(linearLayout.getWidth(), linearLayout.getHeight()));


    LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    for (int i=0; i<3; i++){


    RelativeLayout childLayout = (RelativeLayout) inflater.inflate(R.layout.semplice, null);
    TextView elementoInRelativeLayout = (TextView) childLayout.findViewById(R.id.tvCanzone);
    elementoInRelativeLayout.setText("prova prova prova");
    // elementoInRelativeLayout.doSomething....
    container.addView(childLayout);
    }

    horizontalScrollView.addView(container);

    ReplyDelete
  19. Its very nice tutorial for beginners.thanks a lot.

    ReplyDelete
  20. screenshot would have been better... anyway nice post

    ReplyDelete
  21. Hello and thanks a lot!
    It works, but ho can i make an automatic scroll that interrupts on the first user touch?

    ReplyDelete
  22. Hi there , Can any one tell me how to set the current page,because i have 3 page and i want it show page 2 when app is run

    ReplyDelete
  23. Hello. You mentoin on end next tutorial in which I am interesting a lot. Did you wrote it?If yes, where I can find it?
    Thank you

    ReplyDelete
  24. Hi Crusty, Sorry but i have not yet created that tutorial

    ReplyDelete
  25. hi..i know this post is a long time ago..i hope u can help me with something..for your tutorial i use set of button rather than layout..but i want the the selected button to be at the middle of the layout..how can i achieve this.

    ReplyDelete