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

生成心形线

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

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

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

?
1
2
3
4
5
6
<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的值分别为:

?
1
2
<code class="language-java hljs "> offsetX = width / 2;
 offsetY = height / 2 - 55;</code>

通过这个函数,我们可以将角度从(0,180)变化,不断取点并画点将这个心形线显示出来。好了,我们自定义一个View,然后把这个心形线画出来吧!

?
1
2
3
4
5
6
7
8
9
<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:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
<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如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
<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,主要用于创建花朵以及一些相关配置:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<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。代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
<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保存点信息,或者说是向量信息。包含向量的基本运算。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
<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主要是产生随机数、颜色等

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<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表白程序相关推荐

  1. 浪漫桃心的Android表白程序

    本文转载于  huachao1001的专栏 几年前,看到过有个牛人用HTML5绘制了浪漫的爱心表白动画.地址在这:浪漫程序员 HTML5爱心表白动画.发现原来程序员也是可以很浪--漫-..的.那么在A ...

  2. 【520表白】C语言开发《浪漫流星雨》表白程序,源码来了!

    今天是520告白节,特意为大家准备了我去年用C语言表白成功的浪漫表白流星雨项目源码,把这个发给你喜欢的女生,你还害怕520找不到对象,要一个人过吗? 话不多说,直接上效果: 开发环境:Visual S ...

  3. 几条曲线构建Android表白程序

    每年的情人节和七夕,甜蜜与痛苦的日子,做点什么好呢? 写诗画画送礼物,逛街吃饭看电影? 作为搬砖爱好者,写个表白脚本或者动画什么的吧. 想起之前看到的一段H5动画,在Android平台"临摹 ...

  4. android程序表白,几条曲线构建Android表白程序

    每年的情人节和七夕,做点什么好呢? 写诗画画送礼物,逛街吃饭看电影? 作为搬砖爱好者,写个表白脚本或者动画什么的吧. 想起之前看到的一段H5动画,在Android平台"临摹"了一遍 ...

  5. C语言和图形界面编程打造——浪漫的表白程序

    谁说程序员不浪漫???来来来一起打造一个浪漫的表白程序,配上好听的音乐,瞬间高大上. 下面,发下代码吧,昨天的愤怒的小鸟推箱子版本的没发,今天发这个,,,简单的C语言和easyx一起打造浪漫的程序附带 ...

  6. c++语言表白超炫图形_C/C++图形界面编程打造——浪漫的表白程序!

    谁说程序员不浪漫???来来来一起打造一个浪漫的表白程序,配上好听的音乐,瞬间高大上. 下面,发下代码吧,昨天的愤怒的小鸟推箱子版本的没发,今天发这个,,,简单的C语言和easyx一起打造浪漫的程序附带 ...

  7. c语言代码表白_程序员教你表白:C/C++打造浪漫表白程序,找女朋友从现在开始...

    谁说C/C++程序员不浪漫???来来来一起打造一个浪漫的表白程序,配上好听的音乐,瞬间高大上的感觉. C语言是面向过程的,而C++是面向对象的 C和C++的区别: C是一个结构化语言,它的重点在于算法 ...

  8. python程序 爱意_情人节来袭,Python打造最强表白程序,甜蜜暴击

    原标题:情人节来袭,Python打造最强表白程序,甜蜜暴击 一年一度的七夕又来了,有对象的在享受甜蜜的烦恼,没对象的该干嘛就干嘛去! 没对象的怎么表白女生?有对象的怎么给对方甜蜜惊喜?不用急,小编这就 ...

  9. 浪漫的html表白源代码_Love:程序猿的方式【情人节amp;520—我爱你】动画加音效 → 那些年最浪漫的表白(帮你得到你的她)...

    Love:程序猿的方式~[情人节&520-我爱你]~动画加音效 → 那些年最浪漫的表白(帮你得到你的她) 导读 本文章代码的实现,主要参考GitHub(wuxia2001和hackerzhou ...

最新文章

  1. 内存泄漏检测工具VLD在VS2010中的使用举例
  2. Ubuntu修改终端@前面的用户名
  3. 企业工作流程化重要吗?
  4. 使用GZIPInputStream和GZIPOutputStream压缩、解压java对象
  5. php iis mysql windows2003,Windows Server 2003 IIS6.0+PHP5(FastCGI)+MySQL5环境搭建教程 | 系统运维...
  6. IBM拓展云数据分析服务 用户来“做主”
  7. 复制内存时检测到可能的io争用条件_这篇高并发服务模型大科普,内部分享时被老大表扬了...
  8. 常见三种字符编码的区别:ASCII、Unicode、UTF-8
  9. 每天进步一点点《SVD学习》
  10. 飞步科技三篇论文入选CVPR 2022
  11. hadoop和spark的区别和联系
  12. python列表切片规则_Python 列表切片
  13. 解决eclipse报PermGen space内存溢出异常的问题
  14. Marvelous Designer 服装设计与模拟
  15. root魅族android6,魅族PRO6怎么ROOT ROOT权限获取方法教程
  16. Processor ARM7TDMI/ARM920T raised an exception.Cause:Undefined instruction问题的多个解决方法
  17. 联想笔记本重装系统无法进入记录
  18. 最新微信公众号采集方案详细介绍
  19. python 对excel的函数操作_自动化报表(3)
  20. html div缩放

热门文章

  1. 有源带通滤波器快速实现
  2. mysql 授予数据库权限_MySQL:授予**对数据库的所有**权限
  3. 机器学习D12——决策树
  4. iOS开发之沙盒机制(SandBox)
  5. 阿里刘振飞:我们用了10年,从去IOE到OceanBase世界第一!
  6. migo初始化库存 s4_SAP项目用户操作手册-MIGO 货物移动.docx
  7. your browser doesn't support html5,video
  8. 字体图标库-如何使用-icomoon
  9. 创客学院9天C语言三
  10. Grub2的一些典型菜单写法