qq气泡实现

在这里插入图片描述

使用贝塞尔曲线绘制qq气泡,整个动画过程可分为4个过程:

1、默认状态:此时显示一个气泡和一个消息框。
在这里插入图片描述

2、连接状态:一个固定气泡(大小随着拖拽的长度变化而变化),一个移动气泡和它之上的消息框。
在这里插入图片描述

3、分离状态,此时固定气泡消失,只有移动气泡(和它的消息框)随手指的移动而移动。

在这里插入图片描述

4、消失状态:放开手指后气泡爆炸消失。

在这里插入图片描述

将整个过程分割为这几个步骤,再分别实现每个步骤,整个过程就完成了!

首先是默认状态,就是一开始显示的状态。

在这里插入图片描述这个很简单,覆写onDraw方法,只要在对应的位置使用Canvans对象“画”一个实心的圆,然后“画”文字即可。接着是连接状态的描绘。

if(mBubbleState == BUBBLE_STATE_CONNECT){
            canvas.drawCircle(mFixedBubbleCenter.x,mFixedBubbleCenter.y,mFixedBubbleRadius,mBubblePaint);
            mBezierPath.reset();
            //画贝塞尔曲线
            //PE = p(y) - o(y)
            float PE = mMovedBubbleCenter.y - mFixedBubbleCenter.y;
            //OE = p(x) - o(x)
            float OE = mMovedBubbleCenter.x - mFixedBubbleCenter.x;
            float sinAngle = PE / mDist;
            float cosAngle = OE / mDist;

            //G
            float Gx = (mFixedBubbleCenter.x + mMovedBubbleCenter.x) / 2f;
            float Gy = (mFixedBubbleCenter.y + mMovedBubbleCenter.y) / 2f;

            //B
            float Bx = mMovedBubbleCenter.x + sinAngle * mMovedBubbleRadius;
            float By = mMovedBubbleCenter.y - cosAngle * mMovedBubbleRadius;

            //A
            float Ax = mFixedBubbleCenter.x + mFixedBubbleRadius * sinAngle;
            float Ay = mFixedBubbleCenter.y - mFixedBubbleRadius * cosAngle;

            //D
            float Dx = mFixedBubbleCenter.x - mFixedBubbleRadius * sinAngle;
            float Dy = mFixedBubbleCenter.y + mFixedBubbleRadius * cosAngle;
            //C
            float Cx = mMovedBubbleCenter.x - mMovedBubbleRadius * sinAngle;
            float Cy = mMovedBubbleCenter.y + mMovedBubbleRadius * cosAngle;

            mBezierPath.moveTo(Dx,Dy);
            mBezierPath.quadTo(Gx,Gy,Cx,Cy);

            mBezierPath.lineTo(Bx,By);
            mBezierPath.quadTo(Gx,Gy,Ax,Ay);
            mBezierPath.close();
            canvas.drawPath(mBezierPath,mBubblePaint);
        }

首先“画”屏幕中心固定的小圆,接着就是2条贝塞尔曲线,通过下面这张图就可以很明显地看出如何运用贝塞尔曲线:

在这里插入图片描述
我们需要做的,就是“画”出AB、CD这2条二阶贝塞尔曲线。而ABCD这个不规则多边形,就是“气泡”,根据贝塞尔曲线的定义,可以发现,O点、P点为已知点,G点作为AB、CD这2条贝塞尔曲线的控制点,而A和B、C和D分别是AB、CD贝塞尔曲线的数据点。因此求出A、B、C、D、G5个点的坐标就可以画出这2条贝塞尔曲线了!

关于坐标的求解,一个个来:
G:坐标很简单,直接O点与P点的x,y相加除以2就可以算出来。
ABCD点,根据高中的数学相关知识:

PE = O的y坐标-P的y坐标
OE = P的x坐标-O的x坐标

sin∠POE = PE / OP
cos∠POE = OE / OP

A坐标:
x = O的x坐标 - sin∠POE * 固定圆半径
y = O的y坐标 - cos∠POE * 固定圆半径

B坐标:
x = P的x坐标 - sin∠POE * 动圆半径
y = P的y坐标 - cos∠POE * 动圆半径

C坐标:
x = P的x坐标 + sin∠POE * 动圆半径
y = P的y坐标 + cos∠POE * 动圆半径

D坐标:
x = O的x坐标 + sin∠POE * 固定圆半径
y = O的y坐标 + cos∠POE * 固定圆半径

接着通过Path类的quadTo画二阶贝塞尔曲线。

	  mBezierPath.moveTo(Dx,Dy);
      mBezierPath.quadTo(Gx,Gy,Cx,Cy);

      mBezierPath.lineTo(Bx,By);
      mBezierPath.quadTo(Gx,Gy,Ax,Ay);
      mBezierPath.close();
      canvas.drawPath(mBezierPath,mBubblePaint);

接着是气泡爆炸效果的绘制:
在这里插入图片描述
至此,onDraw方法覆写完成。

接着覆写onTouchEvent方法:

@Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                if(mBubbleState != BUBBLE_STATE_DISMISS) {
                    mDist = (float) Math.hypot(event.getX() - mFixedBubbleCenter.x,
                            event.getY() - mFixedBubbleCenter.y);
                    if(mDist < mFixedBubbleRadius + MOVE_OFFSET){
                        mBubbleState = BUBBLE_STATE_CONNECT;
                    }else{
                        mBubbleState = BUBBLE_STATE_DEFAULT;
                    }
                }
                break;
            case MotionEvent.ACTION_MOVE:
                if(mBubbleState != BUBBLE_STATE_DEFAULT){
                    mDist = (float) Math.hypot(event.getX() - mFixedBubbleCenter.x,
                            event.getY() - mFixedBubbleCenter.y);
                    mMovedBubbleCenter.x = event.getX();
                    mMovedBubbleCenter.y = event.getY();
                    if(mBubbleState == BUBBLE_STATE_CONNECT) {
                        if (mDist < mMaxDist - MOVE_OFFSET){
                            mFixedBubbleRadius = mMovedBubbleRadius - mDist / 8;   //固定气泡半径变化,可自定义更改
                        }else{
                            mBubbleState = BUBBLE_STATE_APART;
                        }
                    }
                    invalidate();    //调用onDraw方法
                }
                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;
    }

将手指在屏幕上的操作分为3步处理:

ACTION_DOWN:当手指触摸屏幕的一瞬间:
计算固定圆与动圆的圆心距离,如果<固定圆半径+偏移量时,将状态设置为连接状态,否则仍然是默认状态,这里加上偏移量是为了扩大触碰的面积,它表示在超出固定圆外一定面积内仍然是有效的触碰面积。

ACTION_MOVE:当手指在屏幕上滑动时:
代码看似复杂,其实主要的操作就是修改动圆的圆心坐标,以及固定圆的半径大小,接着做个判断:超过一定的距离后(这个距离完全可以自行设置),将状态设置为分离,然后调用invalidate()方法。我们知道ACTION_MOVE在手指的移动中会被调用数次(相当多次),因此这些操作也会被调用数次(相当多次),所以手指移动看似产生拖拽的气泡效果,其实只不过不停的重画2个圆以及贝塞尔曲线而已。

ACTION_UP:当手指从屏幕上离开时:

如果仍然是连接状态,那么产生一个回弹的橡皮筋动画效果。
如果分离状态,那么如果这个动圆与固定圆小于一定距离(这个距离可以自己设置),那么不做爆炸效果而是一样产生一个回弹的橡皮筋动画效果。如果大于这个距离,那么就做爆炸效果。

关于动画的实现:

实现橡皮筋回弹效果:

使用属性动画类ValueAnimator 来实现。

private void startBubbleRestAnim(){
        ValueAnimator anim = ValueAnimator.ofObject(new PointFEvaluator(),
                new PointF(mMovedBubbleCenter.x,mMovedBubbleCenter.y),
                new PointF(mFixedBubbleCenter.x,mFixedBubbleCenter.y));
        anim.setDuration(200);
        anim.setInterpolator(new OvershootInterpolator(5f));
        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                mMovedBubbleCenter = (PointF) valueAnimator.getAnimatedValue();
                invalidate();  //反复调用onDraw
            }
        });
        anim.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                mBubbleState = BUBBLE_STATE_DEFAULT;
            }
        });
        anim.start();
    }

实现爆炸效果:

private void startBubbleBurstAnim(){
        mBubbleState = BUBBLE_STATE_DISMISS;
        ValueAnimator anim = ValueAnimator.ofInt(0,mBurstBitmapsArray.length);
        anim.setInterpolator(new LinearInterpolator());
        anim.setDuration(500);
        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                mCurrentDrawableIndex = (int) valueAnimator.getAnimatedValue();
                invalidate();
            }
        });
        anim.start();
    }

demo展示:https://github.com/lyx19970504/qqBubbleDemo

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

哒哒呵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值