쉘핀 스키 카펫 그리기 (Android + Kotlin)
소개
Android+Kotlin의 공부로, 쉘핀스키의 카펫을 그렸을 때의 메모입니다.
쉘핀스키의 카펫은, 같은 패턴의 도형을 자꾸자꾸 생성해 가는 프랙탈의 하나로, 그림으로 하면, 다음과 같은 느낌이 됩니다.
자세한 내용은 Wikipedia을 참조하십시오.
프로그램 이미지 다이어그램
프로그램에서 사용하는 변수를 보여줍니다.
프로그램
드로잉은 Drawable을 상속한 것으로 그려 ImageView에 붙여넣습니다.
package milu.kiriu2010.milumathcaras.gui.draw.fractal.recursion.sierpinski
import android.graphics.*
import android.os.Handler
import milu.kiriu2010.gui.basic.MyPointF
import milu.kiriu2010.milumathcaras.gui.draw.MyDrawable
import milu.kiriu2010.milumathcaras.gui.main.NotifyCallback
// --------------------------------------------------
// シェルピンスキーのカーペット
// --------------------------------------------------
// https://en.wikipedia.org/wiki/Sierpinski_carpet
// --------------------------------------------------
class SierpinskiCarpet01Drawable: MyDrawable() {
// -------------------------------------
// 描画領域
// -------------------------------------
// シェルピンスキーのカーペットは
// 正方形を3分割するので3の階乗を選ぶ
// = 729 = 3^6
// -------------------------------------
private val side = 729f
private val margin = 50f
// ----------------------------------------
// 再帰レベル
// ----------------------------------------
private var nNow = 0
// --------------------------------------------------------
// 再帰レベルの最大値
// 5回描くと遅いので、5回までとしている
// --------------------------------------------------------
private val nMax = 5
// ----------------------------------------
// シェルピンスキーのカーペットの描画点リスト
// ----------------------------------------
private val pointLst = mutableListOf<MyPointF>()
// ---------------------------------------------------------------------
// 描画領域として使うビットマップ
// ---------------------------------------------------------------------
// 画面にタッチするとdrawが呼び出されるようなのでビットマップに描画する
// ---------------------------------------------------------------------
private lateinit var imageBitmap: Bitmap
private val tmpBitmap = Bitmap.createBitmap(intrinsicWidth,intrinsicHeight, Bitmap.Config.ARGB_8888)
// -------------------------------
// 枠に使うペイント
// -------------------------------
private val framePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = Color.BLACK
style = Paint.Style.STROKE
strokeWidth = 10f
}
// -------------------------------
// バックグラウンドに使うペイント
// -------------------------------
private val backPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = Color.WHITE
style = Paint.Style.FILL
}
// -------------------------------------
// シェルピンスキーのカーペットを描くペイント
// -------------------------------------
private val linePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = Color.RED
style = Paint.Style.FILL_AND_STROKE
strokeWidth = 1f
}
// -------------------------------------
// 描画中に呼び出すコールバックを設定
// -------------------------------------
private var notifyCallback: NotifyCallback? = null
// ---------------------------------------
// 別スレッドで描画するためのハンドラ
// ---------------------------------------
val handler = Handler()
// ---------------------------------------
// 描画に使うスレッド
// ---------------------------------------
private lateinit var runnable: Runnable
// ----------------------------------------
// CalculationCallback
// 描画に使うデータを計算する
// ----------------------------------------
// 可変変数 values の引数位置による意味合い
//
// 第1引数:再帰レベル(整数)
// ----------------------------------------
override fun calStart(isKickThread: Boolean, vararg values: Float) {
// 再帰レベル
nNow = 0
values.forEachIndexed { index, fl ->
//Log.d(javaClass.simpleName,"index[$index]fl[$fl]")
when (index) {
// 再帰レベル
0 -> nNow = fl.toInt()
}
}
// シェルピンスキーのカーペットを構築
createPath()
// ビットマップに描画
drawBitmap()
// 描画
invalidateSelf()
// 描画に使うスレッド
if (isKickThread) {
runnable = Runnable {
// 再帰レベルを1つ増やす
incrementLevel()
// 三角波を構築
createPath()
// ビットマップに描画
drawBitmap()
// 描画
invalidateSelf()
// 最初と最後は1秒後に描画
if ( nNow == nMax || nNow == 0 ) {
handler.postDelayed(runnable, 1000)
}
// 500msごとに描画
else {
handler.postDelayed(runnable, 500)
}
}
handler.postDelayed(runnable, 1000)
}
}
// -------------------------------------
// CalculationCallback
// 描画ビューを閉じる際,呼び出す後処理
// -------------------------------------
override fun calStop() {
// 描画に使うスレッドを解放する
handler.removeCallbacks(runnable)
}
// -------------------------------------
// CalculationCallback
// 描画中に呼び出すコールバックを設定
// -------------------------------------
override fun setNotifyCallback(notifyCallback: NotifyCallback) {
this.notifyCallback = notifyCallback
}
// シェルピンスキーのカーペットを構築
private fun createPath() {
// シェルピンスキーのカーペットの描画点リストをクリアする
pointLst.clear()
// ------------------------------
// シェルピンスキーのカーペットの頂点
// ------------------------------
// a:左下,b:右下,c:右上,d:左上
// ------------------------------
val a = MyPointF(0f,0f)
val b = MyPointF(side,0f)
val c = MyPointF(side,side)
val d = MyPointF(0f,side)
// 次レベルのシェルピンスキーのカーペットの描画点を求める
calNextLevel(a,b,c,d,nNow)
// 描画中に呼び出すコールバックをキックし、現在の再帰レベルを通知する
notifyCallback?.receive(nNow.toFloat())
}
// -----------------------------------------------
// 次レベルのシェルピンスキーのカーペットの描画点を求める
// -----------------------------------------------
private fun calNextLevel(a: MyPointF, b: MyPointF, c: MyPointF, d: MyPointF, n: Int) {
// -----------------------------------------------------
// 再帰呼び出しの際、nを減らしていき0以下になったら終了
// -----------------------------------------------------
if ( n <= 0 ) {
pointLst.add(a)
pointLst.add(b)
pointLst.add(c)
pointLst.add(d)
return
}
// -------------------------------------
// 次のレベルのカーペットの頂点を求める
// -------------------------------------
// d3,d2,c3,c2
// d0,d1,c0,c1
// a3,a2,b3,b2
// a0,a1,b0,b1
// -------------------------------------
// "A-B"のX座標の"左:右=1:2"
val l1r2 = (2f*a.x+b.x)/3f
// "A-B"のX座標の"左:右=2:1"
val l2r1 = (a.x+2f*b.x)/3f
// "D-A"のY座標の"上:下=1:2"
val u1d2 = (2f*d.y+a.y)/3f
// "D-A"のY座標の"上:下=2:1"
val u2d1 = (d.y+2f*a.y)/3f
// a0:A
val a0 = a
// a1:("A-B"の1/3,A)
val a1 = MyPointF(l1r2,a.y)
// a2:("A-B"の1/3,"D-A"の2/3)
val a2 = MyPointF(l1r2,u2d1)
// a3:(A,"D-A"の2/3)
val a3 = MyPointF(a.x,u2d1)
// b0:("A-B"の2/3,B)
val b0 = MyPointF(l2r1,b.y)
// b1:B
val b1 = b
// b2:(B,"D-A"の2/3)
val b2 = MyPointF(b.x,u2d1)
// b3:("A-B"の2/3,"D-A"の2/3)
val b3 = MyPointF(l2r1,u2d1)
// c0:("A-B"の2/3,"D-A"の1/3)
val c0 = MyPointF(l2r1,u1d2)
// c1:(C,"D-A"の1/3)
val c1 = MyPointF(c.x,u1d2)
// c2:C
val c2 = c
// c3:("A-B"の2/3,C)
val c3 = MyPointF(l2r1,c.y)
// d0:(D,"D-A"の1/3)
val d0 = MyPointF(d.x,u1d2)
// d1:("A-B"の1/3,"D-A"の1/3)
val d1 = MyPointF(l1r2,u1d2)
// d2:("A-B"の1/3,D)
val d2 = MyPointF(l1r2,d.y)
// d3:D
val d3 = d
// 次レベルのシェルピンスキーのカーペットを描画
// a0,a1,a2,a3を頂点とするカーペット
calNextLevel(a0,a1,a2,a3,n-1)
// a1,b0,b3,a2を頂点とするカーペット
calNextLevel(a1,b0,b3,a2,n-1)
// b0,b1,b2,b3を頂点とするカーペット
calNextLevel(b0,b1,b2,b3,n-1)
// a3,a2,d1,d0を頂点とするカーペット
calNextLevel(a3,a2,d1,d0,n-1)
// b3,b2,c1,c0を頂点とするカーペット
calNextLevel(b3,b2,c1,c0,n-1)
// d0,d1,d2,d3を頂点とするカーペット
calNextLevel(d0,d1,d2,d3,n-1)
// d1,c0,c3,d2を頂点とするカーペット
calNextLevel(d1,c0,c3,d2,n-1)
// c0,c1,c2,c3を頂点とするカーペット
calNextLevel(c0,c1,c2,c3,n-1)
}
// -------------------------------------
// 再帰レベルを1つ増やす
// -------------------------------------
private fun incrementLevel() {
nNow++
// 最大値を超えたら0に戻す
if ( nNow > nMax ) {
nNow = 0
}
}
// -------------------------------
// ビットマップに描画
// -------------------------------
private fun drawBitmap() {
val canvas = Canvas(tmpBitmap)
// バックグランドを描画
canvas.drawRect(RectF(0f, 0f, intrinsicWidth.toFloat(), intrinsicHeight.toFloat()), backPaint)
// 枠を描画
canvas.drawRect(RectF(0f, 0f, intrinsicWidth.toFloat(), intrinsicHeight.toFloat()), framePaint)
// ---------------------------------------------------------------------
// 原点(0,0)の位置
// = (マージン,マージン)
// ---------------------------------------------------------------------
canvas.save()
canvas.translate(margin, margin)
//Log.d(javaClass.simpleName,"===============================")
// シェルピンスキーのカーペットを描画
var path: Path = Path()
pointLst.forEachIndexed { index, myPointF ->
//Log.d(javaClass.simpleName,"index[$index]x[${myPointF.x}]y[${myPointF.y}]")
when ( index%4 ) {
0 -> {
path = Path()
path.moveTo(myPointF.x,myPointF.y)
}
1 -> {
path.lineTo(myPointF.x,myPointF.y)
}
2 -> {
path.lineTo(myPointF.x,myPointF.y)
}
else -> {
path.lineTo(myPointF.x,myPointF.y)
path.close()
canvas.drawPath(path,linePaint)
}
}
}
// 座標を元に戻す
canvas.restore()
// これまでの描画は上下逆なので反転する
val matrix = Matrix()
matrix.postScale(1f,-1f)
imageBitmap = Bitmap.createBitmap(tmpBitmap,0,0,intrinsicWidth,intrinsicHeight,matrix,true)
}
// -------------------------------
// Drawable
// -------------------------------
override fun draw(canvas: Canvas) {
// 描画用ビットマップがインスタンス化されていなければ描画はスキップする
if ( this::imageBitmap.isInitialized == false ) return
canvas.drawBitmap(imageBitmap,0f,0f,framePaint)
}
// -------------------------------
// Drawable
// -------------------------------
override fun setAlpha(alpha: Int) {
linePaint.alpha = alpha
}
// -------------------------------
// Drawable
// -------------------------------
override fun getOpacity(): Int = PixelFormat.TRANSLUCENT
// -------------------------------
// Drawable
// -------------------------------
override fun setColorFilter(colorFilter: ColorFilter?) {
linePaint.colorFilter = colorFilter
}
// -------------------------------
// Drawable
// -------------------------------
override fun getIntrinsicWidth(): Int = (side+margin*2).toInt()
// -------------------------------
// Drawable
// -------------------------------
override fun getIntrinsicHeight(): Int = (side+margin*2).toInt()
}
Reference
이 문제에 관하여(쉘핀 스키 카펫 그리기 (Android + Kotlin)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다
https://qiita.com/milukiriu2010/items/23d081bb715342521cda
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)
프로그램에서 사용하는 변수를 보여줍니다.
프로그램
드로잉은 Drawable을 상속한 것으로 그려 ImageView에 붙여넣습니다.
package milu.kiriu2010.milumathcaras.gui.draw.fractal.recursion.sierpinski
import android.graphics.*
import android.os.Handler
import milu.kiriu2010.gui.basic.MyPointF
import milu.kiriu2010.milumathcaras.gui.draw.MyDrawable
import milu.kiriu2010.milumathcaras.gui.main.NotifyCallback
// --------------------------------------------------
// シェルピンスキーのカーペット
// --------------------------------------------------
// https://en.wikipedia.org/wiki/Sierpinski_carpet
// --------------------------------------------------
class SierpinskiCarpet01Drawable: MyDrawable() {
// -------------------------------------
// 描画領域
// -------------------------------------
// シェルピンスキーのカーペットは
// 正方形を3分割するので3の階乗を選ぶ
// = 729 = 3^6
// -------------------------------------
private val side = 729f
private val margin = 50f
// ----------------------------------------
// 再帰レベル
// ----------------------------------------
private var nNow = 0
// --------------------------------------------------------
// 再帰レベルの最大値
// 5回描くと遅いので、5回までとしている
// --------------------------------------------------------
private val nMax = 5
// ----------------------------------------
// シェルピンスキーのカーペットの描画点リスト
// ----------------------------------------
private val pointLst = mutableListOf<MyPointF>()
// ---------------------------------------------------------------------
// 描画領域として使うビットマップ
// ---------------------------------------------------------------------
// 画面にタッチするとdrawが呼び出されるようなのでビットマップに描画する
// ---------------------------------------------------------------------
private lateinit var imageBitmap: Bitmap
private val tmpBitmap = Bitmap.createBitmap(intrinsicWidth,intrinsicHeight, Bitmap.Config.ARGB_8888)
// -------------------------------
// 枠に使うペイント
// -------------------------------
private val framePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = Color.BLACK
style = Paint.Style.STROKE
strokeWidth = 10f
}
// -------------------------------
// バックグラウンドに使うペイント
// -------------------------------
private val backPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = Color.WHITE
style = Paint.Style.FILL
}
// -------------------------------------
// シェルピンスキーのカーペットを描くペイント
// -------------------------------------
private val linePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = Color.RED
style = Paint.Style.FILL_AND_STROKE
strokeWidth = 1f
}
// -------------------------------------
// 描画中に呼び出すコールバックを設定
// -------------------------------------
private var notifyCallback: NotifyCallback? = null
// ---------------------------------------
// 別スレッドで描画するためのハンドラ
// ---------------------------------------
val handler = Handler()
// ---------------------------------------
// 描画に使うスレッド
// ---------------------------------------
private lateinit var runnable: Runnable
// ----------------------------------------
// CalculationCallback
// 描画に使うデータを計算する
// ----------------------------------------
// 可変変数 values の引数位置による意味合い
//
// 第1引数:再帰レベル(整数)
// ----------------------------------------
override fun calStart(isKickThread: Boolean, vararg values: Float) {
// 再帰レベル
nNow = 0
values.forEachIndexed { index, fl ->
//Log.d(javaClass.simpleName,"index[$index]fl[$fl]")
when (index) {
// 再帰レベル
0 -> nNow = fl.toInt()
}
}
// シェルピンスキーのカーペットを構築
createPath()
// ビットマップに描画
drawBitmap()
// 描画
invalidateSelf()
// 描画に使うスレッド
if (isKickThread) {
runnable = Runnable {
// 再帰レベルを1つ増やす
incrementLevel()
// 三角波を構築
createPath()
// ビットマップに描画
drawBitmap()
// 描画
invalidateSelf()
// 最初と最後は1秒後に描画
if ( nNow == nMax || nNow == 0 ) {
handler.postDelayed(runnable, 1000)
}
// 500msごとに描画
else {
handler.postDelayed(runnable, 500)
}
}
handler.postDelayed(runnable, 1000)
}
}
// -------------------------------------
// CalculationCallback
// 描画ビューを閉じる際,呼び出す後処理
// -------------------------------------
override fun calStop() {
// 描画に使うスレッドを解放する
handler.removeCallbacks(runnable)
}
// -------------------------------------
// CalculationCallback
// 描画中に呼び出すコールバックを設定
// -------------------------------------
override fun setNotifyCallback(notifyCallback: NotifyCallback) {
this.notifyCallback = notifyCallback
}
// シェルピンスキーのカーペットを構築
private fun createPath() {
// シェルピンスキーのカーペットの描画点リストをクリアする
pointLst.clear()
// ------------------------------
// シェルピンスキーのカーペットの頂点
// ------------------------------
// a:左下,b:右下,c:右上,d:左上
// ------------------------------
val a = MyPointF(0f,0f)
val b = MyPointF(side,0f)
val c = MyPointF(side,side)
val d = MyPointF(0f,side)
// 次レベルのシェルピンスキーのカーペットの描画点を求める
calNextLevel(a,b,c,d,nNow)
// 描画中に呼び出すコールバックをキックし、現在の再帰レベルを通知する
notifyCallback?.receive(nNow.toFloat())
}
// -----------------------------------------------
// 次レベルのシェルピンスキーのカーペットの描画点を求める
// -----------------------------------------------
private fun calNextLevel(a: MyPointF, b: MyPointF, c: MyPointF, d: MyPointF, n: Int) {
// -----------------------------------------------------
// 再帰呼び出しの際、nを減らしていき0以下になったら終了
// -----------------------------------------------------
if ( n <= 0 ) {
pointLst.add(a)
pointLst.add(b)
pointLst.add(c)
pointLst.add(d)
return
}
// -------------------------------------
// 次のレベルのカーペットの頂点を求める
// -------------------------------------
// d3,d2,c3,c2
// d0,d1,c0,c1
// a3,a2,b3,b2
// a0,a1,b0,b1
// -------------------------------------
// "A-B"のX座標の"左:右=1:2"
val l1r2 = (2f*a.x+b.x)/3f
// "A-B"のX座標の"左:右=2:1"
val l2r1 = (a.x+2f*b.x)/3f
// "D-A"のY座標の"上:下=1:2"
val u1d2 = (2f*d.y+a.y)/3f
// "D-A"のY座標の"上:下=2:1"
val u2d1 = (d.y+2f*a.y)/3f
// a0:A
val a0 = a
// a1:("A-B"の1/3,A)
val a1 = MyPointF(l1r2,a.y)
// a2:("A-B"の1/3,"D-A"の2/3)
val a2 = MyPointF(l1r2,u2d1)
// a3:(A,"D-A"の2/3)
val a3 = MyPointF(a.x,u2d1)
// b0:("A-B"の2/3,B)
val b0 = MyPointF(l2r1,b.y)
// b1:B
val b1 = b
// b2:(B,"D-A"の2/3)
val b2 = MyPointF(b.x,u2d1)
// b3:("A-B"の2/3,"D-A"の2/3)
val b3 = MyPointF(l2r1,u2d1)
// c0:("A-B"の2/3,"D-A"の1/3)
val c0 = MyPointF(l2r1,u1d2)
// c1:(C,"D-A"の1/3)
val c1 = MyPointF(c.x,u1d2)
// c2:C
val c2 = c
// c3:("A-B"の2/3,C)
val c3 = MyPointF(l2r1,c.y)
// d0:(D,"D-A"の1/3)
val d0 = MyPointF(d.x,u1d2)
// d1:("A-B"の1/3,"D-A"の1/3)
val d1 = MyPointF(l1r2,u1d2)
// d2:("A-B"の1/3,D)
val d2 = MyPointF(l1r2,d.y)
// d3:D
val d3 = d
// 次レベルのシェルピンスキーのカーペットを描画
// a0,a1,a2,a3を頂点とするカーペット
calNextLevel(a0,a1,a2,a3,n-1)
// a1,b0,b3,a2を頂点とするカーペット
calNextLevel(a1,b0,b3,a2,n-1)
// b0,b1,b2,b3を頂点とするカーペット
calNextLevel(b0,b1,b2,b3,n-1)
// a3,a2,d1,d0を頂点とするカーペット
calNextLevel(a3,a2,d1,d0,n-1)
// b3,b2,c1,c0を頂点とするカーペット
calNextLevel(b3,b2,c1,c0,n-1)
// d0,d1,d2,d3を頂点とするカーペット
calNextLevel(d0,d1,d2,d3,n-1)
// d1,c0,c3,d2を頂点とするカーペット
calNextLevel(d1,c0,c3,d2,n-1)
// c0,c1,c2,c3を頂点とするカーペット
calNextLevel(c0,c1,c2,c3,n-1)
}
// -------------------------------------
// 再帰レベルを1つ増やす
// -------------------------------------
private fun incrementLevel() {
nNow++
// 最大値を超えたら0に戻す
if ( nNow > nMax ) {
nNow = 0
}
}
// -------------------------------
// ビットマップに描画
// -------------------------------
private fun drawBitmap() {
val canvas = Canvas(tmpBitmap)
// バックグランドを描画
canvas.drawRect(RectF(0f, 0f, intrinsicWidth.toFloat(), intrinsicHeight.toFloat()), backPaint)
// 枠を描画
canvas.drawRect(RectF(0f, 0f, intrinsicWidth.toFloat(), intrinsicHeight.toFloat()), framePaint)
// ---------------------------------------------------------------------
// 原点(0,0)の位置
// = (マージン,マージン)
// ---------------------------------------------------------------------
canvas.save()
canvas.translate(margin, margin)
//Log.d(javaClass.simpleName,"===============================")
// シェルピンスキーのカーペットを描画
var path: Path = Path()
pointLst.forEachIndexed { index, myPointF ->
//Log.d(javaClass.simpleName,"index[$index]x[${myPointF.x}]y[${myPointF.y}]")
when ( index%4 ) {
0 -> {
path = Path()
path.moveTo(myPointF.x,myPointF.y)
}
1 -> {
path.lineTo(myPointF.x,myPointF.y)
}
2 -> {
path.lineTo(myPointF.x,myPointF.y)
}
else -> {
path.lineTo(myPointF.x,myPointF.y)
path.close()
canvas.drawPath(path,linePaint)
}
}
}
// 座標を元に戻す
canvas.restore()
// これまでの描画は上下逆なので反転する
val matrix = Matrix()
matrix.postScale(1f,-1f)
imageBitmap = Bitmap.createBitmap(tmpBitmap,0,0,intrinsicWidth,intrinsicHeight,matrix,true)
}
// -------------------------------
// Drawable
// -------------------------------
override fun draw(canvas: Canvas) {
// 描画用ビットマップがインスタンス化されていなければ描画はスキップする
if ( this::imageBitmap.isInitialized == false ) return
canvas.drawBitmap(imageBitmap,0f,0f,framePaint)
}
// -------------------------------
// Drawable
// -------------------------------
override fun setAlpha(alpha: Int) {
linePaint.alpha = alpha
}
// -------------------------------
// Drawable
// -------------------------------
override fun getOpacity(): Int = PixelFormat.TRANSLUCENT
// -------------------------------
// Drawable
// -------------------------------
override fun setColorFilter(colorFilter: ColorFilter?) {
linePaint.colorFilter = colorFilter
}
// -------------------------------
// Drawable
// -------------------------------
override fun getIntrinsicWidth(): Int = (side+margin*2).toInt()
// -------------------------------
// Drawable
// -------------------------------
override fun getIntrinsicHeight(): Int = (side+margin*2).toInt()
}
Reference
이 문제에 관하여(쉘핀 스키 카펫 그리기 (Android + Kotlin)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다
https://qiita.com/milukiriu2010/items/23d081bb715342521cda
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)
package milu.kiriu2010.milumathcaras.gui.draw.fractal.recursion.sierpinski
import android.graphics.*
import android.os.Handler
import milu.kiriu2010.gui.basic.MyPointF
import milu.kiriu2010.milumathcaras.gui.draw.MyDrawable
import milu.kiriu2010.milumathcaras.gui.main.NotifyCallback
// --------------------------------------------------
// シェルピンスキーのカーペット
// --------------------------------------------------
// https://en.wikipedia.org/wiki/Sierpinski_carpet
// --------------------------------------------------
class SierpinskiCarpet01Drawable: MyDrawable() {
// -------------------------------------
// 描画領域
// -------------------------------------
// シェルピンスキーのカーペットは
// 正方形を3分割するので3の階乗を選ぶ
// = 729 = 3^6
// -------------------------------------
private val side = 729f
private val margin = 50f
// ----------------------------------------
// 再帰レベル
// ----------------------------------------
private var nNow = 0
// --------------------------------------------------------
// 再帰レベルの最大値
// 5回描くと遅いので、5回までとしている
// --------------------------------------------------------
private val nMax = 5
// ----------------------------------------
// シェルピンスキーのカーペットの描画点リスト
// ----------------------------------------
private val pointLst = mutableListOf<MyPointF>()
// ---------------------------------------------------------------------
// 描画領域として使うビットマップ
// ---------------------------------------------------------------------
// 画面にタッチするとdrawが呼び出されるようなのでビットマップに描画する
// ---------------------------------------------------------------------
private lateinit var imageBitmap: Bitmap
private val tmpBitmap = Bitmap.createBitmap(intrinsicWidth,intrinsicHeight, Bitmap.Config.ARGB_8888)
// -------------------------------
// 枠に使うペイント
// -------------------------------
private val framePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = Color.BLACK
style = Paint.Style.STROKE
strokeWidth = 10f
}
// -------------------------------
// バックグラウンドに使うペイント
// -------------------------------
private val backPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = Color.WHITE
style = Paint.Style.FILL
}
// -------------------------------------
// シェルピンスキーのカーペットを描くペイント
// -------------------------------------
private val linePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = Color.RED
style = Paint.Style.FILL_AND_STROKE
strokeWidth = 1f
}
// -------------------------------------
// 描画中に呼び出すコールバックを設定
// -------------------------------------
private var notifyCallback: NotifyCallback? = null
// ---------------------------------------
// 別スレッドで描画するためのハンドラ
// ---------------------------------------
val handler = Handler()
// ---------------------------------------
// 描画に使うスレッド
// ---------------------------------------
private lateinit var runnable: Runnable
// ----------------------------------------
// CalculationCallback
// 描画に使うデータを計算する
// ----------------------------------------
// 可変変数 values の引数位置による意味合い
//
// 第1引数:再帰レベル(整数)
// ----------------------------------------
override fun calStart(isKickThread: Boolean, vararg values: Float) {
// 再帰レベル
nNow = 0
values.forEachIndexed { index, fl ->
//Log.d(javaClass.simpleName,"index[$index]fl[$fl]")
when (index) {
// 再帰レベル
0 -> nNow = fl.toInt()
}
}
// シェルピンスキーのカーペットを構築
createPath()
// ビットマップに描画
drawBitmap()
// 描画
invalidateSelf()
// 描画に使うスレッド
if (isKickThread) {
runnable = Runnable {
// 再帰レベルを1つ増やす
incrementLevel()
// 三角波を構築
createPath()
// ビットマップに描画
drawBitmap()
// 描画
invalidateSelf()
// 最初と最後は1秒後に描画
if ( nNow == nMax || nNow == 0 ) {
handler.postDelayed(runnable, 1000)
}
// 500msごとに描画
else {
handler.postDelayed(runnable, 500)
}
}
handler.postDelayed(runnable, 1000)
}
}
// -------------------------------------
// CalculationCallback
// 描画ビューを閉じる際,呼び出す後処理
// -------------------------------------
override fun calStop() {
// 描画に使うスレッドを解放する
handler.removeCallbacks(runnable)
}
// -------------------------------------
// CalculationCallback
// 描画中に呼び出すコールバックを設定
// -------------------------------------
override fun setNotifyCallback(notifyCallback: NotifyCallback) {
this.notifyCallback = notifyCallback
}
// シェルピンスキーのカーペットを構築
private fun createPath() {
// シェルピンスキーのカーペットの描画点リストをクリアする
pointLst.clear()
// ------------------------------
// シェルピンスキーのカーペットの頂点
// ------------------------------
// a:左下,b:右下,c:右上,d:左上
// ------------------------------
val a = MyPointF(0f,0f)
val b = MyPointF(side,0f)
val c = MyPointF(side,side)
val d = MyPointF(0f,side)
// 次レベルのシェルピンスキーのカーペットの描画点を求める
calNextLevel(a,b,c,d,nNow)
// 描画中に呼び出すコールバックをキックし、現在の再帰レベルを通知する
notifyCallback?.receive(nNow.toFloat())
}
// -----------------------------------------------
// 次レベルのシェルピンスキーのカーペットの描画点を求める
// -----------------------------------------------
private fun calNextLevel(a: MyPointF, b: MyPointF, c: MyPointF, d: MyPointF, n: Int) {
// -----------------------------------------------------
// 再帰呼び出しの際、nを減らしていき0以下になったら終了
// -----------------------------------------------------
if ( n <= 0 ) {
pointLst.add(a)
pointLst.add(b)
pointLst.add(c)
pointLst.add(d)
return
}
// -------------------------------------
// 次のレベルのカーペットの頂点を求める
// -------------------------------------
// d3,d2,c3,c2
// d0,d1,c0,c1
// a3,a2,b3,b2
// a0,a1,b0,b1
// -------------------------------------
// "A-B"のX座標の"左:右=1:2"
val l1r2 = (2f*a.x+b.x)/3f
// "A-B"のX座標の"左:右=2:1"
val l2r1 = (a.x+2f*b.x)/3f
// "D-A"のY座標の"上:下=1:2"
val u1d2 = (2f*d.y+a.y)/3f
// "D-A"のY座標の"上:下=2:1"
val u2d1 = (d.y+2f*a.y)/3f
// a0:A
val a0 = a
// a1:("A-B"の1/3,A)
val a1 = MyPointF(l1r2,a.y)
// a2:("A-B"の1/3,"D-A"の2/3)
val a2 = MyPointF(l1r2,u2d1)
// a3:(A,"D-A"の2/3)
val a3 = MyPointF(a.x,u2d1)
// b0:("A-B"の2/3,B)
val b0 = MyPointF(l2r1,b.y)
// b1:B
val b1 = b
// b2:(B,"D-A"の2/3)
val b2 = MyPointF(b.x,u2d1)
// b3:("A-B"の2/3,"D-A"の2/3)
val b3 = MyPointF(l2r1,u2d1)
// c0:("A-B"の2/3,"D-A"の1/3)
val c0 = MyPointF(l2r1,u1d2)
// c1:(C,"D-A"の1/3)
val c1 = MyPointF(c.x,u1d2)
// c2:C
val c2 = c
// c3:("A-B"の2/3,C)
val c3 = MyPointF(l2r1,c.y)
// d0:(D,"D-A"の1/3)
val d0 = MyPointF(d.x,u1d2)
// d1:("A-B"の1/3,"D-A"の1/3)
val d1 = MyPointF(l1r2,u1d2)
// d2:("A-B"の1/3,D)
val d2 = MyPointF(l1r2,d.y)
// d3:D
val d3 = d
// 次レベルのシェルピンスキーのカーペットを描画
// a0,a1,a2,a3を頂点とするカーペット
calNextLevel(a0,a1,a2,a3,n-1)
// a1,b0,b3,a2を頂点とするカーペット
calNextLevel(a1,b0,b3,a2,n-1)
// b0,b1,b2,b3を頂点とするカーペット
calNextLevel(b0,b1,b2,b3,n-1)
// a3,a2,d1,d0を頂点とするカーペット
calNextLevel(a3,a2,d1,d0,n-1)
// b3,b2,c1,c0を頂点とするカーペット
calNextLevel(b3,b2,c1,c0,n-1)
// d0,d1,d2,d3を頂点とするカーペット
calNextLevel(d0,d1,d2,d3,n-1)
// d1,c0,c3,d2を頂点とするカーペット
calNextLevel(d1,c0,c3,d2,n-1)
// c0,c1,c2,c3を頂点とするカーペット
calNextLevel(c0,c1,c2,c3,n-1)
}
// -------------------------------------
// 再帰レベルを1つ増やす
// -------------------------------------
private fun incrementLevel() {
nNow++
// 最大値を超えたら0に戻す
if ( nNow > nMax ) {
nNow = 0
}
}
// -------------------------------
// ビットマップに描画
// -------------------------------
private fun drawBitmap() {
val canvas = Canvas(tmpBitmap)
// バックグランドを描画
canvas.drawRect(RectF(0f, 0f, intrinsicWidth.toFloat(), intrinsicHeight.toFloat()), backPaint)
// 枠を描画
canvas.drawRect(RectF(0f, 0f, intrinsicWidth.toFloat(), intrinsicHeight.toFloat()), framePaint)
// ---------------------------------------------------------------------
// 原点(0,0)の位置
// = (マージン,マージン)
// ---------------------------------------------------------------------
canvas.save()
canvas.translate(margin, margin)
//Log.d(javaClass.simpleName,"===============================")
// シェルピンスキーのカーペットを描画
var path: Path = Path()
pointLst.forEachIndexed { index, myPointF ->
//Log.d(javaClass.simpleName,"index[$index]x[${myPointF.x}]y[${myPointF.y}]")
when ( index%4 ) {
0 -> {
path = Path()
path.moveTo(myPointF.x,myPointF.y)
}
1 -> {
path.lineTo(myPointF.x,myPointF.y)
}
2 -> {
path.lineTo(myPointF.x,myPointF.y)
}
else -> {
path.lineTo(myPointF.x,myPointF.y)
path.close()
canvas.drawPath(path,linePaint)
}
}
}
// 座標を元に戻す
canvas.restore()
// これまでの描画は上下逆なので反転する
val matrix = Matrix()
matrix.postScale(1f,-1f)
imageBitmap = Bitmap.createBitmap(tmpBitmap,0,0,intrinsicWidth,intrinsicHeight,matrix,true)
}
// -------------------------------
// Drawable
// -------------------------------
override fun draw(canvas: Canvas) {
// 描画用ビットマップがインスタンス化されていなければ描画はスキップする
if ( this::imageBitmap.isInitialized == false ) return
canvas.drawBitmap(imageBitmap,0f,0f,framePaint)
}
// -------------------------------
// Drawable
// -------------------------------
override fun setAlpha(alpha: Int) {
linePaint.alpha = alpha
}
// -------------------------------
// Drawable
// -------------------------------
override fun getOpacity(): Int = PixelFormat.TRANSLUCENT
// -------------------------------
// Drawable
// -------------------------------
override fun setColorFilter(colorFilter: ColorFilter?) {
linePaint.colorFilter = colorFilter
}
// -------------------------------
// Drawable
// -------------------------------
override fun getIntrinsicWidth(): Int = (side+margin*2).toInt()
// -------------------------------
// Drawable
// -------------------------------
override fun getIntrinsicHeight(): Int = (side+margin*2).toInt()
}
Reference
이 문제에 관하여(쉘핀 스키 카펫 그리기 (Android + Kotlin)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://qiita.com/milukiriu2010/items/23d081bb715342521cda텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)