贝塞尔曲线之聊天未读数气泡-基础

期初,对贝塞尔曲线都保持一定的距离,后来需要实现一定的效果,在简单了解贝塞尔曲线之后,自己试一下


bubble.gif
  • 首先分析一下它有几种状态
    1. 气泡静止状态 -- 画气泡小球和数字
    2. 气泡相连状态 -- 画两个相连的气泡小球(类似橡皮筋效果)、数字
    3. 气泡分离状态 -- 单个气泡小球的拖动
    4. 气泡消失状态 -- 爆炸动画
  • 需要用到的知识
    1. 跟path、绘图相关---相连的粘连效果(贝塞尔曲线)
    2. 监听拖拽事件
    3. 动画

经过分析,我们先完成画的工作,步骤如下

        //1.  气泡静止状态
            //1.画禁止气泡
            //2.画文字
        //2.  气泡相连状态
            //1.画静止气泡
            //2.画文字
            //3.画相连的曲线
            //4.画拖拽的气泡
        //3.  气泡分离状态
            //1.画文字
            //2.画拖拽的气泡
        //4.  气泡消失状态

这是最直接的画法,但是发现有很多重复的地方,在1.1静禁止气泡,其实可以看做需要拖拽的气泡,所以有了如下思路

        //1.当状态不为消失状态时,画 拖拽气泡和文字
        //2.当状态为相连是,画禁止气泡 画曲线
        //3.消失状态,画爆炸动画

这样就巧妙的避开了重复的地方~ 就像本市(天津)巧妙的避开下雪一样~

pic.jpg

回到正题。。。

        // 1、不为消失状态时 画拖拽的气泡 和 文字
        if(mBubbleState != BUBBLE_STATE_DISMISS){}

        // 2、当状态为相连是,画相连的气泡状态
        if(mBubbleState == BUBBLE_STATE_CONNECT) { }

        // 3、画消失状态---爆炸动画
        if(mIsBurstAnimStart){ }

这就是最终的绘画思路,但是这样画有一个问题,我们最后再说~
然后开始画气泡和文字

        // 1、画拖拽的气泡 和 文字
        if(mBubbleState != BUBBLE_STATE_DISMISS){
            canvas.drawCircle(mBubMoveableCenter.x,mBubMoveableCenter.y,
                    mBubMoveableRadius,mBubblePaint);

            mTextPaint.getTextBounds(mTextStr,0,mTextStr.length(),mTextRect);

            canvas.drawText(mTextStr,mBubMoveableCenter.x - mTextRect.width() / 2,
                    mBubMoveableCenter.y + mTextRect.height() / 2,mTextPaint);
        }

PointF mBubMoveableCenter 可动气泡的圆心,画一个圆,然后拿到Text的Bound计算文字的位置。
然后是第二个状态

        // 2、当状态为相连时
        if(mBubbleState == BUBBLE_STATE_CONNECT) {
            //画禁止气泡 画曲线
            // 1、画静止气泡
            canvas.drawCircle(mBubStillCenter.x,mBubStillCenter.y,
                    mBubStillRadius,mBubblePaint);
// 2、画相连曲线
            // 计算控制点坐标,两个圆心的中点
            int iAnchorX = (int) ((mBubStillCenter.x + mBubMoveableCenter.x) / 2);
            int iAnchorY = (int) ((mBubStillCenter.y + mBubMoveableCenter.y) / 2);

            float cosTheta = (mBubMoveableCenter.x - mBubStillCenter.x) / mDist;
            float sinTheta = (mBubMoveableCenter.y - mBubStillCenter.y) / mDist;

            //D
            float iBubStillStartX = mBubStillCenter.x - mBubStillRadius * sinTheta;
            float iBubStillStartY = mBubStillCenter.y + mBubStillRadius * cosTheta;
            //C
            float iBubMoveableEndX = mBubMoveableCenter.x - mBubMoveableRadius * sinTheta;
            float iBubMoveableEndY = mBubMoveableCenter.y + mBubMoveableRadius * cosTheta;
            //B
            float iBubMoveableStartX = mBubMoveableCenter.x + mBubMoveableRadius * sinTheta;
            float iBubMoveableStartY = mBubMoveableCenter.y - mBubMoveableRadius * cosTheta;
            //A
            float iBubStillEndX = mBubStillCenter.x + mBubStillRadius * sinTheta;
            float iBubStillEndY = mBubStillCenter.y - mBubStillRadius * cosTheta;

            mBezierPath.reset();
            // 画下半弧
            mBezierPath.moveTo(iBubStillStartX,iBubStillStartY);
            mBezierPath.quadTo(iAnchorX,iAnchorY,iBubMoveableEndX,iBubMoveableEndY);
            // 画上半弧
            mBezierPath.lineTo(iBubMoveableStartX,iBubMoveableStartY);
            mBezierPath.quadTo(iAnchorX,iAnchorY,iBubStillEndX,iBubStillEndY);
            mBezierPath.close();
            canvas.drawPath(mBezierPath,mBubblePaint);
        }
QQ气泡.png

图中绿色标注的角度都是一样的,已知 两圆心坐标,半径,求ABCD
然后通过二阶贝塞尔曲线连上四个点,因为之前忽略了 正负值,所以这里从图中D点开始画的。
如 图中 D 点 图中的sinTheta 其实是一个负值,所以在求不动圆心(mBubStillCenter)的时候,其实是X值变大,Y值变大,就到了图中的D点~ 其他点类似

这样就把主要用到点都写上了,然后就是如何给它们赋值,赋值就需要在触摸事件里对他们进行处理。

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            //手指下落
            case MotionEvent.ACTION_DOWN:
            {
                //当状态不为消失的时候
                if(mBubbleState != BUBBLE_STATE_DISMISS){
                    //获取下落点到小圆的距离
                    mDist = (float) Math.hypot(event.getX() - mBubStillCenter.x,
                            event.getY() - mBubStillCenter.y);
                    //如果距离小于 圆半径+偏移量
                    if(mDist < mBubbleRadius + MOVE_OFFSET){
                        // 加上MOVE_OFFSET是为了方便拖拽
                        mBubbleState = BUBBLE_STATE_CONNECT;
                    }else{
                        mBubbleState = BUBBLE_STATE_DEFAUL;
                    }
                }
            }
            break;
            case MotionEvent.ACTION_MOVE:
            {
                if(mBubbleState != BUBBLE_STATE_DEFAUL && mBubbleState != BUBBLE_STATE_DISMISS){
                    //改变大圆圆心
                    mBubMoveableCenter.x = event.getX();
                    mBubMoveableCenter.y = event.getY();
                    //计算圆心距
                    mDist = (float) Math.hypot(event.getX() - mBubStillCenter.x,
                            event.getY() - mBubStillCenter.y);
                    if(mBubbleState == BUBBLE_STATE_CONNECT){
                        // 减去MOVE_OFFSET是为了让不动气泡半径到一个较小值时就直接消失
                        // 或者说是进入分离状态
                        if(mDist < mMaxDist - MOVE_OFFSET){
                            //根据圆心距缩小 小圆的半径
                            mBubStillRadius = mBubbleRadius - mDist / 8;
                        }else{
                            mBubbleState = BUBBLE_STATE_APART;
                        }
                    }
                    invalidate();
                }
            }
            break;
            case MotionEvent.ACTION_UP:
            {
                //如果是相连状态
                if(mBubbleState == BUBBLE_STATE_CONNECT){
                    startBubbleRestAnim();
                }else if(mBubbleState == BUBBLE_STATE_APART){//如果是分离状态

                    //是否返回原点还是爆炸~
                    if(mDist < 2 * mBubbleRadius){
                        startBubbleRestAnim();
                    }else{
                        startBubbleBurstAnim();
                    }
                }
            }
            break;
        }
        return true;
    }

手指下落时(ACTION_DOWN)判断状态,如果是消失(爆炸之后为消失)状态不进行处理,如果不是消失状态,需要算下落点到圆心的距离,如果距离小于 半径加偏移量,就改变状态。
手指移动时(ACTION_MOVE),正常情况下基本手指下落了就会触发手指移动,当状态不为消失(爆炸之后为消失)或默认(初始化就是默认的)的时候,证明在 Down的收手指下落在了指定范围内,需要对 动圆心(mBubMoveableCenter)进行变化,然后计算圆心距(mDist),之前在画相连曲线的时候,需要用到 不动圆心(mBubStillCenter) 固定已知动圆心(mBubMoveableCenter) 初始和不动一样,手指滑动时变化圆心距(mDist) 默认0手指滑动计算具体值

分别对下落,移动,抬起 做了状态的变化和相对的处理,注释都说明了~

path相关,事件监听完成了,这样可以基本的拖拽了~ 但是回不去也,爆炸不了,剩下两个动画了

    /**
     * 爆炸动画
     */
    private void startBubbleBurstAnim() {
        //气泡改为消失状态
        mBubbleState = BUBBLE_STATE_DISMISS;
        //是否在执行气泡爆炸动画
        mIsBurstAnimStart = true;
        //做一个int型属性动画,从0~mBurstDrawablesArray.length结束
        ValueAnimator anim = ValueAnimator.ofInt(0, mBurstDrawablesArray.length);
        anim.setInterpolator(new LinearInterpolator());
        anim.setDuration(500);
        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                //设置当前绘制的爆炸图片index
                mCurDrawableIndex = (int) animation.getAnimatedValue();
                invalidate();
            }
        });
        anim.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                //修改动画执行标志
                mIsBurstAnimStart = false;
            }
        });
        anim.start();

    }

    /**
     * 回到原点动画
     */
    private void startBubbleRestAnim() {

        mBubbleState = BUBBLE_STATE_APART;
        //PointFEvaluator  简单理解为 两点之间 值的变化
        ValueAnimator anim = ValueAnimator.ofObject(new PointFEvaluator(),
                new PointF(mBubMoveableCenter.x,mBubMoveableCenter.y),
                new PointF(mBubStillCenter.x,mBubStillCenter.y));

        anim.setDuration(100);
        anim.setInterpolator(new OvershootInterpolator(5f));
        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mBubMoveableCenter = (PointF) animation.getAnimatedValue();
                invalidate();
            }
        });
        anim.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                mBubbleState = BUBBLE_STATE_DEFAUL;
            }
        });
        anim.start();
    }

这里 在我之前 属性动画基础 上又多了一个PointFEvaluator 点估值器(可以这么说吧 = =)加上插值器完成了动圆回弹,爆炸动画就是在换图片~

        // 3、是否执行爆炸动画
        if(mIsBurstAnimStart){
            //画爆炸动画
            mBurstRect.set((int) (mBubMoveableCenter.x - mBubMoveableRadius),
                    (int) (mBubMoveableCenter.y - mBubMoveableRadius),
                    (int) (mBubMoveableCenter.x + mBubMoveableRadius),
                    (int) (mBubMoveableCenter.y + mBubMoveableRadius));

            canvas.drawBitmap(mBurstBitmapsArray[mCurDrawableIndex], null,
                    mBurstRect, mBubblePaint);
        }

最后看一下效果~

bug.gif

什么鬼,我的字明明写的1001,这么字还被挡住了,爆炸效果也看不清(背景颜色原因~),这这这。。。肯定是画的顺序问题 = = ,之前我说有问题的地方只要把第一步放到第二步下就会好了
base.gif

但是这个实现的也太粗糙了,而且这项目中这么用????莫着急这只是基础思路,具体优化请看
贝塞尔曲线之聊天未读数气泡-进化
有没有超进化???
具体初始化情况,请移步源码,看完代码是不是感觉代码都能看懂???就是想不到思路???这就是思路~ 举一反三,小学就得会

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

推荐阅读更多精彩内容