Bezier curves (a Bezier) of love animation curve thumbs

Disclaimer: This article is a blogger original article, follow the CC 4.0 BY-SA copyright agreement, reproduced, please attach the original source link and this statement.
This link: https://blog.csdn.net/smile_Running/article/details/98170645

Bloggers statement:

Please reprint this article at the beginning of additional links and author information, and marked as reserved. This article by the blogger  Whiskers Meow  original audience for support and advice.

This article first appeared in this    blogger : Whiskers Meow   |   blog home page : https://blog.csdn.net/smile_running

    Directly come to the question, we want to achieve is a point of an Android client application inside Like effects, such as you tap the type of love that picture, it will have a little love, and will be increased in order to curve the way, until it disappeared.

    Text can only be described like this, we look at the direct dynamic map it, the effect is more intuitive.

   This case was written by myself, because before there is a little bit of understanding, and accidentally saw this effect, I feel very praise, on the way to write a little demo, and learn something about this Bezier curve Bethesda Seoul knowledge curve.

    First, we must understand the code of this case, you need to have basic knowledge of the Custom View Android, and you have to understand some formulas and algorithms on the Bezier curve. But never mind, we do not need a deep understanding of Bessel, so long as basic according to the formula, apply the code just fine.

    Take a look at some of the relevant knowledge of the Bezier curve, I also learn from big brother's blog comes from. Let's see what is the Bezier curve?

引用百科的相关资料:
    贝塞尔曲线(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(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);
    }

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

    最后提一下就是点击这个图片才绘制的功能,我是在 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;//构成曲线随机点集合

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

    @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 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 < 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 里面,运行一下就可以看到效果了。

Guess you like

Origin blog.csdn.net/smile_Running/article/details/98170645