0
0
0
1. 云栖社区>
2. 博客>
3. 正文

## 一、树枝

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) {
}
}
return branches[0];
}

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 part;

float growX;
float growY;

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]);
maxLen = a[9];
part = 1.0f / maxLen;
}

public boolean grow(Canvas canvas, float scareFactor){
if(currLen <= maxLen){
bezier(part * currLen);
draw(canvas, scareFactor);
currLen++;
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.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;
}

if(childList == null){
}
}
}


## 二、花瓣

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 class Bloom {
protected static float sMaxScale = 0.2f;
protected static float sFactor;

/**
* 初始化显示参数
* @param resolutionFactor 根据屏幕分辨率设定缩放因子
*/
public static void initDisplayParam(float resolutionFactor){
sFactor = resolutionFactor;
sMaxScale = 0.2f * resolutionFactor;
}

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;
}
}

}

private void draw(Canvas canvas) {
Paint paint = CommonUtil.getPaint();
paint.setColor(color);

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();
}
}

## 三、树冠

    private static float r;
private static float c;

/**
* 初始化参数
* @param canvasHeight 画布的高度
*/
public static void init(int canvasHeight, float 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)) {
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;
}

## 绘制动画

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

+ 关注