游戏攻略网
当前位置: 首页 游戏攻略

贝塞尔工具画曲线的原理是什么(贝塞尔曲线之爱心点赞代码全解析)

时间:2023-07-08 作者: 小编 阅读量: 1 栏目名: 游戏攻略

首先,要看懂本案例的代码,你需要具备Android自定义View的基本知识,并且你还有了解一些关于贝塞尔曲线的公式和算法。贝塞尔曲线,又称贝兹曲线或贝济埃曲线,是应用于二维图形应用程序的数学曲线。而我们要实现的效果,运用的就是三阶贝塞尔曲线的公式。接下来,我们的任务就是开启一个子线程去跟新k值,将k值有0加到1,然后返回的每个point对象,就是整条曲线的坐标散点。

作者 | 威威喵

责编 | 屠敏

出品 | CSDN 博客

直接步入正题,我们要实现的是一个 Android 客户端应用里面的一种点赞效果,比如你点一下那个爱心型的图片,就会产生一个小爱心,而且会以曲线的方式进行上升,直到它消失为止。

文字描述只能是这样的了,我们直接来看动态图吧,效果更直观。

本案例是由我自己写的,因为之前对这个贝塞尔曲线有一点点了解,还有无意间看到了这个效果,觉得挺赞的,就顺便写了一下demo,并且学习了一些关于贝塞尔曲线的相关知识。

首先,要看懂本案例的代码,你需要具备 Android 自定义 View 的基本知识,并且你还有了解一些关于贝塞尔曲线的公式和算法。不过没关系,我们并不需要对贝塞尔深刻了解,只要会基本的根据公式,套用代码就好了。

来看一下贝塞尔曲线的一些相关知识,我也是从大佬的博客中学习得来的。我们来看看什么是贝塞尔曲线?

贝塞尔曲线(Bézier curve),又称贝兹曲线或贝济埃曲线,是应用于二维图形应用程序的数学曲线。一般的矢量图形软件通过它来精确画出曲线,贝兹曲线由线段与节点组成,节点是可拖动的支点,线段像可伸缩的皮筋,我们在绘图工具上看到的钢笔工具就是来做这种矢量曲线的。

更形象的就直接来看动态图吧。

一阶贝塞尔曲线公式:由 P0 至 P1 的连续点, 描述的一条线段

二阶贝塞尔曲线公式:曲线的切线 P0-P1、P1-P2 组成的运动轨迹

三阶贝塞尔曲线公式:

从上面的动态图,可以很直观的看到曲线的计算公式和它的路径形成的规律。而我们要实现的效果,运用的就是三阶贝塞尔曲线的公式。首先,需要确定曲线的路径的话,就必须先确定它的点位置。我以是这样的方式来确定点位置的,如下图:

我使用的就是这三个点,两边都可以,随机的选择一边。这样的话,我们的曲线就在屏幕内,它的形成大致和我们上面的动态图有点类似。那么看代码:

private Point setPoint1 {

Point points = new Point{

new Point(mLoveX, mLoveY),

new Point(0, mcanvasHeight / 2),

new Point(mCanvasWidth20, -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].xk * points[j1].x);

points[j].y = (int) ((1 - k) * points[j].yk * points[j1].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, mLoveBitmapYmLoveBitmapHeight100, mPaint);

canvas.drawLine(0, mLoveBitmapYmLoveBitmapHeight20, mCanvasWidth, mLoveBitmapYmLoveBitmapHeight20, mPaint);

}

这里的爱心,我使用的是六张不同的图片,我之前想尝试使用爱心函数公式来绘制的,不过也放弃了,计算太慢了,每个爱心算出来都要停顿一下,只好换图片的形式。

最后提一下就是点击这个图片才绘制的功能,我是在 onTouchEvent 中拿到点击的坐标位置,然后去判断它的点击位置是不是在那个爱心图片里面,代码如下:

private boolean isTouchLoveArea(int touchX, int touchY) {

return touchX >= mLoveBitmapX && touchX <= mLoveBitmapXmLoveBitmapWidth

&& touchY > mLoveBitmapY && touchY <= mLoveBitmapYmLoveBitmapHeight;

}

好了,最后也没什么好介绍的了,剩下的基本都是自定义 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, mLoveBitmapYmLoveBitmapHeight100, mPaint);

canvas.drawLine(0, mLoveBitmapYmLoveBitmapHeight20, mCanvasWidth, mLoveBitmapYmLoveBitmapHeight20, mPaint);

}

private Point setPoint1 {

Point points = new Point{

new Point(mLoveX, mLoveY),

new Point(0, mCanvasHeight / 2),

new Point(mCanvasWidth20, -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].xk * points[j1].x);

points[j].y = (int) ((1 - k) * points[j].yk * points[j1].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 <= mLoveBitmapXmLoveBitmapWidth

&& touchY > mLoveBitmapY && touchY <= mLoveBitmapYmLoveBitmapHeight;

}

}

这就是整个效果的代码图了,将它放到 activity_main 里面,运行一下就可以看到效果了。

声明:本文为 CSDN 博客精选文章,版权归作者所有。作者:威威喵

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

【END】

,
    推荐阅读
  • steam棋牌游戏推荐(幸运之夜新版本亮相TGC)

    steam棋牌游戏推荐12月1日,2017腾讯游戏嘉年华正式在成都开幕,腾讯的VR社交游戏《幸运之夜》在现场发布了最新版本。VR《幸运之夜》在TGC2017上惊艳亮相《幸运之夜》新版本发布邀请好友一起游戏今年7月底,《幸运之夜》正式在Steam发布,并推出了首款游戏作品“德州扑克”。今年的TGC2017现场,《幸运之夜》全新版本便带来了对互动性方面的提升。

  • 儿童睡前故事卖火柴的小女孩大全(卖火柴的小女孩)

    在长发公主的故事里,兔子小姐变成了手持宝剑的骑士,穿过了山川和河流,战胜了地狱恶犬,最终救出了长着一头金黄色长发的熊猫先生。随着一阵空间的波动,熊猫先生和兔子小姐来到了冰天雪地的圣诞节。小女孩被这突如其来的变化惊呆了。熊猫先生没有回答,轻轻摸了摸小女孩的头。小女孩点燃了第二根火柴。熊猫先生和兔子小姐则来到壁炉前,商量起小女孩最后一个愿望。熊猫先生蹲下来,握起小女孩的手。

  • 怎样做ppr管快一点(PPR管安装方法及技巧)

    怎样做ppr管快一点PPR管安装方法及技巧首先准备好需要的材料:热熔机,小剪刀,ppr管,管件,手巾。一定要根据自己热熔ppr管的口径,准备相应的热熔头。清洁:清洁管材与管件的焊接段部位,建议用95%浓度酒精擦净。在熔接时间内迅速的将管材无旋转的垂直插入管件中,并维持5秒以上,然后按相应冷却时间冷却。热熔后做到横平竖直,美观大方。

  • 大托特包搭配技巧(大托特包搭配技巧简述)

    西装外套+托特包复古时尚的格子,由黑白交错的条纹形成,文艺而又端庄搭配撞色托特包,优雅而不失俏皮,让气场变得灵动起来内搭白色连帽卫衣,减龄又可爱,接下来我们就来聊聊关于大托特包搭配技巧?大托特包搭配技巧西装外套+托特包复古时尚的格子,由黑白交错的条纹形成,文艺而又端庄。衬衫+托特包白色的衬衫休闲慵懒,给人一种空灵的感觉以及干净纯粹的气质。

  • 刘涛电视剧口碑(电视剧景气指数第一)

    还记得3月份刘涛在和周渝民主演的《大宋宫词》中扮嫩出演少妇被很多观众吐槽。万万没想到时隔数月,刘涛带着她的新剧《星辰大海》杀回来了。目前主要的剧情线在刘涛饰演的女主简爱身上。因为小时候意外发现母亲出轨的事,得知真相的父亲激愤之下杀死母亲并自杀,简爱因此成为了一个孤女。逃出傻子家的简爱在与姑姑的争执中误伤姑姑,从此开启逃命生涯。简爱从面馆辞职走投无路,误打误撞进入大公司之后面临着同事的故意刁难。

  • 外墙装修材料有哪些 外墙装修材料有哪些类型

    外墙涂料具有装饰性良好、耐污染耐老化以及施工维修容易和价格合理的特点。一般来说釉面外墙砖有亚光面与无光面两大类。它的装饰的效果也不错,有柚木色、深灰色等等可供选择。由于它的表面的肌理很清晰,所以色泽漂亮且装饰性极强。本站,中国知名大型装修平台,装修领导品牌。

  • 世界上有哪些花(世界上有哪些花 名字)

    瓜叶菊、香豌豆、夏兰、石竹、石蒜、荷花、翠菊、睡莲、福禄考、晚香玉、万寿菊、千日红、建兰、铃兰、报岁兰、香堇、大岩桐、水仙、小草兰、瓜叶菊、蒲包花、免子花、入腊红、三色堇、百日草、鸡冠花、一串红。孔雀草、大波斯菊、金盏菊、非洲凤仙花、菊花、非洲菊、观赏凤梨类、射干、非洲紫罗兰、天堂鸟、炮竹红、菊花、康乃馨、红掌、满天星、星辰花、三角梅、虞美人。

  • 长歌行李长歌母亲是谁杀的(长歌行李长歌的简介)

    下面更多详细答案一起来看看吧!长歌行李长歌母亲是谁杀的《长歌行》李长歌母亲是自杀的。李长歌,太子李建成之女,生母则是回纥王族。父母手足均死于玄武门之变,满怀愤恨的长歌凭高超武艺逃出皇宫,并在追捕过程中制造“坠崖假死”而逃生,其后女扮男装隐瞒身份流落民间,一心只想为父母复仇,在家和国的利害冲突中,最后放弃复仇,和阿诗勒隼一起成为了民族和解的使者。

  • 什么时候喝蛋白粉增肌效果最好(什么时候喝蛋白粉增肌效果最好)

    从长远来看,这种方法被证明可以促使肌肉明显增长。如果摄入量超过一定的阈值,蛋白质的合成就会受限。如果是以乳清饮料的形式摄入乳清蛋白,运动者可以在运动结束后立即饮用。按每公斤体重1克的标准,在健身前后立即摄入以及在运动后1小时内摄入可快速吸收的碳水化合物,可以明显抑制肌肉分解,并大大促进肌肉快速和明显的增长。在这种情况下,大量分泌的胰岛素促进了氨基酸向工作中的肌肉运输,为蛋白质合成奠定基础。

  • 窦骁周冬雨山楂树之恋结局(周冬雨18岁第一次出演)

    周冬雨18岁第一次出演要说最近最火的电影,非《少年的你》莫属,上映14天,已经收获了12.45亿的票房成绩,成为现阶段最强的票房黑马而作为该片主演的周冬雨和易烊千玺,也凭借在该片中的精彩演出,演技得到大众的进一步认可作为“。