По привычке создав Android Project, остановимся и немного подумаем. Поскольку мы
собираемся изобразить фрактал, используя при этом генератор случайных чисел,
очевидно, имеет смысл рисовать его постепенно. Так оно будет и красивее и
загадочнее, но нам придется не просто рисовать статичное изображение, а
дорисовывать его постепенно, по таймеру. Для этих целей вполне подходит SurfaceView.
Создадим класс MainView, унаследовавшись от него и, чтобы два раза не
вставать, сразу и от SurfaceHolder.Callback:
import java.util.Random;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class MainView extends SurfaceView implements SurfaceHolder.Callback {
private static final int stepCnt = 100;
private int maxX = 10000;
private int maxY = 10000;
private int szX;
private int szY;
private Random rand = new Random();
public MainView(FernActivity ctx) {
super(ctx);
this.getHolder().addCallback(this);
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format,
int width, int height) {}
@Override
public void surfaceCreated(SurfaceHolder holder) {
update();
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {}
public void update() {
Canvas c = null;
try {
c = this.getHolder().lockCanvas();
if (c != null) {
onDraw(c);
}
} catch (Exception e) {
} finally {
if (c != null) {
this.getHolder().unlockCanvasAndPost(c);
}
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if ((MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.UNSPECIFIED) ||
(MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.UNSPECIFIED)) {
setMeasuredDimension(maxX, maxY);
} else {
szX = MeasureSpec.getSize(widthMeasureSpec);
szY = MeasureSpec.getSize(heightMeasureSpec);
if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.AT_MOST) {
if (szX > maxX) {
szX = maxX;
}
}
if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST) {
if (szY > maxY) {
szY = maxY;
}
}
setMeasuredDimension(szX, szY);
}
}
private float getSizeX(float x) {
return (x * szX) / 640;
}
private float getSizeY(float y) {
return (y * szY) / 480;
}
@Override
protected void onDraw(Canvas c) {
Paint paint = new Paint();
paint.setColor(Color.GREEN);
float x = 1.0f;
float y = 1.0f;
for (int i = 0; i < stepCnt; i++) {
float p = rand.nextFloat();
float t = x;
if (p <= 0.85f) {
x= (0.84f * x) + (0.04f * y);
y =(0.04f * t) + (0.85f * y) + 1.6f;
} else if (p < 0.93f) {
x = (0.20f * x) - (0.26f * y);
y = (0.26f * t) + (0.212f * y) + 0.44f;
} else if (p < 0.99f) {
x = (-0.15f * x) + (0.28f * y);
y = (0.26f * t) + (0.24f * y) + 0.44f;
} else {
x = 0.0f;
y = 0.16f * y;
}
c.drawCircle((szX / 2) + (getSizeX(61) * x),
szY - (getSizeY(40) * y), 0.5f, paint);
}
}
}
Код, в целом, стандартный и особых вопросов не вызывающий. Сам алгоритм
рисования фрактала определяется непосредственно в onDraw, onMeasure захватывает всю доступную область и инициализирует размеры холста
используемого для рисования, а метод update безопасным образом получает Canvas и выполняет отрисовку.
Рисование выполняется постепенно, по 100 точек за один вызов update. Очевидно, нам необходимо
создать Timer, чтобы дорисовывать изображение через заданные интервалы времени:
package com.WhiteRabbit.Fern;
import java.util.Timer;
import java.util.TimerTask;
public class Task extends TimerTask {
private static long SECONDS_INTERVAL = 1000L;
private static long FRAME_RATE = 15L;
private FernActivity ctx;
private Timer timer = new Timer();
private int maxIteration = 500;
public Task(FernActivity ctx) {
this.ctx = ctx;
timer.scheduleAtFixedRate(this, 0, SECONDS_INTERVAL / FRAME_RATE);
}
public void terminate() {
if (timer != null) {
timer.cancel();
timer = null;
}
}
@Override
public void run() {
if (maxIteration > 0) {
ctx.viewUpdate();
maxIteration--;
} else {
terminate();
}
}
import java.util.Timer;
import java.util.TimerTask;
public class Task extends TimerTask {
private static long SECONDS_INTERVAL = 1000L;
private static long FRAME_RATE = 15L;
private FernActivity ctx;
private Timer timer = new Timer();
private int maxIteration = 500;
public Task(FernActivity ctx) {
this.ctx = ctx;
timer.scheduleAtFixedRate(this, 0, SECONDS_INTERVAL / FRAME_RATE);
}
public void terminate() {
if (timer != null) {
timer.cancel();
timer = null;
}
}
@Override
public void run() {
if (maxIteration > 0) {
ctx.viewUpdate();
maxIteration--;
} else {
terminate();
}
}
}
Здесь все тоже довольно стандартно. В конструкторе мы инициализируем таймер
так, чтобы он вызвался 15 раз в секунду, а после 1000 итераций его
останавливаем. Для того чтобы завершить приложение, осталось дописать Activity:
package com.WhiteRabbit.Fern;
import android.app.Activity;
import android.content.pm.ActivityInfo;
import android.graphics.Color;
import android.os.Bundle;
import android.view.ViewGroup;
import android.widget.LinearLayout;
public class FernActivity extends Activity {
private MainView view;
private Task task;
public void viewUpdate() {
view.update();
}
@Override
protected void onPause() {
task.terminate();
super.onPause();
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
LinearLayout.LayoutParams containerParams =
new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT, 0.0F);
LinearLayout root = new LinearLayout(this);
root.setOrientation(LinearLayout.VERTICAL);
root.setBackgroundColor(Color.BLACK);
root.setLayoutParams(containerParams);
view = new MainView(this);
root.addView(view);
setContentView(root);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
task = new Task(this);
}
}
Вот собственно и все.
Запустив приложение на выполнение, мы видим, как из хаоса рождается наше маленькое
чудо:
Немножко смахивает на коноплю, но задумывалось, безусловно, как папоротник.
Всех с наступающим Праздником!
P.S. Если кому-то лень набирать код :)
P.S. Если кому-то лень набирать код :)
Комментариев нет:
Отправить комментарий