本文转载于  huachao1001的专栏

几年前,看到过有个牛人用HTML5绘制了浪漫的爱心表白动画。地址在这:浪漫程序员 HTML5爱心表白动画。发现原来程序员也是可以很浪……漫…..的。那么在Android怎么打造如此这个效果呢?参考了一下前面HTML5的算法,在Android中实现了类似的效果。先贴上最终效果图:

生成心形线

心形线的表达式可以参考:桃心线。里面对桃心线的表达式解析的挺好。可以通过使用极坐标的方式,传入角度和距离(常量)计算出对应的坐标点。其中距离是常量值,不需改变,变化的是角度。
桃心线极坐标方程式为:

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

如果生成的桃心线不够大,可以吧x、y乘以一个常数,使之变大。考虑到大部分人都不愿去研究具体的数学问题,我们直接把前面HTML5的JS代码直接翻译成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,然后把这个心形线画出来吧!

 @Overrideprotected 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:

 1  package com.hc.testheart;
 2
 3 import android.graphics.Canvas;
 4 import android.graphics.Paint;
 5 import android.graphics.Path;
 6
 7 /**
 8  * Package com.example.administrator.testrecyclerview
 9  * Created by HuaChao on 2016/5/25.
10  */
11 public class Petal {
12     private float stretchA;//第一个控制点延长线倍数
13     private float stretchB;//第二个控制点延长线倍数
14     private float startAngle;//起始旋转角,用于确定第一个端点
15     private float angle;//两条线之间夹角,由起始旋转角和夹角可以确定第二个端点
16     private int radius = 2;//花芯的半径
17     private float growFactor;//增长因子,花瓣是有开放的动画效果,这个参数决定花瓣展开速度
18     private int color;//花瓣颜色
19     private boolean isFinished = false;//花瓣是否绽放完成
20     private Path path = new Path();//用于保存三次贝塞尔曲线
21     private Paint paint = new Paint();//画笔
22     //构造函数,由花朵类调用
23     public Petal(float stretchA, float stretchB, float startAngle, float angle, int color, float growFactor) {
24         this.stretchA = stretchA;
25         this.stretchB = stretchB;
26         this.startAngle = startAngle;
27         this.angle = angle;
28         this.color = color;
29         this.growFactor = growFactor;
30         paint.setColor(color);
31     }
32     //用于渲染花瓣,通过不断更改半径使得花瓣越来越大
33     public void render(Point p, int radius, Canvas canvas) {
34         if (this.radius <= radius) {
35             this.radius += growFactor; // / 10;
36         } else {
37             isFinished = true;
38         }
39         this.draw(p, canvas);
40     }
41
42     //绘制花瓣,参数p是花芯的圆心的坐标
43     private void draw(Point p, Canvas canvas) {
44         if (!isFinished) {
45
46             path = new Path();
47             //将向量(0,radius)旋转起始角度,第一个控制点根据这个旋转后的向量计算
48             Point t = new Point(0, this.radius).rotate(MyUtil.degrad(this.startAngle));
49             //第一个端点,为了保证圆心不会随着radius增大而变大这里固定为3
50             Point v1 = new Point(0, 3).rotate(MyUtil.degrad(this.startAngle));
51             //第二个端点
52             Point v2 = t.clone().rotate(MyUtil.degrad(this.angle));
53             //延长线,分别确定两个控制点
54             Point v3 = t.clone().mult(this.stretchA);
55             Point v4 = v2.clone().mult(this.stretchB);
56             //由于圆心在p点,因此,每个点要加圆心坐标点
57             v1.add(p);
58             v2.add(p);
59             v3.add(p);
60             v4.add(p);
61             path.moveTo(v1.x, v1.y);
62             //参数分别是:第一个控制点,第二个控制点,终点
63             path.cubicTo(v3.x, v3.y, v4.x, v4.y, v2.x, v2.y);
64         }
65         canvas.drawPath(path, paint);
66     }
67
68
69 }

View Code

花瓣类是最重要的类,因为真正绘制在屏幕上的是一个个小花瓣。每个花朵包含一系列花瓣,花朵类Bloom如下:

 1 package com.hc.testheart;
 2
 3 import android.graphics.Canvas;
 4
 5 import java.util.ArrayList;
 6
 7 /**
 8  * Package com.example.administrator.testrecyclerview
 9  * Created by HuaChao on 2016/5/25.
10  */
11 public class Bloom {
12     private int color;//整个花朵的颜色
13     private Point point;//花芯圆心
14     private int radius; //花芯半径
15     private ArrayList petals;//用于保存花瓣
16
17     public Point getPoint() {
18         return point;
19     }
20
21
22     public Bloom(Point point, int radius, int color, int petalCount) {
23         this.point = point;
24         this.radius = radius;
25         this.color = color;
26         petals = new ArrayList<>(petalCount);
27
28
29         float angle = 360f / petalCount;
30         int startAngle = MyUtil.randomInt(0, 90);
31         for (int i = 0; i < petalCount; i++) {
32             //随机产生第一个控制点的拉伸倍数
33             float stretchA = MyUtil.random(Garden.Options.minPetalStretch, Garden.Options.maxPetalStretch);
34             //随机产生第二个控制地的拉伸倍数
35             float stretchB = MyUtil.random(Garden.Options.minPetalStretch, Garden.Options.maxPetalStretch);
36             //计算每个花瓣的起始角度
37             int beginAngle = startAngle + (int) (i * angle);
38             //随机产生每个花瓣的增长因子(即绽放速度)
39             float growFactor = MyUtil.random(Garden.Options.minGrowFactor, Garden.Options.maxGrowFactor);
40             //创建一个花瓣,并添加到花瓣列表中
41             this.petals.add(new Petal(stretchA, stretchB, beginAngle, angle, color, growFactor));
42         }
43     }
44
45     //绘制花朵
46     public void draw(Canvas canvas) {
47         Petal p;
48         for (int i = 0; i < this.petals.size(); i++) {
49             p = petals.get(i);
50             //渲染每朵花朵
51             p.render(point, this.radius, canvas);
52
53         }
54
55     }
56
57     public int getColor() {
58         return color;
59     }
60 }

View Code

接下来是花园类Garden,主要用于创建花朵以及一些相关配置:

 1 package com.hc.testheart;
 2
 3 import java.util.ArrayList;
 4
 5 /**
 6  * Package com.example.administrator.testrecyclerview
 7  * Created by HuaChao on 2016/5/24.
 8  */
 9 public class Garden {
10
11     //创建一个随机的花朵
12     public Bloom createRandomBloom(int x, int y) {
13         //创建一个随机的花朵半径
14         int radius = MyUtil.randomInt(Options.minBloomRadius, Options.maxBloomRadius);
15         //创建一个随机的花朵颜色
16         int color = MyUtil.randomrgba(Options.minRedColor, Options.maxRedColor, Options.minGreenColor, Options.maxGreenColor, Options.minBlueColor, Options.maxBlueColor, Options.opacity);
17         //创建随机的花朵中花瓣个数
18         int petalCount = MyUtil.randomInt(Options.minPetalCount, Options.maxPetalCount);
19         return createBloom(x, y, radius, color, petalCount);
20     }
21
22     //创建花朵
23     public Bloom createBloom(int x, int y, int radius, int color, int petalCount) {
24         return new Bloom(new Point(x, y), radius, color, petalCount);
25     }
26
27     static class Options {
28         //用于控制产生随机花瓣个数范围
29         public static int minPetalCount = 8;
30         public static int maxPetalCount = 15;
31         //用于控制产生延长线倍数范围
32         public static float minPetalStretch = 2f;
33         public static float maxPetalStretch = 3.5f;
34         //用于控制产生随机增长因子范围,增长因子决定花瓣绽放速度
35         public static float minGrowFactor = 1f;
36         public static float maxGrowFactor = 1.1f;
37         //用于控制产生花朵半径随机数范围
38         public static int minBloomRadius = 8;
39         public static int maxBloomRadius = 10;
40         //用于产生随机颜色
41         public static int minRedColor = 128;
42         public static int maxRedColor = 255;
43         public static int minGreenColor = 0;
44         public static int maxGreenColor = 128;
45         public static int minBlueColor = 0;
46         public static int maxBlueColor = 128;
47         //花瓣的透明度
48         public static int opacity = 50;//0.1
49     }
50 }

View Code

考虑到刷新的比较频繁,选择使用SurfaceView作为显示视图。自定义一个HeartView继承SurfaceView。代码如下:

  1 package com.hc.testheart;
  2
  3 import android.content.Context;
  4 import android.graphics.Bitmap;
  5 import android.graphics.Canvas;
  6 import android.graphics.Color;
  7 import android.graphics.Paint;
  8 import android.util.AttributeSet;
  9 import android.view.SurfaceHolder;
 10 import android.view.SurfaceView;
 11
 12 import java.util.ArrayList;
 13
 14 /**
 15  * Package com.hc.testheart
 16  * Created by HuaChao on 2016/5/25.
 17  */
 18 public class HeartView extends SurfaceView implements SurfaceHolder.Callback {
 19     SurfaceHolder surfaceHolder;
 20     int offsetX;
 21     int offsetY;
 22     private Garden garden;
 23     private int width;
 24     private int height;
 25     private Paint backgroundPaint;
 26     private boolean isDrawing = false;
 27     private Bitmap bm;
 28     private Canvas canvas;
 29     private int heartRadio = 1;
 30
 31     public HeartView(Context context) {
 32         super(context);
 33         init();
 34     }
 35
 36     public HeartView(Context context, AttributeSet attrs) {
 37         super(context, attrs);
 38         init();
 39     }
 40
 41
 42     private void init() {
 43         surfaceHolder = getHolder();
 44         surfaceHolder.addCallback(this);
 45         garden = new Garden();
 46         backgroundPaint = new Paint();
 47         backgroundPaint.setColor(Color.rgb(0xff, 0xff, 0xe0));
 48
 49
 50     }
 51
 52     ArrayList blooms = new ArrayList<>();
 53
 54     public Point getHeartPoint(float angle) {
 55         float t = (float) (angle / Math.PI);
 56         float x = (float) (heartRadio * (16 * Math.pow(Math.sin(t), 3)));
 57         float y = (float) (-heartRadio * (13 * Math.cos(t) - 5 * Math.cos(2 * t) - 2 * Math.cos(3 * t) - Math.cos(4 * t)));
 58
 59         return new Point(offsetX + (int) x, offsetY + (int) y);
 60     }
 61
 62
 63     //绘制列表里所有的花朵
 64     private void drawHeart() {
 65         canvas.drawRect(0, 0, width, height, backgroundPaint);
 66         for (Bloom b : blooms) {
 67             b.draw(canvas);
 68         }
 69         Canvas c = surfaceHolder.lockCanvas();
 70
 71         c.drawBitmap(bm, 0, 0, null);
 72
 73         surfaceHolder.unlockCanvasAndPost(c);
 74
 75     }
 76
 77     public void reDraw() {
 78         blooms.clear();
 79
 80
 81         drawOnNewThread();
 82     }
 83
 84     @Override
 85     public void draw(Canvas canvas) {
 86         super.draw(canvas);
 87
 88     }
 89
 90     //开启一个新线程绘制
 91     private void drawOnNewThread() {
 92         new Thread() {
 93             @Override
 94             public void run() {
 95                 if (isDrawing) return;
 96                 isDrawing = true;
 97
 98                 float angle = 10;
 99                 while (true) {
100
101                     Bloom bloom = getBloom(angle);
102                     if (bloom != null) {
103                         blooms.add(bloom);
104                     }
105                     if (angle >= 30) {
106                         break;
107                     } else {
108                         angle += 0.2;
109                     }
110                     drawHeart();
111                     try {
112                         sleep(20);
113                     } catch (InterruptedException e) {
114                         e.printStackTrace();
115                     }
116                 }
117                 isDrawing = false;
118             }
119         }.start();
120     }
121
122
123     private Bloom getBloom(float angle) {
124
125         Point p = getHeartPoint(angle);
126
127         boolean draw = true;
128         /**循环比较新的坐标位置是否可以创建花朵,
129          * 为了防止花朵太密集
130          * */
131         for (int i = 0; i < blooms.size(); i++) {
132
133             Bloom b = blooms.get(i);
134             Point bp = b.getPoint();
135             float distance = (float) Math.sqrt(Math.pow(p.x - bp.x, 2) + Math.pow(p.y - bp.y, 2));
136             if (distance < Garden.Options.maxBloomRadius * 1.5) {
137                 draw = false;
138                 break;
139             }
140         }
141         //如果位置间距满足要求,就在该位置创建花朵并将花朵放入列表
142         if (draw) {
143             Bloom bloom = garden.createRandomBloom(p.x, p.y);
144             return bloom;
145         }
146         return null;
147     }
148
149
150     @Override
151     public void surfaceCreated(SurfaceHolder holder) {
152
153
154     }
155
156     @Override
157     public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
158
159         this.width = width;
160         this.height = height;
161         //我的手机宽度像素是1080,发现参数设置为30比较合适,这里根据不同的宽度动态调整参数
162         heartRadio = width * 30 / 1080;
163
164         offsetX = width / 2;
165         offsetY = height / 2 - 55;
166         bm = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
167         canvas = new Canvas(bm);
168         drawOnNewThread();
169     }
170
171     @Override
172     public void surfaceDestroyed(SurfaceHolder holder) {
173
174     }
175 }

View Code

还有两个比较重要的工具类
Point.java保存点信息,或者说是向量信息。包含向量的基本运算。

 1 package com.hc.testheart;
 2
 3 /**
 4  * Package com.hc.testheart
 5  * Created by HuaChao on 2016/5/25.
 6  */
 7 public class Point {
 8
 9     public int x;
10     public int y;
11
12     public Point(int x, int y) {
13         this.x = x;
14         this.y = y;
15     }
16
17     //旋转
18     public Point rotate(float theta) {
19         int x = this.x;
20         int y = this.y;
21         this.x = (int) (Math.cos(theta) * x - Math.sin(theta) * y);
22         this.y = (int) (Math.sin(theta) * x + Math.cos(theta) * y);
23         return this;
24     }
25
26     //乘以一个常数
27     public Point mult(float f) {
28         this.x *= f;
29         this.y *= f;
30         return this;
31     }
32
33     //复制
34     public Point clone() {
35         return new Point(this.x, this.y);
36     }
37
38     //该点与圆心距离
39     public float length() {
40         return (float) Math.sqrt(this.x * this.x + this.y * this.y);
41     }
42
43     //向量相减
44     public Point subtract(Point p) {
45         this.x -= p.x;
46         this.y -= p.y;
47         return this;
48     }
49
50     //向量相加
51     public Point add(Point p) {
52         this.x += p.x;
53         this.y += p.y;
54         return this;
55     }
56
57     public Point set(int x, int y) {
58         this.x = x;
59         this.y = y;
60         return this;
61     }
62 }

View Code

工具类MyUtil.java主要是产生随机数、颜色等

 1 package com.hc.testheart;
 2
 3 import android.graphics.Color;
 4
 5 /**
 6  * Package com.example.administrator.testrecyclerview
 7  * Created by HuaChao on 2016/5/25.
 8  */
 9 public class MyUtil {
10
11     public static float circle = (float) (2 * Math.PI);
12
13     public static int rgba(int r, int g, int b, int a) {
14         return Color.argb(a, r, g, b);
15     }
16
17     public static int randomInt(int min, int max) {
18         return (int) Math.floor(Math.random() * (max - min + 1)) + min;
19     }
20
21     public static float random(float min, float max) {
22         return (float) (Math.random() * (max - min) + min);
23     }
24
25     //产生随机的argb颜色
26     public static int randomrgba(int rmin, int rmax, int gmin, int gmax, int bmin, int bmax, int a) {
27         int r = Math.round(random(rmin, rmax));
28         int g = Math.round(random(gmin, gmax));
29         int b = Math.round(random(bmin, bmax));
30         int limit = 5;
31         if (Math.abs(r - g) <= limit && Math.abs(g - b) <= limit && Math.abs(b - r) <= limit) {
32             return rgba(rmin, rmax, gmin, gmax);
33         } else {
34             return rgba(r, g, b, a);
35         }
36     }
37
38     //角度转弧度
39     public static float degrad(float angle) {
40         return circle / 360 * angle;
41     }
42 }

View Code

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

转载于:https://www.cnblogs.com/Sharley/p/5783342.html

浪漫桃心的Android表白程序相关推荐

  1. 打造浪漫的Android表白程序

    几年前,看到过有个牛人用HTML5绘制了浪漫的爱心表白动画.地址在这:浪漫程序员 HTML5爱心表白动画.发现原来程序员也是可以很浪--漫-..的(PS:刚过520,被妹子骂不够浪漫).那么在Andr ...

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

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

  3. android程序表白,几条曲线构建Android表白程序

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

  4. C语言和图形界面编程打造——浪漫的表白程序

    谁说程序员不浪漫???来来来一起打造一个浪漫的表白程序,配上好听的音乐,瞬间高大上. 下面,发下代码吧,昨天的愤怒的小鸟推箱子版本的没发,今天发这个,,,简单的C语言和easyx一起打造浪漫的程序附带 ...

  5. c++语言表白超炫图形_C/C++图形界面编程打造——浪漫的表白程序!

    谁说程序员不浪漫???来来来一起打造一个浪漫的表白程序,配上好听的音乐,瞬间高大上. 下面,发下代码吧,昨天的愤怒的小鸟推箱子版本的没发,今天发这个,,,简单的C语言和easyx一起打造浪漫的程序附带 ...

  6. c语言代码表白_程序员教你表白:C/C++打造浪漫表白程序,找女朋友从现在开始...

    谁说C/C++程序员不浪漫???来来来一起打造一个浪漫的表白程序,配上好听的音乐,瞬间高大上的感觉. C语言是面向过程的,而C++是面向对象的 C和C++的区别: C是一个结构化语言,它的重点在于算法 ...

  7. 用 Python 实现浪漫表白程序

    作者 | 李秋键 出品 | CSDN(ID:CSDNnews) 相信很多小伙伴都曾在抖音.快手以及 B 站等平台刷到过表白程序,不论是各种的程序制作爱心或者是程序制作心动符号等,在各个平台很受欢迎.但 ...

  8. 【520表白】C语言开发《浪漫流星雨》表白程序,源码来了!

    今天是520告白节,特意为大家准备了我去年用C语言表白成功的浪漫表白流星雨项目源码,把这个发给你喜欢的女生,你还害怕520找不到对象,要一个人过吗? 话不多说,直接上效果: 开发环境:Visual S ...

  9. C/C++实现你的浪漫表白:浪漫流星雨表白程序,

    想要讨女朋友欢心也巩固自己所学的知识,各位小伙伴有自己的想法了吗?准备好想要怎样实施了吗?有什么美好的计划了吗?如果没有的话那么别慌,我知道,在座的各位肯定都是有自己的心仪的姑娘,那么今天就教大家一招 ...

最新文章

  1. vue点击增加class_Vuevbind动态绑定class
  2. 处理js事件时,获取键盘数字注意
  3. linux-``反引号
  4. Ask Me Anything #1 我是新晋CNCF TOC张磊,你有什么想问我的?
  5. Linux下c的进一步学习
  6. 数据结构和算法分析英语生词整理
  7. 025Python路--安装第三方模块
  8. 【代码源 Div1 - 109】#454. Minimum Or Spanning Tree(最小生成树,边权按位或,贪心,并查集) CF1624G
  9. css-flex 常见面试题
  10. vs 2008 xp 试用版 升级办法
  11. 记录一次es商品模糊查询
  12. JavaIO流实现文件传输
  13. java hsqldb数据库,HSQLDB数据库的使用
  14. IV值和WOE值的理解
  15. Navicat premium 导入Excel文件失败
  16. 在虚拟机中搭建设zookeeper集群配置
  17. 张曦予巴黎时装周儿童单元T台走秀演绎童真风采
  18. C语言初步-顺序结构-已知三边求三角形面积-海伦公式
  19. UNICODE汉字数据库
  20. 无标号有根仙人掌计数

热门文章

  1. 【COGS 1873】 [国家集训队2011]happiness(吴确) 最小割
  2. 写给嵌入式方向的某些同学 - 基于WINCE系统的程序开发[不完整版]
  3. (三)Omniglot Dataset介绍
  4. YOLO系列专题——YOLOv1理论篇
  5. 数据结构笔记(十三)-- 串的堆分配存储表示
  6. mfc gridctrl 设置某列自动伸长_三明桥梁智能张拉设备数控智能张拉系统全自动智能张拉系统...
  7. ubuntu lvs keepalived mysql_ubuntu下LVS + Keepalived 实现mysql高可用负载均衡实现
  8. OpenCV-Python实战(番外篇)——基于 Haar 级联的猫脸检测器
  9. phpcount数组报错_joomla中的PHP错误警告:count():参数必须是实现Countable的数组或对象-问答-阿里云开发者社区-阿里云...
  10. maven 内存不足_如何修复Maven内存不足问题