浪漫桃心的Android表白程序
本文转载于 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表白程序相关推荐
- 打造浪漫的Android表白程序
几年前,看到过有个牛人用HTML5绘制了浪漫的爱心表白动画.地址在这:浪漫程序员 HTML5爱心表白动画.发现原来程序员也是可以很浪--漫-..的(PS:刚过520,被妹子骂不够浪漫).那么在Andr ...
- 几条曲线构建Android表白程序
每年的情人节和七夕,甜蜜与痛苦的日子,做点什么好呢? 写诗画画送礼物,逛街吃饭看电影? 作为搬砖爱好者,写个表白脚本或者动画什么的吧. 想起之前看到的一段H5动画,在Android平台"临摹 ...
- android程序表白,几条曲线构建Android表白程序
每年的情人节和七夕,做点什么好呢? 写诗画画送礼物,逛街吃饭看电影? 作为搬砖爱好者,写个表白脚本或者动画什么的吧. 想起之前看到的一段H5动画,在Android平台"临摹"了一遍 ...
- C语言和图形界面编程打造——浪漫的表白程序
谁说程序员不浪漫???来来来一起打造一个浪漫的表白程序,配上好听的音乐,瞬间高大上. 下面,发下代码吧,昨天的愤怒的小鸟推箱子版本的没发,今天发这个,,,简单的C语言和easyx一起打造浪漫的程序附带 ...
- c++语言表白超炫图形_C/C++图形界面编程打造——浪漫的表白程序!
谁说程序员不浪漫???来来来一起打造一个浪漫的表白程序,配上好听的音乐,瞬间高大上. 下面,发下代码吧,昨天的愤怒的小鸟推箱子版本的没发,今天发这个,,,简单的C语言和easyx一起打造浪漫的程序附带 ...
- c语言代码表白_程序员教你表白:C/C++打造浪漫表白程序,找女朋友从现在开始...
谁说C/C++程序员不浪漫???来来来一起打造一个浪漫的表白程序,配上好听的音乐,瞬间高大上的感觉. C语言是面向过程的,而C++是面向对象的 C和C++的区别: C是一个结构化语言,它的重点在于算法 ...
- 用 Python 实现浪漫表白程序
作者 | 李秋键 出品 | CSDN(ID:CSDNnews) 相信很多小伙伴都曾在抖音.快手以及 B 站等平台刷到过表白程序,不论是各种的程序制作爱心或者是程序制作心动符号等,在各个平台很受欢迎.但 ...
- 【520表白】C语言开发《浪漫流星雨》表白程序,源码来了!
今天是520告白节,特意为大家准备了我去年用C语言表白成功的浪漫表白流星雨项目源码,把这个发给你喜欢的女生,你还害怕520找不到对象,要一个人过吗? 话不多说,直接上效果: 开发环境:Visual S ...
- C/C++实现你的浪漫表白:浪漫流星雨表白程序,
想要讨女朋友欢心也巩固自己所学的知识,各位小伙伴有自己的想法了吗?准备好想要怎样实施了吗?有什么美好的计划了吗?如果没有的话那么别慌,我知道,在座的各位肯定都是有自己的心仪的姑娘,那么今天就教大家一招 ...
最新文章
- vue点击增加class_Vuevbind动态绑定class
- 处理js事件时,获取键盘数字注意
- linux-``反引号
- Ask Me Anything #1 我是新晋CNCF TOC张磊,你有什么想问我的?
- Linux下c的进一步学习
- 数据结构和算法分析英语生词整理
- 025Python路--安装第三方模块
- 【代码源 Div1 - 109】#454. Minimum Or Spanning Tree(最小生成树,边权按位或,贪心,并查集) CF1624G
- css-flex 常见面试题
- vs 2008 xp 试用版 升级办法
- 记录一次es商品模糊查询
- JavaIO流实现文件传输
- java hsqldb数据库,HSQLDB数据库的使用
- IV值和WOE值的理解
- Navicat premium 导入Excel文件失败
- 在虚拟机中搭建设zookeeper集群配置
- 张曦予巴黎时装周儿童单元T台走秀演绎童真风采
- C语言初步-顺序结构-已知三边求三角形面积-海伦公式
- UNICODE汉字数据库
- 无标号有根仙人掌计数
热门文章
- 【COGS 1873】 [国家集训队2011]happiness(吴确) 最小割
- 写给嵌入式方向的某些同学 - 基于WINCE系统的程序开发[不完整版]
- (三)Omniglot Dataset介绍
- YOLO系列专题——YOLOv1理论篇
- 数据结构笔记(十三)-- 串的堆分配存储表示
- mfc gridctrl 设置某列自动伸长_三明桥梁智能张拉设备数控智能张拉系统全自动智能张拉系统...
- ubuntu lvs keepalived mysql_ubuntu下LVS + Keepalived 实现mysql高可用负载均衡实现
- OpenCV-Python实战(番外篇)——基于 Haar 级联的猫脸检测器
- phpcount数组报错_joomla中的PHP错误警告:count():参数必须是实现Countable的数组或对象-问答-阿里云开发者社区-阿里云...
- maven 内存不足_如何修复Maven内存不足问题