浪漫桃心的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,然后把这个心形线画出来吧!
@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;
}
}
运行结果如下:
绘制花瓣原理
我们想要的并不是简单绘制一个桃心线,要的是将花朵在桃心线上摆放。首先,得要知道怎么绘制花朵,而花朵是由一个个花瓣组成。因此绘制花朵的核心是绘制花瓣。绘制花瓣的原理是:3次贝塞尔曲线。三次贝塞尔曲线是由两个端点和两个控制点决定。假设花芯是一个圆,有n个花瓣,那么两个端点与花芯的圆心连线之间的夹角即为360/n。因此可以根据花瓣数量和花芯半径确定每个花瓣的位置。将两个端点与花芯的圆心连线的延长线分别确定另外两个控制点。通过随机生成花芯半径、每个花瓣的起始角以及随机确定延长线得到两个控制点,可以绘制一个随机的花朵。参数的改变如下图所示:
将花朵绘制到桃心线上
一大波代码来袭
首先定义花瓣类Petal:
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);
} }
花瓣类是最重要的类,因为真正绘制在屏幕上的是一个个小花瓣。每个花朵包含一系列花瓣,花朵类Bloom如下:
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 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;
}
}
接下来是花园类Garden,主要用于创建花朵以及一些相关配置:
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
}
}
考虑到刷新的比较频繁,选择使用SurfaceView作为显示视图。自定义一个HeartView继承SurfaceView。代码如下:
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 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) { }
}
还有两个比较重要的工具类
Point.java保存点信息,或者说是向量信息。包含向量的基本运算。
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;
}
}
工具类MyUtil.java主要是产生随机数、颜色等
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;
}
}
好了,目前为止,就可以得到上面的效果了。
浪漫桃心的Android表白程序的更多相关文章
- 表白程序源代码,android
弄了一个表白程序,还是不错的,内容能够自己设置.并附上源代码:http://download.csdn.net/detail/a358763471/7803571 看下效果图吧.是动画的哦...
- 零基础入门C/C++实现你的浪漫表白:浪漫流星雨表白程序
想要讨女朋友欢心也巩固自己所学的知识,各位小伙伴有自己的想法了吗?准备好想要怎样实施了吗?有什么美好的计划了吗?如果没有的话那么别慌,我知道,在座的各位肯定都是有自己的心仪的姑娘,那么今天就教大家一招 ...
- android表白app
一.前言 马上就要520和521了,是不是还有像我一样的单身狗啊.我就知道有,所以这两天简单写了这个小程序(其实是替别人写的),虽然我并不会用去骗女孩子(因为最近太忙了,实习完之后要搞毕设,要搞论文啊 ...
- 与Android应用程序相关的文件目录都有哪些?(转载)
与Android应用程序相关的文件目录都有哪些? | 浏览:1312 | 更新:2014-09-28 19:43 | 标签:android 一.方法介绍: 每个Android应用程序都可以通过Co ...
- [转]Android应用程序框架思路整理
一.一般Android应用程序架构(Book,购彩,Market). 普通的应用程序由于只需要用到Android的联网与显示的功能,所以应用程序大体上是呈现为UI(Activities)与网络(Net ...
- android的程序运行数据存放在哪里?
Android应用开发中,给我们提供了5种数据的存储方式1 使用SharedPreferences存储数据2 文件存储数据3 SQLite数据库存储数据4 使用ContentProvider存储数据5 ...
- Android卸载程序之后跳转到指定的反馈页面
一个应用被用户卸载肯定是有理由的,而开发者却未必能得知这一重要的理由,毕竟用户很少会主动反馈建议,多半就是用得不爽就卸,如果能在被卸载后获取到用户的一些反馈,那对开发者进一步改进应用是非常有利的.目前 ...
- 使用 Eclipse PhoneGap 构建 Android 应用程序入门
Eclipse 是一种支持多种技术的开源集成开发环境 (IDE),但本文重点介绍 Java 支持,这也是 Android 应用程序的“母语”.Android 是 Google 发布的开源移动操作系统. ...
- IONIC 开发的Android应用程序签名(或重新签名)详解
完全通过DOS命令来完成apk签名 给apk签名一共要用到3个工具,或者说3个命令,分别是:keytool.jarsigner和zipalign,下面是对这3个工具的简单介绍: ...
随机推荐
- openerp 报表字段 report_rml_content_data
按需求修改一个报表,本想按照打印出来的报表内容搜索rml文件,找到需要修改的rml问加. 但是发现搜索不到,后来查询报表动作,确认对应的rml文件中,没有发现 “报表中” 的字串. 猜测可能是rml直 ...
- js动态加载脚本
最近公司的前端地图产品需要做一下模块划分,希望用户用到哪一块的功能再加载哪一块的模块,这样可以提高用户体验. 所以到处查资料研究js动态脚本的加载,不过真让人伤心啊!,网上几乎都是同一篇文章,4种方法 ...
- NandFlash
一.概述 1.NandFlash NAND结构能提供极高的单元密度,可以达到高存储密度,比如能达到256M,并且写入和擦除的速度也很快.应用NAND的困难在于flash的管理需要特殊的系统接口. 2. ...
- 转:2014年最酷的30个JavaScript库
原文来自于:http://www.gbtags.com/gb/share/3701.htm 使用JavaScript库将会使开发变的更简单,大部分JavaScript库提供的功能都是极好的,当我们在为 ...
- hadoop hdfs 命令行 设置文件夹大小的上限 quota:配额
>bin/hdfs dfs -put readme.txt /finance >bin/hdfs dfs -du -s /finance > /finance >bin/hdf ...
- 人在江湖飘,哪能不挨刀。CENTOS之后,UBUNTU,FEDORA都要安装起来作测试啊
还好,我们有VIRTUAL BOX.
- android-wear开发之定义布局
Android Wear使用跟手机一样的布局技术,但需要对特定情况进行设计.不要把手机的UI直接照搬过来.更多可查看:Android Wear Design Guidelines 当创建android ...
- Java native(转)
Java不是完美的,Java的不足除了体现在运行速度上要比传统的C++慢许多之外,Java无法直接访问到操作系统底层(如系统硬件等),为此Java使用native方法来扩展Java程序的功能. 可以将 ...
- pygame安装
进入这个网站 http://www.pygame.org/wiki/Compilation 可以选择不同系统的安装方法 其中ubuntu的安装命令是 #这是python .X #install dep ...
- uboot从SD卡烧写内核和文件系统
环境:ubuntu 13.04一.首先制作sd启动盘: 插入SD卡 sudo dd iflag=dsync oflag=dsync if=tiny210v2-uboot.binof=/dev/m ...