QQ气泡效果分析
QQ气泡 = 红色圆 + 消息数字 + 拖拽粘性效果 + 回弹效果 + 跟随移动 + 爆炸效果。
不得不说在这么个小小的UI小球上实现了这么多的效果,QQ是真的很有心呐~
-
根据我们上边拆分出来的公式,我们分别看看每一个效果需要如何去实现:
- 红色圆:canvas.drawCircle
- 消息数字:canvas.drawText
- 拖拽粘性效果:canvas.drawPath、 (两条二阶)贝塞尔曲线
- 回弹效果:属性动画
- 跟随移动:OnTouchEvent处理MotionEvent.ACTION_MOVE事件
- 爆炸效果:属性动画
首先我们来看一下最终的效果:
View自定义属性
-
为了提高自定义View的灵活性,我们需要提供几种自定义属性给外部来设置,有如下属性:
- 气泡半径:bubble_radius
- 气泡颜色:bubble_color
- 气泡消息数字:bubble_text
- 气泡消息数字字体大小:bubble_textSize
- 气泡消息数字颜色:bubble_textColor
属性定义
在res -> values下添加如下attrs.xml文件:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="DragBubbleView">
<attr name="bubble_radius" format="dimension"/>
<attr name="bubble_color" format="color"/>
<attr name="bubble_text" format="string"/>
<attr name="bubble_textSize" format="dimension"/>
<attr name="bubble_textColor" format="color"/>
</declare-styleable>
</resources>
属性读取
我们在View构造函数中读取自定义属性
DragBubbleView.java
public class DragBubbleView extends View {
/**
* 气泡半径
*/
private float mBubbleRadius;
/**
* 气泡颜色
*/
private int mBubbleColor;
/**
* 气泡消息文字
*/
private String mTextStr;
/**
* 气泡消息文字大小
*/
private float mTextSize;
/**
* 气泡文字颜色
*/
private int mTextColor;
public DragBubbleView(Context context) {
this(context, null);
}
public DragBubbleView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public DragBubbleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// 读取属性值
readAttributes(context, attrs, defStyleAttr);
// 初始化
init();
}
private void readAttributes(Context context, AttributeSet attrs, int defStyleAttr){
// 获取自定义属性数组
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.DragBubbleView, defStyleAttr, 0);
// 获取气泡半径
mBubbleRadius = array.getDimension(R.styleable.DragBubbleView_bubble_radius, mBubbleRadius);
// 气泡颜色,默认红色
mBubbleColor = array.getColor(R.styleable.DragBubbleView_bubble_color, Color.RED);
// 消息数字
mTextStr = array.getString(R.styleable.DragBubbleView_bubble_text);
// 字体大小
mTextSize = array.getDimension(R.styleable.DragBubbleView_bubble_textSize, mTextSize);
// 消息数字颜色
mTextColor = array.getColor(R.styleable.DragBubbleView_bubble_textColor, Color.WHITE);
// 回收属性数组
array.recycle();
}
private void init() {
//...
}
}
初始化
接下来需要根据自定义属性值初始化数据,这里需要注意的是,我们是需要绘制两个气泡来完成我们想要的效果:一个是固定在初始位置,在粘性拖拽时大小随粘性距离的增大而减小;另一个是大小不变,位置始终跟随点击位置移动的圆。
初始化两个圆的大小及画笔
DragBubbleView.java
public class DragBubbleView extends View {
//...
/**
* 不动气泡的半径
*/
private float mBubFixedRadius;
/**
* 可动气泡的半径
*/
private float mBubMovableRadius;
/**
* 气泡相连状态最大圆心距离
*/
private float mMaxDist;
/**
* 手指触摸偏移量
*/
private static float MOVE_OFFSET;
/**
* 气泡的画笔
*/
private Paint mBubblePaint;
/**
* 贝塞尔曲线path
*/
private Path mBezierPath;
/**
* 绘制文本的画笔
*/
private Paint mTextPaint;
/**
* 文本绘制区域
*/
private Rect mTextRect;
/**
* 爆炸图片的画笔
*/
private Paint mBurstPaint;
/**
* 爆炸绘制区域
*/
private Rect mBurstRect;
/**
* 气泡爆炸的bitmap数组
*/
private Bitmap[] mBurstBitmapsArray;
/**
* 气泡爆炸的图片id数组
*/
private int[] mBurstDrawablesArray = {
R.mipmap.burst_1,
R.mipmap.burst_2,
R.mipmap.burst_3,
R.mipmap.burst_4,
R.mipmap.burst_5
};
private void init() {
// 两个圆初始半径一致
mBubFixedRadius = mBubbleRadius;
mBubMovableRadius = mBubFixedRadius;
// 设置粘性拖拽最大距离
mMaxDist = 8 * mBubbleRadius;
// 该值是为了增大气泡对触摸事件的识别范围
MOVE_OFFSET = mMaxDist / 4;
// 初始化气泡画笔
// 设置抗锯齿
mBubblePaint = new Paint(Paint.ANTI_ALIAS_FLAG