android 自定义控件之QQ气泡

  本篇文章主要是一个对贝塞尔曲线以及Path的一个应用,仿照QQ做了一个气泡的效果,效果如下:

  

 首先实现这个效果的主要步骤

(1)绘制出QQ气泡静止时候的样式

(2)绘制出当手指拖动时候相连接的样式

(3)超过一定距离气泡炸裂的样式

下面进行详细的讲解,首先第一步我们通过Canvas.drawCircle()以及drawText()方法绘制出气泡和气泡上的文字

  canvas.drawCircle(moveBubbleCenter.x, moveBubbleCenter.y, bubbleRadius, bPaint);
            textPaint.getTextBounds(bubbleText, 0, bubbleText.length(), textRect);
            canvas.drawText(bubbleText, moveBubbleCenter.x - textRect.width() / 2, moveBubbleCenter.y + textRect.height() / 2, textPaint);

这一步较简单就是简单的API的使用。接下来我们进行手指拖动的时候相连接的绘制,通过观察我们可以知道在拖动的时候View主要可以分为三部分,一、原地不动的随距离不断变小的气泡,二、可以移动的大气泡,三、与气泡相连接的弧形。在这三部分中其中大小两个气泡实现方式主要也是通过Canvas.drawCircle()实现的,随着invalidate()的不断调用,更改小圆的绘制的半径和大圆的位置即可,随着手指移动大圆的位置和小圆的半径计算如下:

 if (event.getAction() == MotionEvent.ACTION_MOVE) {
            if (mBubbleState != BUBBLE_STATE_STATIC) {
                moveBubbleCenter.x = event.getX();
                moveBubbleCenter.y = event.getY();
                dist = (float) Math.hypot(event.getX() - stillBubbleCenter.x,
                        event.getY() - stillBubbleCenter.y);
                if (mBubbleState == BUBBLE_STATE_CONNECTION) {
                    // 减去MOVE_OFFSET是为了让不动气泡半径到一个较小值时就直接消失
                    // 或者说是进入分离状态
                    if (dist < maxDist - moveOffsize) {

                        bubbleStillRadius = bubbleRadius - dist / 10;
                    } else {
                        mBubbleState = BUBBLE_STATE_APART;//消失的状态
                    }
                }
                invalidate();
            }
        } 

大圆的绘制依然如上再次不重复贴代码,小圆的绘制代码如下:

 canvas.drawCircle(stillBubbleCenter.x, stillBubbleCenter.y, bubbleStillRadius, bPaint);

那么重点以及难点就来了怎么实现两个大圆和小圆之间的连接部分,在点与点的连线中实现弧线的方式是通过贝塞尔曲线,因此我们可以把这个连接部分看成是上下两条二阶贝塞尔曲线,通过path进行绘制,最后一闭合就能实现该种效果。

思路是有了,那么我们怎么找这个贝塞尔曲线的起点终点和控制点,我们可以看出曲线开始部分是在圆的边缘部分,凹下去的部分是在他们差不多距离的中间位置,因此我们可以把两个圆心的连线的中间点看做控制点,并在两个圆中做这条连线的垂线,分别交两个圆与两点,我们可以暂时称为A、B、C、D点,其图像以及运算思路如下图:

代码实现如下:

// 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;

            float iBubStillStartX = mBubStillCenter.x - mBubStillRadius * sinTheta;
            float iBubStillStartY = mBubStillCenter.y + mBubStillRadius * cosTheta;
            float iBubMoveableEndX = mBubMoveableCenter.x - mBubMoveableRadius * sinTheta;
            float iBubMoveableEndY = mBubMoveableCenter.y + mBubMoveableRadius * cosTheta;
            float iBubMoveableStartX = mBubMoveableCenter.x + mBubMoveableRadius * sinTheta;
            float iBubMoveableStartY = mBubMoveableCenter.y - mBubMoveableRadius * cosTheta;
            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);
            //path闭合
            mBezierPath.close();
            canvas.drawPath(mBezierPath,mBubblePaint);

以上就是连接的贝塞尔曲线。

接下来最后一步,炸裂效果,在这里我们是通过几张炸裂的图片,使用属性动画依次绘制出来这几张图片。

mBubbleState = BUBBLE_STATE_DISMISS;//设置为消失的状态
        isBombAnimStarting = true;
        ValueAnimator valueAnimator = ValueAnimator.ofInt(0, BOOM_ARRAY.length);
        valueAnimator.setDuration(500);
        valueAnimator.setInterpolator(new LinearInterpolator());
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                bombDrawableIndex = (int) animation.getAnimatedValue();
                invalidate();

            }
        });

        valueAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                isBombAnimStarting = false;
            }
        });
        valueAnimator.start();
 bombRect.set((int) (moveBubbleCenter.x - bubbleRadius),
                    (int) (moveBubbleCenter.y - bubbleRadius),
                    (int) (moveBubbleCenter.x + bubbleRadius),
                    (int) (moveBubbleCenter.y + bubbleRadius));
            canvas.drawBitmap(bomb_bitmaps[bombDrawableIndex], null, bombRect, bombPaint);

以上即为QQ气泡的模仿效果,其实可以看出它也只是对于一些简单算法以及android UI绘制一些简单的API的使用。项目源码我已经上传至Github上,有需要的可以下载使用仿QQ拖拽气泡

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值