# 打造浪漫的Android表白程序

## 生成心形线

x=16×sin3α

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

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 = width / 2;
offsetY = height / 2 - 55;

  @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;
}
}

## 将花朵绘制到桃心线上

 package com.hc.testheart;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
/**
* Created by HuaChao on 2016/5/25.
*/
public class Petal {
private float stretchA;//第一个控制点延长线倍数
private float stretchB;//第二个控制点延长线倍数
private float startAngle;//起始旋转角，用于确定第一个端点
private float angle;//两条线之间夹角，由起始旋转角和夹角可以确定第二个端点
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) {
this.radius += growFactor; // / 10;
} else {
isFinished = true;
}
this.draw(p, canvas);
}
//绘制花瓣，参数p是花芯的圆心的坐标
private void draw(Point p, Canvas canvas) {
if (!isFinished) {
path = new Path();
Point v1 = new Point(0, 3).rotate(MyUtil.degrad(this.startAngle));
//第二个端点
//延长线，分别确定两个控制点
Point v3 = t.clone().mult(this.stretchA);
Point v4 = v2.clone().mult(this.stretchB);
//由于圆心在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);
}
}


package com.hc.testheart;
import android.graphics.Canvas;
import java.util.ArrayList;
/**
* Created by HuaChao on 2016/5/25.
*/
public class Bloom {
private int color;//整个花朵的颜色
private Point point;//花芯圆心
private ArrayList<Petal> petals;//用于保存花瓣
public Point getPoint() {
return point;
}
public Bloom(Point point, int radius, int color, int petalCount) {
this.point = point;
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);
//渲染每朵花朵
}
}
public int getColor() {
return color;
}
}


package com.hc.testheart;
import java.util.ArrayList;
/**
* Created by HuaChao on 2016/5/24.
*/
public class Garden {
//创建一个随机的花朵
public Bloom createRandomBloom(int x, int y) {
//创建一个随机的花朵半径
//创建一个随机的花朵颜色
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
}
}


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;
public HeartView(Context context) {
super(context);
init();
}
public HeartView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
surfaceHolder = getHolder();
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();
}
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
}
//开启一个新线程绘制
@Override
public void run() {
if (isDrawing) return;
isDrawing = true;
float angle = 10;
while (true) {
Bloom bloom = getBloom(angle);
if (bloom != null) {
}
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);
}
@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;
}
//向量相加
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;
}
}


package com.hc.testheart;
import android.graphics.Color;
/**
* 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;
}
}


|
28天前
|

Android面经分享，失业两个月，五一节前拿到Offer，设计思想与代码质量优化+程序性能优化+开发效率优化
Android面经分享，失业两个月，五一节前拿到Offer，设计思想与代码质量优化+程序性能优化+开发效率优化
20 2
|
30天前
|
Linux Android开发

22 0
|
11月前
|

Android流媒体开发之路二:NDK C++开发Android端RTMP直播推流程序
Android流媒体开发之路二:NDK C++开发Android端RTMP直播推流程序
234 0
|
8月前
|

《移动互联网技术》 第四章 移动应用开发： Android Studio开发环境的使用方法：建立工程，编写源程序，编译链接，安装模拟器，通过模拟器运行和调试程序
《移动互联网技术》 第四章 移动应用开发： Android Studio开发环境的使用方法：建立工程，编写源程序，编译链接，安装模拟器，通过模拟器运行和调试程序
106 0
|
8月前
|
Java Android开发 开发者
169 0
|
XML Java Android开发
Android Studio开发APP启动程序时开屏简单动画效果快速有效解决方案
Android Studio开发APP启动程序时开屏简单动画效果快速有效解决方案
1413 0
|
11天前
|

05. 【Android教程】Android 程序签名打包
05. 【Android教程】Android 程序签名打包
12 1
|
30天前
|
XML Java Android开发

11 2
|
30天前
|
Android开发
Android修改默认system/bin/下可执行程序拥有者和权限，使用实例，只有root和系统app权限才能执行某个命令。
Android修改默认system/bin/下可执行程序拥有者和权限，使用实例，只有root和系统app权限才能执行某个命令。 【5月更文挑战第2天】
29 0
|
30天前
|
Linux 测试技术 Android开发

25 0