qcustomplot绘制动态曲线_贝塞尔曲线之爱心点赞代码全解析!| CSDN 博文精选

2320f3ef15796280aa7233e2fb559320.gif

54f7566b1e6e6f2db0c426154e77697b.png

作者 | 威威喵

责编 | 屠敏

出品 | CSDN 博客

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

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

fe90dbb088257dc13889829f38896820.gif

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

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

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

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

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

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

a7f871a1d48c2c22798866b3b485051c.png

a2d9b24ff0542c29be5b275a91fef8cb.gif

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

30129d8186b38e553bcf0a84ff1fd8f5.png

b18cb6bd6abc8ad2344833fcdee6d443.gif

三阶贝塞尔曲线公式:

7e5fb82d8ce56a4240e4a80c4f4a223b.png

585d43b722852fe0c40f244e910d098a.gif

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

13154a9f2f9760718d1b912e24278867.png

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

    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                 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() {@Overridepublic 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() 里面将它进行绘制就搞定啦。

    @Overrideprotected 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);
    }

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

08140eea2f3340b14d166a4dee917efb.png

最后提一下就是点击这个图片才绘制的功能,我是在 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.Nullable;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;//构成曲线随机点集合@Overrideprotected 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, null);
    }public LoveView(Context context, @Nullable AttributeSet attrs) {this(context, attrs, 0);
    }public LoveView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);
        init();
    }@Overrideprotected 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() {@Overridepublic 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 null;
    }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                 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];
    }@Overridepublic 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】

954756a92bb7b1028e89044f48192857.png

 热 文 推 荐 

☞华为方舟编译器开源,我命由我不由天!

☞如何将 MySQL 去重操作优化到极致?| CSDN 博文精选

☞吊打 IE、Firefox,谷歌 Chrome 十年封神记

☞沃尔玛也要发币了,Libra忙活半天为他人做了嫁衣?

☞华为高通5G华山论剑,一文看懂5G芯片背后的明争暗斗

☞做实验、修电脑、命题相亲……IT 大佬教你七夕如何撩妹!

☞自然语言处理十问!独家福利

☞七夕大礼包:26个AI学习资源送给你!

☞痛!为什么说李彦宏无法拯救百度?

8d9942b18350089b3d2f5b1e8d66ac38.gif

8ea2a6475b7175fe63c9f2c2239d82d9.png 你点的每个“在看”,我都认真当成了喜欢 8746f7cebae6e15429925d432d0ed11b.png
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值