红橙Darren视频笔记 贝塞尔曲线实现消息拖拽粘性效果 画笔练习

效果图
在这里插入图片描述
要实现这样的效果 我们要先知道那段弧线是如何绘制的,实际上那段弧线就是贝塞尔曲线

一. 什么是贝塞尔曲线

https://www.jianshu.com/p/8f82db9556d2
上面有个链接 里面的动图比较形象
在这里插入图片描述
二阶Bezier曲线公式:
P0A/P1A=P1B/P2B=AC/BC
在维持以上比例的同时,A点从P0出发到达P1点
点C所经过的路径就是Bezier曲线
其中P1叫做控制点,控制点不同,绘制的路径也会大有不同
当控制点有0个时,点C的路径叫1阶Bezier曲线
当控制点有1个时,点C的路径叫2阶Bezier曲线
当控制点有2个时,点C的路径叫3阶Bezier曲线
以此类推
这里我们只需要了解2阶Bezier曲线即可
最后绘制路径大致如下
在这里插入图片描述
图上t表示比值 说了一堆东西 其实要用的东西很简单

二.分析实现思路

2.1 有两个圆,一个是固定圆,位置固定不动但是半径会变化(两个圆之间的距离越远半径就越小)
还有一个是拖拽圆,半径是不变的,位置是跟随手指移动
遇到问题:getRawY的坐标似乎不对 似乎差一个状态栏的高度
解决:使用getY代替 getY是相对于当前的View的Y getRawY是相对于android.id.content的Y,注意不包含状态栏和titileBar

2.2 在两个圆之间中间会有一个粘性的不规则图像(贝塞尔曲线)
求法如下,需要用到简单的三角函数
在这里插入图片描述
已知r1,r2的坐标,大圆与小圆的半径,A1B1和A2B2分别垂直于r1r2,求A1A2B1B2四点坐标
乍一看好像没什么头绪,让我们画几个辅助线
在这里插入图片描述
如图 ∠C1和∠C2都是直角
那么A1的坐标可以表示为 (r1.x+r1C1,r1.y+r1y+A1C1)
B1B2A2三点的坐标类似
且三角形r1C1A1相似于三角形r1C2r2
那么∠r1A1C1=∠r1r2C2=∠a
我们可以求得∠r1r2C2 即∠a
(r1.y-r2.y)/(r1.x-r2.x)=tan∠r1r2C2
那么∠a = arctan((r1.y-r2.y)/(r1.x-r2.x))
r1C1/r1A1=sin∠a
为了简便 我们记r1C1=x A1C1=y
x=sin∠a小圆半径
y=cos∠a
小圆半径
那么x=sin(arctan((r1.y-r2.y)/(r1.x-r2.x)))r1A1
y=sin(arctan((r1.y-r2.y)/(r1.x-r2.x)))r1A1
A1B1的坐标分别表示为
r1x+x,r1y+y
r1x-x,r1y-y
计算A2B2的算法类似 只不过此时应该使用r2的半径
x’=sin∠a
大圆半径
y’=cos∠a
大圆半径
接下来计算控制点的位置 我们取A1A2连线的中心点以及B1B2的中心点
其实最复杂的是Bezier曲线的绘制,但是Android已经给我们写好了方法 我们直接调用即可,即Path的quadTo方法
坑:这里面涉及很多加加减减,一不小心写错了,效果不对的时候排查很难,所以写的时候就得小心。这次我就栽在这里,写错了两个±号,又找了40分钟。。。
最终实现效果大致这样
在这里插入图片描述

三.部分代码

自定view:

class BezierView extends View {
    private PointF mFixPoint;
    private PointF mFingerPoint;
    private Paint mPaint;
    private float mFixPointInitRadius = 10;//固定圆初始半径
    private float mFixPointChangedRadius;//挪动手指后的固定圆半径
    private float mFingerPointRadius = 10;


    public BezierView(Context context) {
        this(context, null);
    }

    public BezierView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public BezierView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initPaint();
    }

    private void initPaint() {
        mPaint = new Paint();
        mPaint.setDither(true);
        mPaint.setAntiAlias(true);
        mPaint.setColor(Color.RED);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (mFingerPoint == null || mFixPoint == null) {
            return;
        }
        //绘制固定圆
        if (mFixPointChangedRadius > 5) {//当手指离固定圆太远 不绘制固定圆
            canvas.drawCircle(mFixPoint.x, mFixPoint.y, mFixPointChangedRadius, mPaint);
            canvas.drawPath(getBezierPath(), mPaint);
        }

        //绘制跟随手指的圆
        canvas.drawCircle(mFingerPoint.x, mFingerPoint.y, mFingerPointRadius, mPaint);
    }

    //计算两点之间的距离
    double distanceOfPoints(PointF point1, PointF point2) {
        //勾股定理
        return Math.sqrt(Math.pow(point1.x - point2.x, 2) + Math.pow(point1.y - point2.y, 2));
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mFixPoint = new PointF();
                mFingerPoint = new PointF();
                mFixPoint.x = event.getX();
                mFixPoint.y = event.getY();
                mFingerPoint.x = event.getX();
                mFingerPoint.y = event.getY();
            case MotionEvent.ACTION_MOVE:
                mFingerPoint.x = event.getX();
                mFingerPoint.y = event.getY();
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        //根据手指的移动 计算两点之间的距离 根据距离改变mFixPoint的半径
        mFixPointChangedRadius = calculateFixPointRadius(distanceOfPoints(mFingerPoint, mFixPoint));
        invalidate();
        return true;
    }

    private float calculateFixPointRadius(double distanceOfPoints) {
        return mFixPointInitRadius - (float) distanceOfPoints / 25f;//除数(20f)越大,代表半径随距离变化的程度越小
    }

    private Path getBezierPath() {
        Path bezierPath = new Path();
        //计算四点坐标
        //先计算∠a ∠a = arctan((r1.y-r2.y)/(r1.x-r2.x))
        double angleA = Math.atan((mFixPoint.y - mFingerPoint.y) / (mFixPoint.x - mFingerPoint.x));
        //计算x y以及 x' y'
        float x = (float) (Math.sin(angleA) * mFixPointChangedRadius);
        float y = (float) (Math.cos(angleA) * mFixPointChangedRadius);

        float x2 = (float) (Math.sin(angleA) * mFingerPointRadius);
        float y2 = (float) (Math.cos(angleA) * mFingerPointRadius);
        //四个点坐标分别为 A1  B1 A2 B2 他们坐标表示为
        float A1x = mFixPoint.x + x;
        float A1y = mFixPoint.y - y;

        float B1x = mFixPoint.x - x;
        float B1y = mFixPoint.y + y;

        float A2x = mFingerPoint.x + x2;
        float A2y = mFingerPoint.y - y2;

        float B2x = mFingerPoint.x - x2;
        float B2y = mFingerPoint.y + y2;

        float controlPint1x = (mFixPoint.x + mFingerPoint.x) * 0.5f;
        float controlPint1y = (mFixPoint.y + mFingerPoint.y) * 0.5f;

        //绘制路径为A1 A2 B2 B1
        bezierPath.moveTo(A1x, A1y);
        bezierPath.quadTo(controlPint1x, controlPint1y, A2x, A2y);
        bezierPath.lineTo(B2x, B2y);
        bezierPath.quadTo(controlPint1x, controlPint1y, B1x, B1y);
        bezierPath.close();
        return bezierPath;
    }
}

后记

其实我觉得贝塞尔曲线的绘制四个点有一点问题,应该如下图所示:
在这里插入图片描述
这样计算应该更正确,不过这样计算更复杂(主要是不会算),而且效果应该差不了多少,想想还是放弃了,就直接用的上面的计算方式计算贝塞尔曲线的四个点

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值