A few years ago, I saw a guy who drew a romantic love animation using HTML5. Romantic programmer HTML5 love confession animation. Found that programmers can also be very wave… Diffuse… . Of (PS: just over 520, the girl scolded not romantic enough). So how do you create this effect on Android? A similar effect is achieved in Android with reference to the previous HTML5 algorithm. First paste the final effect:

Generate heart-shaped line

The expression of heart line can be referred to: heart line. The expression for the heart line is well resolved. The corresponding coordinate points can be calculated by passing in angles and distances (constants) using polar coordinates. Where the distance is a constant value, it doesn’t need to change, but the change is the Angle. The polar coordinate equation of the heart line is:

X = 16 x sin3 alpha y = 13 x cosine alpha 5 x cos2 – alpha – 2 x cos3 alpha – cos4 alpha

If the resulting string of hearts is not large enough, you can multiply x and y by a constant to make them larger. Since most people don’t want to do math, let’s just translate the HTML5 JS code directly into Java code. The code is as follows:

public Point getHeartPoint(float angle) { float t = (float) (angle / Math.PI); Float x = (float) (19.5 * (16 * math.h 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); }Copy the code

Where offsetX and offsetY are offsets. The main reason for using offsets is to keep the heart in the center. The values offsetX and offsetY are:

 offsetX = width / 2;
 offsetY = height / 2 - 55;Copy the code

Using this function, we can change the Angle from (0,180), taking points and drawing points to show the heart line. Ok, let’s customize a View and draw the heart line.

@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; }}Copy the code

The running results are as follows:

Principle of petal drawing

What we want is not simply to draw a heart line, but to place the flowers on the heart line. First, you have to know how to draw a flower, and a flower is made up of petals. So the heart of drawing flowers is drawing petals. Petal drawing principle is: 3 times Bezier curve. The cubic Bezier curve is determined by two endpoints and two control points. If the flower core is a circle with n petals, then the Angle between the two ends and the line at the center of the flower core is 360/n. Therefore, the position of each petal can be determined according to the number of petals and the radius of the flower core. The extension lines connecting the two endpoints to the center of the flower core determine the other two control points. A random flower can be drawn by randomly generating the radius of the flower core, the starting Angle of each petal, and randomly determining the extension line to obtain two control points. The change of parameters is shown in the figure below:

Draw the flowers onto the heart line

There’s a whole bunch of code coming in

First we define the Petal class, 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;" }="" else="" isfinished="true;" this.draw(p,="" canvas); ="" private="" void="" draw(point="" p,="" canvas="" canvas)="" if="" (! isfinished)="" path="new" path(); ="" point="" t="new" point(0,="" this.radius).rotate(myutil.degrad(this.startangle)); ="" v1="new" 3).rotate(myutil.degrad(this.startangle)); ="" v2="t.clone().rotate(MyUtil.degrad(this.angle));" v3="t.clone().mult(this.stretchA);" v4="v2.clone().mult(this.stretchB);" 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="">Copy the code

The petal class is the most important class, because what is really drawn on the screen are the small petals. Each flower contains a series of petals that Bloom as follows:

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; }}Copy the code

Next comes the Garden class Garden, which is used to create flowers and related configurations:

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.2f; 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; }}Copy the code

Considering the frequency of refreshing, the SurfaceView is chosen as the display view. Custom HeartView inherits SurfaceView. The code is as follows:

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, 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 (short < Garden. The Options. MaxBloomRadius * 1.5) {the 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; 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) { } }Copy the code

Two more important utility classes, Point.java, hold Point information, or vector information. Contains the basic operations of vectors.

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; }}Copy the code

The utility class myUtil.java mainly generates random numbers, colors, etc

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); } 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) <= 360="" limit="" &&="" math.abs(g="" -="" b)="" <="limit" math.abs(b="" r)="" {="" return="" rgba(rmin,="" rmax,="" gmin,="" gmax); ="" }="" else="" rgba(r,="" g,="" b,="" a); ="" public="" static="" float="" degrad(float="" angle)="" circle="" *="" angle; ="" code="">Copy the code

Ok, so for now, you have the effect above. Android romantic heart line