揭开Android万花筒算法的神秘面纱

497 阅读11分钟

在数学的奇妙世界中,有一种图案以其炫目的美丽和无尽的复杂性吸引着我们,那就是万花筒。这个看似简单的玩具,实则是一部隐藏着深奥数学原理和精妙算法的神秘机器。在现代科技的照耀下,万花筒的图案不再仅仅依赖于物理镜子的反射,而是通过精密的算法在屏幕上绽放。尤其在Android设备上,用户可以通过触摸屏幕,亲手创造出千变万化的图案。今天,我们将一同揭开Android万花筒算法的神秘面纱,探索其背后的数学原理和编程技巧。

首先,让我们一起探索万花筒的奥秘。万花筒的图案是由一个或多个圆形图形通过反射和旋转形成的。在传统的万花筒中,这是通过镜子和小珠子来实现的。然而,在数字万花筒中,我们使用数学公式来模拟这个过程。

在Android万花筒算法中,我们关注的是一种特殊类型的万花筒,它是通过摆线来生成图案的。摆线是一个点在一个滚动的圆周上的轨迹。这个点可以在圆内部、圆上或圆外部。通过改变基圆和动圆的半径,以及动点的位置,我们可以创造出无尽的图案。

让我们深入探索这个算法的核心思想。在Android设备上,当用户在屏幕上用手指画圈时,算法会生成一个基圆和一个动圆。基圆是固定的,而动圆则在基圆上滚动。动点位于动圆内部,并随着动圆的滚动而移动。动点的轨迹是我们在屏幕上看到的图案。

算法的关键在于计算动点的位置。这是通过以下公式来实现的:

X=(Rr)cos(t)+lcos(Rr1)tX = (R-r)\cos(t) + l\cos\left(\frac{R}{r} - 1\right)t

Y=(Rr)sin(t)lsin(Rr1)tY = (R-r)\sin(t) - l\sin\left(\frac{R}{r} - 1\right)t

其中,(R) 是基圆的半径,(r) 是动圆的半径,(l) 是动点距离动圆圆心的距离,而 (t) 是动圆圆心相对于x轴张开的角度。

这个算法的一个重要特性是,r可以大于R,l可以大于r。这意味着动圆可以比基圆大,动点可以在动圆的外部。这为生成各种各样的图案提供了很大的灵活性。

最后,为了使动圆的滚动与动点的轨迹同步,动圆的圆心的横坐标和纵坐标可以通过以下公式计算:

Xcenter=(Rr)cos(t)X_{center} = (R-r)\cos(t)

Ycenter=(Rr)sin(t)Y_{center} = (R-r)\sin(t)

然后,以这个圆心和半径r画出动圆。这样,动圆的滚动就会和动点的轨迹同步。

现在,让我们将这些数学原理转化为视觉艺术。在Android设备上,我们可以通过编程来实现这个算法,并让用户通过触摸屏幕来控制基圆和动圆的半径,以及动点的位置来实现万花筒图案。

在接下来的文章中,我们将深入探讨这个算法的具体实现,包括如何优化性能,如何添加额外的特效,以及如何创建一个用户友好的界面。

以圆为基线:

image.png

image.png

image.png

以圆为基线绘制类KaleidoscopeView


public class KaleidoscopeView extends SurfaceView implements SurfaceHolder.Callback {
    private static final String LOGTAG = LogUtil.makeLogTag(KaleidoscopeView.class);
    private boolean isRunning = false;
    private boolean isStart = false;
    private boolean isDrawing = false;
    private SurfaceHolder holder;
    private Paint drawPaint;
    //    private Paint drawTrack;
    private Paint drawXyPaint;
    private Paint radiusPaint;
    private Paint radiusPaint1;
    private Paint trackPaint;
    private Paint drawTextPaint;
    //    private Path drawPath;
//    private Xfermode mClearMode;
    private int paintColor = 0xFF000000, paintAlpha = 255;
    private int xyPaintColor = 0xFFf54949;
    private int radiusPaintColor = 0xFFf54949;
    private float brushSize, lastBrushSize;
    private float m_prevX, m_prevY;

    private int divisor = 360 * 100;
    //动圆圆心
    private List<TrackRadiusPoint> trackRadiusList = new ArrayList();
    //动点
    private List<TrackPoint> trackPointList = new ArrayList();

    private int index = 0;

    private int screenWidth;
    private int screenHeight;

    class TrackRadiusPoint {
        float cx;
        float cy;
        float t;
        float radius;
    }

    class TrackPoint {
        float x;
        float y;
        float t;
    }

    private float oRRadius;//定圆半径
    private float orRadius;//动圆半径
    private float orl;//点到动圆周距离

    public KaleidoscopeView(Context context) {
        super(context);
        init();
    }

    public KaleidoscopeView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public KaleidoscopeView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }


    private void init() {
        holder = getHolder();
        holder.addCallback(this);

        drawPaint = initPaint(brushSize, paintColor, 0, 0);
        //
        isRunning = false;
        //x,y轴
        drawXyPaint = new Paint();
        drawXyPaint.setAntiAlias(true);
        drawXyPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
        drawXyPaint.setStrokeJoin(Paint.Join.ROUND);
        drawXyPaint.setStrokeCap(Paint.Cap.ROUND);
        drawXyPaint.setColor(xyPaintColor);
        //radius
        radiusPaint = new Paint();
        radiusPaint.setAntiAlias(true);
        radiusPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
        radiusPaint.setStrokeJoin(Paint.Join.ROUND);
        radiusPaint.setStrokeCap(Paint.Cap.ROUND);
        radiusPaint.setColor(radiusPaintColor);
        //radius
        trackPaint = new Paint();
        trackPaint.setAntiAlias(true);
        trackPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
//        trackPaint.setStrokeJoin(Paint.Join.ROUND);
//        trackPaint.setStrokeCap(Paint.Cap.ROUND);
        trackPaint.setColor(radiusPaintColor);
        trackPaint.setTextSize(30);
        trackPaint.setStyle(Paint.Style.FILL);


        radiusPaint1 = new Paint();
        radiusPaint1.setAntiAlias(true);
        radiusPaint1.setFlags(Paint.ANTI_ALIAS_FLAG);
        radiusPaint1.setStrokeJoin(Paint.Join.ROUND);
        radiusPaint1.setStrokeCap(Paint.Cap.ROUND);
        radiusPaint1.setColor(0xFF000000);
        //test
        drawTextPaint = new Paint();
        drawTextPaint.setAntiAlias(true);
        drawTextPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
        drawTextPaint.setStrokeJoin(Paint.Join.ROUND);
        drawTextPaint.setStrokeCap(Paint.Cap.ROUND);
        drawTextPaint.setColor(paintColor);
        drawTextPaint.setTextSize(20.0f);


    }

    /**
     * init paint
     *
     * @param brushSize
     * @param paintColor
     * @param drawType
     * @return
     */
    private Paint initPaint(float brushSize, int paintColor, int drawType, int paintAlpha) {
        Paint drawPaint = new Paint();
        drawPaint.setAntiAlias(true);
        drawPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
        drawPaint.setStrokeJoin(Paint.Join.ROUND);
        drawPaint.setStrokeCap(Paint.Cap.ROUND);

        switch (drawType) {
            case 0://drawing pen
//                calculateColor();
                drawPaint.setXfermode(null);
                drawPaint.setColor(paintColor);
                drawPaint.setStrokeWidth(brushSize);
                drawPaint.setStyle(Paint.Style.STROKE);
//                setPenAlpha(paintAlpha);
                break;
            case 1://drawing eraser
                drawPaint.setStyle(Paint.Style.FILL);
                drawPaint.setColor(0x0FFFFFFFF);
                drawPaint.setStrokeWidth(brushSize);
                break;
        }

        return drawPaint;
    }

    public boolean isRunning() {
        return isRunning;
    }

    public void setRunning(boolean running) {
        isRunning = running;
    }

    public float getoRRadius() {
        return oRRadius;
    }

    public void setoRRadius(float oRRadius) {
        this.oRRadius = oRRadius;
    }

    public float getOrRadius() {
        return orRadius;
    }

    public void setOrRadius(float orRadius) {
        this.orRadius = orRadius;
    }

    public float getOrl() {
        return orl;
    }

    public void setOrl(float orl) {
        this.orl = orl;
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        DebugLog.i(LOGTAG, "surfaceCreated...");
//        isRunning = false;
        new Thread(runnable).start();
    }


    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        DebugLog.i(LOGTAG, "surfaceChanged...");
        this.screenWidth = width;
        this.screenHeight = height;

        DebugLog.i(LOGTAG, "screenWidth:" + screenWidth);
        DebugLog.i(LOGTAG, "screenHeight:" + screenHeight);
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        DebugLog.i(LOGTAG, "surfaceDestroyed...");
        isRunning = false;
    }

    int[] location = new int[2];
    public float downX, downY, preX, preY, curX, curY;
    public int drawDensity = 2;//绘制密度,数值越高图像质量越低、性能越好

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        getLocationInWindow(location); //获取在当前窗口内的绝对坐标
        curX = (event.getRawX() - location[0]) / drawDensity;
        curY = (event.getRawY() - location[1]) / drawDensity;
        float touchX = event.getX();
        float touchY = event.getY();
        switch (event.getAction() & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_POINTER_DOWN:
//                DebugLog.i(LOGTAG, "ACTION_POINTER_DOWN-touchX:" + touchX);
//                DebugLog.i(LOGTAG, "ACTION_POINTER_DOWN-touchY:" + touchY);
                break;
            case MotionEvent.ACTION_DOWN:
                //start
//                DebugLog.i(LOGTAG, "ACTION_DOWN-touchX:" + touchX);
//                DebugLog.i(LOGTAG, "ACTION_DOWN-touchY:" + touchY);
//                ++index;

                break;
            case MotionEvent.ACTION_MOVE:
                //move
//                DebugLog.i(LOGTAG, "ACTION_MOVE-touchX:" + touchX);
//                DebugLog.i(LOGTAG, "ACTION_MOVE-touchY:" + touchY);
                ++index;
                break;
            case MotionEvent.ACTION_UP:
//                DebugLog.i(LOGTAG, "ACTION_UP-touchX:" + touchX);
//                DebugLog.i(LOGTAG, "ACTION_UP-touchY:" + touchY);
                break;
            default:
                return false;
        }
        preX = curX;
        preY = curY;
        return true;

    }

    private Runnable runnable = new Runnable() {
        @Override
        public void run() {
            while (isRunning) {
//                DebugLog.i(LOGTAG, "isRunning....");
                drawBackground();
                float cx = screenWidth / 2;
                float cy = screenHeight / 2;
                //动圆圆心
//                calculateTrack(cx, cy, radiusOT);
                float radiusOR = getoRRadius();
                float radiusOr = getOrRadius();
                float l = getOrl();
                calculateNewTrack(cx, cy, radiusOR, radiusOr, l);
                int count = trackPointList.size();
                for (int i = 0; i < count; i++) {
                    if (isRunning) {
                        if (index > count - 1) {
                            index = 0;
                        }
                        draw(index, radiusOR);
                    }
                }
                SystemClock.sleep(500);
            }
        }
    };

    private void sort() {
        Collections.sort(trackPointList, new Comparator<TrackPoint>() {
            @Override
            public int compare(TrackPoint o1, TrackPoint o2) {
                float diff = o2.t - o1.t;
                if (diff > 0) {
                    return 1;
                } else if (diff < 0) {
                    return -1;
                }
                return 0; //相等为0
            }
        });
    }

    /**
     *
     */
    private void drawBackground() {
        if (holder.getSurface().isValid()) {
            Canvas canvas = holder.lockCanvas(null);
            if (canvas != null) {
                canvas.drawColor(Color.WHITE);
            }
            holder.unlockCanvasAndPost(canvas);
        }
    }

    /**
     * draw
     */
    private void draw(int i, float radiusOR) {
        Canvas canvas = null;
        try {
            if (holder != null && holder.getSurface().isValid()) {
                canvas = holder.lockCanvas(null);
                if (canvas != null) {
                    canvas.drawColor(Color.WHITE);
//                canvas.translate(screenWidth / 2, screenHeight / 2);//坐标系
                    float cx = screenWidth / 2;
                    float cy = screenHeight / 2;
                    //动圆圆心
//                   calculateTrack(cx, cy, radiusOT);
                    //xy
                    drawXY(canvas, cx, cy);
                    //OR 定圆
                    drawORCircle(canvas, cx, cy, radiusOR);
                    //Or 动圆
                    //动圆Or的轨道
                    drawOrTrack(canvas, i);

                    drawMovingTrack(canvas, i);
                }

            }
        } catch (Exception e) {

        } finally {
            if (holder != null && canvas != null)
                holder.unlockCanvasAndPost(canvas);
        }

    }

    /**
     * x,y
     *
     * @param canvas
     */
    private void drawXY(Canvas canvas, float cx, float cy) {
        canvas.drawLine(0, cy, screenWidth, cy, drawXyPaint);
        canvas.drawLine(cx, 0, cx, screenHeight, drawXyPaint);
    }
    /**
     * draw Or circle
     * 母圆(动圆) 半径r
     *
     * @param canvas
     */
    private void drawOrCircle(Canvas canvas, float cx, float cy, float radius{

        Path path = new Path();
        path.addCircle(cx, cy, radius, Path.Direction.CCW);
        canvas.drawPath(path, drawPaint);

    }

    /**
     * draw OR circle
     * 基圆(定圆) 半径r
     *
     * @param canvas
     */
    private void drawORCircle(Canvas canvas, float x, float y, float radius) {

        Path path = new Path();
        path.addCircle(x, y, radius, Path.Direction.CCW);

        canvas.drawPath(path, drawPaint);
        //圆心
        canvas.drawCircle(x, y, 5, radiusPaint);

    }
    /**
     * 圆在圆里滚那个,动圆按圆心横坐标(R-r)*cost,圆心纵坐标(R-r)*sint,半径r画圆,这样圆的滚动就会和曲线同步
     *
     * @param x
     * @param y
     * @param OR
     * @param Or
     * @param l
     */
    private void calculateNewTrack(float x, float y, float OR, float Or, float l) {

        float degrees = 0.0f;
        for (int i = 0; i < divisor; i++) {
            float tx = (float) ((OR - Or) * Math.cos(degrees));
            float ty = (float) ((OR - Or) * Math.sin(degrees));

            float X = x - tx;
            float Y = y + ty;
            TrackRadiusPoint trackRadius = new TrackRadiusPoint();
            trackRadius.cx = X;
            trackRadius.cy = Y;
            trackRadius.radius = Or;
            trackRadius.t = degrees;
            trackRadiusList.add(trackRadius);
            calculateTrackPoint(x, y, OR, Or, l, degrees);
            degrees += 0.03;
        }
    }


    /**
     * @param axisX 坐标轴
     * @param axisY 坐标轴
     * @param oR    定圆半径
     * @param or    动圆半径
     * @param l     动点距or的距离
     * @param t     角度
     */
    private void calculateTrackPoint(float axisX, float axisY, float oR, float or, float l, float t) {

        float x = (float) ((oR - or) * Math.cos(t) + l * Math.cos((oR / or - 1) * t));
        float y = (float) ((oR - or) * Math.sin(t) - l * Math.sin((oR / or - 1) * t));

        float X = axisX - x;
        float Y = axisY + y;

        TrackPoint point = new TrackPoint();
        point.x = X;
        point.y = Y;
        point.t = t;
        trackPointList.add(point);

    }


    /**
     * 动圆 Or的轨道
     * 动圆圆心在此轨道上
     *
     * @param canvas
     * @param x        轨道圆心x
     * @param y        轨道圆心y
     * @param radius   轨道圆半径
     * @param radiusOR 动圆半径
     */
    /**
     * @param canvas
     */
    private void drawOrTrack(Canvas canvas, int position) {

        TrackRadiusPoint radius = trackRadiusList.get(position);
        drawOrCircle(canvas, radius.cx, radius.cy, radius.radius);


    }

    /**
     * 基圆(定圆)半径R
     * 母圆(动圆)半径r
     * 动点轨迹的横纵坐标公式:
     * X=(R-r)cost+lcos(R/r-1)t
     * Y=(R-r)sint-lsin(R/r-1)t
     * t为随着Or转动,Or圆心相对于x轴张开的角度。
     * r可以大于R,l可以大于r
     */

    private void drawMovingTrack(Canvas canvas, int position) {

        int count = trackPointList.size();
        for (int i = 0; i < index; i++) {
            if (i > count - 1) break;
            TrackPoint point = trackPointList.get(i);
            canvas.drawCircle(point.x, point.y, 2, radiusPaint);
        }
    }
}

KaleidoscopeView的类,它继承自SurfaceView并实现了SurfaceHolder.Callback接口。

  1. 类变量:这个类有一些类变量,包括用于绘制的Paint对象,用于控制绘制状态的布尔值,以及用于存储绘制路径的列表等。

  2. 内部类:TrackRadiusPointTrackPoint是两个内部类,用于存储动圆圆心和动点的坐标以及其他相关信息。

  3. 构造函数:这个类有三个构造函数,它们都调用了init方法来初始化类的状态。

  4. init方法:这个方法初始化了类的状态,包括创建Paint对象,设置画笔颜色和大小等。

  5. surfaceCreatedsurfaceChangedsurfaceDestroyed方法:这些方法是SurfaceHolder.Callback接口的方法,用于处理SurfaceView的生命周期。

  6. onTouchEvent方法:这个方法处理用户的触摸事件,当用户在屏幕上画圈时,会计算出基圆和动圆的半径,以及动点的位置。

  7. runnable:这是一个Runnable对象,它在一个新的线程中运行,负责绘制万花筒效果。

  8. drawBackgrounddrawXYdrawOrCircledrawORCircledrawOrTrackdrawMovingTrack方法:这些方法用于绘制不同的图形,包括背景,坐标轴,基圆,动圆,动圆的轨道,以及动点的轨迹。

  9. calculateNewTrackcalculateTrackPoint方法:这两个方法用于计算动圆圆心和动点的坐标。

  10. initPaint方法:这个方法用于初始化Paint对象,设置画笔的颜色,大小,类型等。

主要功能是通过计算动圆圆心和动点的坐标,然后在SurfaceView上绘制出对应的图形,从而实现万花筒效果。

以直线为基线:

image.png

image.png

以直线为基线绘制类Kaleidoscope2View


public class Kaleidoscope2View extends SurfaceView implements SurfaceHolder.Callback {
    private static final String LOGTAG = LogUtil.makeLogTag(Kaleidoscope2View.class);
    private boolean isRunning = false;
    private boolean isStart = false;
    private boolean isDrawing = false;
    private SurfaceHolder holder;
    private Paint drawPaint;
    private Paint drawTrack;
    private Paint drawXyPaint;
    private Paint radiusPaint;
    private Paint radiusPaint1;
    private Paint trackPaint;
    private Paint drawTextPaint;
    private int paintColor = 0xFF000000, paintAlpha = 255;
    private int xyPaintColor = 0xFFf54949;
    private int radiusPaintColor = 0xFFf54949;
    private float brushSize, lastBrushSize;
    private float m_prevX, m_prevY;

    private int divisor = 360 * 100;
    //动圆圆心
    private List<TrackRadiusPoint> trackRadiusList = new ArrayList();
    //动点
    private List<TrackPoint> trackPointList = new ArrayList();

    private int index = 0;

    private int screenWidth;
    private int screenHeight;

    private float oR;
    private float ol;

    private float axisX;
    private float axisY;

    class TrackRadiusPoint {
        float cx;
        float cy;
        float t;
        float radius;
        float lr;
    }

    class TrackPoint {
        float x;
        float y;
        float t;
    }

    public Kaleidoscope2View(Context context) {
        super(context);
        init();
    }

    public Kaleidoscope2View(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public Kaleidoscope2View(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }


    private void init() {
        holder = getHolder();
        holder.addCallback(this);

        drawPaint = initPaint(brushSize, paintColor, 0, 0);
        drawTrack = initPaint(brushSize, radiusPaintColor, 0, 0);

        //
        isRunning = false;
        //x,y轴
        drawXyPaint = new Paint();
        drawXyPaint.setAntiAlias(true);
        drawXyPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
        drawXyPaint.setStrokeJoin(Paint.Join.ROUND);
        drawXyPaint.setStrokeCap(Paint.Cap.ROUND);
        drawXyPaint.setColor(xyPaintColor);
        //radius
        radiusPaint = new Paint();
        radiusPaint.setAntiAlias(true);
        radiusPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
        radiusPaint.setStrokeJoin(Paint.Join.ROUND);
        radiusPaint.setStrokeCap(Paint.Cap.ROUND);
        radiusPaint.setColor(radiusPaintColor);
        //radius
        trackPaint = new Paint();
        trackPaint.setAntiAlias(true);
        trackPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
//        trackPaint.setStrokeJoin(Paint.Join.ROUND);
//        trackPaint.setStrokeCap(Paint.Cap.ROUND);
        trackPaint.setColor(radiusPaintColor);
        trackPaint.setTextSize(30);
        trackPaint.setStyle(Paint.Style.FILL);


        radiusPaint1 = new Paint();
        radiusPaint1.setAntiAlias(true);
        radiusPaint1.setFlags(Paint.ANTI_ALIAS_FLAG);
        radiusPaint1.setStrokeJoin(Paint.Join.ROUND);
        radiusPaint1.setStrokeCap(Paint.Cap.ROUND);
        radiusPaint1.setColor(0xFF000000);
        //test
        drawTextPaint = new Paint();
        drawTextPaint.setAntiAlias(true);
        drawTextPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
        drawTextPaint.setStrokeJoin(Paint.Join.ROUND);
        drawTextPaint.setStrokeCap(Paint.Cap.ROUND);
        drawTextPaint.setColor(paintColor);
        drawTextPaint.setTextSize(20.0f);


    }

    /**
     * init paint
     *
     * @param brushSize
     * @param paintColor
     * @param drawType
     * @return
     */
    private Paint initPaint(float brushSize, int paintColor, int drawType, int paintAlpha) {
        Paint drawPaint = new Paint();
        drawPaint.setAntiAlias(true);
        drawPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
        drawPaint.setStrokeJoin(Paint.Join.ROUND);
        drawPaint.setStrokeCap(Paint.Cap.ROUND);

        switch (drawType) {
            case 0://drawing pen
//                calculateColor();
                drawPaint.setXfermode(null);
                drawPaint.setColor(paintColor);
                drawPaint.setStrokeWidth(brushSize);
                drawPaint.setStyle(Paint.Style.STROKE);
//                setPenAlpha(paintAlpha);
                break;
            case 1://drawing eraser
                drawPaint.setStyle(Paint.Style.FILL);
                drawPaint.setColor(0x0FFFFFFFF);
                drawPaint.setStrokeWidth(brushSize);
                break;
        }

        return drawPaint;
    }

    public boolean isRunning() {
        return isRunning;
    }

    public void setRunning(boolean running) {
        isRunning = running;
    }

    public float getoR() {
        return oR;
    }

    public void setoR(float oR) {
        this.oR = oR;
    }

    public float getOl() {
        return ol;
    }

    public void setOl(float ol) {
        this.ol = ol;
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        DebugLog.i(LOGTAG, "surfaceCreated...");
        isRunning = true;
        new Thread(runnable).start();
    }


    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        DebugLog.i(LOGTAG, "surfaceChanged...");
        this.screenWidth = width;
        this.screenHeight = height;

        DebugLog.i(LOGTAG, "screenWidth:" + screenWidth);
        DebugLog.i(LOGTAG, "screenHeight:" + screenHeight);
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        DebugLog.i(LOGTAG, "surfaceDestroyed...");
        isRunning = false;
    }

    int[] location = new int[2];
    public float downX, downY, preX, preY, curX, curY;
    public int drawDensity = 2;//绘制密度,数值越高图像质量越低、性能越好

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        getLocationInWindow(location); //获取在当前窗口内的绝对坐标
        curX = (event.getRawX() - location[0]) / drawDensity;
        curY = (event.getRawY() - location[1]) / drawDensity;
        float touchX = event.getX();
        float touchY = event.getY();
        switch (event.getAction() & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_POINTER_DOWN:
//                DebugLog.i(LOGTAG, "ACTION_POINTER_DOWN-touchX:" + touchX);
//                DebugLog.i(LOGTAG, "ACTION_POINTER_DOWN-touchY:" + touchY);
                break;
            case MotionEvent.ACTION_DOWN:
                //start
//                DebugLog.i(LOGTAG, "ACTION_DOWN-touchX:" + touchX);
//                DebugLog.i(LOGTAG, "ACTION_DOWN-touchY:" + touchY);
//                ++index;
                break;
            case MotionEvent.ACTION_MOVE:
                //move
//                DebugLog.i(LOGTAG, "ACTION_MOVE-touchX:" + touchX);
//                DebugLog.i(LOGTAG, "ACTION_MOVE-touchY:" + touchY);
                ++index;
                break;
            case MotionEvent.ACTION_UP:
//                DebugLog.i(LOGTAG, "ACTION_UP-touchX:" + touchX);
//                DebugLog.i(LOGTAG, "ACTION_UP-touchY:" + touchY);
                break;
            default:
                return false;
        }
        preX = curX;
        preY = curY;
        return true;

    }

    private Runnable runnable = new Runnable() {
        @Override
        public void run() {
            while (isRunning) {
                drawBackground();
                //
                axisX = getoR();
                axisY = screenHeight * 3 / 4;

                //动圆圆心所在的圆
                float radiusOr = getoR();
                float l = getOl();
                calculateNewTrack(axisX, axisY, radiusOr, l);


                int count = trackPointList.size();
                for (int i = 0; i < divisor; i++) {
                    if (isRunning) {
                        if (index > count - 1) {
                            index = 0;
                        }
                        draw(index);
                    }
                }
                SystemClock.sleep(500);
            }
        }
    };


    /**
     *
     */
    private void drawBackground() {
        if (holder.getSurface().isValid()) {
            Canvas canvas = holder.lockCanvas(null);
            if (canvas != null) {
                canvas.drawColor(Color.WHITE);
            }
            holder.unlockCanvasAndPost(canvas);
        }
    }


    /**
     * draw
     */
    private void draw(int i) {
        Canvas canvas = null;
        try {
            if (holder != null && holder.getSurface().isValid()) {
                canvas = holder.lockCanvas(null);
                if (canvas != null) {
                    //
                    canvas.drawColor(Color.WHITE);
                    //
                    drawXY(canvas, axisX, axisY);
                    //
                    drawOrTrack(canvas, i);
                    //
                    drawMovingTrack(canvas);
                }
            }
        } catch (Exception e) {

        } finally {

            if (holder != null && canvas != null)
                holder.unlockCanvasAndPost(canvas);
        }

    }

    /**
     * x,y
     *
     * @param canvas
     */
    private void drawXY(Canvas canvas, float cx, float cy) {

        float lx = 0;
        float ly = cy - getoR();
        //动圆圆心所在的轨迹
        //radius line
        canvas.drawLine(lx, ly, screenWidth, ly, drawXyPaint);
        //x
        canvas.drawLine(lx, cy, screenWidth, cy, drawXyPaint);
        //y
        canvas.drawLine(cx, lx, cx, screenHeight, drawXyPaint);

    }


    /**
     * draw Or circle
     * 母圆(动圆) 半径r
     *
     * @param canvas
     */
    private void drawOrCircle(Canvas canvas, float cx, float cy, float radius) {

        Path path = new Path();
        path.addCircle(cx, cy, radius, Path.Direction.CCW);
        canvas.drawPath(path, drawPaint);

    }

    /**
     * draw OR circle
     * 基圆(定圆) 半径r
     *
     * @param canvas
     */
    private void drawORCircle(Canvas canvas, float x, float y, float radius) {

        Path path = new Path();
        path.addCircle(x, y, radius, Path.Direction.CCW);
        canvas.drawPath(path, drawPaint);
        //圆心
        canvas.drawCircle(x, y, 5, radiusPaint);

    }

    /**
     * 计算轨迹
     *
     * @param axisX
     * @param axisY
     * @param OR
     * @param l
     */
    private void calculateNewTrack(float axisX, float axisY, float OR, float l) {

        float cy = axisY - OR;
        float degrees = 0f;
        for (int i = 0; i < divisor; i++) {
            if (degrees > 12.4) break;
            TrackRadiusPoint trackRadius = new TrackRadiusPoint();
            //
            trackRadius.cx = degrees * OR + axisX;
            trackRadius.cy = cy;
            //
            trackRadius.t = degrees;
            trackRadius.radius = OR;
            trackRadius.lr = l;
            trackRadiusList.add(trackRadius);
            //
            calculateTrackPoint(axisX, axisY, OR, l, degrees);
            degrees += 0.025;
        }


    }


    /**
     * 计算动点轨迹
     *
     * @param axisX 坐标轴
     * @param axisY 坐标轴
     * @param oR    定圆半径
     * @param l     动点距or的距离
     * @param t     角度
     */
    private void calculateTrackPoint(float axisX, float axisY, float oR, float l, float t) {
        //x=Rt-lsint
        //y=R-lcost
        //动点
        float x = (float) (oR * t - l * Math.sin(t));
        float y = (float) (oR - l * Math.cos(t));
        //
        float X = axisX + x;
        float Y = axisY - y;
        DebugLog.i(LOGTAG, "X:" + X);
        DebugLog.i(LOGTAG, "Y:" + Y);
        //(R-r)*cost
        //(R-r)*sint
        TrackPoint point = new TrackPoint();
        point.x = X;
        point.y = Y;
        point.t = t;
        trackPointList.add(point);

    }


    /**
     * 动圆 Or的轨道
     * 动圆圆心在此轨道上
     *
     * @param canvas
     * @param x        轨道圆心x
     * @param y        轨道圆心y
     * @param radius   轨道圆半径
     * @param radiusOR 动圆半径
     */
    /**
     * @param canvas
     */
    private void drawOrTrack(Canvas canvas, int position) {

        TrackRadiusPoint radius = trackRadiusList.get(position);
        //半径R
        drawOrCircle(canvas, radius.cx, radius.cy, radius.radius);
        //同心圆半径l
        drawOrCircle(canvas, radius.cx, radius.cy, radius.lr);
        //
        canvas.drawCircle(radius.cx, radius.cy, 10, drawXyPaint);

    }

    /**
     * 基圆(定圆)半径R
     * 母圆(动圆)半径r
     * 动点轨迹的横纵坐标公式:
     * X=(R-r)cost+lcos(R/r-1)t
     * Y=(R-r)sint-lsin(R/r-1)t
     * t为随着Or转动,Or圆心相对于x轴张开的角度。
     * r可以大于R,l可以大于r
     */
    private void drawMovingTrack(Canvas canvas) {

        for (int i = 0; i < index; i++) {
            TrackPoint point = trackPointList.get(i);
            canvas.drawCircle(point.x, point.y, 2, radiusPaint);
        }
    }
}
  • drawXY():这个方法用于绘制坐标轴。
  • drawOrCircle():这个方法用于绘制动圆。
  • drawORCircle():这个方法用于绘制定圆。
  • calculateNewTrack():这个方法用于计算动圆圆心的轨迹。
  • calculateTrackPoint():这个方法用于计算动点的轨迹。
  • drawOrTrack():这个方法用于绘制动圆的轨迹。
  • drawMovingTrack():这个方法用于绘制动点的轨迹。

在这篇文章中,我们深入探讨了如何在Android平台上实现万花筒效果。我们首先介绍了万花筒的基本原理,然后通过两个自定义的Android视图类——KaleidoscopeViewKaleidoscope2View,展示了如何在实践中应用这些原理。

KaleidoscopeView类使用圆形路径作为基线进行绘制,而Kaleidoscope2View类则使用直线路径作为基线进行绘制。这两个类都包含了一系列的方法和变量,用于初始化画笔,处理触摸事件,绘制背景和坐标轴,以及计算和绘制动态的轨迹。

我们还详细讨论了如何通过调整参数来改变万花筒的效果,以及如何处理用户的触摸事件来实时更新万花筒的视图。此外,我们还介绍了如何使用SurfaceViewCanvas来进行高效的图形绘制。

无论你是一个有经验的Android开发者,还是一个对图形编程感兴趣的新手,你都可以从这篇文章中获得有价值的知识和灵感。

开源项目地址:kaleidoscope

希望对您有所帮助谢谢!!!