每年的情人节和七夕,做点什么好呢?

写诗画画送礼物,逛街吃饭看电影?

作为搬砖爱好者,写个表白脚本或者动画什么的吧。

想起之前看到的一段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 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 )。

就像一张图片,如果分辨率是200x200, 缩小到100x100显示,图片还是清晰的,如果放大到400x400,可能会模糊。

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。

我们需要做的,是在心形内部选取位置,而非绘制曲线,故此,标准方程相对于参数方程更合适。

坐标系中的点(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 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程序表白,几条曲线构建Android表白程序相关推荐

  1. 几条曲线构建Android表白程序

    每年的情人节和七夕,甜蜜与痛苦的日子,做点什么好呢? 写诗画画送礼物,逛街吃饭看电影? 作为搬砖爱好者,写个表白脚本或者动画什么的吧. 想起之前看到的一段H5动画,在Android平台"临摹 ...

  2. Android Studio目录结构和Gradle构建Android项目

    一,Android Studio的目录结构和工程项目介绍 二,Eclipse工程与Android Studio工程的区别 1,Eclipse工程能导入AS运行,但AS建立的工程不能再Eclipse中运 ...

  3. android 自定义圆形进度条拖动样式,android自定义圆形进度条

    首先在布局文件定义Progressbar xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tool ...

  4. android 布局中绘制语音曲线,基于android平台的老年人用药助手软件的开发-仪器仪表工程专业论文.docx...

    基于android平台的老年人用药助手软件的开发-仪器仪表工程专业论文 Classified Index: TP319 U.D.C: 004.45 Dissertation for the Degre ...

  5. java窗口程序实例_Java Swing快速构建窗体应用程序

    以前接触java感觉其在桌面开发上,总是不太方便,没有一个好的拖拽界面布局工具,可以快速构建窗体. 最近学习了一下NetBeans IDE 8.1,感觉其窗体设计工具还是很不错的 , 就尝试一下做了一 ...

  6. 凸轮设计c语言程序,凸轮理论轮廓曲线设计c语言程序.doc

    /************习题4-1凸轮轮廓曲线绘制程序**********/ #include #include #define PI 3.1415926 void main() { double ...

  7. cordova指定版本_Cordova/Ionic构建android Gradle错误:支持的最小Gradle版本是2.14.1。当前版本是2.13...

    如果您不小心,Android Studio使用的Gradle版本与Cordova/cordova-android在其自动生成的应用程序代码中指定的Gradle版本可能不匹配.如你所知,运行 $ cor ...

  8. docker 构建Android编译环境

    docker 构建Android编译环境 文章目录 docker 构建Android编译环境 @[toc] ubuntu:14.04镜像 FAQs ubuntu:18.04镜像 FAQs 由于电脑升级 ...

  9. android supportv4最新版本19.1,Android Studio:无法找到:’com.android.support:support-v4:19.1.0′...

    我想在Android Studio中使用支持库构建一个应用程序,但是当添加支持库的依赖关系时,我收到以下错误: Error:Failed to find: com.android.support:su ...

最新文章

  1. 数据结构之二叉搜索树(BST)
  2. MIT CSAIL最新研究:将AI应用于流媒体视频,可获得更好的播放体验
  3. C# Datatable排序与取前几行数据
  4. Jenkins Pipeline 语法
  5. php explore im,浏栏器-explore.class.php
  6. Python高性能HTTP客户端库requests的使用
  7. 每天一道博弈论之“巴什博弈”
  8. fancyupload java_javascript-如何在内部Windows身份验证Intranet上...
  9. 对/proc和/sys的一些理解
  10. 《广义动量定理与系统思考----战争…
  11. 初级Java程序员如何快速提升自己的能力?
  12. 位运算中的一些数学原理
  13. css设置文本(上下)居中显示
  14. Ubuntu 9.04正式版下安装Sopcast看在线网络电视
  15. 游戏测试和软件测试有什么区别?
  16. cityscape讲解
  17. 五步快速安装android模拟器
  18. 计算机游戏教学法的创新之处,游戏教学法在小学英语课堂论文开题报告的创新点...
  19. 关于AD20的PCB电路图打印设置
  20. The server encountered an internal error () that prevented it from fulfilling this requet异常解决

热门文章

  1. php查询记录是否存在,php – 如果记录存在,我可以更新记录,如果不存在,可以在单个查询中更新多行吗?...
  2. vue.js表格赋值_vue.js input框之间赋值方法
  3. 命名实体识别之基本概念
  4. 数据库MySQL基础---JDBC开发步骤--JDBC封装工具类--PreparedStatement实现CRUD操作
  5. 解决172.17 或者172.18 机房环境下harbor服务器不通的问题
  6. kubernetes 简介:kube-dns 和服务发现
  7. 数据分析系统DIY1/3:CentOS7+MariaDB安装纪实
  8. [转载] 华中科技大学期刊分类办法
  9. [转] 81条经典话语~~~当裤子失去皮带,才懂得什麽叫做依赖
  10. Android之蓝牙开发浅析