半圆形列表视图
列表项将在一个圆圈内显示和滚动。
引言
制作上下垂直滚动的列表视图很容易也很常见,如下所示

但是,我们如何创建一个可以在圆弧段内滚动的列表视图呢?

该如何实现?
当然,我们需要自定义视图来创建这样的列表。我决定使用 SurfaceView 来创建这个列表视图。经过分析,问题包括
- 如何在圆圈内绘制图像?
- 如何在圆弧段内滚动列表视图?
如何在圆圈内绘制图像?
假设有一个圆,其中心点为 (centerX, centerY),半径为 r。点 P 的角度为 alpha。它将由以下公式指定
- P(x) = centerX + cos(alpha) * r。
- P(y) = centerY - sin(alpha) * r。
基于此,我们可以很容易地在圆圈内绘制一个项目。
如何在圆弧段内滚动列表视图?
问题在于,当用户滚动时,我们如何更新每个项目的角度?确切地说,我们必须在滚动时指定滚动角度,然后将其添加到列表视图中每个项目的当前角度。为了解决这个需求,我决定使用 GestureDetector 来控制这个事件。并且在这个类中,我专注于以下函数来指定滚动角度。
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)
基于 e2、distanceX 和 distanceY,滚动角度将如下所示

基于这张图片,我们给出以下公式

=> 滚动角度 = β - α
使用代码
我将创建一个玩家列表作为示例,并将玩家图像准备在资源文件夹中。您可以参考我上传的源代码。
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 };
如何在圆圈内绘制图像?
首先,创建列表视图的项目类 CircleDrawItem。
public class CircleDrawItem {
    public Bitmap mIconBitmap;
    public double mAngle;
}
- mIconBitmap:列表视图中每个项目的位图。
- mAngle:项目以弧度表示的角度。此角度将在滚动时更新。
由于圆圈将显示在屏幕的左侧,因此中心点的 x 坐标将为负数。除此之外,y 坐标将位于屏幕的中心。
mCenterX = (int) (-Global.dp * 200);
mCenterY = (int) (Global.dh / 2.0f);
mRadius = (int) (450 * Global.dp);
mStartAngleInRadian = Math.PI / 4; 
使用 Global 值进行计算
Global.dw = getResources().getDisplayMetrics().widthPixels;
Global.dh = getResources().getDisplayMetrics().heightPixels;
Global.dp = Global.density / 1.5f; 
圆圈将保留一个 CircleDrawItem 数组列表,第一个项目的角度为 PI/4,并且两个项目之间的距离为 PI/10。第一个项目的角度和两个项目之间的距离可以根据您的要求进行更改。
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);      
}
我创建一个线程来绘制圆圈中的数据项。
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);
} 如何在圆弧段内滚动列表视图?
实现一个 OnGestureListener 并重写 onScroll() 方法。
@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;
}  
并且计算滚动角度的方式将是
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;
}
历史
- 2013-08-24:添加带有滚动事件的源代码。




