Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 打造浪漫的Android表白程序

打造浪漫的Android表白程序

編輯:關於Android編程

幾年前,看到過有個牛人用HTML5繪制了浪漫的愛心表白動畫。發現原來程序員也是可以很浪……漫…..的(PS:剛過520,被妹子罵不夠浪漫)。那麼在Android怎麼打造如此這個效果呢?參考了一下前面HTML5的算法,在Android中實現了類似的效果。先貼上最終效果圖:

這裡寫圖片描述

生成心形線

心形線的表達式可以參考:桃心線。裡面對桃心線的表達式解析的挺好。可以通過使用極坐標的方式,傳入角度和距離(常量)計算出對應的坐標點。其中距離是常量值,不需改變,變化的是角度。
桃心線極坐標方程式為:

x=16×sin3α
y=13×cosα?5×cos2α?2×cos3α?cos4α

如果生成的桃心線不夠大,可以吧x、y乘以一個常數,使之變大。考慮到大部分人都不願去研究具體的數學問題,我們直接把前面HTML5的JS代碼直接翻譯成Java/" target="_blank">Java代碼就好。代碼如下:

public Point getHeartPoint(float angle) { float t = (float) (angle / Math.PI); float x = (float) (19.5 * (16 * Math.pow(Math.sin(t), 3))); float y = (float) (-20 * (13 * Math.cos(t) - 5 * Math.cos(2 * t) - 2 * Math.cos(3 * t) - Math.cos(4 * t))); return new Point(offsetX + (int) x, offsetY + (int) y); }

其中offsetX和offsetY是偏移量。使用偏移量主要是為了能讓心形線處於中央。offsetX和offsetY的值分別為:

offsetX = width / 2; offsetY = height / 2 - 55; 

通過這個函數,我們可以將角度從(0,180)變化,不斷取點並畫點將這個心形線顯示出來。好了,我們自定義一個View,然後把這個心形線畫出來吧!

@Override protected void onDraw(Canvas canvas) { float angle = 10; while (angle < 180) { Point p = getHeartPoint(angle); canvas.drawPoint(p.x, p.y, paint); angle = angle + 0.02f; } }

運行結果如下:
顯示的心形線

繪制花瓣原理

我們想要的並不是簡單繪制一個桃心線,要的是將花朵在桃心線上擺放。首先,得要知道怎麼繪制花朵,而花朵是由一個個花瓣組成。因此繪制花朵的核心是繪制花瓣。繪制花瓣的原理是:3次貝塞爾曲線。三次貝塞爾曲線是由兩個端點和兩個控制點決定。假設花芯是一個圓,有n個花瓣,那麼兩個端點與花芯的圓心連線之間的夾角即為360/n。因此可以根據花瓣數量和花芯半徑確定每個花瓣的位置。將兩個端點與花芯的圓心連線的延長線分別確定另外兩個控制點。通過隨機生成花芯半徑、每個花瓣的起始角以及隨機確定延長線得到兩個控制點,可以繪制一個隨機的花朵。參數的改變如下圖所示:

這裡寫圖片描述

將花朵繪制到桃心線上

一大波代碼來襲首先定義花瓣類Petal:

package com.hc.testheart; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Path; /** * Package com.example.administrator.testrecyclerview * Created by HuaChao on 2016/5/25. */ public class Petal { private float stretchA;//第一個控制點延長線倍數 private float stretchB;//第二個控制點延長線倍數 private float startAngle;//起始旋轉角,用於確定第一個端點 private float angle;//兩條線之間夾角,由起始旋轉角和夾角可以確定第二個端點 private int radius = 2;//花芯的半徑 private float growFactor;//增長因子,花瓣是有開放的動畫效果,這個參數決定花瓣展開速度 private int color;//花瓣顏色 private boolean isFinished = false;//花瓣是否綻放完成 private Path path = new Path();//用於保存三次貝塞爾曲線 private Paint paint = new Paint();//畫筆 //構造函數,由花朵類調用 public Petal(float stretchA, float stretchB, float startAngle, float angle, int color, float growFactor) { this.stretchA = stretchA; this.stretchB = stretchB; this.startAngle = startAngle; this.angle = angle; this.color = color; this.growFactor = growFactor; paint.setColor(color); } //用於渲染花瓣,通過不斷更改半徑使得花瓣越來越大 public void render(Point p, int radius, Canvas canvas) { if (this.radius <= radius) { this.radius += growFactor; // / 10; } else { isFinished = true; } this.draw(p, canvas); } //繪制花瓣,參數p是花芯的圓心的坐標 private void draw(Point p, Canvas canvas) { if (!isFinished) { path = new Path(); //將向量(0,radius)旋轉起始角度,第一個控制點根據這個旋轉後的向量計算 Point t = new Point(0, this.radius).rotate(MyUtil.degrad(this.startAngle)); //第一個端點,為了保證圓心不會隨著radius增大而變大這裡固定為3 Point v1 = new Point(0, 3).rotate(MyUtil.degrad(this.startAngle)); //第二個端點 Point v2 = t.clone().rotate(MyUtil.degrad(this.angle)); //延長線,分別確定兩個控制點 Point v3 = t.clone().mult(this.stretchA); Point v4 = v2.clone().mult(this.stretchB); //由於圓心在p點,因此,每個點要加圓心坐標點 v1.add(p); v2.add(p); v3.add(p); v4.add(p); path.moveTo(v1.x, v1.y); //參數分別是:第一個控制點,第二個控制點,終點 path.cubicTo(v3.x, v3.y, v4.x, v4.y, v2.x, v2.y); } canvas.drawPath(path, paint); } }

花瓣類是最重要的類,因為真正繪制在屏幕上的是一個個小花瓣。每個花朵包含一系列花瓣,花朵類Bloom如下:

package com.hc.testheart; import android.graphics.Canvas; import java.util.ArrayList; /** * Package com.example.administrator.testrecyclerview * Created by HuaChao on 2016/5/25. */ public class Bloom { private int color;//整個花朵的顏色 private Point point;//花芯圓心 private int radius; //花芯半徑 private ArrayList petals;//用於保存花瓣 public Point getPoint() { return point; } public Bloom(Point point, int radius, int color, int petalCount) { this.point = point; this.radius = radius; this.color = color; petals = new ArrayList<>(petalCount); float angle = 360f / petalCount; int startAngle = MyUtil.randomInt(0, 90); for (int i = 0; i < petalCount; i++) { //隨機產生第一個控制點的拉伸倍數 float stretchA = MyUtil.random(Garden.Options.minPetalStretch, Garden.Options.maxPetalStretch); //隨機產生第二個控制地的拉伸倍數 float stretchB = MyUtil.random(Garden.Options.minPetalStretch, Garden.Options.maxPetalStretch); //計算每個花瓣的起始角度 int beginAngle = startAngle + (int) (i * angle); //隨機產生每個花瓣的增長因子(即綻放速度) float growFactor = MyUtil.random(Garden.Options.minGrowFactor, Garden.Options.maxGrowFactor); //創建一個花瓣,並添加到花瓣列表中 this.petals.add(new Petal(stretchA, stretchB, beginAngle, angle, color, growFactor)); } } //繪制花朵 public void draw(Canvas canvas) { Petal p; for (int i = 0; i < this.petals.size(); i++) { p = petals.get(i); //渲染每朵花朵 p.render(point, this.radius, canvas); } } public int getColor() { return color; } }

接下來是花園類Garden,主要用於創建花朵以及一些相關配置:

package com.hc.testheart; import java.util.ArrayList; /** * Package com.example.administrator.testrecyclerview * Created by HuaChao on 2016/5/24. */ public class Garden { //創建一個隨機的花朵 public Bloom createRandomBloom(int x, int y) { //創建一個隨機的花朵半徑 int radius = MyUtil.randomInt(Options.minBloomRadius, Options.maxBloomRadius); //創建一個隨機的花朵顏色 int color = MyUtil.randomrgba(Options.minRedColor, Options.maxRedColor, Options.minGreenColor, Options.maxGreenColor, Options.minBlueColor, Options.maxBlueColor, Options.opacity); //創建隨機的花朵中花瓣個數 int petalCount = MyUtil.randomInt(Options.minPetalCount, Options.maxPetalCount); return createBloom(x, y, radius, color, petalCount); } //創建花朵 public Bloom createBloom(int x, int y, int radius, int color, int petalCount) { return new Bloom(new Point(x, y), radius, color, petalCount); } static class Options { //用於控制產生隨機花瓣個數范圍 public static int minPetalCount = 8; public static int maxPetalCount = 15; //用於控制產生延長線倍數范圍 public static float minPetalStretch = 2f; public static float maxPetalStretch = 3.5f; //用於控制產生隨機增長因子范圍,增長因子決定花瓣綻放速度 public static float minGrowFactor = 1f; public static float maxGrowFactor = 1.1f; //用於控制產生花朵半徑隨機數范圍 public static int minBloomRadius = 8; public static int maxBloomRadius = 10; //用於產生隨機顏色 public static int minRedColor = 128; public static int maxRedColor = 255; public static int minGreenColor = 0; public static int maxGreenColor = 128; public static int minBlueColor = 0; public static int maxBlueColor = 128; //花瓣的透明度 public static int opacity = 50;//0.1 } }

考慮到刷新的比較頻繁,選擇使用SurfaceView作為顯示視圖。自定義一個HeartView繼承SurfaceView。代碼如下:

package com.hc.testheart; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.util.AttributeSet; import android.view.SurfaceHolder; import android.view.SurfaceView; import java.util.ArrayList; /** * Package com.hc.testheart * Created by HuaChao on 2016/5/25. */ public class HeartView extends SurfaceView implements SurfaceHolder.Callback { SurfaceHolder surfaceHolder; int offsetX; int offsetY; private Garden garden; private int width; private int height; private Paint backgroundPaint; private boolean isDrawing = false; private Bitmap bm; private Canvas canvas; private int heartRadio = 1; public HeartView(Context context) { super(context); init(); } public HeartView(Context context, AttributeSet attrs) { super(context, attrs); init(); } private void init() { surfaceHolder = getHolder(); surfaceHolder.addCallback(this); garden = new Garden(); backgroundPaint = new Paint(); backgroundPaint.setColor(Color.rgb(0xff, 0xff, 0xe0)); } ArrayList blooms = new ArrayList<>(); public Point getHeartPoint(float angle) { float t = (float) (angle / Math.PI); float x = (float) (heartRadio * (16 * Math.pow(Math.sin(t), 3))); float y = (float) (-heartRadio * (13 * Math.cos(t) - 5 * Math.cos(2 * t) - 2 * Math.cos(3 * t) - Math.cos(4 * t))); return new Point(offsetX + (int) x, offsetY + (int) y); } //繪制列表裡所有的花朵 private void drawHeart() { canvas.drawRect(0, 0, width, height, backgroundPaint); for (Bloom b : blooms) { b.draw(canvas); } Canvas c = surfaceHolder.lockCanvas(); c.drawBitmap(bm, 0, 0, null); surfaceHolder.unlockCanvasAndPost(c); } public void reDraw() { blooms.clear(); drawOnNewThread(); } @Override public void draw(Canvas canvas) { super.draw(canvas); } //開啟一個新線程繪制 private void drawOnNewThread() { new Thread() { @Override public void run() { if (isDrawing) return; isDrawing = true; float angle = 10; while (true) { Bloom bloom = getBloom(angle); if (bloom != null) { blooms.add(bloom); } if (angle >= 30) { break; } else { angle += 0.2; } drawHeart(); try { sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } } isDrawing = false; } }.start(); } private Bloom getBloom(float angle) { Point p = getHeartPoint(angle); boolean draw = true; /**循環比較新的坐標位置是否可以創建花朵, * 為了防止花朵太密集 * */ for (int i = 0; i < blooms.size(); i++) { Bloom b = blooms.get(i); Point bp = b.getPoint(); float distance = (float) Math.sqrt(Math.pow(p.x - bp.x, 2) + Math.pow(p.y - bp.y, 2)); if (distance < Garden.Options.maxBloomRadius * 1.5) { draw = false; break; } } //如果位置間距滿足要求,就在該位置創建花朵並將花朵放入列表 if (draw) { Bloom bloom = garden.createRandomBloom(p.x, p.y); return bloom; } return null; } @Override public void surfaceCreated(SurfaceHolder holder) { } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { this.width = width; this.height = height; //我的手機寬度像素是1080,發現參數設置為30比較合適,這裡根據不同的寬度動態調整參數 heartRadio = width * 30 / 1080; offsetX = width / 2; offsetY = height / 2 - 55; bm = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565); canvas = new Canvas(bm); drawOnNewThread(); } @Override public void surfaceDestroyed(SurfaceHolder holder) { } }

還有兩個比較重要的工具類
Point.java保存點信息,或者說是向量信息。包含向量的基本運算。

package com.hc.testheart; /** * Package com.hc.testheart * Created by HuaChao on 2016/5/25. */ public class Point { public int x; public int y; public Point(int x, int y) { this.x = x; this.y = y; } //旋轉 public Point rotate(float theta) { int x = this.x; int y = this.y; this.x = (int) (Math.cos(theta) * x - Math.sin(theta) * y); this.y = (int) (Math.sin(theta) * x + Math.cos(theta) * y); return this; } //乘以一個常數 public Point mult(float f) { this.x *= f; this.y *= f; return this; } //復制 public Point clone() { return new Point(this.x, this.y); } //該點與圓心距離 public float length() { return (float) Math.sqrt(this.x * this.x + this.y * this.y); } //向量相減 public Point subtract(Point p) { this.x -= p.x; this.y -= p.y; return this; } //向量相加 public Point add(Point p) { this.x += p.x; this.y += p.y; return this; } public Point set(int x, int y) { this.x = x; this.y = y; return this; } }

工具類MyUtil.java主要是產生隨機數、顏色等

package com.hc.testheart; import android.graphics.Color; /** * Package com.example.administrator.testrecyclerview * Created by HuaChao on 2016/5/25. */ public class MyUtil { public static float circle = (float) (2 * Math.PI); public static int rgba(int r, int g, int b, int a) { return Color.argb(a, r, g, b); } public static int randomInt(int min, int max) { return (int) Math.floor(Math.random() * (max - min + 1)) + min; } public static float random(float min, float max) { return (float) (Math.random() * (max - min) + min); } //產生隨機的argb顏色 public static int randomrgba(int rmin, int rmax, int gmin, int gmax, int bmin, int bmax, int a) { int r = Math.round(random(rmin, rmax)); int g = Math.round(random(gmin, gmax)); int b = Math.round(random(bmin, bmax)); int limit = 5; if (Math.abs(r - g) <= limit && Math.abs(g - b) <= limit && Math.abs(b - r) <= limit) { return rgba(rmin, rmax, gmin, gmax); } else { return rgba(r, g, b, a); } } //角度轉弧度 public static float degrad(float angle) { return circle / 360 * angle; } }

好了,目前為止,就可以得到上面的效果了。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved