幾條曲線構建Android表白程式
每年的情人節和七夕,甜蜜與痛苦的日子,做點什麼好呢?
寫詩畫畫送禮物,逛街吃飯看電影?
作為搬磚愛好者,寫個表白指令碼或者動畫什麼的吧。
想起之前看到的一段H5動畫,在Android平臺“臨摹”了一遍。
效果如下圖:其構圖還是比較簡單的,樹枝加上由心形花瓣構成的心形樹冠(後面做成動畫之後會有隨機的花瓣飄落)。
一、樹枝
樹枝是通過貝塞爾曲線來構造的,二階貝塞爾曲線。
準備資料
getBranches()函式中,定義各個樹枝的位置和形狀,最終返回樹幹。
繪製的時候,先繪製樹幹,然後繪製其分支,最後繪製分支的分支(只有三層)。
public static Branch getBranches() {
// 共10列,分別是id, parentId, 貝塞爾曲線控制點(3點,6列), 最大半徑, 長度
int[][] data = new int[][]{
{0, -1, 217, 490, 252, 60, 182, 10, 30, 100},
{1, 0, 222, 310, 137, 227, 22, 210, 13, 100},
{2, 1, 132, 245, 116, 240, 76, 205, 2, 40},
{3, 0, 232, 255, 282, 166, 362, 155, 12, 100},
{4, 3, 260, 210, 330, 219, 343, 236, 3, 80},
{5, 0, 221, 91, 219, 58, 216, 27, 3, 40},
{6, 0, 228, 207, 95, 57, 10, 54, 9, 80},
{7, 6, 109, 96, 65, 63, 53, 15, 2, 40},
{8, 6, 180, 155, 117, 125, 77, 140, 4, 60},
{9, 0, 228, 167, 290, 62, 360, 31, 6, 100},
{10, 9, 272, 103, 328, 87, 330, 81, 2, 80}
};
int n = data.length;
Branch[] branches = new Branch[n];
for (int i = 0; i < n; i++) {
branches[i] = new Branch(data[i]);
int parent = data[i][1];
if (parent != -1) {
branches[parent].addChild(branches[i]);
}
}
return branches[0];
}
封裝Branch類
主要包含樹枝的構建(建構函式,addChild函式),以及繪製。
繪製樹枝時,不斷地呼叫grow函式,繪製點(currLen)逐漸靠近末端(maxLen), 樹枝的半徑逐漸變小;
最終控制點到達樹枝末端(currLen==maxLen), 繪製結束。
如果是繪製靜態畫面,while迴圈直到grow返回false;
如果是繪製動畫, 可通過呼叫postInvalidate(),不斷地對回撥繪製函式, 每一幀樹枝成長一截。
public class Branch {
private static final int BRANCH_COLOR = Color.rgb(35, 31, 32);
// control point
Point[] cp = new Point[3];
int currLen;
int maxLen;
float radius;
float part;
float growX;
float growY;
LinkedList<Branch> childList;
public Branch(int[] a){
cp[0] = new Point(a[2], a[3]);
cp[1] = new Point(a[4], a[5]);
cp[2] = new Point(a[6], a[7]);
radius = a[8];
maxLen = a[9];
part = 1.0f / maxLen;
}
public boolean grow(Canvas canvas, float scareFactor){
if(currLen <= maxLen){
bezier(part * currLen);
draw(canvas, scareFactor);
currLen++;
radius *= 0.97f;
return true;
}else{
return false;
}
}
private void draw(Canvas canvas, float scareFactor){
Paint paint = CommonUtil.getPaint();
paint.setColor(BRANCH_COLOR);
canvas.save();
canvas.scale(scareFactor, scareFactor);
canvas.translate(growX, growY);
canvas.drawCircle(0,0, radius, paint);
canvas.restore();
}
private void bezier(float t) {
float c0 = (1 - t) * (1 - t);
float c1 = 2 * t * (1 - t);
float c2 = t * t;
growX = c0 * cp[0].x + c1 * cp[1].x + c2* cp[2].x;
growY = c0 * cp[0].y + c1 * cp[1].y + c2* cp[2].y;
}
public void addChild(Branch branch){
if(childList == null){
childList = new LinkedList<>();
}
childList.add(branch);
}
}
效果圖如下:
二、花瓣
花瓣的繪製,是通過一條曲線實現的:本文的主角,自帶愛情故事的心形線。
心形線有很多種,有的用標準方程表示,有的用引數方程表示。
對於繪製曲線來說,引數方程更方便一些。
在網站wolframalpha)上,可以輸入方程直接預覽曲線。
計算心形線
因為要繪製很多花瓣,所以可以將其形狀預先計算好,快取起來。
或許是因為精度的原因, 如果直接取樣上圖的點,繪製時如果有scale(縮放)操作,可能會顯示不平滑;
所以在取樣心形線的點時我們放大一定比率(SCALE_FACTOR )。
就像一張圖片,如果解析度是200×200, 縮小到100×100顯示,圖片還是清晰的,如果放大到400×400,可能會模糊。
public class Heart {
private static final Path PATH = new Path();
private static final float SCALE_FACTOR = 10f;
private static final float RADIUS = 18 * SCALE_FACTOR;
static {
// x = 16 sin^3 t
// y = 13 cos t - 5 cos 2t - 2 cos 3t - cos 4t
// http://www.wolframalpha.com/input/?i=x+%3D+16+sin%5E3+t%2C+y+%3D+(13+cos+t+-+5+cos+2t+-+2+cos+3t+-+cos+4t)
int n = 101;
Point[] points = new Point[n];
float t = 0f;
float d = (float) (2 * Math.PI / (n - 1));
for (int i = 0; i < n; i++) {
float x = (float) (16 * Math.pow(Math.sin(t), 3));
float y = (float) (13 * Math.cos(t) - 5 * Math.cos(2 * t) - 2 * Math.cos(3 * t) - Math.cos(4 * t));
points[i] = new Point(SCALE_FACTOR * x , -SCALE_FACTOR * y );
t += d;
}
PATH.moveTo(points[0].x, points[0].y);
for (int i = 1; i < n; i++) {
PATH.lineTo(points[i].x, points[i].y);
}
PATH.close();
}
public static Path getPath(){
return PATH;
}
public static float getRadius(){
return RADIUS;
}
}
封裝Bloom類
一片花瓣,除了形狀之外,還有方位,顏色,方向,大小等引數。
故此,和Branch一樣,封裝了一個類。
花瓣的顏色和方向引數是隨機初始化的。
顏色方面,ARGB中Red通道固定為最大值0xff, 效果就是花瓣的顏色為紅,紫,黃,白等。
因為要適應移動裝置的多解析度,所以一些引數要根據解析度來動態設定。
public class Bloom {
protected static float sMaxScale = 0.2f;
protected static int sMaxRadius = Math.round(sMaxScale * Heart.getRadius());
protected static float sFactor;
/**
* 初始化顯示引數
* @param resolutionFactor 根據螢幕解析度設定縮放因子
*/
public static void initDisplayParam(float resolutionFactor){
sFactor = resolutionFactor;
sMaxScale = 0.2f * resolutionFactor;
sMaxRadius = Math.round(sMaxScale * Heart.getRadius());
}
Point position;
int color;
float angle;
float scale;
// 調速器,控制開花動畫的快慢
int governor = 0;
public Bloom(Point position) {
this.position = position;
this.color = Color.argb(CommonUtil.random(76, 255), 0xff, CommonUtil.random(255), CommonUtil.random(255));
this.angle = CommonUtil.random(360);
}
public boolean grow(Canvas canvas) {
if (scale <= sMaxScale) {
if((governor & 1) == 0) {
scale += 0.0125f * sFactor;
draw(canvas);
}
governor++;
return true;
} else {
return false;
}
}
protected float getRadius() {
return Heart.getRadius() * scale;
}
private void draw(Canvas canvas) {
Paint paint = CommonUtil.getPaint();
paint.setColor(color);
float r = getRadius();
canvas.save();
canvas.translate(position.x, position.y);
canvas.saveLayerAlpha(-r, -r, r, r, Color.alpha(color));
canvas.save();
canvas.rotate(angle);
canvas.scale(scale, scale);
canvas.drawPath(Heart.getPath(), paint);
canvas.restore();
canvas.restore();
canvas.restore();
}
}
三、樹冠
樹冠是由數百片花瓣構成,關鍵點在於確定這些花瓣的位置。
這裡用到另一條心形線(x^2 + y^2 -1)^3 – x^2 * y^3 = 0%5E3-x%5E2*y%5E3%3D0)。
我們需要做的,是在心形內部選取位置,而非繪製曲線,故此,標準方程相對於引數方程更合適。
座標系中的點(x,y), 計算ax+by, 大於0和小於0分別在直線的兩側, x^2 + y^2 – r^2 則分別在圓外和圓內;
這個現象還蠻奇妙的,雖然我不知道這在數學中叫什麼-_-。
類似的,在x=[-c, c], y=[-c,c]的範圍內隨機選取(x^2 + y^2 -1)^3 – x^2 * y^3<0的點,即可使得花瓣的位置錯落於心形線中。
private static float r;
private static float c;
/**
* 初始化引數
* @param canvasHeight 畫布的高度
* @param crownRadiusFactor 樹冠半徑的縮放因子
*/
public static void init(int canvasHeight, float crownRadiusFactor){
r = canvasHeight * crownRadiusFactor;
c = r * 1.35f;
}
public static void fillBlooms(List<Bloom> blooms, int num) {
int n = 0;
while (n < num) {
float x = CommonUtil.random(-c, c);
float y = CommonUtil.random(-c, c);
if (inHeart(x, y, r)) {
blooms.add(new Bloom(new Point(x, -y)));
n++;
}
}
}
private static boolean inHeart(float px, float py, float r) {
// (x^2+y^2-1)^3-x^2*y^3=0
float x = px / r;
float y = py / r;
float sx = x * x;
float sy = y * y;
float a = sx + sy - 1;
return a * a * a - sx * sy * y < 0;
}
繪製動畫
不斷地觸發onDraw()回撥,在每一幀裡面改變繪製引數,就形成動畫了。
在這個例子中,劃分了幾個動畫階段,每個階段各自變化自己的引數,到達一定的狀態就切換到下一階段。
總之,就是分而治之,然後串聯起來。
public class TreeView extends View {
private static Tree tree;
public TreeView(Context context) {
super(context);
}
@Override
protected void onDraw(Canvas canvas) {
if(tree == null){
tree = new Tree(getWidth(), getHeight());
}
tree.draw(canvas);
// 這個函式只是標記view為invalidate狀態,並不會馬上觸發重繪;
// 標記invalidate狀態後,下一個繪製週期(約16s), 會回撥onDraw();
// 故此,要想動畫平滑流暢,tree.draw(canvas)需在16s內完成。
postInvalidate();
}
}
public void draw(Canvas canvas) {
// 繪製背景顏色
canvas.drawColor(0xffffffee);
// 繪製動畫元素
canvas.save();
canvas.translate(snapshotDx + xOffset, 0);
switch (step) {
case BRANCHES_GROWING:
drawBranches();
drawSnapshot(canvas);
break;
case BLOOMS_GROWING:
drawBlooms();
drawSnapshot(canvas);
break;
case MOVING_SNAPSHOT:
movingSnapshot();
drawSnapshot(canvas);
break;
case BLOOM_FALLING:
drawSnapshot(canvas);
drawFallingBlooms(canvas);
break;
default:
break;
}
canvas.restore();
}
後記
- 本來打算七夕前的週末搞定它的,無奈很多知識忘記了,需要回頭溫習,沒趕上。
很多時候就是這樣,學的時候不知道有什麼用,用的時候又記不起來-_- - 調整引數也消耗不少時間,寫程式碼比較客觀,調引數則比較主觀:方位擺放,顯示大小,動畫快慢……
- 構圖中左上角有留白,可以在那裡輸出一些表白文字。
- 考慮到移動端的流量,動圖部分只擷取最後一個階段的動畫。
- 篇幅限制,文中只是貼了部分程式碼,完整程式碼可到github下載HeartTree。
相關文章
- 一條曲線的誕生
- Android動畫曲線庫AndroidEasingFunctionsAndroid動畫IdeaFunction
- 用Gradle 構建你的android程式GradleAndroid
- 表白小程式
- Android 身高體重曲線的實現Android
- 浪漫表白程式碼
- Python打造最強表白程式Python
- 如何判斷兩條軌跡(或曲線)的相似度?
- 從零構建Android工程Android
- Gradle for Android ( 構建變體 )GradleAndroid
- 構建Docker幾個小技巧Docker
- 校園表白牆告白牆表白網站程式原始碼搭建教程網站原始碼
- Android教你一步一步從學習貝塞爾曲線到實現波浪進度條Android
- ROC曲線,曲線下的面積(Aera Under Curve,AUC),P-R曲線
- 使用 Kotlin 構建 MVVM 應用程式—提高篇:Dagger-AndroidKotlinMVVMAndroid
- Android Note - 構建速度優化Android優化
- Android Note - 使用構建分析工具Android
- 使用CMake構建Android JNI工程Android
- Android-貝塞爾曲線實現水波紋動畫Android動畫
- 構建映象的幾個小技巧
- Qt開發技術:QCharts(三)QCharts樣條曲線圖介紹、Demo以及程式碼詳解QT
- 程式設計幾點建議程式設計
- dwg解析-樣條曲線擬合點解析(中望CAD匯出)
- App 如何為中小企業帶來第二條增長曲線APP
- devops-5:從0開始構建一條完成的CI CD流水線dev
- 第三個java程式(表白小卡片)Java
- 表白程式碼製作(附原始碼)原始碼
- Android 窗簾(Curtain)效果四之賽貝爾曲線優化AndroidAI優化
- Android 第一次構建Android
- Android 自定義構建型別 BuildTypeAndroid型別UI
- [譯]改善 Android Studio 的構建速度Android
- Android打包之配置構建變體Android
- Ale Giorgini的復古幾何線條與浪漫
- 給程式設計菜鳥的16條忠告,你做到幾條程式設計
- Mac Jenkins 構建 Android App 時動態設定程式碼引數MacJenkinsAndroidAPP
- 給計算機專業學生的幾條建議計算機
- MySql資料庫最佳化的幾條核心建議MySql資料庫
- 建站四部曲之移動端篇(Android+上線)Android