贝塞尔曲线(Bezier)之 QQ 消息拖拽动画效果

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/smile_Running/article/details/98217562

博主声明:

转载请在开头附加本文链接及作者信息,并标记为转载。本文由博主 威威喵 原创,请多支持与指教。

本文首发于此   博主威威喵  |  博客主页https://blog.csdn.net/smile_running

    这几天突然发现 QQ 的消息拖拽动画效果还挺不错的,以前都没去留意它,这几看了一点关于贝塞尔曲线的知识,这不刚好沙场练兵。于是从昨天开始呢,我就已经开始补点高数的知识了。虽然我现在已经准大四了,眨眼间就快毕业了,高数的知识还是从大一开始学习的,现在基本忘了差不多了。

    扯了一点关于我的学习经历,回到本篇问题的关键,QQ 消息拖拽效果是怎样的呢?于是,我在模拟器装了一个 QQ 应用,特地找了一下小号,记得这个号好像是我初中申请的账号,以前那会儿 cf、飞车、dnf 特别流行,搞了几个小号搬砖,哈哈。我们来看看消息拖拽的效果吧:

    这样的效果做起来并不简单,尤其是曲线的计算方面,如果你也像我一样忘了高数的知识点的话,建议你去翻翻三角函数那部分的知识, 本文不会教你这些基本公式,也不会教你自定义 view 的基本流程,本篇目的:计算和实现拖拽的粘性效果。如果这些基本知识不具备的话,推荐你去看下我的自定义 view 相关文章。

    有了上一篇(点击这里:贝塞尔曲线(Bezier)之爱心点赞曲线动画效果)对贝塞尔曲线的基本了解和写了一个小案例的铺垫,在这次写这个 QQ 消息拖拽效果的时候,显然轻松了许多。好了,废话就说这么多,下面进入重点内容。

    首先,看上面的效果显示情况,可以看成两个小圆,一个比较大一点,可以拖拽出去,另一个小一点,但会随着两个圆的距离改变大小。我们的步骤:在 onDraw 里面绘制两个圆,用手指可以拖动一个大圆,并且小圆的大小会随着两圆的距离更改。这部分代码非常简单,我就不做多的介绍了,如果你对下面代码有不解之处,还请自己补充知识。直接贴代码:

package nd.no.xww.qqmessagedragview;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

/**
 * @author xww
 * @desciption : 仿 QQ 消息拖拽消失的效果(大圆:不会消失,且大小一致。小圆:与大圆的距离协调改变大小)
 * @date 2019/8/2
 * @time 8:54
 */
public class QQMessageDragView extends View {

    private Paint mPaint;

    //大圆
    private float mBigCircleX;
    private float mBigCircleY;
    private final int BIG_CIRCLE_RADUIS = 50;
    //小圆
    private float mSmallCircleX;
    private float mSmallCircleY;
    private int mSmallDefRaduis = 40;
    private int mSmallHideRaduis = 15;
    private int mSmallCircleRaduis = mSmallDefRaduis;

    private Bitmap mMessageBitmap;

    private void init() {
        mPaint = new Paint();
        mPaint.setDither(true);
        mPaint.setAntiAlias(true);
        mPaint.setColor(getResources().getColor(android.R.color.holo_red_dark));

        mMessageBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.message);
        mMessageBitmap = Bitmap.createScaledBitmap(mMessageBitmap, 150, 150, false);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY ? MeasureSpec.getSize(widthMeasureSpec) : 200
                , MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY ? MeasureSpec.getSize(heightMeasureSpec) : 200);
    }

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

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

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

    @Override
    protected void onDraw(Canvas canvas) {
        if (mSmallCircleRaduis > mSmallHideRaduis) {
            canvas.drawCircle(mSmallCircleX, mSmallCircleY, mSmallCircleRaduis, mPaint);
        }
        canvas.drawCircle(mBigCircleX, mBigCircleY, BIG_CIRCLE_RADUIS, mPaint);
//        canvas.drawBitmap(mMessageBitmap, mBigCircleX, mBigCircleY, mPaint);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float downX = event.getX();
        float downY = event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mSmallCircleRaduis = mSmallDefRaduis;
                mSmallCircleX = mBigCircleX = downX;
                mSmallCircleY = mBigCircleY = downY;
                break;
            case MotionEvent.ACTION_MOVE:
                mBigCircleX = event.getX();
                mBigCircleY = event.getY();
                int disCircle = calculateDisCircle(mSmallCircleX, mSmallCircleY, mBigCircleX, mBigCircleY);
                mSmallCircleRaduis = mSmallDefRaduis - disCircle / mSmallHideRaduis;
                break;
            case MotionEvent.ACTION_UP:
                mSmallCircleRaduis = 0;
                break;
        }
        invalidate();
        return true;
    }

    // 两点之间的距离公式 √(x2-x1)²+(y2-y1)²
    private int calculateDisCircle(float mSmallCircleX, float mSmallCircleY, float mBigCircleX, float mBigCircleY) {
        return (int) Math.sqrt(Math.pow((mSmallCircleX - mBigCircleX), 2) + Math.pow((mSmallCircleY - mBigCircleY), 2));
    }

}

运行上面的代码,你就会看到和我一样的效果:

   好了,上面的代码只是做了一个铺垫,也是必须实现的第一步。接下来重头戏开始,我们讲讲一些数学相关知识吧,本人高数也不怎么样,大学除了基本必修高数,也没去深入学习,不过这也不影响我们下面的操作。

    首先,扔出一张草图,画的就这样,将就看吧:

    这上面应该不难看懂吧,两个红色圆就相当于我们拖拽的圆一样,从上面的草图中,我们目前已知的有 c1 c2 r1 r2 这四个属性值,c1 c2 代表圆心坐标,r1 r2 是半径。

    当用手指去拖拽大圆的时候,它们之间的联系就用那两根蓝色的曲线来表示,两曲线对应的在两圆上的坐标点就是 p1 p2 p3 p4 四个点,这四个点会伴随这两圆的距离发生改变,你可以想象一下效果。

    那么,从上图中,我们就要去计算 p1 p2 p3 p4 这四个点的坐标,然后将四点封闭起来绘制成路径即可。可是,说的比较轻巧,从目前我们已知的条件当中,能用得上的就 c1 c2 r1 r2 四个了,如何去求呢?看接下来的这张图:

    从这张图的计算过程中,我们可以求得绿色三角形的角 a 的相关方程式。因为我们已知 c1 圆心的坐标值,就可以得出 p1 点的坐标值,如上图 p1x  p1y 的值。

    这样的话,我们可以利用三角函数公式得出 b 边和 c 边的值,如上图,最终得到的一个方程式中,仅存在一个角 a 是我们未知的,接下来我们就要去计算角 a 的值,看下图:

    来到第一张图,看上面的黄色辅助线,假设它形成的是直角。我们就可以得到这两条辅助线的边长 dy 和 dx 。又根据三角形的补角两平行线之间的夹角相等的定理,我们得出图中的三个角 a 都是一样的大小。

    这样我们可以得到一个等式: tanA = dy / dx ,最终,角 a = arctan( tanA ) 

    这时候我们就取到了 a 相关的等式了,而 dx dy 都是可以计算出来的,所以一连串下来,相关的等式都成立了,从而就可以计算出一个点 p1,获得 p1 点后,p2 p3 p4 不就手到擒来嘛。

    最后要想形成贝塞尔曲线的效果,除了 p1 p2 p3 p4 以外,我们还需要一个控制点,如图上的点 M,它是形成曲线的控制点,也是至关重要的一个点,它的坐标就是 M点 ( (c1x+c2x) / 2  ,  (c1y+c2y) / 2 )

    那么本篇数学相关的计算部分就已经结束了,你还以为程序员不需要数学知识嘛,哈哈。下面就是该怎么写程序了,把数学公式化为程序代码,这就得看你的编程水平啦。

    我写了好一会儿,都是那个坐标值正负的问题卡了我挺久的,不过最终还是把代码给搞出来了,四个点的计算方法如下:

    private float p1X;
    private float p1Y;
    private float p2X;
    private float p2Y;
    private float p3X;
    private float p3Y;
    private float p4X;
    private float p4Y;
    //控制点
    private float controlX;
    private float controlY;

    private float dx, dy;
    private double angleA;
    private double tanA;
    private Path bezierPath;
    private Path mBezierPath;

    /**
     * 贝塞尔 p1 p2 p3 p4 四个点坐标的计算
     *
     * @return
     */
    private Path drawDragBezier() {
        if (mSmallCircleRaduis < mSmallHideRaduis) {
            return null;
        }

        dx = mBigCircleX - mSmallCircleX;
        dy = mBigCircleY - mSmallCircleY;

        tanA = dy / dx;
        angleA = Math.atan(tanA);

        //控制点的计算
        controlX = (mSmallCircleX + mBigCircleX) / 2;
        controlY = (mSmallCircleY + mBigCircleY) / 2;

        p1X = (float) (mSmallCircleX + Math.sin(angleA) * mSmallCircleRaduis);
        p1Y = (float) (mSmallCircleY - Math.cos(angleA) * mSmallCircleRaduis);

        p2X = (float) (mBigCircleX + Math.sin(angleA) * BIG_CIRCLE_RADUIS);
        p2Y = (float) (mBigCircleY - Math.cos(angleA) * BIG_CIRCLE_RADUIS);

        p3X = (float) (mBigCircleX - Math.sin(angleA) * BIG_CIRCLE_RADUIS);
        p3Y = (float) (mBigCircleY + Math.cos(angleA) * BIG_CIRCLE_RADUIS);

        p4X = (float) (mSmallCircleX - Math.sin(angleA) * mSmallCircleRaduis);
        p4Y = (float) (mSmallCircleY + Math.cos(angleA) * mSmallCircleRaduis);

        //绘制路径
        bezierPath = new Path();
        bezierPath.moveTo(p1X, p1Y);
        bezierPath.quadTo(controlX, controlY, p2X, p2Y);
        bezierPath.lineTo(p3X, p3Y);
        bezierPath.quadTo(controlX, controlY, p4X, p4Y);
        bezierPath.close();
        return bezierPath;
    }

然后呢,使用就很简单了。返回一个路径,我们只要画出来就好了,修改 onDraw 代码如下:

    @Override
    protected void onDraw(Canvas canvas) {
        mBezierPath = drawDragBezier();
        if (mBezierPath != null) {
            canvas.drawCircle(mSmallCircleX, mSmallCircleY, mSmallCircleRaduis, mPaint);
            canvas.drawPath(mBezierPath, mPaint);
        }
        canvas.drawCircle(mBigCircleX, mBigCircleY, BIG_CIRCLE_RADUIS, mPaint);
    }

好了吧,点击运行,你将会看到如下的效果:

    最后做了一点点小优化,拖拽时没有超出范围可以回到原来的位置,若超出拖拽的极限方法,导致两个圆失去关联时,代表要摧毁那个大圆,手指松开那一刹那,要将它隐藏掉,效果如下:

    那么至此,我们的QQ消息的粘性动画已经实现了,代码倒是不难,难的是通过数学公式来计算出 p1 p2 p3 p4 点的坐标值,这可能会卡住很多人,主要还是因为数学功底不足,还是抽时间补补数学,它可是个很有魅力的机灵鬼。

补充:(对上面的特效进行优化处理)

    今天,8 月 8 日,早上 5 点半左右,台湾不幸遭到了地震,连我在福建中北部地带都能偶感晃动,我好像迷迷糊糊中感觉床在摇晃,是 6 点多级的地震,在此祝愿台湾人民安好。而且,受台风的影响,家里下了好大的雨,不过倒是清凉了许多。

    好了,让我们来优化一下这个效果吧,博主之前还没有处理的一些细节问题,比如这个 QQ 消息拖动,如果我们没有将它拖断掉,也就是线还连着,上次的做法是将它的坐标赋值给初始按下的坐标,这导致的效果是一瞬间就回去了,动画太过生硬,体验不是特别好,接下来我们来优化一下,让它慢慢的回去,有一个过渡时间。

    上次的代码是这样做的,直接回到手指起始按下的那一个点位置:

            case MotionEvent.ACTION_UP:
                if (!isAttached) {
                    //被扯断了
                    isShowed = false;
                } else if (mSmallCircleRaduis >= mSmallHideRaduis) {//小圆的半径如果大于显示的半径,意味着没有拖段线
                    isShowed = true;//大圆要显示
                    //回到原来手指按下的位置
                    mBigCircleX = mSmallCircleX;
                    mBigCircleY = mSmallCircleY;
                }
                mSmallCircleRaduis = 0;//每次手松开,小圆半径规 0
                break;

    这个肯定不行,要对它的值进行修改,我们的思想是这个样子的,看图

    我们需要慢慢的改变大圆的半径,就相当于改变被我们拉出来的那个圆的 x 坐标和 y 坐标,我们给它定一个时间段,让它们一起开始变化,这个就得使用到属性动画来处理了,我们把上部分的代码做如下修改即可

            case MotionEvent.ACTION_UP:
                if (!isAttached) {
                    //被扯断了
                    isShowed = false;
                } else if (mSmallCircleRaduis >= mSmallHideRaduis) {//小圆的半径如果大于显示的半径,意味着没有拖段线。松开手,弹回去
                    isShowed = true;//大圆要显示

                    animatorSet = new AnimatorSet();
                    xAnimator = ObjectAnimator.ofFloat(mBigCircleX, mSmallCircleX);
                    xAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                        @Override
                        public void onAnimationUpdate(ValueAnimator animation) {
                            mBigCircleX = (float) animation.getAnimatedValue();

                            int disCircle = calculateDisCircle(mSmallCircleX, mSmallCircleY, mBigCircleX, mBigCircleY);
                            mSmallCircleRaduis = mSmallDefRaduis - disCircle / mSmallHideRaduis;//在拖动过程中,小圆半径一直在缩小
                        }
                    });

                    yAnimator = ObjectAnimator.ofFloat(mBigCircleY, mSmallCircleY);
                    yAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                        @Override
                        public void onAnimationUpdate(ValueAnimator animation) {
                            mBigCircleY = (float) animation.getAnimatedValue();
                            invalidate();
                        }
                    });
                    animatorSet.playTogether(xAnimator, yAnimator);
                    animatorSet.setInterpolator(new OvershootInterpolator(3f));
                    animatorSet.setDuration(10000);
                    animatorSet.start();
                    animatorSet.addListener(new AnimatorListenerAdapter() {
                        @Override
                        public void onAnimationEnd(Animator animation) {
                            //动画结束时,隐藏小圆
                            mSmallCircleRaduis = 0;//每次手松开,小圆半径规 0
                        }
                    });
                }
                break;

     那么,绘制那个粘性的贝塞尔曲线也要一直绘制了,不能松开就没了吧,所以要把 onDraw 的里面的代码改为如下:

    @Override
    protected void onDraw(Canvas canvas) {
        mBezierPath = drawDragBezier();
        //两个圆还有联系
        if (mBezierPath != null) {
            canvas.drawPath(mBezierPath, mPaint);
        }
        if (isAttached) {
            canvas.drawCircle(mSmallCircleX, mSmallCircleY, mSmallCircleRaduis, mPaint);
        }
        //如果是显示的
        if (isShowed) {
            canvas.drawCircle(mBigCircleX, mBigCircleY, BIG_CIRCLE_RADUIS, mPaint);
        }
    }

    好了,一起来看看效果吧。为了使效果更加明显,我特地把缩回来的动画改为 10S,足够你看清楚了吧

    我给它加了一个插值器,回来的时候有一个反弹的效果!弹弹弹,弹走鱼尾纹。。。 

    不过呢,还有一个地方需要优化的,就是拖断掉的时候,再松开会有一个消失的效果,我就搞的简单一点,让它慢慢的消失就好了。不过也可以学那个爆炸效果,会比较炫酷一点,我找了一下那个爆炸的图片,懒得图改成透明颜色了,需要的自己去查一查帧动画就好了。

    下面是放快的效果

最后的完整代码

package nd.no.xww.qqmessagedragview;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.OvershootInterpolator;

/**
 * @author xww
 * @desciption : 仿 QQ 消息拖拽消失的效果(大圆:不会消失,且大小一致。小圆:与大圆的距离协调改变大小)
 * @date 2019/8/2
 * @time 8:54
 * @博主:威威喵
 */
public class QQMessageDragView extends View {

    private Paint mPaint;

    //大圆
    private float mBigCircleX;
    private float mBigCircleY;
    private float mBigCircleRaduis = 50;
    //小圆
    private float mSmallCircleX;
    private float mSmallCircleY;
    private int mSmallDefRaduis = 40;
    private int mSmallHideRaduis = 15;//扯断的距离
    private int mSmallCircleRaduis = mSmallDefRaduis;

    private Bitmap mMessageBitmap;

    private boolean isAttached;//代表两个关联
    private boolean isFirst = true;//显示大圆


    private void init() {
        mPaint = new Paint();
        mPaint.setDither(true);
        mPaint.setAntiAlias(true);
        mPaint.setColor(getResources().getColor(android.R.color.holo_red_dark));
        mPaint.setTextSize(30f);
        mMessageBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.message);
        mMessageBitmap = Bitmap.createScaledBitmap(mMessageBitmap, 150, 150, false);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY ? MeasureSpec.getSize(widthMeasureSpec) : 200
                , MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY ? MeasureSpec.getSize(heightMeasureSpec) : 200);
    }

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

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

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

    @Override
    protected void onDraw(Canvas canvas) {
        mBezierPath = drawDragBezier();
        //两个圆还有联系
        if (mBezierPath != null) {
            canvas.drawPath(mBezierPath, mPaint);
        }
        if (isAttached) {
            canvas.drawCircle(mSmallCircleX, mSmallCircleY, mSmallCircleRaduis, mPaint);
        }
        //如果第一次,不绘制圆
        if (isFirst) {
            return;
        }
        canvas.drawCircle(mBigCircleX, mBigCircleY, mBigCircleRaduis, mPaint);

    }

    private float raduis;

    AnimatorSet animatorSet;
    ValueAnimator xAnimator;
    ValueAnimator yAnimator;

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float downX = event.getX();
        float downY = event.getY();

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                // 两个圆关联了
                mBigCircleRaduis = 50; // 大圆的初始值
                isFirst = false;
                isAttached = true;

                mSmallCircleRaduis = mSmallDefRaduis;
                mSmallCircleX = mBigCircleX = downX;
                mSmallCircleY = mBigCircleY = downY;
                break;
            case MotionEvent.ACTION_MOVE:
                mBigCircleX = event.getX();
                mBigCircleY = event.getY();

                int disCircle = calculateDisCircle(mSmallCircleX, mSmallCircleY, mBigCircleX, mBigCircleY);
                mSmallCircleRaduis = mSmallDefRaduis - disCircle / mSmallHideRaduis;//在拖动过程中,小圆半径一直在缩小

                if (mSmallCircleRaduis < mSmallHideRaduis) {//小圆的半径如果太小了,不显示了。
                    isAttached = false;//表示两个圆没有关联了,意味这线被拖断了
                }
                break;
            case MotionEvent.ACTION_UP:
                if (!isAttached) { // 被扯断了,两圆没有联系了
                    ValueAnimator raduisAnimator = ObjectAnimator.ofFloat(mBigCircleRaduis, 0);
                    raduisAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                        @Override
                        public void onAnimationUpdate(ValueAnimator animation) {
                            mBigCircleRaduis = (float) animation.getAnimatedValue();
                            invalidate();
                        }
                    });
                    raduisAnimator.setDuration(500);
                    raduisAnimator.start();
                } else if (mSmallCircleRaduis >= mSmallHideRaduis) {//小圆的半径如果大于显示的半径,意味着没有拖段线。松开手,弹回去
                    animatorSet = new AnimatorSet();
                    xAnimator = ObjectAnimator.ofFloat(mBigCircleX, mSmallCircleX);
                    xAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                        @Override
                        public void onAnimationUpdate(ValueAnimator animation) {
                            mBigCircleX = (float) animation.getAnimatedValue();

                            int disCircle = calculateDisCircle(mSmallCircleX, mSmallCircleY, mBigCircleX, mBigCircleY);
                            mSmallCircleRaduis = mSmallDefRaduis - disCircle / mSmallHideRaduis;//在拖动过程中,小圆半径一直在缩小
                        }
                    });

                    yAnimator = ObjectAnimator.ofFloat(mBigCircleY, mSmallCircleY);
                    yAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                        @Override
                        public void onAnimationUpdate(ValueAnimator animation) {
                            mBigCircleY = (float) animation.getAnimatedValue();
                            invalidate();
                        }
                    });
                    animatorSet.playTogether(xAnimator, yAnimator);
                    animatorSet.setInterpolator(new OvershootInterpolator(2.5f));
                    animatorSet.setDuration(500);
                    animatorSet.start();
                    animatorSet.addListener(new AnimatorListenerAdapter() {
                        @Override
                        public void onAnimationEnd(Animator animation) {
                            //动画结束时,隐藏小圆
                            mSmallCircleRaduis = 0;//每次手松开,小圆半径规 0
                        }
                    });
                }
                break;
        }
        invalidate();
        return true;
    }


    // 两点之间的距离公式 √(x2-x1)²+(y2-y1)²
    private int calculateDisCircle(float mSmallCircleX, float mSmallCircleY, float mBigCircleX, float mBigCircleY) {
        return (int) Math.sqrt(Math.pow((mSmallCircleX - mBigCircleX), 2) + Math.pow((mSmallCircleY - mBigCircleY), 2));
    }

    private float p1X;
    private float p1Y;
    private float p2X;
    private float p2Y;
    private float p3X;
    private float p3Y;
    private float p4X;
    private float p4Y;
    //控制点
    private float controlX;
    private float controlY;

    private float dx, dy;
    private double angleA;
    private double tanA;
    private Path bezierPath;
    private Path mBezierPath;

    /**
     * 贝塞尔 p1 p2 p3 p4 四个点坐标的计算
     *
     * @return
     */
    private Path drawDragBezier() {
        if (mSmallCircleRaduis < mSmallHideRaduis || !isAttached) {
            return null;
        }

        dx = mBigCircleX - mSmallCircleX;
        dy = mBigCircleY - mSmallCircleY;

        tanA = dy / dx;
        angleA = Math.atan(tanA);

        //控制点的计算
        controlX = (mSmallCircleX + mBigCircleX) / 2;
        controlY = (mSmallCircleY + mBigCircleY) / 2;

        p1X = (float) (mSmallCircleX + Math.sin(angleA) * mSmallCircleRaduis);
        p1Y = (float) (mSmallCircleY - Math.cos(angleA) * mSmallCircleRaduis);

        p2X = (float) (mBigCircleX + Math.sin(angleA) * mBigCircleRaduis);
        p2Y = (float) (mBigCircleY - Math.cos(angleA) * mBigCircleRaduis);

        p3X = (float) (mBigCircleX - Math.sin(angleA) * mBigCircleRaduis);
        p3Y = (float) (mBigCircleY + Math.cos(angleA) * mBigCircleRaduis);

        p4X = (float) (mSmallCircleX - Math.sin(angleA) * mSmallCircleRaduis);
        p4Y = (float) (mSmallCircleY + Math.cos(angleA) * mSmallCircleRaduis);

        //绘制路径
        bezierPath = new Path();
        bezierPath.moveTo(p1X, p1Y);
        bezierPath.quadTo(controlX, controlY, p2X, p2Y);
        bezierPath.lineTo(p3X, p3Y);
        bezierPath.quadTo(controlX, controlY, p4X, p4Y);
        bezierPath.close();
        return bezierPath;
    }

}

    最后呢,给出本效果的全部代码,期间由于隔了几天再来继续写这个效果,代码的关键处也补了一点点注释。哈哈,隔了几天没去瞧一眼,差点给我整懵逼了,还好,还好。

猜你喜欢

转载自blog.csdn.net/smile_Running/article/details/98217562
今日推荐