Listview Endless Scrolling
Make a vertical listview with scroll looping
Introduction
Today, I will guide the way to create a listview
with looping while scrolling. Looping means that:
- When scrolling down, the last item will be the next of first item
- And when scrolling up, the first item will be the next of last item
Besides that, I added more requirements as below:
Listview
is scrolled verticallyListview
displays 5 items in maximum
Background
I continue using SurfaceView to develop this listview
to detect various gestures and events in SurfaceView, using GestureDetector.
Using the Code
List Item Definition
The same with this article, define an ImageItem
with properties below:
public float x;
public float y;
public Bitmap bitmap;
public Translate translate;
x y:
is the coordinate of image item. The coordinate will be updated at real time. Based on x, y, item will be drawn in a thread by usingCanvas
canvas.drawBitmap(bitmap, x, y, paint);
bitmap
: The itembitmap
will be drawn byCanvas
translate
: WhenonFling()
of the detectorGestureDetector
is detected, this animation will be set to item to start translating.
How to Scroll List Vertically?
Our listview
will implement OnGestureListener, and it must override method:
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)
When scrolling, updating each item of list by adding distanceY
to item.y
, listview
will scroll vertically. For this reason, if Listview
keeps a list item items
, it will be scrolled by the below method:
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
float distanceY) {
for (ImageItem item : items) {
item.animations.clear();
item.y -= distanceY;
layout(item);
}
return false;
}
How to Loop when Scrolling?
You can see that there is layout(item)
in the above onScroll()
method. This function is implemented to loop the list. But how to do that?
Suppose that our listview
has vertical coordinates from ymin
to ymax
. When scrolling, the item.y
will be updated from [ymin
, ymax
]. To loop, we must do the following:
- If
item.y
>ymax
, reset it toymax
- If
item.y
<ymin
, reset it toymin
Based on the above thinking, layout(item)
will be implemented:
private void layout(ImageItem item) {
if (item.y > maxY) {
float dif = item.x - maxY;
item.y = minY + dif;
} else if (item.y < minY) {
float dif = minY - item.y;
item.y = maxY - dif;
}
}
How to Calculate ymin and ymax
ymin
= they
coordinate of the first itemymax
= the height oflistview
(item's height * item count)
How to Fling
Method to detect GetureDetector
is:
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)
When this event happens, I will translate each item of listview
a distance velocityY
, in 1 second.
@Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { long current = System.currentTimeMillis(); for (ImageItem item : items) { Translate translateY = new Translate(); translateY.startTime = current; translateY.duration = 1500; translateY.totalDistanceY = (int) velocityY; item.translate = translateY; } return false; }
I created Animation
and Translate
which extends Animation
to do the idea above.
Animation.java
abstract public class Animation {
public enum Type {
Translate, Alpha, Scale, Rotate,
};
public long duration;
public long startOffsetTime;
public long startTime;
public Type type;
public boolean isActive = false;
private Interpolator interpolator = new DecelerateInterpolator();
public Animation(Type type) {
this.type = type;
}
abstract public void refresh();
public long getEndTime() {
return startTime + duration;
}
public void start(long currentTime) {
startTime = currentTime + startOffsetTime;
isActive = true;
refresh();
}
protected float getRatio(long currentTime) {
if (isActive == false) {
return 0;
}
if (duration == 0) {
return 1;
}
float progress = (float) (currentTime - startTime) / (float) duration;
return interpolator.getInterpolation(progress);
}
}
Translate.java
public class Translate extends Animation {
public Translate() {
super(Type.Translate);
};
public int totalDistanceX;
public int totalDistanceY;
private int currentValueX = 0;
private int currentValueY = 0;
public int getCurrentPosX(long currentTime) {
if (currentTime > getEndTime()) {
return totalDistanceX;
} else if (currentTime < startTime) {
return 0;
}
return (int) (this.totalDistanceX * getRatio(currentTime));
}
public int getCurrentPosY(long currentTime) {
if (currentTime > getEndTime()) {
return totalDistanceY;
} else if (currentTime < startTime) {
return 0;
}
return (int) (this.totalDistanceY * getRatio(currentTime));
}
public int getDifX(long currentTime) {
int pos = getCurrentPosX(currentTime);
int dis = pos - currentValueX;
currentValueX = pos;
return dis;
}
public int getDifY(long currentTime) {
int pos = getCurrentPosY(currentTime);
int dis = pos - currentValueY;
currentValueY = pos;
return dis;
}
@Override
public void refresh() {
currentValueX = 0;
currentValueY = 0;
}
}
History
- 20130907: First created