本篇文章主要是一个对贝塞尔曲线以及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拖拽气泡