贝塞尔曲线 - QQ消息汽包拖拽

1.概述


消息气泡拖拽资料有很多,网上也有开源代码,下载下来就可以用。为什么还要折腾呢?我想证明一下数学已经初中毕业,其次像贝塞尔这种效果还是很常见的,虽然目前我只有一个 APP 用了这个效果。我想一行代码让所有的控件都可以拖动爆炸,不是为了重复造轮子而是为了装B。


QQ消息汽包拖拽

2.效果实现


** 2.1 效果分析 **

看上面的效果感觉有点麻烦,怎么做到任何控件都可以拖动爆炸,我想说网上应该仅此一家。首先可以不要搞得这么麻烦,比如我再上一张图片看下:


简单版.gif

  上面这个效果就比较简单了,先分析一下实现方式。我手指在任何一个位置触摸拖动都会是如上图的这个样式,这个实现的起来就相对简单许多了:

2.1.1: 手指按下拖动的时候有一个拖拽的圆这个圆的半径是不会变化的但是位置会随着手指移动;
2.1.2: 手指按下拖动的时候有一个固定的圆这个圆的是会变化的但是位置不会变化,圆的半径取决于两个圆的距离,两个圆的距离越大圆半径越小,距离越小圆半径越大;
2.1.2: 两个圆之间有一个不规则的东西将两个圆连在一起感觉像粘液一样,这就是大家所说的贝塞尔效果。

2.2 效果实现

2.2.1: 监听触摸绘制两个圆
  我们先挑简单的写,首先监听手指触摸不断的绘制两个圆(固定圆和拖拽圆),如果对触摸监听事件以及画笔使用不是特别熟悉,请留意看看我之前的一些自定义 View 的文章。Android进阶之旅 - 自定义View篇

/**
 * description: 消息气泡拖拽的 View
 * author: Darren on 2017/7/21 10:40
 * email: 240336124@qq.com
 * version: 1.0
 */
public class MessageBubbleView extends View {
    // 拖拽圆的圆心点
    private PointF mDragPoint;
    // 固定圆的圆心点
    private PointF mFixationPoint;
    // 拖拽圆的半径
    private int mDragRadius = 10;
    // 固定圆的半径
    private int mFixationRadius = 7;
    // 固定圆的最小半径
    private int FIXATION_RADIUS_MIN = 3;
    // 固定圆的最大半径
    private int FIXATION_RADIUS_MAX = 7;
    // 用来绘制的画笔
    private Paint mPaint;

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

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

    public MessageBubbleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        // 初始化画笔
        mPaint = new Paint();
        mPaint.setColor(Color.RED);
        mPaint.setDither(true);
        mPaint.setAntiAlias(true);
        // 初始化一些距离
        mDragRadius = dip2px(mDragRadius);
        mFixationRadius = dip2px(mFixationRadius);
        FIXATION_RADIUS_MIN = dip2px(FIXATION_RADIUS_MIN);
        FIXATION_RADIUS_MAX = dip2px(FIXATION_RADIUS_MAX);
    }

    private int dip2px(int dip) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,dip,getResources().getDisplayMetrics());
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (mDragPoint == null && mFixationPoint == null) {
            return;
        }

        // 1.绘制拖拽圆
        canvas.drawCircle(mDragPoint.x, mDragPoint.y, mDragRadius, mPaint);

        // 计算两个圆之间的距离
        int distance = BubbleUtils.getDistance(mDragPoint, mFixationPoint);

        // 计算固定圆的半径,距离越大圆半径越小
        mFixationRadius = FIXATION_RADIUS_MAX - distance / 14;

        if (mFixationRadius > FIXATION_RADIUS_MIN) {
            // 2.绘制固定圆
            canvas.drawCircle(mFixationPoint.x, mFixationPoint.y, mFixationRadius, mPaint);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                initPoint(event.getX(), event.getY());
                break;
            case MotionEvent.ACTION_MOVE:
                updateDragPoint(event.getX(), event.getY());
                break;
            case MotionEvent.ACTION_UP:

                break;
        }
        invalidate();
        return true;
    }

    /**
     * 更新拖拽圆的位置
     *
     * @param x
     * @param y
     */
    private void updateDragPoint(float x, float y) {
        mDragPoint.x = x;
        mDragPoint.y = y;
    }

    /**
     * 初始化圆的位置
     *
     * @param x
     * @param y
     */
    private void initPoint(float x, float y) {
        mDragPoint = new PointF(x, y);
        mFixationPoint = new PointF(x, y);
    }
}

2.2.2: 绘制贝塞尔曲线

贝塞尔曲线绘制起来有点小麻烦,我没记错的话是初中的数学知识,如果你不是特别了解贝塞尔曲线和三角函数可以百度一下,这里给两个链接 贝塞尔曲线三角函数 文章中我就不做过多的解释,下面讲一下求解思路:


  
  看上面这张图画得不咋地但纯手工,蓝色部分和黑色部分是已知,黄色部分是辅助线是可以利用三角公式求出来的,红色部分是未知。我们只要求得角 a,有了角 a 我们就能求 x 和 y 这样我们就知道了 p0 的位置,依葫芦画瓢求能求得 p0,p1,p2,p3的值,有了四个点有了控制点自然就能画贝塞尔曲线了。

    /**
     * 获取 Bezier 曲线
     *
     * @return
     */
    public Path getBezierPath() {
        if (mFixationRadius < FIXATION_RADIUS_MIN) {
            return null;
        }

        Path bezierPath = new Path();
        // 贝塞尔曲线怎么求?

        // 计算斜率
        float dx = mFixationPoint.x - mDragPoint.x;
        float dy = mFixationPoint.y - mDragPoint.y;
        if (dx == 0) {
            dx = 0.001f;
        }
        float tan = dy / dx;
        // 获取角a度值
        float arcTanA = (float) Math.atan(tan);

        // 依次计算 p0 , p1 , p2 , p3 点的位置
        float P0X = (float) (mFixationPoint.x + mFixationRadius * Math.sin(arcTanA));
        float P0Y = (float) (mFixationPoint.y - mFixationRadius * Math.cos(arcTanA));

        float P1X = (float) (mDragPoint.x + mDragRadius * Math.sin(arcTanA));
        float P1Y = (float) (mDragPoint.y - mDragRadius * Math.cos(arcTanA));

        float P2X = (float) (mDragPoint.x - mDragRadius * Math.sin(arcTanA));
        float P2Y = (float) (mDragPoint.y + mDragRadius * Math.cos(arcTanA));

        float P3X = (float) (mFixationPoint.x - mFixationRadius * Math.sin(arcTanA));
        float P3Y = (float) (mFixationPoint.y + mFixationRadius * Math.cos(arcTanA));
        // 求控制点 两个点的中心位置作为控制点
        PointF controlPoint = BubbleUtils.getPointByPercent(mDragPoint, mFixationPoint, 0.5f);

        // 整合贝塞尔曲线路径
        bezierPath.moveTo(P0X, P0Y);
        bezierPath.quadTo(controlPoint.x, controlPoint.y, P1X, P1Y);
        bezierPath.lineTo(P2X, P2Y);
        bezierPath.quadTo(controlPoint.x, controlPoint.y, P3X, P3Y);
        bezierPath.close();

        return bezierPath;
    }

下一节我们来实现一下如何能够让任何一个 View 都能拖动消失,就像 QQ 的消息气泡一样,当然到时可能又免不了源码分析。

所有分享大纲:Android进阶之旅 - 自定义View篇

视频讲解地址:http://pan.baidu.com/s/1nvNZSTV

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容