zoukankan      html  css  js  c++  java
  • 浪漫桃心的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 }
    View Code

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

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

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

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

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

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

  • 相关阅读:
    codeforces round 512 F. Putting Boxes Together 树状数组维护区间加权平均数
    sgu 110 射线关于球的反射光线
    快速读模板
    HDU-3506 二维四边形不等式
    BZOJ 1563 四边形不等式
    HIT Summer Day17 计算几何初步 题解
    IME Starters Try-outs 2018 C. China Adventures
    IME Starters Try-outs 2018 J. JHADCBEIGF
    ACM International Collegiate Programming Contest, Amman Collegiate Programming Contest (2018) GYM 100810 K. League of Demacia
    Codeforces Round #493 (Div. 1) C. Sky Full of Stars
  • 原文地址:https://www.cnblogs.com/Sharley/p/5783342.html
Copyright © 2011-2022 走看看