canvas贝塞尔曲线爱心_贝塞尔曲线之爱心点赞代码全解析!| CSDN 博文精选
原标题:贝塞尔曲线之爱心点赞代码全解析!| CSDN 博文精选
作者 | 威威喵
责编 | 屠敏
出品 | CSDN 博客
直接步入正题,我们要实现的是一个 Android 客户端应用里面的一种点赞效果,比如你点一下那个爱心型的图片,就会产生一个小爱心,而且会以曲线的方式进行上升,直到它消失为止。
文字描述只能是这样的了,我们直接来看动态图吧,效果更直观。
本案例是由我自己写的,因为之前对这个贝塞尔曲线有一点点了解,还有无意间看到了这个效果,觉得挺赞的,就顺便写了一下demo,并且学习了一些关于贝塞尔曲线的相关知识。
首先,要看懂本案例的代码,你需要具备 Android 自定义 View 的基本知识,并且你还有了解一些关于贝塞尔曲线的公式和算法。不过没关系,我们并不需要对贝塞尔深刻了解,只要会基本的根据公式,套用代码就好了。
来看一下贝塞尔曲线的一些相关知识,我也是从大佬的博客中学习得来的。我们来看看什么是贝塞尔曲线?
贝塞尔曲线(Bézier curve),又称贝兹曲线或贝济埃曲线,是应用于二维图形应用程序的数学曲线。一般的矢量图形软件通过它来精确画出曲线,贝兹曲线由线段与节点组成,节点是可拖动的支点,线段像可伸缩的皮筋,我们在绘图工具上看到的钢笔工具就是来做这种矢量曲线的。
更形象的就直接来看动态图吧。
一阶贝塞尔曲线公式:由 P0 至 P1 的连续点, 描述的一条线段
二阶贝塞尔曲线公式:曲线的切线 P0-P1、P1-P2 组成的运动轨迹
三阶贝塞尔曲线公式:
从上面的动态图,可以很直观的看到曲线的计算公式和它的路径形成的规律。而我们要实现的效果,运用的就是三阶贝塞尔曲线的公式。首先,需要确定曲线的路径的话,就必须先确定它的点位置。我以是这样的方式来确定点位置的,如下图:
我使用的就是这三个点,两边都可以,随机的选择一边。这样的话,我们的曲线就在屏幕内,它的形成大致和我们上面的动态图有点类似。那么看代码:
privatePoint[] setPoint1(){
Point[] points = newPoint[]{
newPoint(mLoveX, mLoveY),
newPoint(0, mCanvasHeight / 2),
newPoint(mCanvasWidth + 20, -mLoveWidth - 10),
};
returnpoints;
}
privatePoint[] setPoint2(){
Point[] points = newPoint[]{
newPoint(mLoveX, mLoveY),
newPoint(mCanvasWidth, mCanvasHeight / 2),
newPoint(-mLoveWidth - 20, -mLoveWidth - 10),
};
returnpoints;
}
上面代码是初始化两种点的坐标,mLoveX,mLoveY 表示我们的爱心起始的位置。第一个集合点,对应图中的蓝线,第二个集合点,就对应橙色了。
接下来是重点部分,也就是把贝塞尔曲线公式转化为代码的形式,根据动态图中有一个 t 值,它的区间是 [0,1] 的,这个也很形象,t 从 0 变到 1 时,意味着曲线已经绘制完了。看代码:
/**
* 根据点得到曲线的路径上的点,k 是变化趋势
*/
privatePoint deCasteljau(Point[] points, floatk){
final intn = points.length;
for(inti = 1; i <= n; i++)
for(intj = 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);
}
returnpoints[0];
}
刚刚我们定义的两种点的集合,就可以将它传入了,这样根据 k 值的变化,就可以得到对应位置曲线上的点坐标。接下来,我们的任务就是开启一个子线程去跟新 k 值,将 k 值有 0 加到 1,然后返回的每个 point 对象,就是整条曲线的坐标散点。执行子线程获取点的代码:
mLoveThread = newThread(newRunnable {
@Override
publicvoidrun{
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
protectedvoidonDraw(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);
}
这里的爱心,我使用的是六张不同的图片,我之前想尝试使用爱心函数公式来绘制的,不过也放弃了,计算太慢了,每个爱心算出来都要停顿一下,只好换图片的形式。
最后提一下就是点击这个图片才绘制的功能,我是在 onTouchEvent 中拿到点击的坐标位置,然后去判断它的点击位置是不是在那个爱心图片里面,代码如下:
privatebooleanisTouchLoveArea(inttouchX, inttouchY){
returntouchX >= mLoveBitmapX && touchX <= mLoveBitmapX + mLoveBitmapWidth
&& touchY > mLoveBitmapY && touchY <= mLoveBitmapY + mLoveBitmapHeight;
}
好了,最后也没什么好介绍的了,剩下的基本都是自定义 View 的知识,我们主要是关注这个贝塞尔曲线是如何绘制的就好,那么完整代码如下:
packagecom.example.xww.myapplication;
importandroid.content.Context;
importandroid.graphics.Bitmap;
importandroid.graphics.BitmapFactory;
importandroid.graphics.Canvas;
importandroid.graphics.Paint;
importandroid.graphics.Point;
importandroid.os.Build;
importandroid.support.annotation.Nullable;
importandroid.support.annotation.RequiresApi;
importandroid.util.AttributeSet;
importandroid.view.MotionEvent;
importandroid.view.View;
importjava.util.Random;
importjava.util.concurrent.ExecutorService;
importjava.util.concurrent.Executors;
/**
* @authorxww
* @desciption: 点赞时爱心飘了,爱心路径绘制的是贝塞尔曲线
* @博客:https://blog.csdn.net/smile_running
* @date2019/7/30
* @time20:59
*/
@RequiresApi(api = Build.VERSION_CODES.N)
publicclassLoveViewextendsView{
privatePaint mPaint;
//爱心图片
privateBitmap mLoveBitmap;
privateBitmap mLove1;
privateBitmap mLove2;
privateBitmap mLove3;
privateBitmap mLove4;
privateBitmap mLove5;
privateBitmap mLove6;
privateBitmap mDefLove;
privateintmLoveWidth;
privateintmLoveX;
privateintmLoveY;
//图片绘制的 x,y 坐标
privateintmLoveBitmapX;
privateintmLoveBitmapY;
//图片的宽、高
privateintmLoveBitmapWidth;
privateintmLoveBitmapHeight;
// 画布宽、高
privateintmCanvasWidth;
privateintmCanvasHeight;
//触摸点
privateintmTouchX;
privateintmTouchY;
privateExecutorService mExecutorService;
privateThread mLoveThread;
//随机数
privateRandom mRandom;
privatefloatk;//曲线斜率 k:[0,1]
privatePoint[] mPoints;//构成曲线随机点集合
@Override
protectedvoidonMeasure(intwidthMeasureSpec, intheightMeasureSpec){
setMeasuredDimension(measureSpecWidth(widthMeasureSpec), measureSpecHeigth(heightMeasureSpec));
}
/**
* EXACTLY :精确值,即 64dp 这样的具体值
* AT_MOST :最大值,即 wrap_content 类型,可以达到父 View 一样的大小
* UNSPECIFIED :未指定,即这个 View 可以无限大
*
* @paramwidthMeasureSpec 传入的 width 值
* @return宽度值
*/
privateintmeasureSpecWidth(intwidthMeasureSpec){
intmode = MeasureSpec.getMode(widthMeasureSpec);
intsize = MeasureSpec.getSize(widthMeasureSpec);
returnmode == MeasureSpec.EXACTLY ? size : Math.min(200, size);
}
privateintmeasureSpecHeigth(intheightMeasureSpec){
intmode = MeasureSpec.getMode(heightMeasureSpec);
intsize = MeasureSpec.getSize(heightMeasureSpec);
returnmode == MeasureSpec.EXACTLY ? size : Math.min(200, size);
}
privatevoidinit{
initPaint;
initBitmap;
mRandom = newRandom;
mExecutorService = Executors.newWorkStealingPool(6);
}
privatevoidinitBitmap{
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;
}
privateBitmap reSizeLove(Bitmap src){
returnBitmap.createScaledBitmap(src, 160, 160, false);
}
privatevoidinitPaint{
mPaint = newPaint;
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);
}
publicLoveView(Context context){
this(context, null);
}
publicLoveView(Context context, @Nullable AttributeSet attrs){
this(context, attrs, 0);
}
publicLoveView(Context context, @Nullable AttributeSet attrs, intdefStyleAttr){
super(context, attrs, defStyleAttr);
init;
}
@Override
protectedvoidonDraw(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);
}
privatePoint[] setPoint1 {
Point[] points = newPoint[]{
newPoint(mLoveX, mLoveY),
newPoint(0, mCanvasHeight / 2),
newPoint(mCanvasWidth + 20, -mLoveWidth - 10),
};
returnpoints;
}
privatePoint[] setPoint2 {
Point[] points = newPoint[]{
newPoint(mLoveX, mLoveY),
newPoint(mCanvasWidth, mCanvasHeight / 2),
newPoint(-mLoveWidth - 20, -mLoveWidth - 10),
};
returnpoints;
}
privatevoidsetDefPosition{
mLoveX = mCanvasWidth / 2- mLoveWidth / 2;
mLoveY = mLoveBitmapY - 80;
}
privatevoiddrawDynamicLove{
setDefPosition;
//设置爱心的样式和位置
intcolor = mRandom.nextInt(6) + 1;
mDefLove = getBitmap(color);
k = 0;//开始
//添加贝塞尔路径的点
if(mRandom.nextInt(2) == 0) {
mPoints = setPoint1;
} else{
mPoints = setPoint2;
}
mLoveThread = newThread(newRunnable {
@Override
publicvoidrun{
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);
}
privateBitmap getBitmap(intcolor){
switch(color) {
case1:
returnmLove1;
case2:
returnmLove2;
case3:
returnmLove3;
case4:
returnmLove4;
case5:
returnmLove5;
case6:
returnmLove6;
}
returnnull;
}
privatevoiddrawLoveBitmap(Canvas canvas){
canvas.drawBitmap(mLoveBitmap, mLoveBitmapX, mLoveBitmapY, mPaint);
}
/**
* 根据点得到曲线的路径上的点,k 是变化趋势
*/
privatePoint deCasteljau(Point[] points, floatk){
finalintn = points.length;
for(inti = 1; i <= n; i++)
for(intj = 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);
}
returnpoints[0];
}
@Override
publicbooleanonTouchEvent(MotionEvent event){
mTouchX = (int) event.getX;
mTouchY = (int) event.getY;
switch(event.getAction) {
caseMotionEvent.ACTION_DOWN:
if(isTouchLoveArea(mTouchX, mTouchY)) {
drawDynamicLove;
}
break;
caseMotionEvent.ACTION_UP:
break;
}
returnsuper.onTouchEvent(event);
}
privatebooleanisTouchLoveArea(inttouchX, inttouchY){
returntouchX >= mLoveBitmapX && touchX <= mLoveBitmapX + mLoveBitmapWidth
&& touchY > mLoveBitmapY && touchY <= mLoveBitmapY + mLoveBitmapHeight;
}
}
这就是整个效果的代码图了,将它放到 activity_main 里面,运行一下就可以看到效果了。
声明:本文为 CSDN 博客精选文章,版权归作者所有。作者:威威喵
原文:https://blog.csdn.net/smile_Running/article/details/98170645
热 文 推 荐
责任编辑:
canvas贝塞尔曲线爱心_贝塞尔曲线之爱心点赞代码全解析!| CSDN 博文精选相关推荐
- 贝塞尔曲线之爱心点赞代码全解析!| CSDN 博文精选
作者 | 威威喵 责编 | 屠敏 出品 | CSDN 博客 直接步入正题,我们要实现的是一个 Android 客户端应用里面的一种点赞效果,比如你点一下那个爱心型的图片,就会产生一个小爱心,而且会以曲 ...
- java 固定长度队列_如何彻底搞懂 Java 数据结构?|CSDN 博文精选
作者 | 张振华.Jack 责编 | 郭芮 出品 | CSDN 博客 本文和大家一起来重温<Java数据结构>经典之作. Java数据结构 要理解Java数据结构,必须能清楚何为数据结构? ...
- java贝塞尔曲线 简单_贝塞尔曲线画出简单弧线
最近产品提了一个需求,说是把View的上面设置成弧形,当初首先想到的就是让美工给切张图,当然这是最简单的,后来想了想,用贝塞尔曲线也可以实现这种效果,正好也熟悉下贝塞尔.结果发现,单纯的画个弧线还是挺 ...
- 单位弹性需求曲线形状_需求曲线为直线时,在单位弹性点上总收益为最大。 - 问答库...
问题: [判断题] 需求曲线为直线时,在单位弹性点上总收益为最大. A . 正确 B . 错误 在泵运行中,若泵吸入()或固体,会发出异常音响,并随之振动. 正确. 错误. 返本期法 正确. 错误. ...
- cass生成曲线要素_公路曲线要素怎么录入CAS?
2015-06-01 在CASS9.0里编辑好横断面设计文件 1.生成里程文件:[工程应用]菜单->[生成里程文件]->[由纵断面线生成] 功能:由道路中桩线即纵断面线生成里程文件. 操作 ...
- python立体爱心_总算认识磨砂爱心美甲步骤
爱心美甲非常的简单,绝对是新手也可以学的会的美甲,下面是磨砂爱心美甲步骤,一起来了解下吧: [磨砂爱心美甲步骤] 01.刷上底胶,照灯固化.刷上一层黑色甲胶(HS72),照灯固化.为了颜色饱满再刷一遍 ...
- java队列_如何彻底搞懂 Java 数据结构?CSDN 博文精选
作者 | 张振华.Jack 责编 | 郭芮 出品 | CSDN 博客 本文和大家一起来重温<Java数据结构>经典之作. Java数据结构 要理解Java数据结构,必须能清楚何为数据结构? ...
- html 如何实现一条竖线边上有 刻度_记一次腾讯面试:进程之间究竟有哪些通信方式?如何通信? ---- 告别死记硬背||CSDN博文精选...
作者:帅地 有一次面试的时候,被问到进程之间有哪些通信方式,不过由于之前没深入思考且整理过,说的并不好.想必大家也都知道进程有哪些通信方式,可是我猜很多人都是靠着"背"来记忆的,所 ...
- c++代码表白_一行 Python 代码能实现什么丧心病狂的功能? | CSDN博文精选
作者 | 许文武 责编 | 郭芮 出品 | CSDN 博客 手头有 109 张头部 CT 的断层扫描图片,我打算用这些图片尝试头部的三维重建.基础工作之一,就是要把这些图片数据读出来,组织成一个三维的 ...
- 工程勘察设计收费标准2002修订版_全过程工程咨询收费模式超全解析
国内全过程工程咨询试点方案中收费标准比较 全过程工程咨询是近两年提出的新的工程建设组织模式,它的管理机制.运行方式还处于探索阶段.各试点省.市相继发布了全过程工程咨询试点工作方案,对全过程工程咨询收费 ...
最新文章
- 机器人(机械臂)动力学建模方法(Newton-Euler equation)
- Hacker(25)----病毒攻防之认识病毒
- java常用类--------File类基本用法
- 脑动力:C语言函数速查效率手册(附DVD光盘1张) [平
- 【Linux】一步一步学Linux——dpkg-query命令(271)
- SAP CRM long text harmonization debug
- 【Leetcode | 52】257. 二叉树的所有路径
- 在windows xp下编译出ffmpeg.exe
- 某高校计算机系举办了一场,国家二级MS+OFFICE高级应用机试(操作题)-试卷25
- 持续集成框架,自动部署服务搭建jenkins+maven+svn(git)+shell
- php dio获取串口数据,flutter通过dio读取二进制数据,比如通过api接口读取图片
- 原来 8 张图,就可以搞懂「零拷贝」了!
- 解决android自定义标题充满的问题
- Spring Data JPA根据属性名查询
- 计算机打音乐光辉岁月,光辉岁月(完美版)
- mysql secure file_mysql load data secure-file-priv问题
- fhkldlkfhj
- 中国“钱”途光明10所大学
- 小繁的Binary_Indexed_Tree学习笔记
- 梯度值与参数更新optimizer.zero_grad(),loss.backward、和optimizer.step()、lr_scheduler.step原理解析
热门文章
- 利用Epplus将数据导出到Excel
- 这三个究极骚气的炫酷底部导航栏,只有经常逛GitHub划水的人才知道!
- Oracle数据库的备份方法
- Quartz 源码解析(一) —— 基本介绍
- 如何解决硬盘固件区损坏?只要学会这几步
- c语言编译器安装到c盘吗,PE安装到C盘的详细教程
- 【从Northwind学习数据库】数据更新
- linux gt240驱动下载,NVIDIA官方发布Linux 256.53正式版驱动
- ShareX(截图工具) 绿色版,功能异常强大
- ubuntu 降级linux内核,ubuntu16内核降级