Infinite Horizontal Scrollview or Circular Scrollview
Posted By : Piyush Choudhary | 15-Apr-2017
This blog guides android developers who want to use horizontal scrollview which is applied in most cases like tuner for radio , volume btton for media player , tuning a video in videoplayer to show a video frame by frame and many more applications .
This widget can be scrolled for either side and is never ending scroll for both sides .
Import these classes in your project :
PSCloneableView PSSize PSInfiniteScrollView
PSCloneableView.java
import android.content.Context;
import android.util.AttributeSet;
import android.widget.ImageView;
public abstract class PSCloneableView extends ImageView {
public PSCloneableView(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
public PSCloneableView(Context context, AttributeSet attrs) {
super(context,attrs);
// TODO Auto-generated constructor stub
}
public abstract PSCloneableView clone();
}
PSInfiniteScrollView.java
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Timer;
import java.util.TimerTask;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
import android.app.Activity;
public class PSInfiniteScrollView extends FrameLayout implements View.OnTouchListener{
Context context;
ArrayList<PSCloneableView> carouselItems;
static boolean touchDown = false;
float previousScrollX = 0, currentScrollX = 0;
Timer scrollTimer;
PSCloneableView replicaF, replicaB;
int itemCount = 0;
private float scrollX;
private float firstX;
VelocityTracker vTracker;
PSSize itemSize;
static int FRAME_RATE = (int) (1000.0/60.0);
static float DECELERATION_RATE = (float) (1.0/1.1);
public PSInfiniteScrollView(Context ctx, PSSize itemsize) {
super(ctx);
context = ctx;
itemSize = itemsize;
RelativeLayout.LayoutParams sparams = new RelativeLayout.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
this.setClipChildren(true);
this.setLayoutParams(sparams);
this.setOnTouchListener(this);
scrollTimer = new Timer();
scrollTimer.schedule(new ScrollTimerTask(),FRAME_RATE, FRAME_RATE);
carouselItems = new ArrayList<PSCloneableView>();
}
public PSInfiniteScrollView(Context ctx, AttributeSet attrs, PSSize itemsize) {
super(ctx,attrs);
context = ctx;
itemSize = itemsize;
RelativeLayout.LayoutParams sparams = new RelativeLayout.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
this.setClipChildren(true);
this.setLayoutParams(sparams);
this.setOnTouchListener(this);
scrollTimer = new Timer();
scrollTimer.schedule(new ScrollTimerTask(), FRAME_RATE, FRAME_RATE);
carouselItems = new ArrayList<PSCloneableView>();
}
@Override
public boolean onTouch(View arg0, MotionEvent arg1) {
if (arg1.getAction() == MotionEvent.ACTION_DOWN) {
vTracker = VelocityTracker.obtain();
touchDown = true;
firstX = (float) (arg1.getX() * -1);
} else if (arg1.getAction() == MotionEvent.ACTION_MOVE) {
scrollX = firstX - (float) (arg1.getX() * -1);
firstX = (float) (arg1.getX() * -1);
vTracker.addMovement(arg1);
} else if (arg1.getAction() == MotionEvent.ACTION_UP) {
touchDown = false;
vTracker.computeCurrentVelocity(1000);
scrollX = (float) (vTracker.getXVelocity() * 0.1);
if(scrollX > itemSize.width){
scrollX = itemSize.width;
}
if(scrollX < -itemSize.width){
scrollX = -itemSize.width;
}
}
return true;
}
public class ScrollTimerTask extends TimerTask {
public synchronized void arrangeViews() {
if (scrollX != 0) {
synchronized (carouselItems) {
((Activity)context).runOnUiThread(new Runnable() {
public void run() {
Collections.sort(carouselItems,
new Comparator<View>() {
@Override
public int compare(View lhs,
View rhs) {
FrameLayout.LayoutParams lhsParams = (FrameLayout.LayoutParams) lhs
.getLayoutParams();
FrameLayout.LayoutParams rhsParams = (FrameLayout.LayoutParams) rhs
.getLayoutParams();
return ((Integer) lhsParams.leftMargin)
.compareTo((Integer) rhsParams.leftMargin);
}
});
for (int i = 0; i < carouselItems.size(); i++) {
// arrange
PSCloneableView vw = carouselItems.get(i);
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) vw
.getLayoutParams();
if (i == 0) {
params.leftMargin += scrollX;
} else {
FrameLayout.LayoutParams fparams = (FrameLayout.LayoutParams) carouselItems
.get(0).getLayoutParams();
params.leftMargin = (int) (fparams.leftMargin
+ (i * itemSize.width));
}
if (vw != replicaF && vw != replicaB) {
if (scrollX > 0) {
if (params.leftMargin > ((itemCount - 1) * itemSize.width)
&& params.leftMargin <= (itemCount * itemSize.width)) {
FrameLayout.LayoutParams rparams = null;
if (replicaF == null) {
replicaF = vw.clone();
rparams = new FrameLayout.LayoutParams(
(int) itemSize.width, (int) itemSize.height);
rparams.leftMargin = (int) -itemSize.width;
replicaF.setLayoutParams(rparams);
}
if (PSInfiniteScrollView.this
.findViewById(replicaF
.getId()) == null) {
PSInfiniteScrollView.this
.addView(replicaF);
carouselItems.add(0, replicaF);
Log.d("Carousel",
"Added to 0 index" + i);
}
}
if (params.leftMargin >= (itemCount * itemSize.width)) {
FrameLayout.LayoutParams fparams = (FrameLayout.LayoutParams)replicaF.getLayoutParams();
Log.d("Carousel", "First margin " + fparams.leftMargin + " Last Margin " + params.leftMargin);
if (replicaF != null) {
if (PSInfiniteScrollView.this
.findViewById(replicaF
.getId()) != null) {
PSInfiniteScrollView.this
.removeView(replicaF);
carouselItems
.remove(replicaF);
carouselItems.remove(vw);
carouselItems.add(0, vw);
i = 0;
params.leftMargin = 0;
Log.d("Carousel",
"Removed from 0 index"
+ i);
}
}
replicaF = null;
}
} else {
if (params.leftMargin < 0
&& params.leftMargin >= -itemSize.width) {
FrameLayout.LayoutParams rparams = null;
if (replicaB == null) {
replicaB = vw.clone();
rparams = new FrameLayout.LayoutParams(
(int) itemSize.width, (int) itemSize.height);
rparams.leftMargin = (int) ((itemCount - 1) * itemSize.width) ;
replicaB.setLayoutParams(rparams);
}
if (PSInfiniteScrollView.this
.findViewById(replicaB
.getId()) == null) {
PSInfiniteScrollView.this
.addView(replicaB);
carouselItems.add(replicaB);
Log.d("Carousel",
"Added index" + i);
}
}
if (params.leftMargin < -itemSize.width) {
if (replicaB != null) {
if (PSInfiniteScrollView.this
.findViewById(replicaB
.getId()) != null) {
PSInfiniteScrollView.this
.removeView(replicaB);
carouselItems
.remove(replicaB);
carouselItems.remove(vw);
carouselItems.add(vw);
i = 0;
params.leftMargin = (int) (itemCount * itemSize.width);
Log.d("Carousel",
"Removed index"
+ i);
}
}
replicaB = null;
}
}
}else{
}
vw.setLayoutParams(params);
}
if(replicaF != null){
FrameLayout.LayoutParams fparams = (FrameLayout.LayoutParams)replicaF.getLayoutParams();
if(fparams.leftMargin < -itemSize.width || fparams.leftMargin > 0){
if (PSInfiniteScrollView.this
.findViewById(replicaF
.getId()) != null) {
replicaF
.getLayoutParams();
PSInfiniteScrollView.this.removeView(replicaF);
carouselItems
.remove(replicaF);
Log.d("Carousel","Removed Front " + fparams.leftMargin);
}
replicaF = null;
}
}
if(touchDown){
scrollX = 0;
}else{
scrollX = (float) (scrollX * DECELERATION_RATE);
if(Math.abs(scrollX) < 0.01)
scrollX = 0;
Log.d("Carousel", "Velocity " + vTracker.getXVelocity() + " Scroll " + scrollX);
}
Collections.sort(carouselItems,
new Comparator<View>() {
@Override
public int compare(View lhs,
View rhs) {
FrameLayout.LayoutParams lhsParams = (FrameLayout.LayoutParams) lhs
.getLayoutParams();
FrameLayout.LayoutParams rhsParams = (FrameLayout.LayoutParams) rhs
.getLayoutParams();
return ((Integer) lhsParams.leftMargin)
.compareTo((Integer) rhsParams.leftMargin);
}
});
}
});
}
}
}
@Override
public void run() {
arrangeViews();
}
}
public void addItem(PSCloneableView vw){
FrameLayout.LayoutParams rparams = new FrameLayout.LayoutParams(
(int) itemSize.width, (int) itemSize.height);
rparams.leftMargin = (int) (carouselItems.size() * itemSize.width);
vw.setLayoutParams(rparams);
super.addView(vw, carouselItems.size());
carouselItems.add(vw);
itemCount = carouselItems.size();
}
public void removeItem(PSCloneableView vw){
super.removeView(vw);
carouselItems.remove(vw);
itemCount = carouselItems.size();
}
}
PSSize.java
package com.precisosol.infinitescrollview;
public class PSSize {
float height;
float width;
public PSSize(float w, float h){
width = w;
height = h;
}
}
You have to use all these three files in your parent activity of application in the way as described : -
MainActivity.java
import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import android.view.Menu;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
public class MainActivity extends Activity {
LinearLayout linearLayout;
int[] c = new int[] { Color.YELLOW, Color.BLUE, Color.RED, Color.GRAY,
Color.GREEN, Color.CYAN, Color.LTGRAY, Color.WHITE, Color.DKGRAY,
Color.BLACK };
String[] cs = new String[] { "YELLOW", "BLUE", "RED", "GRAY", "GREEN",
"CYAN", "LTGRAY", "WHITE", "DKGRAY", "BLACK" };
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
RelativeLayout container = (RelativeLayout) findViewById(R.id.relativeLayout);
PSInfiniteScrollView scrollView = new PSInfiniteScrollView(this,new PSSize(120,120));
for (int i = 0; i < 10; i++) {
MyCloneableView img = new MyCloneableView(MainActivity.this);
img.setId(i + 20);
img.setImageResource(R.drawable.ic_launcher);
img.setScaleType(ImageView.ScaleType.FIT_XY);
img.setBackgroundColor(c[i]);
img.setTag(c[i]);
scrollView.addItem(img);
}
container.addView(scrollView);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
}
You can either use any widget to show a self repeating pattern such as ImageDrawable , textview etc. to form a scrollable view in either direction .
By importing these classes in your project and using these classes in the way as described in MainActivity.java you can implement infinite horizontal scrollview
Cookies are important to the proper functioning of a site. To improve your experience, we use cookies to remember log-in details and provide secure log-in, collect statistics to optimize site functionality, and deliver content tailored to your interests. Click Agree and Proceed to accept cookies and go directly to the site or click on View Cookie Settings to see detailed descriptions of the types of cookies and choose whether to accept certain cookies while on the site.
About Author
Piyush Choudhary
Piyush is a bright mobile app developer, he has expertise in building Native Android Applications and Core Java.His hobbies are reading tehnical blogs, playing snooker and console gaming.