如何制作 Android 3D Carousel Drawable

How to Make an Android 3D Carousel Drawable

我正在尝试在 Android (Java) 中创建一个 3D 旋转木马,但我不能完全让它工作。这是我想要的那种轮播:

我已经尝试了几种漫长而复杂的方法来做到这一点,包括使用大量数学方法手工绘制路径,但它总是不太正确。

感觉最接近的想法是通过计算图像在圆上的位置在 x-y 平面中绘制图像轮播,然后“翻转”该平面,使轮播现在位于 x-z 方向。这似乎几乎可以工作(虽然角落不太接触),但正方形是平躺的,我需要它们站在最外边。我无法弄清楚如何进行最终旋转,但不会撤消相机之前的旋转。

这是我的结果目前的视觉效果:

我是不是走错了路?我很接近但只需要改变几件事吗?老实说,我不确定。请帮我弄清楚如何更好地做到这一点!

这是我的可绘制代码:

public class CarouselCard extends Drawable {

    Paint paintBackground = null;
    
    /* CONSTRUCTOR */

    public CarouselCard() {
        super();
        invalidateSelf();
    }

    @Override
    public void draw(Canvas canvas) {

        if (paintBackground == null) {    // Just starting
            paintBackground = new Paint();
            paintBackground.setStyle(Paint.Style.FILL);
            paintBackground.setColor(Color.BLUE);
        }

        int HALF_CHORD_WIDTH = 150;
        float RADIUS = 200;

        float INTERIOR_ANGLE = 30;

        INTERIOR_ANGLE %= 360;
        int numObjects = (int) (360 / INTERIOR_ANGLE);

        for (int i = 0; i < numObjects; i++) {

            float theta = INTERIOR_ANGLE * i;
            double rad = Math.toRadians(theta);

            float circleX = (float) (RADIUS * Math.sin(rad));
            float circleY = (float) (RADIUS * Math.cos(rad));

            canvas.save();
            canvas.translate(canvas.getWidth() / 2, canvas.getHeight() / 2);

            Camera camera = new Camera();
            camera.save();

            camera.rotateX(80); // Approximates 90 but keeps it visible since flat
            camera.rotateZ(theta);

            camera.applyToCanvas(canvas);
            camera.restore();

            if (i % 4 == 0) paintBackground.setColor(Color.YELLOW);
            if (i % 4 == 1) paintBackground.setColor(Color.GREEN);
            if (i % 4 == 2) paintBackground.setColor(Color.BLUE);
            if (i % 4 == 3) paintBackground.setColor(Color.GRAY);

            paintBackground.setAlpha(128);

            canvas.drawRect(circleX-HALF_CHORD_WIDTH, circleY-HALF_CHORD_WIDTH,
                    circleX+HALF_CHORD_WIDTH, circleY+HALF_CHORD_WIDTH, paintBackground);

            canvas.restore();
        }
    }

    @Override
    public void setAlpha(int alpha) {}

    @Override
    public void setColorFilter(ColorFilter colorFilter) {}

    @Override
    public int getOpacity() {
        return PixelFormat.UNKNOWN;
    }
}

或者,我可以让所有东西都朝向正确的方向并正确定向,但前提是它们都位于旋转木马的中心。我不确定如何适当地移动它们,同时仍然保持使这张照片成为可能的转换:

此图像是通过调整上述 for 循环生成的,如下所示:

for (int i = 0; i < numObjects; i++) {

    if (i % 4 == 0) paintBackground.setColor(Color.YELLOW);
    if (i % 4 == 1) paintBackground.setColor(Color.GREEN);
    if (i % 4 == 2) paintBackground.setColor(Color.BLUE);
    if (i % 4 == 3) paintBackground.setColor(Color.GRAY);
    paintBackground.setAlpha(128);

    float theta = INTERIOR_ANGLE * i;

    canvas.save();
    canvas.translate(canvas.getWidth() / 2, canvas.getHeight() / 2);

    Camera camera = new Camera();
    camera.save();
    
    camera.rotateY(theta);

    camera.applyToCanvas(canvas);
    camera.restore();

    canvas.drawRect(
            -HALF_CHORD_WIDTH,
            -HALF_CHORD_WIDTH,
            HALF_CHORD_WIDTH,
            HALF_CHORD_WIDTH,
            paintBackground);

    canvas.restore();
}

我能够创建以下轮播:

它的代码是:

@Override
public void draw(Canvas canvas) {

    if (paintBackground == null) {    // Just starting
        paintBackground = new Paint();
        paintBackground.setStyle(Paint.Style.FILL);
        paintBackground.setColor(Color.BLUE);
    }

    // ######## CRUCIAL - START - Modify constants with care!

    // Decreasing chord denominator makes carousel bigger, while increasing it
    // makes front side contain more squares (but gets smaller)

    float HALF_CHORD_WIDTH = canvas.getWidth()/31f; // Width of each square
    float RADIUS = HALF_CHORD_WIDTH; // Radius of carousel
    float CAMERA_MULT = HALF_CHORD_WIDTH/10f;

    // ######## CRUCIAL - END

    float INTERIOR_ANGLE = 30;

    INTERIOR_ANGLE %= 360;
    int numObjects = (int) (360 / INTERIOR_ANGLE);

    for (int i = 0; i < numObjects; i++) {

        if (i % 4 == 0) paintBackground.setColor(Color.YELLOW);
        if (i % 4 == 1) paintBackground.setColor(Color.GREEN);
        if (i % 4 == 2) paintBackground.setColor(Color.BLUE);
        if (i % 4 == 3) paintBackground.setColor(Color.GRAY);
        paintBackground.setAlpha(128);

        float theta = INTERIOR_ANGLE * i;
        double rad = Math.toRadians(theta);

        float circleX = (float) (RADIUS * Math.sin(rad));
        float circleZ = (float) (RADIUS * Math.cos(rad));

        canvas.save();
        canvas.translate(canvas.getWidth() / 2, canvas.getHeight() / 2);

        Camera camera = new Camera();
        camera.save();

        camera.translate(circleX*CAMERA_MULT, 0, -circleZ*CAMERA_MULT);
        camera.rotateY(theta);

        camera.applyToCanvas(canvas);
        camera.restore();

        canvas.drawRect(
                -HALF_CHORD_WIDTH,
                -HALF_CHORD_WIDTH,
                HALF_CHORD_WIDTH,
                HALF_CHORD_WIDTH,
                paintBackground);

        canvas.restore();
    }
}