在数学的奇妙世界中,有一种图案以其炫目的美丽和无尽的复杂性吸引着我们,那就是万花筒。这个看似简单的玩具,实则是一部隐藏着深奥数学原理和精妙算法的神秘机器。在现代科技的照耀下,万花筒的图案不再仅仅依赖于物理镜子的反射,而是通过精密的算法在屏幕上绽放。尤其在Android设备上,用户可以通过触摸屏幕,亲手创造出千变万化的图案。今天,我们将一同揭开Android万花筒算法的神秘面纱,探索其背后的数学原理和编程技巧。
首先,让我们一起探索万花筒的奥秘。万花筒的图案是由一个或多个圆形图形通过反射和旋转形成的。在传统的万花筒中,这是通过镜子和小珠子来实现的。然而,在数字万花筒中,我们使用数学公式来模拟这个过程。
在Android万花筒算法中,我们关注的是一种特殊类型的万花筒,它是通过摆线来生成图案的。摆线是一个点在一个滚动的圆周上的轨迹。这个点可以在圆内部、圆上或圆外部。通过改变基圆和动圆的半径,以及动点的位置,我们可以创造出无尽的图案。
让我们深入探索这个算法的核心思想。在Android设备上,当用户在屏幕上用手指画圈时,算法会生成一个基圆和一个动圆。基圆是固定的,而动圆则在基圆上滚动。动点位于动圆内部,并随着动圆的滚动而移动。动点的轨迹是我们在屏幕上看到的图案。
算法的关键在于计算动点的位置。这是通过以下公式来实现的:
和
其中,(R) 是基圆的半径,(r) 是动圆的半径,(l) 是动点距离动圆圆心的距离,而 (t) 是动圆圆心相对于x轴张开的角度。
这个算法的一个重要特性是,r可以大于R,l可以大于r。这意味着动圆可以比基圆大,动点可以在动圆的外部。这为生成各种各样的图案提供了很大的灵活性。
最后,为了使动圆的滚动与动点的轨迹同步,动圆的圆心的横坐标和纵坐标可以通过以下公式计算:
和
然后,以这个圆心和半径r画出动圆。这样,动圆的滚动就会和动点的轨迹同步。
现在,让我们将这些数学原理转化为视觉艺术。在Android设备上,我们可以通过编程来实现这个算法,并让用户通过触摸屏幕来控制基圆和动圆的半径,以及动点的位置来实现万花筒图案。
在接下来的文章中,我们将深入探讨这个算法的具体实现,包括如何优化性能,如何添加额外的特效,以及如何创建一个用户友好的界面。
以圆为基线:
以圆为基线绘制类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接口。
-
类变量:这个类有一些类变量,包括用于绘制的
Paint对象,用于控制绘制状态的布尔值,以及用于存储绘制路径的列表等。 -
内部类:
TrackRadiusPoint和TrackPoint是两个内部类,用于存储动圆圆心和动点的坐标以及其他相关信息。 -
构造函数:这个类有三个构造函数,它们都调用了
init方法来初始化类的状态。 -
init方法:这个方法初始化了类的状态,包括创建Paint对象,设置画笔颜色和大小等。 -
surfaceCreated,surfaceChanged,surfaceDestroyed方法:这些方法是SurfaceHolder.Callback接口的方法,用于处理SurfaceView的生命周期。 -
onTouchEvent方法:这个方法处理用户的触摸事件,当用户在屏幕上画圈时,会计算出基圆和动圆的半径,以及动点的位置。 -
runnable:这是一个Runnable对象,它在一个新的线程中运行,负责绘制万花筒效果。 -
drawBackground,drawXY,drawOrCircle,drawORCircle,drawOrTrack,drawMovingTrack方法:这些方法用于绘制不同的图形,包括背景,坐标轴,基圆,动圆,动圆的轨道,以及动点的轨迹。 -
calculateNewTrack和calculateTrackPoint方法:这两个方法用于计算动圆圆心和动点的坐标。 -
initPaint方法:这个方法用于初始化Paint对象,设置画笔的颜色,大小,类型等。
主要功能是通过计算动圆圆心和动点的坐标,然后在SurfaceView上绘制出对应的图形,从而实现万花筒效果。
以直线为基线:
以直线为基线绘制类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视图类——KaleidoscopeView和Kaleidoscope2View,展示了如何在实践中应用这些原理。
KaleidoscopeView类使用圆形路径作为基线进行绘制,而Kaleidoscope2View类则使用直线路径作为基线进行绘制。这两个类都包含了一系列的方法和变量,用于初始化画笔,处理触摸事件,绘制背景和坐标轴,以及计算和绘制动态的轨迹。
我们还详细讨论了如何通过调整参数来改变万花筒的效果,以及如何处理用户的触摸事件来实时更新万花筒的视图。此外,我们还介绍了如何使用SurfaceView和Canvas来进行高效的图形绘制。
无论你是一个有经验的Android开发者,还是一个对图形编程感兴趣的新手,你都可以从这篇文章中获得有价值的知识和灵感。
希望对您有所帮助谢谢!!!