java浪漫之心代码_浪漫桃心的Android表白程序

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

e3a06ed86e47edefd0e115662a8be3e5.gif

生成心形线

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

桃心线极坐标方程式为:

x=16×sin3α

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

如果生成的桃心线不够大,可以吧x、y乘以一个常数,使之变大。考虑到大部分人都不愿去研究具体的数学问题,我们直接把前面HTML5的JS代码直接翻译成Java代码就好。代码如下:

public Point getHeartPoint(floatangle) {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 voidonDraw(Canvas canvas) {float angle = 10;while (angle < 180) {

Point p=getHeartPoint(angle);

canvas.drawPoint(p.x, p.y, paint);

angle= angle + 0.02f;

}

}

运行结果如下:

aebbd660969d9b6b7bd1c2a7cfc6662e.png

绘制花瓣原理

我们想要的并不是简单绘制一个桃心线,要的是将花朵在桃心线上摆放。首先,得要知道怎么绘制花朵,而花朵是由一个个花瓣组成。因此绘制花朵的核心是绘制花瓣。绘制花瓣的原理是:3次贝塞尔曲线。三次贝塞尔曲线是由两个端点和两个控制点决定。假设花芯是一个圆,有n个花瓣,那么两个端点与花芯的圆心连线之间的夹角即为360/n。因此可以根据花瓣数量和花芯半径确定每个花瓣的位置。将两个端点与花芯的圆心连线的延长线分别确定另外两个控制点。通过随机生成花芯半径、每个花瓣的起始角以及随机确定延长线得到两个控制点,可以绘制一个随机的花朵。参数的改变如下图所示:

fad81bea40210909f472659db00334d9.gif

将花朵绘制到桃心线上

一大波代码来袭

首先定义花瓣类Petal:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 packagecom.hc.testheart;2

3 importandroid.graphics.Canvas;4 importandroid.graphics.Paint;5 importandroid.graphics.Path;6

7 /**

8 * Package com.example.administrator.testrecyclerview9 * Created by HuaChao on 2016/5/25.10 */

11 public classPetal {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, floatgrowFactor) {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, intradius, 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 voiddraw(Point p, Canvas canvas) {44 if (!isFinished) {45

46 path = newPath();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如下:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 packagecom.hc.testheart;2

3 importandroid.graphics.Canvas;4

5 importjava.util.ArrayList;6

7 /**

8 * Package com.example.administrator.testrecyclerview9 * Created by HuaChao on 2016/5/25.10 */

11 public classBloom {12 private int color;//整个花朵的颜色

13 private Point point;//花芯圆心

14 private int radius; //花芯半径

15 private ArrayList petals;//用于保存花瓣

16

17 publicPoint getPoint() {18 returnpoint;19 }20

21

22 public Bloom(Point point, int radius, int color, intpetalCount) {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(newPetal(stretchA, stretchB, beginAngle, angle, color, growFactor));42 }43 }44

45 //绘制花朵

46 public voiddraw(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 intgetColor() {58 returncolor;59 }60 }

View Code

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

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 packagecom.hc.testheart;2

3 importjava.util.ArrayList;4

5 /**

6 * Package com.example.administrator.testrecyclerview7 * Created by HuaChao on 2016/5/24.8 */

9 public classGarden {10

11 //创建一个随机的花朵

12 public Bloom createRandomBloom(int x, inty) {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 returncreateBloom(x, y, radius, color, petalCount);20 }21

22 //创建花朵

23 public Bloom createBloom(int x, int y, int radius, int color, intpetalCount) {24 return new Bloom(newPoint(x, y), radius, color, petalCount);25 }26

27 static classOptions {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。代码如下:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 packagecom.hc.testheart;2

3 importandroid.content.Context;4 importandroid.graphics.Bitmap;5 importandroid.graphics.Canvas;6 importandroid.graphics.Color;7 importandroid.graphics.Paint;8 importandroid.util.AttributeSet;9 importandroid.view.SurfaceHolder;10 importandroid.view.SurfaceView;11

12 importjava.util.ArrayList;13

14 /**

15 * Package com.hc.testheart16 * Created by HuaChao on 2016/5/25.17 */

18 public class HeartView extends SurfaceView implementsSurfaceHolder.Callback {19 SurfaceHolder surfaceHolder;20 intoffsetX;21 intoffsetY;22 privateGarden garden;23 private intwidth;24 private intheight;25 privatePaint backgroundPaint;26 private boolean isDrawing = false;27 privateBitmap bm;28 privateCanvas canvas;29 private int heartRadio = 1;30

31 publicHeartView(Context context) {32 super(context);33 init();34 }35

36 publicHeartView(Context context, AttributeSet attrs) {37 super(context, attrs);38 init();39 }40

41

42 private voidinit() {43 surfaceHolder =getHolder();44 surfaceHolder.addCallback(this);45 garden = newGarden();46 backgroundPaint = newPaint();47 backgroundPaint.setColor(Color.rgb(0xff, 0xff, 0xe0));48

49

50 }51

52 ArrayList blooms = new ArrayList<>();53

54 public Point getHeartPoint(floatangle) {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 voiddrawHeart() {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 voidreDraw() {78 blooms.clear();79

80

81 drawOnNewThread();82 }83

84 @Override85 public voiddraw(Canvas canvas) {86 super.draw(canvas);87

88 }89

90 //开启一个新线程绘制

91 private voiddrawOnNewThread() {92 newThread() {93 @Override94 public voidrun() {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(floatangle) {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 returnbloom;145 }146 return null;147 }148

149

150 @Override151 public voidsurfaceCreated(SurfaceHolder holder) {152

153

154 }155

156 @Override157 public void surfaceChanged(SurfaceHolder holder, int format, int width, intheight) {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 = newCanvas(bm);168 drawOnNewThread();169 }170

171 @Override172 public voidsurfaceDestroyed(SurfaceHolder holder) {173

174 }175 }

View Code

还有两个比较重要的工具类

Point.java保存点信息,或者说是向量信息。包含向量的基本运算。

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 packagecom.hc.testheart;2

3 /**

4 * Package com.hc.testheart5 * Created by HuaChao on 2016/5/25.6 */

7 public classPoint {8

9 public intx;10 public inty;11

12 public Point(int x, inty) {13 this.x =x;14 this.y =y;15 }16

17 //旋转

18 public Point rotate(floattheta) {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(floatf) {28 this.x *=f;29 this.y *=f;30 return this;31 }32

33 //复制

34 publicPoint clone() {35 return new Point(this.x, this.y);36 }37

38 //该点与圆心距离

39 public floatlength() {40 return (float) Math.sqrt(this.x * this.x + this.y * this.y);41 }42

43 //向量相减

44 publicPoint subtract(Point p) {45 this.x -=p.x;46 this.y -=p.y;47 return this;48 }49

50 //向量相加

51 publicPoint add(Point p) {52 this.x +=p.x;53 this.y +=p.y;54 return this;55 }56

57 public Point set(int x, inty) {58 this.x =x;59 this.y =y;60 return this;61 }62 }

View Code

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

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 packagecom.hc.testheart;2

3 importandroid.graphics.Color;4

5 /**

6 * Package com.example.administrator.testrecyclerview7 * Created by HuaChao on 2016/5/25.8 */

9 public classMyUtil {10

11 public static float circle = (float) (2 *Math.PI);12

13 public static int rgba(int r, int g, int b, inta) {14 returnColor.argb(a, r, g, b);15 }16

17 public static int randomInt(int min, intmax) {18 return (int) Math.floor(Math.random() * (max - min + 1)) +min;19 }20

21 public static float random(float min, floatmax) {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, inta) {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 returnrgba(rmin, rmax, gmin, gmax);33 } else{34 returnrgba(r, g, b, a);35 }36 }37

38 //角度转弧度

39 public static float degrad(floatangle) {40 return circle / 360 *angle;41 }42 }

View Code

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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值