者:huachao1001
博客:https://juejin.im/entry/576b6430816dfa0055bdaf71
碼仔,你知道今天是什么日子嗎?
嘿嘿~當然知道!5.2.0~
喲~ 這也知道!
那是~~還能給你show一段!
看好了!
幾年前,看到過有個牛人用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;
}
}
考慮到刷新的比較頻繁,選擇使用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, );
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 != ) {
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 ;
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
this.width = width;
this.height = height;
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;
}
}
好了,目前為止,就可以得到上面的效果了。
碼仔只能幫你到這里了!
(源碼地址請查看原文~)
近期文章:
送三個福利(專屬)
今天我把APP的編譯速度縮短了近5倍
從XML變成View,它經歷了什么?
今日問題:
你有沒有寫過浪漫的程序?
快來碼仔社群解鎖新姿勢吧!社群升級:Max你的學習效率
,就關注我。
人狠,話不多,直接進入主題;
簡單粗暴“我愛你”
#include <stdio.h>
#include <stdlib.h>
int main()
{
int i,k;
printf("我我我我我我 愛愛愛愛愛愛 你你你你你你你\n");
for(i=0;i<3;i++)
printf("我 愛 你 你\n");
printf("我我我我我我 愛愛愛愛愛愛 你 你\n");
for(k=0;k<3;k++)
printf(" 我 愛 你 你\n");
printf("我我我我我我 愛愛愛愛愛愛 你你你你你你你\n");
return 0;
}
效果
我比C呆瓜浪漫點:
代碼太長,見鏈接:
https://blog.csdn.net/qq_42185999/article/details/88386998
專屬java程序員的浪漫
我要在你掌心里畫一條優美的弧線:
源碼:https://www.jianshu.com/p/329339c58dd5
安卓傻瓜的浪漫表白,可以自定義加字
心動態生成,右側時間實時更新,左邊可以作為表白的語句,一個字一個字顯現。
源碼(算是幫別人免費推薦了一波,我心痛):https://www.html5tricks.com/html5-heart-animation.html
我愛你,結束。
歡迎關注技術閣,發布有趣的科技資訊,分享有料的技術干貨。
記得關注哦,謝謝!
天是2019年的第一個情人節!,是的你沒聽錯,是情人節到了,還在辦公室加班的你還在加班嗎?
是不是樂此不疲 ?
還在趕項目進度而 忘記吃飯甚至忘記給女朋友驚喜 的你,知道老板已經已經在為情人節狂歡了嗎?
好了,這些都沒有關系,誰叫你是程序猿呢,程序猿的情人節就要跟別人不一樣!
程序猿要是有喜歡的人(沒有也假設有),那各種表白送情意的招兒那還少嗎?
什么?一臉懵逼? 下面還是給大家普及普及吧
小編和廣大程序猿一樣,一直認為情人節最好的禮物就是...
送你一桶代碼!~~
沒錯,你沒看錯的。
| Java 程序猿 |
在生命結束之前,每天都會愛你多一點。
| C 程序猿 |
without u(沒有了你),我再也不能對 the world(這個世界) say hello!
| 高級的程序猿 |
兩隔的世界;
無名的信件;
短暫的停留;
長久的記憶;
說這么多,只想告訴你:我想你了。
| Python 程序猿 |
山無陵,江水為竭,冬雷震震,夏雨雪,天地合,乃敢與君絕!
| Javascript 程序猿 |
春風十里不如你!
還有... ~~~~!
| 搞 DBA的 |
把你的心、我的心,串一串,串一株幸運草,串一個同心圓...
| 搞運維的 |
每天只想對你道下晚安:做個好夢,my 甜心!
| 搞前端的 | 硬是用Html5 整出了一個3D表白圖:
~~!!! 還是搞前端的有優勢啊
| 后端程序猿 | 表示不服,祭出了這段代碼:
也不知道是什么意思,反正小編粘貼到 Shell 客戶端后就變成了這樣:
| 搞架構的 |
都 三四十的人了,不敢亂表白呀 `_`!!,很有責任心呢,實在是好榜樣。
| 搞臨時工的 |
俺也不會啥代碼的,就送幾個鉆戒吧..
小編呢,也偷偷找了一個有個性的詩句,可以借鑒采納
我能抽象出整個世界...
但是我不能抽象出你...
因為你在我心中是那么的具體...
所以我的世界并不完整...
我可以重載甚至覆蓋這個世界里的任何一種方法...
但是我卻不能重載對你的思念...
也許命中注定了 你在我的世界里永遠的烙上了靜態的屬性...
而我不慎調用了愛你這個方法...
當我義無返顧的把自己作為參數傳進這個方法時...
我才發現愛上你是一個死循環...
它不停的返回對你的思念壓入我心里的堆棧...
在這無盡的黑夜中...
我的內存里已經再也裝不下別人...
謹以此篇送給還在猶豫的你,不管結果如何努力過就好,祝愿天下有情猿終有眷屬!
本文大多內容摘自互聯網,如有雷同請勿噴勿念,大家開心就好。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。