Introduction
It is easy and normal to make a list view scroll up and down vertically, like below:
But how do we create a list view like that can be scrolled in
a segment of a circle, like that:
How to do?
Of course, we must customize the view to create a list like that. I decided to use SurfaceView
to create this list view. After analysis, the problems will contain:
- How to draw image in a circle?
- How to scroll list view in
a segment of a circle?
How to draw image in a circle?
Suppose that there is
a circle that has the center point (centerX, centerY) and radius r. And point P has angle
alpha. It will be specified by the formula below:
- P(x) = centerX + cos(alpha) *r.
- P(y) = centerY - sin(alpha) * r.
Based on it, we can easily draw an item in a circle.
How to scroll list view in a segment of a circle?
The problem is when the user is scrolling, how do we update the angle of each item? Exactly, we must specify the scroll angle at scrolling time, then add to
the current angle of each item of the list view. To resolve this requirement, I decided to use GestureDetector
to control this event. And in this class, I focused on the function below to
specify the scroll angle.
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)
Based on e2
, distanceX
, and distanceY
, the scroll angle will be specified as
in the picture below:
Based on this picture, we give the formula below
=> scroll angle = β - α
Using the code
I will create a list of players as a sample, with player images prepared in a resource folder. You can refer
to the source code I uploaded.
int[] playerDrawableResourceIds = new int[] { R.drawable.ronaldo,
R.drawable.zindance, R.drawable.congvinh, R.drawable.huynhduc,
R.drawable.gerrard, R.drawable.nagatomo, R.drawable.messi,
R.drawable.minhphuong, R.drawable.neymar, R.drawable.ronaldo_beo,
R.drawable.ronaldinho, R.drawable.xavi };
How to draw an image in a circle?
At first, create the item class CircleDrawItem
of the list view.
public class CircleDrawItem {
public Bitmap mIconBitmap;
public double mAngle;
}
mIconBitmap
: the bitmap of each item of the list view.mAngle
: the angle in radian of item. This angle will be updated when scrolling.
Because the circle will be displayed in the left of the screen, the center will have x coordinate
as negative number. Besides that, the y coordinate will be in the center of the screen.
mCenterX = (int) (-Global.dp * 200);
mCenterY = (int) (Global.dh / 2.0f);
mRadius = (int) (450 * Global.dp);
mStartAngleInRadian = Math.PI / 4;
With the value of Global
is calculated:
Global.dw = getResources().getDisplayMetrics().widthPixels;
Global.dh = getResources().getDisplayMetrics().heightPixels;
Global.dp = Global.density / 1.5f;
The circle will keep an array list of CircleDrawItem
s, with the first item having
angle PI/4, and the distance between two items PI/10.
The angle of the first item and the distance between two items can be changed based on your requirements.
for (int i = 0; i < playerDrawableResourceIds.length; i++) {
CircleDrawItem circleDrawItem = new CircleDrawItem();
circleDrawItem.mIconBitmap = decodeSampledBitmapFromResource(
getResources(), playerDrawableResourceIds[i], 50, 50);
circleDrawItem.mAngle = mStartAngleInRadian - i * Math.PI / 10;
datas.add(circleDrawItem);
}
I create a thread to draw data items in circle.
protected void draw() {
Canvas canvas = getHolder().lockCanvas();
if (canvas == null) {
return;
}
canvas.save();
canvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setFilterBitmap(true);
paint.setAntiAlias(true);
for (int i = 0; i < datas.size(); i++) {
canvas.save();
CircleDrawItem item = datas.get(i);
double x = mCenterX + Math.cos(item.mAngle) * mRadius;
double y = mCenterY - Math.sin(item.mAngle) * mRadius;
canvas.drawBitmap(item.mIconBitmap,
(int) x - item.mIconBitmap.getWidth() / 2, (int) y
- item.mIconBitmap.getHeight() / 2, paint);
canvas.restore();
}
canvas.restore();
getHolder().unlockCanvasAndPost(canvas);
}
How to scroll list view in segment of a circle?
Implement a OnGestureListener
and override the onScroll()
method.
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
float distanceY) {
float tpx = e2.getX();
float tpy = e2.getY();
float disx = (int) distanceX;
float disy = (int) distanceY;
double scrollAngle = calculateScrollAngle(tpx, tpy, tpx + disx, tpy
+ disy);
for (int i = 0; i < datas.size(); i++) {
datas.get(i).mAngle += scrollAngle;
}
return true;
}
And the way to calculate the scroll angle will be:
private double calculateScrollAngle(float px1, float py1, float px2,
float py2) {
double radian1 = Math.atan2(py1, px1);
double radian2 = Math.atan2(py2, px2);
double diff = radian2 - radian1;
return diff;
}
History
- 2013-08-24: Add source code with scroll event.