浪漫桃心的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:

 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 }

花瓣类是最重要的类,因为真正绘制在屏幕上的是一个个小花瓣。每个花朵包含一系列花瓣,花朵类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 }

接下来是花园类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 }

考虑到刷新的比较频繁,选择使用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 }

还有两个比较重要的工具类
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 }

工具类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 }

好了,目前为止,就可以得到上面的效果了。

时间: 2024-10-05 19:14:54

浪漫桃心的Android表白程序的相关文章

打造浪漫的Android表白程序

几年前,看到过有个牛人用HTML5绘制了浪漫的爱心表白动画.地址在这:浪漫程序员 HTML5爱心表白动画.发现原来程序员也是可以很浪--漫-..的(PS:刚过520,被妹子骂不够浪漫).那么在Android怎么打造如此这个效果呢?参考了一下前面HTML5的算法,在Android中实现了类似的效果.先贴上最终效果图: 生成心形线 心形线的表达式可以参考:桃心线.里面对桃心线的表达式解析的挺好.可以通过使用极坐标的方式,传入角度和距离(常量)计算出对应的坐标点.其中距离是常量值,不需改变,变化的是角

表白程序源代码,android

弄了一个表白程序,还是不错的,内容能够自己设置.并附上源代码:http://download.csdn.net/detail/a358763471/7803571 看下效果图吧.是动画的哦...

android表白app

一.前言 马上就要520和521了,是不是还有像我一样的单身狗啊.我就知道有,所以这两天简单写了这个小程序(其实是替别人写的),虽然我并不会用去骗女孩子(因为最近太忙了,实习完之后要搞毕设,要搞论文啊,谁能帮帮我...),但是我想很多人肯定会感兴趣吧.如果你感兴趣就拿去逗妹子一乐吧. 如果你很感兴趣,你可以在我写的基础上增辉加彩,或者根据我提供的资源自己动手,尝试一下. 二.先show一下效果 三.Android手机如何录制屏幕及转GIF https://www.aswifter.com/201

Android应用程序的组成部分

Android应用程序由松散耦合的组件组成,并使用应用程序Manifest绑定到一起:应用程序Manifest描述了每一个组件和它们之间的交互方式,还用于指定应用程序元数据,其硬件和平台要求,外部库以及必需的权限. 下面几个组件提供了应用程序的基本结构模块: Activity:应用程序的表示层.应用程序中的每一个UI都是通过Activity类的一个或多个扩展实现的.Activity使用Fragment和视图来布局和显示信息,以及响应用户动作. Service:应用程序中不可见的工作者.Servi

Android应用程序安装与Launcher启动机制

以下资料摘录整理自老罗的Android之旅博客,是对老罗的博客关于Android底层原理的一个抽象的知识概括总结(如有错误欢迎指出)(侵删):http://blog.csdn.net/luoshengyang/article/details/8923485http://blog.csdn.net/luoshengyang/article/details/12957169 整理by Doing Android系统在启动的过程中,会启动一个应用程序管理服务PackageManagerService,

【苦读官方文档】2.Android应用程序基本原理概述

官方文档原文地址 应用程序原理 Android应用程序是通过Java编程语言来写.Android软件开发工具把你的代码和其它数据.资源文件一起编译.打包成一个APK文件,这个文档以.apk为后缀,保存了一个Android应用程序全部的内容.Android设备通过它来安装相应的应用. 一旦安装到设备上.每一个Android应用程序就执行在各自独立的安全沙盒中: Android系统是一个多用户的Linux系统.每一个应用都是一个用户. Android系统默认会给每一个应用分配一个唯一的用户ID(这个

Android应用程序窗口(Activity)的窗口对象(Window)的创建过程分析

http://blog.csdn.net/luoshengyang/article/details/8223770 在前文中,我们分析了Android应用程序窗口的运行上下文环境的创建过程.由此可知,每一个Activity组件都有一个关联的ContextImpl对象,同时,它还关联有一个Window对象,用来描述一个具体的应用程序窗口.由此又可知,Activity只不过是一个高度抽象的UI组件,它的具体UI实现其实是由其它的一系列对象来实现的.在本文中,我们就将详细分析Android应用程序窗口

Android应用程序签名

Android系统要求每一个Android应用程序必须要经过数字签名才能够安卓到系统中.Android通过数字签名来标识应用程序的作者和在应用程序之间建立信任关系,这个数字签名由应用程序的作者完成,并不需要权威的数字证书签名机构认证,它只是用来让应用程序自我认证的. Android应用程序签名的影响 使用同一个签名证书,则不同签名的应用无法覆盖生成的应用程序,即使包名相同,因此,主要的签名影响有以下两点: 1.应用升级.使用相同签名的升级软件可以正常覆盖安装老版本的软件,否则,系统发现签名证书不

单例访问Android应用程序对象

1.单例模式: 单例模式是一种常用的软件设计模式.在它的核心结构中只包含一个被称为单例类的特殊类.通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源.如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案. 2.单例模式优势: 由于单例模式在内存中只有一个实例,减少了内存开销 单例模式可以避免对资源的多重占用. 单例模式可以在系统设置全局的访问点,优化和共享资源访问. 2.问题:需要从android应用程序中去访问全局数据 解决