打造浪漫的Android表白程序
来源:互联网 发布:台湾政治知乎 编辑:程序博客网 时间:2024/04/30 20:14
几年前,看到过有个牛人用HTML5绘制了浪漫的爱心表白动画。地址在这:浪漫程序员 HTML5爱心表白动画。发现原来程序员也是可以很浪……漫…..的(PS:刚过520,被妹子骂不够浪漫)。那么在Android怎么打造如此这个效果呢?参考了一下前面HTML5的算法,在Android中实现了类似的效果。先贴上最终效果图:
生成心形线
心形线的表达式可以参考:桃心线。里面对桃心线的表达式解析的挺好。可以通过使用极坐标的方式,传入角度和距离(常量)计算出对应的坐标点。其中距离是常量值,不需改变,变化的是角度。
桃心线极坐标方程式为:
x=16×sin3α y=13×cosα?5×cos2α?2×cos3α?cos4α
如果生成的桃心线不够大,可以吧x、y乘以一个常数,使之变大。考虑到大部分人都不愿去研究具体的数学问题,我们直接把前面HTML5的JS代码直接翻译成Java代码就好。代码如下:
<code
class
=
"language-java hljs "
>
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);
}</code>
其中offsetX和offsetY是偏移量。使用偏移量主要是为了能让心形线处于中央。offsetX和offsetY的值分别为:
<code
class
=
"language-java hljs "
> offsetX = width /
2
;
offsetY = height /
2
-
55
;</code>
通过这个函数,我们可以将角度从(0,180)变化,不断取点并画点将这个心形线显示出来。好了,我们自定义一个View,然后把这个心形线画出来吧!
<code
class
=
"language-java hljs "
>
@Override
protected
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;
}
}</code>
运行结果如下:
绘制花瓣原理
我们想要的并不是简单绘制一个桃心线,要的是将花朵在桃心线上摆放。首先,得要知道怎么绘制花朵,而花朵是由一个个花瓣组成。因此绘制花朵的核心是绘制花瓣。绘制花瓣的原理是:3次贝塞尔曲线。三次贝塞尔曲线是由两个端点和两个控制点决定。假设花芯是一个圆,有n个花瓣,那么两个端点与花芯的圆心连线之间的夹角即为360/n。因此可以根据花瓣数量和花芯半径确定每个花瓣的位置。将两个端点与花芯的圆心连线的延长线分别确定另外两个控制点。通过随机生成花芯半径、每个花瓣的起始角以及随机确定延长线得到两个控制点,可以绘制一个随机的花朵。参数的改变如下图所示:
将花朵绘制到桃心线上
一大波代码来袭
首先定义花瓣类Petal:
<code
class
=
"language-java hljs "
>
package
com.hc.testheart;
import
android.graphics.Canvas;
import
android.graphics.Paint;
import
android.graphics.Path;
/**
* Package com.example.administrator.testrecyclerview
* Created by HuaChao on 2016/5/25.
*/
public
class
Petal {
private
float
stretchA;
//第一个控制点延长线倍数
private
float
stretchB;
//第二个控制点延长线倍数
private
float
startAngle;
//起始旋转角,用于确定第一个端点
private
float
angle;
//两条线之间夹角,由起始旋转角和夹角可以确定第二个端点
private
int
radius =
2
;
//花芯的半径
private
float
growFactor;
//增长因子,花瓣是有开放的动画效果,这个参数决定花瓣展开速度
private
int
color;
//花瓣颜色
private
boolean
isFinished =
false
;
//花瓣是否绽放完成
private
Path path =
new
Path();
//用于保存三次贝塞尔曲线
private
Paint paint =
new
Paint();
//画笔
//构造函数,由花朵类调用
public
Petal(
float
stretchA,
float
stretchB,
float
startAngle,
float
angle,
int
color,
float
growFactor) {
this
.stretchA = stretchA;
this
.stretchB = stretchB;
this
.startAngle = startAngle;
this
.angle = angle;
this
.color = color;
this
.growFactor = growFactor;
paint.setColor(color);
}
//用于渲染花瓣,通过不断更改半径使得花瓣越来越大
public
void
render(Point p,
int
radius, Canvas canvas) {
if
(
this
.radius <= radius) {
this
.radius += growFactor;
// / 10;
}
else
{
isFinished =
true
;
}
this
.draw(p, canvas);
}
//绘制花瓣,参数p是花芯的圆心的坐标
private
void
draw(Point p, Canvas canvas) {
if
(!isFinished) {
path =
new
Path();
//将向量(0,radius)旋转起始角度,第一个控制点根据这个旋转后的向量计算
Point t =
new
Point(
0
,
this
.radius).rotate(MyUtil.degrad(
this
.startAngle));
//第一个端点,为了保证圆心不会随着radius增大而变大这里固定为3
Point v1 =
new
Point(
0
,
3
).rotate(MyUtil.degrad(
this
.startAngle));
//第二个端点
Point v2 = t.clone().rotate(MyUtil.degrad(
this
.angle));
//延长线,分别确定两个控制点
Point v3 = t.clone().mult(
this
.stretchA);
Point v4 = v2.clone().mult(
this
.stretchB);
//由于圆心在p点,因此,每个点要加圆心坐标点
v1.add(p);
v2.add(p);
v3.add(p);
v4.add(p);
path.moveTo(v1.x, v1.y);
//参数分别是:第一个控制点,第二个控制点,终点
path.cubicTo(v3.x, v3.y, v4.x, v4.y, v2.x, v2.y);
}
canvas.drawPath(path, paint);
}
}
</code>
花瓣类是最重要的类,因为真正绘制在屏幕上的是一个个小花瓣。每个花朵包含一系列花瓣,花朵类Bloom如下:
<code
class
=
"language-java hljs "
>
package
com.hc.testheart;
import
android.graphics.Canvas;
import
java.util.ArrayList;
/**
* Package com.example.administrator.testrecyclerview
* Created by HuaChao on 2016/5/25.
*/
public
class
Bloom {
private
int
color;
//整个花朵的颜色
private
Point point;
//花芯圆心
private
int
radius;
//花芯半径
private
ArrayList<petal> petals;
//用于保存花瓣
public
Point getPoint() {
return
point;
}
public
Bloom(Point point,
int
radius,
int
color,
int
petalCount) {
this
.point = point;
this
.radius = radius;
this
.color = color;
petals =
new
ArrayList<>(petalCount);
float
angle = 360f / petalCount;
int
startAngle = MyUtil.randomInt(
0
,
90
);
for
(
int
i =
0
; i < petalCount; i++) {
//随机产生第一个控制点的拉伸倍数
float
stretchA = MyUtil.random(Garden.Options.minPetalStretch, Garden.Options.maxPetalStretch);
//随机产生第二个控制地的拉伸倍数
float
stretchB = MyUtil.random(Garden.Options.minPetalStretch, Garden.Options.maxPetalStretch);
//计算每个花瓣的起始角度
int
beginAngle = startAngle + (
int
) (i * angle);
//随机产生每个花瓣的增长因子(即绽放速度)
float
growFactor = MyUtil.random(Garden.Options.minGrowFactor, Garden.Options.maxGrowFactor);
//创建一个花瓣,并添加到花瓣列表中
this
.petals.add(
new
Petal(stretchA, stretchB, beginAngle, angle, color, growFactor));
}
}
//绘制花朵
public
void
draw(Canvas canvas) {
Petal p;
for
(
int
i =
0
; i <
this
.petals.size(); i++) {
p = petals.get(i);
//渲染每朵花朵
p.render(point,
this
.radius, canvas);
}
}
public
int
getColor() {
return
color;
}
}
</petal></code>
接下来是花园类Garden,主要用于创建花朵以及一些相关配置:
<code
class
=
"language-java hljs "
>
package
com.hc.testheart;
import
java.util.ArrayList;
/**
* Package com.example.administrator.testrecyclerview
* Created by HuaChao on 2016/5/24.
*/
public
class
Garden {
//创建一个随机的花朵
public
Bloom createRandomBloom(
int
x,
int
y) {
//创建一个随机的花朵半径
int
radius = MyUtil.randomInt(Options.minBloomRadius, Options.maxBloomRadius);
//创建一个随机的花朵颜色
int
color = MyUtil.randomrgba(Options.minRedColor, Options.maxRedColor, Options.minGreenColor, Options.maxGreenColor, Options.minBlueColor, Options.maxBlueColor, Options.opacity);
//创建随机的花朵中花瓣个数
int
petalCount = MyUtil.randomInt(Options.minPetalCount, Options.maxPetalCount);
return
createBloom(x, y, radius, color, petalCount);
}
//创建花朵
public
Bloom createBloom(
int
x,
int
y,
int
radius,
int
color,
int
petalCount) {
return
new
Bloom(
new
Point(x, y), radius, color, petalCount);
}
static
class
Options {
//用于控制产生随机花瓣个数范围
public
static
int
minPetalCount =
8
;
public
static
int
maxPetalCount =
15
;
//用于控制产生延长线倍数范围
public
static
float
minPetalStretch = 2f;
public
static
float
maxPetalStretch =
3
.5f;
//用于控制产生随机增长因子范围,增长因子决定花瓣绽放速度
public
static
float
minGrowFactor = 1f;
public
static
float
maxGrowFactor =
1
.1f;
//用于控制产生花朵半径随机数范围
public
static
int
minBloomRadius =
8
;
public
static
int
maxBloomRadius =
10
;
//用于产生随机颜色
public
static
int
minRedColor =
128
;
public
static
int
maxRedColor =
255
;
public
static
int
minGreenColor =
0
;
public
static
int
maxGreenColor =
128
;
public
static
int
minBlueColor =
0
;
public
static
int
maxBlueColor =
128
;
//花瓣的透明度
public
static
int
opacity =
50
;
//0.1
}
}
</code>
考虑到刷新的比较频繁,选择使用SurfaceView作为显示视图。自定义一个HeartView继承SurfaceView。代码如下:
<code
class
=
"language-java hljs "
>
package
com.hc.testheart;
import
android.content.Context;
import
android.graphics.Bitmap;
import
android.graphics.Canvas;
import
android.graphics.Color;
import
android.graphics.Paint;
import
android.util.AttributeSet;
import
android.view.SurfaceHolder;
import
android.view.SurfaceView;
import
java.util.ArrayList;
/**
* Package com.hc.testheart
* Created by HuaChao on 2016/5/25.
*/
public
class
HeartView
extends
SurfaceView
implements
SurfaceHolder.Callback {
SurfaceHolder surfaceHolder;
int
offsetX;
int
offsetY;
private
Garden garden;
private
int
width;
private
int
height;
private
Paint backgroundPaint;
private
boolean
isDrawing =
false
;
private
Bitmap bm;
private
Canvas canvas;
private
int
heartRadio =
1
;
public
HeartView(Context context) {
super
(context);
init();
}
public
HeartView(Context context, AttributeSet attrs) {
super
(context, attrs);
init();
}
private
void
init() {
surfaceHolder = getHolder();
surfaceHolder.addCallback(
this
);
garden =
new
Garden();
backgroundPaint =
new
Paint();
backgroundPaint.setColor(Color.rgb(
0xff
,
0xff
,
0xe0
));
}
ArrayList<bloom> blooms =
new
ArrayList<>();
public
Point getHeartPoint(
float
angle) {
float
t = (
float
) (angle / Math.PI);
float
x = (
float
) (heartRadio * (
16
* Math.pow(Math.sin(t),
3
)));
float
y = (
float
) (-heartRadio * (
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);
}
//绘制列表里所有的花朵
private
void
drawHeart() {
canvas.drawRect(
0
,
0
, width, height, backgroundPaint);
for
(Bloom b : blooms) {
b.draw(canvas);
}
Canvas c = surfaceHolder.lockCanvas();
c.drawBitmap(bm,
0
,
0
,
null
);
surfaceHolder.unlockCanvasAndPost(c);
}
public
void
reDraw() {
blooms.clear();
drawOnNewThread();
}
@Override
public
void
draw(Canvas canvas) {
super
.draw(canvas);
}
//开启一个新线程绘制
private
void
drawOnNewThread() {
new
Thread() {
@Override
public
void
run() {
if
(isDrawing)
return
;
isDrawing =
true
;
float
angle =
10
;
while
(
true
) {
Bloom bloom = getBloom(angle);
if
(bloom !=
null
) {
blooms.add(bloom);
}
if
(angle >=
30
) {
break
;
}
else
{
angle +=
0.2
;
}
drawHeart();
try
{
sleep(
20
);
}
catch
(InterruptedException e) {
e.printStackTrace();
}
}
isDrawing =
false
;
}
}.start();
}
private
Bloom getBloom(
float
angle) {
Point p = getHeartPoint(angle);
boolean
draw =
true
;
/**循环比较新的坐标位置是否可以创建花朵,
* 为了防止花朵太密集
* */
for
(
int
i =
0
; i < blooms.size(); i++) {
Bloom b = blooms.get(i);
Point bp = b.getPoint();
float
distance = (
float
) Math.sqrt(Math.pow(p.x - bp.x,
2
) + Math.pow(p.y - bp.y,
2
));
if
(distance < Garden.Options.maxBloomRadius *
1.5
) {
draw =
false
;
break
;
}
}
//如果位置间距满足要求,就在该位置创建花朵并将花朵放入列表
if
(draw) {
Bloom bloom = garden.createRandomBloom(p.x, p.y);
return
bloom;
}
return
null
;
}
@Override
public
void
surfaceCreated(SurfaceHolder holder) {
}
@Override
public
void
surfaceChanged(SurfaceHolder holder,
int
format,
int
width,
int
height) {
this
.width = width;
this
.height = height;
//我的手机宽度像素是1080,发现参数设置为30比较合适,这里根据不同的宽度动态调整参数
heartRadio = width *
30
/
1080
;
offsetX = width /
2
;
offsetY = height /
2
-
55
;
bm = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
canvas =
new
Canvas(bm);
drawOnNewThread();
}
@Override
public
void
surfaceDestroyed(SurfaceHolder holder) {
}
}
</bloom></code>
还有两个比较重要的工具类
Point.java保存点信息,或者说是向量信息。包含向量的基本运算。
<code
class
=
"language-java hljs "
>
package
com.hc.testheart;
/**
* Package com.hc.testheart
* Created by HuaChao on 2016/5/25.
*/
public
class
Point {
public
int
x;
public
int
y;
public
Point(
int
x,
int
y) {
this
.x = x;
this
.y = y;
}
//旋转
public
Point rotate(
float
theta) {
int
x =
this
.x;
int
y =
this
.y;
this
.x = (
int
) (Math.cos(theta) * x - Math.sin(theta) * y);
this
.y = (
int
) (Math.sin(theta) * x + Math.cos(theta) * y);
return
this
;
}
//乘以一个常数
public
Point mult(
float
f) {
this
.x *= f;
this
.y *= f;
return
this
;
}
//复制
public
Point clone() {
return
new
Point(
this
.x,
this
.y);
}
//该点与圆心距离
public
float
length() {
return
(
float
) Math.sqrt(
this
.x *
this
.x +
this
.y *
this
.y);
}
//向量相减
public
Point subtract(Point p) {
this
.x -= p.x;
this
.y -= p.y;
return
this
;
}
//向量相加
public
Point add(Point p) {
this
.x += p.x;
this
.y += p.y;
return
this
;
}
public
Point set(
int
x,
int
y) {
this
.x = x;
this
.y = y;
return
this
;
}
}
</code>
工具类MyUtil.java主要是产生随机数、颜色等
<code
class
=
"language-java hljs "
>
package
com.hc.testheart;
import
android.graphics.Color;
/**
* Package com.example.administrator.testrecyclerview
* Created by HuaChao on 2016/5/25.
*/
public
class
MyUtil {
public
static
float
circle = (
float
) (
2
* Math.PI);
public
static
int
rgba(
int
r,
int
g,
int
b,
int
a) {
return
Color.argb(a, r, g, b);
}
public
static
int
randomInt(
int
min,
int
max) {
return
(
int
) Math.floor(Math.random() * (max - min +
1
)) + min;
}
public
static
float
random(
float
min,
float
max) {
return
(
float
) (Math.random() * (max - min) + min);
}
//产生随机的argb颜色
public
static
int
randomrgba(
int
rmin,
int
rmax,
int
gmin,
int
gmax,
int
bmin,
int
bmax,
int
a) {
int
r = Math.round(random(rmin, rmax));
int
g = Math.round(random(gmin, gmax));
int
b = Math.round(random(bmin, bmax));
int
limit =
5
;
if
(Math.abs(r - g) <= limit && Math.abs(g - b) <= limit && Math.abs(b - r) <= limit) {
return
rgba(rmin, rmax, gmin, gmax);
}
else
{
return
rgba(r, g, b, a);
}
}
//角度转弧度
public
static
float
degrad(
float
angle) {
return
circle /
360
* angle;
}
}
</code>
好了,目前为止,就可以得到上面的效果了。
- 打造浪漫的Android表白程序
- 打造浪漫的Android表白程序
- 打造浪漫的Android表白程序
- 打造浪漫的Android表白程序
- 程序员也是会浪漫的->打造浪漫的Android表白程序
- 程序员也是会浪漫的->打造浪漫的Android表白程序
- C语言打造表白程序——教你做一个浪漫的程序员
- 程序猿的浪漫之二进制表白篇
- 理工科的表白多浪漫
- 表白程序源码,android
- html5表白神器开发,做一个浪漫的程序员
- 含蓄表白的C程序
- 转载的动态表白程序
- 【云计算的1024种玩法】第1招:制作一个浪漫的表白网页
- 【云计算的1024种玩法】第1招:制作一个浪漫的表白网页
- c语言:浪漫的putchar(),用数字向女朋友表白吧(ps:单身汪勿入)
- 程序员也懂浪漫,JQuery实现表白。
- 不浪漫的浪漫
- Learnning Dlib(二) Create a DlibTest Project
- android:clipToPadding和android:clipChildren
- 工作随笔——贰(关于字符串非空判断与数据库中日期时间问题)
- Spring Boot下Druid连接池的使用配置分析
- 屏幕旋转
- 打造浪漫的Android表白程序
- 第一课(试听)
- hql语句基础的增删改查
- 阿里云 OSS+CDN
- Git命令详解
- json解析 Code=3840问题
- node.js使用记录
- Qt之界面换肤
- 阿里云Linux系统挂载数据盘