當前位置:
首頁 > 知識 > 貝塞爾曲線之愛心點贊代碼全解析!| CSDN 博文精選

貝塞爾曲線之愛心點贊代碼全解析!| CSDN 博文精選

貝塞爾曲線之愛心點贊代碼全解析!| CSDN 博文精選

貝塞爾曲線之愛心點贊代碼全解析!| CSDN 博文精選

作者 | 威威喵

責編 | 屠敏

出品 | CSDN 博客

直接步入正題,我們要實現的是一個 Android 客戶端應用裡面的一種點贊效果,比如你點一下那個愛心型的圖片,就會產生一個小愛心,而且會以曲線的方式進行上升,直到它消失為止。

文字描述只能是這樣的了,我們直接來看動態圖吧,效果更直觀。

貝塞爾曲線之愛心點贊代碼全解析!| CSDN 博文精選

本案例是由我自己寫的,因為之前對這個貝塞爾曲線有一點點了解,還有無意間看到了這個效果,覺得挺贊的,就順便寫了一下demo,並且學習了一些關於貝塞爾曲線的相關知識。

首先,要看懂本案例的代碼,你需要具備 Android 自定義 View 的基本知識,並且你還有了解一些關於貝塞爾曲線的公式和演算法。不過沒關係,我們並不需要對貝塞爾深刻了解,只要會基本的根據公式,套用代碼就好了。

來看一下貝塞爾曲線的一些相關知識,我也是從大佬的博客中學習得來的。我們來看看什麼是貝塞爾曲線?


貝塞爾曲線(Bézier curve),又稱貝茲曲線或貝濟埃曲線,是應用於二維圖形應用程序的數學曲線。一般的矢量圖形軟體通過它來精確畫出曲線,貝茲曲線由線段與節點組成,節點是可拖動的支點,線段像可伸縮的皮筋,我們在繪圖工具上看到的鋼筆工具就是來做這種矢量曲線的。

更形象的就直接來看動態圖吧。

一階貝塞爾曲線公式:由 P0 至 P1 的連續點, 描述的一條線段

貝塞爾曲線之愛心點贊代碼全解析!| CSDN 博文精選

貝塞爾曲線之愛心點贊代碼全解析!| CSDN 博文精選

二階貝塞爾曲線公式:曲線的切線 P0-P1、P1-P2 組成的運動軌跡

貝塞爾曲線之愛心點贊代碼全解析!| CSDN 博文精選

貝塞爾曲線之愛心點贊代碼全解析!| CSDN 博文精選

三階貝塞爾曲線公式:

貝塞爾曲線之愛心點贊代碼全解析!| CSDN 博文精選

貝塞爾曲線之愛心點贊代碼全解析!| CSDN 博文精選

從上面的動態圖,可以很直觀的看到曲線的計算公式和它的路徑形成的規律。而我們要實現的效果,運用的就是三階貝塞爾曲線的公式。首先,需要確定曲線的路徑的話,就必須先確定它的點位置。我以是這樣的方式來確定點位置的,如下圖:

貝塞爾曲線之愛心點贊代碼全解析!| CSDN 博文精選

我使用的就是這三個點,兩邊都可以,隨機的選擇一邊。這樣的話,我們的曲線就在屏幕內,它的形成大致和我們上面的動態圖有點類似。那麼看代碼:

private Point setPoint1 {

Point points = new Point{

new Point(mLoveX, mLoveY),

new Point(0, mCanvasHeight / 2),

new Point(mCanvasWidth + 20, -mLoveWidth - 10),

};

return points;

}

private Point setPoint2 {

Point points = new Point{

new Point(mLoveX, mLoveY),

new Point(mCanvasWidth, mCanvasHeight / 2),

new Point(-mLoveWidth - 20, -mLoveWidth - 10),

};

return points;

}

上面代碼是初始化兩種點的坐標,mLoveX,mLoveY 表示我們的愛心起始的位置。第一個集合點,對應圖中的藍線,第二個集合點,就對應橙色了。

接下來是重點部分,也就是把貝塞爾曲線公式轉化為代碼的形式,根據動態圖中有一個 t 值,它的區間是 [0,1] 的,這個也很形象,t 從 0 變到 1 時,意味著曲線已經繪製完了。看代碼:

/**

* 根據點得到曲線的路徑上的點,k 是變化趨勢

*/

private Point deCasteljau(Point[] points, float k) {

final int n = points.length;

for (int i = 1; i <= n; i++)

for (int j = 0; j < n - i; j++) {

points[j].x = (int) ((1 - k) * points[j].x + k * points[j + 1].x);

points[j].y = (int) ((1 - k) * points[j].y + k * points[j + 1].y);

}

return points[0];

}

剛剛我們定義的兩種點的集合,就可以將它傳入了,這樣根據 k 值的變化,就可以得到對應位置曲線上的點坐標。接下來,我們的任務就是開啟一個子線程去跟新 k 值,將 k 值有 0 加到 1,然後返回的每個 point 對象,就是整條曲線的坐標散點。執行子線程獲取點的代碼:

mLoveThread = new Thread(new Runnable {

@Override

public void run {

while (k < 1) {

k += 0.01;

Point point = deCasteljau(mPoints, k);

mLoveX = point.x;

mLoveY = point.y;

if (mLoveY <= -mLoveWidth || mLoveY >= mCanvasHeight) {

k = 1;

}

if (mLoveX <= -mLoveWidth || mLoveX >= mCanvasWidth) {

k = 1;

}

postInvalidate;//非同步刷新

try {

Thread.sleep(80);

} catch (InterruptedException e) {

e.printStackTrace;

}

}

}

});

通過上面代碼,我們就可以獲取愛心圖片的 x,y 坐標值了,然後再通過 onDraw 裡面將它進行繪製就搞定啦。

@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

mCanvasWidth = canvas.getWidth;

mCanvasHeight = canvas.getHeight;

mLoveBitmapX = mCanvasWidth / 2 - mLoveBitmapWidth / 2;

mLoveBitmapY = mCanvasHeight - 2 * mLoveBitmapHeight;

drawLoveBitmap(canvas);

canvas.drawBitmap(mDefLove, mLoveX, mLoveY, mPaint);

//隨便畫的

canvas.drawText("點贊", mCanvasWidth / 2 - mPaint.getTextSize, mLoveBitmapY + mLoveBitmapHeight + 100, mPaint);

canvas.drawLine(0, mLoveBitmapY + mLoveBitmapHeight + 20, mCanvasWidth, mLoveBitmapY + mLoveBitmapHeight + 20, mPaint);

}

這裡的愛心,我使用的是六張不同的圖片,我之前想嘗試使用愛心函數公式來繪製的,不過也放棄了,計算太慢了,每個愛心算出來都要停頓一下,只好換圖片的形式。

貝塞爾曲線之愛心點贊代碼全解析!| CSDN 博文精選

最後提一下就是點擊這個圖片才繪製的功能,我是在 onTouchEvent 中拿到點擊的坐標位置,然後去判斷它的點擊位置是不是在那個愛心圖片裡面,代碼如下:

private boolean isTouchLoveArea(int touchX, int touchY) {

return touchX >= mLoveBitmapX && touchX <= mLoveBitmapX + mLoveBitmapWidth

&& touchY > mLoveBitmapY && touchY <= mLoveBitmapY + mLoveBitmapHeight;

}

好了,最後也沒什麼好介紹的了,剩下的基本都是自定義 View 的知識,我們主要是關注這個貝塞爾曲線是如何繪製的就好,那麼完整代碼如下:

package com.example.xww.myapplication;

import android.content.Context;

import android.graphics.Bitmap;

import android.graphics.BitmapFactory;

import android.graphics.Canvas;

import android.graphics.Paint;

import android.graphics.Point;

import android.os.Build;

import android.support.annotation.able;

import android.support.annotation.RequiresApi;

import android.util.AttributeSet;

import android.view.MotionEvent;

import android.view.View;

import java.util.Random;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

/**

* @author xww

* @desciption : 點贊時愛心飄了,愛心路徑繪製的是貝塞爾曲線

* @博客:https://blog.csdn.net/smile_running

* @date 2019/7/30

* @time 20:59

*/

@RequiresApi(api = Build.VERSION_CODES.N)

public class LoveView extends View {

private Paint mPaint;

//愛心圖片

private Bitmap mLoveBitmap;

private Bitmap mLove1;

private Bitmap mLove2;

private Bitmap mLove3;

private Bitmap mLove4;

private Bitmap mLove5;

private Bitmap mLove6;

private Bitmap mDefLove;

private int mLoveWidth;

private int mLoveX;

private int mLoveY;

//圖片繪製的 x,y 坐標

private int mLoveBitmapX;

private int mLoveBitmapY;

//圖片的寬、高

private int mLoveBitmapWidth;

private int mLoveBitmapHeight;

// 畫布寬、高

private int mCanvasWidth;

private int mCanvasHeight;

//觸摸點

private int mTouchX;

private int mTouchY;

private ExecutorService mExecutorService;

private Thread mLoveThread;

//隨機數

private Random mRandom;

private float k;//曲線斜率 k:[0,1]

private Point mPoints;//構成曲線隨機點集合

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

setMeasuredDimension(measureSpecWidth(widthMeasureSpec), measureSpecHeigth(heightMeasureSpec));

}

/**

* EXACTLY :精確值,即 64dp 這樣的具體值

* AT_MOST :最大值,即 wrap_content 類型,可以達到父 View 一樣的大小

* UNSPECIFIED :未指定,即這個 View 可以無限大

*

* @param widthMeasureSpec 傳入的 width 值

* @return 寬度值

*/

private int measureSpecWidth(int widthMeasureSpec) {

int mode = MeasureSpec.getMode(widthMeasureSpec);

int size = MeasureSpec.getSize(widthMeasureSpec);

return mode == MeasureSpec.EXACTLY ? size : Math.min(200, size);

}

private int measureSpecHeigth(int heightMeasureSpec) {

int mode = MeasureSpec.getMode(heightMeasureSpec);

int size = MeasureSpec.getSize(heightMeasureSpec);

return mode == MeasureSpec.EXACTLY ? size : Math.min(200, size);

}

private void init {

initPaint;

initBitmap;

mRandom = new Random;

mExecutorService = Executors.newWorkStealingPool(6);

}

private void initBitmap {

mLoveBitmap = BitmapFactory.decodeResource(getResources, R.drawable.loveclick);

mLoveBitmap = Bitmap.createScaledBitmap(mLoveBitmap, 180, 180, false);

mLoveBitmapWidth = mLoveBitmap.getWidth;

mLoveBitmapHeight = mLoveBitmap.getHeight;

mLove1 = BitmapFactory.decodeResource(getResources, R.drawable.love1);

mLove2 = BitmapFactory.decodeResource(getResources, R.drawable.love2);

mLove3 = BitmapFactory.decodeResource(getResources, R.drawable.love3);

mLove4 = BitmapFactory.decodeResource(getResources, R.drawable.love4);

mLove5 = BitmapFactory.decodeResource(getResources, R.drawable.love5);

mLove6 = BitmapFactory.decodeResource(getResources, R.drawable.love6);

mLove1 = reSizeLove(mLove1);

mLove2 = reSizeLove(mLove2);

mLove3 = reSizeLove(mLove3);

mLove4 = reSizeLove(mLove4);

mLove5 = reSizeLove(mLove5);

mLove6 = reSizeLove(mLove6);

mDefLove = mLove1;

mLoveWidth = mLove1.getWidth;

setDefPosition;

}

private Bitmap reSizeLove(Bitmap src) {

return Bitmap.createScaledBitmap(src, 160, 160, false);

}

private void initPaint {

mPaint = new Paint;

mPaint.setColor(getResources.getColor(android.R.color.holo_purple));

mPaint.setStrokeWidth(8f);

mPaint.setStyle(Paint.Style.FILL);

mPaint.setDither(true);

mPaint.setAntiAlias(true);

mPaint.setTextSize(45f);

}

public LoveView(Context context) {

this(context, );

}

public LoveView(Context context, @able AttributeSet attrs) {

this(context, attrs, 0);

}

public LoveView(Context context, @able AttributeSet attrs, int defStyleAttr) {

super(context, attrs, defStyleAttr);

init;

}

@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

mCanvasWidth = canvas.getWidth;

mCanvasHeight = canvas.getHeight;

mLoveBitmapX = mCanvasWidth / 2 - mLoveBitmapWidth / 2;

mLoveBitmapY = mCanvasHeight - 2 * mLoveBitmapHeight;

drawLoveBitmap(canvas);

canvas.drawBitmap(mDefLove, mLoveX, mLoveY, mPaint);

//隨便畫的

canvas.drawText("點贊", mCanvasWidth / 2 - mPaint.getTextSize, mLoveBitmapY + mLoveBitmapHeight + 100, mPaint);

canvas.drawLine(0, mLoveBitmapY + mLoveBitmapHeight + 20, mCanvasWidth, mLoveBitmapY + mLoveBitmapHeight + 20, mPaint);

}

private Point setPoint1 {

Point points = new Point{

new Point(mLoveX, mLoveY),

new Point(0, mCanvasHeight / 2),

new Point(mCanvasWidth + 20, -mLoveWidth - 10),

};

return points;

}

private Point setPoint2 {

Point points = new Point{

new Point(mLoveX, mLoveY),

new Point(mCanvasWidth, mCanvasHeight / 2),

new Point(-mLoveWidth - 20, -mLoveWidth - 10),

};

return points;

}

private void setDefPosition {

mLoveX = mCanvasWidth / 2 - mLoveWidth / 2;

mLoveY = mLoveBitmapY - 80;

}

private void drawDynamicLove {

setDefPosition;

//設置愛心的樣式和位置

int color = mRandom.nextInt(6) + 1;

mDefLove = getBitmap(color);

k = 0;//開始

//添加貝塞爾路徑的點

if (mRandom.nextInt(2) == 0) {

mPoints = setPoint1;

} else {

mPoints = setPoint2;

}

mLoveThread = new Thread(new Runnable {

@Override

public void run {

while (k < 1) {

k += 0.01;

Point point = deCasteljau(mPoints, k);

mLoveX = point.x;

mLoveY = point.y;

if (mLoveY <= -mLoveWidth || mLoveY >= mCanvasHeight) {

k = 1;

}

if (mLoveX <= -mLoveWidth || mLoveX >= mCanvasWidth) {

k = 1;

}

postInvalidate;//非同步刷新

try {

Thread.sleep(80);

} catch (InterruptedException e) {

e.printStackTrace;

}

}

}

});

mExecutorService.execute(mLoveThread);

}

private Bitmap getBitmap(int color) {

switch (color) {

case 1:

return mLove1;

case 2:

return mLove2;

case 3:

return mLove3;

case 4:

return mLove4;

case 5:

return mLove5;

case 6:

return mLove6;

}

return ;

}

private void drawLoveBitmap(Canvas canvas) {

canvas.drawBitmap(mLoveBitmap, mLoveBitmapX, mLoveBitmapY, mPaint);

}

/**

* 根據點得到曲線的路徑上的點,k 是變化趨勢

*/

private Point deCasteljau(Point[] points, float k) {

final int n = points.length;

for (int i = 1; i <= n; i++)

for (int j = 0; j < n - i; j++) {

points[j].x = (int) ((1 - k) * points[j].x + k * points[j + 1].x);

points[j].y = (int) ((1 - k) * points[j].y + k * points[j + 1].y);

}

return points[0];

}

@Override

public boolean onTouchEvent(MotionEvent event) {

mTouchX = (int) event.getX;

mTouchY = (int) event.getY;

switch (event.getAction) {

case MotionEvent.ACTION_DOWN:

if (isTouchLoveArea(mTouchX, mTouchY)) {

drawDynamicLove;

}

break;

case MotionEvent.ACTION_UP:

break;

}

return super.onTouchEvent(event);

}

private boolean isTouchLoveArea(int touchX, int touchY) {

return touchX >= mLoveBitmapX && touchX <= mLoveBitmapX + mLoveBitmapWidth

&& touchY > mLoveBitmapY && touchY <= mLoveBitmapY + mLoveBitmapHeight;

}

}

這就是整個效果的代碼圖了,將它放到 activity_main 裡面,運行一下就可以看到效果了。

聲明:本文為 CSDN 博客精選文章,版權歸作者所有。作者:威威喵

原文:https://blog.csdn.net/smile_Running/article/details/98170645

【END】

貝塞爾曲線之愛心點贊代碼全解析!| CSDN 博文精選

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 CSDN 的精彩文章:

wxPython + PyOpenGL 打造三維數據分析的利器!| CSDN 博文精選
剛剛!為吊打穀歌,微軟砸10億美元布局AI,網友炸了!發帖上熱門……

TAG:CSDN |