先上效果图
先将这个过程分状态(阶段):
1.静止状态,小球的默认状态,由红色的气泡和中间的文本组成;
2.连接状态(拖拽半径短),拖拽过程中由随着手指一定的气泡和中间不断缩小的小气泡组成;
3.分离状态(拖拽半径长),中间的小气泡消失,连接的中间线也消失;
4.回弹或隐藏状态,当从连接状态松手,移动的气泡会回弹到中心,从分离状态松手,移动的气泡爆炸,整个页面隐藏。
1.实现思路
自定义一个DragBubbleView继承View,静止、连接和分离状态都是需要绘制一个大的可动气泡,只是坐标不同,连接状态时,首先随着手指的移动,改变可动气泡的坐标,同时在中心绘制固定气泡,固定气泡的圆心随拖拽半径的变大二变小,大气泡和小气泡中间连接处是两条二阶贝赛尔曲线,贝塞尔曲线可以参考上一小节总结 Android Path进阶之Path常用API,第一条二阶贝塞尔曲线的两个数据点分别是A和B,第二条二阶贝塞尔曲线的两个数据点分别是C和D,两条贝塞尔曲线的控制点都是大气泡和小气泡的圆心连线的中点G,然后两条贝塞尔曲线和两条直线BC和DA闭和,外加大圆和小圆的部分,3部分刚好形成连接状态时的图形绘制,分离状态只需要绘制大气泡,爆炸时使用5张连续的图片顺序播放形成爆炸效果。
2.初始化
对过程进行了分解之后开始实现,首先确定需要使用到的变量,并会其初始化。一只画笔绘制可动气泡中心文本,一只画笔绘制两个气泡和中心不规则图形,一只画笔绘制爆炸时效果,一个路径(Path)用来记录两个气泡中间连接不规则的图形,然后在构造方法中对画笔和路径进行初始化,在onSizeChange中对坐标点初始化。
public class DragBubbleView extends View {
private static final int BUBBLE_STATE_DEFAULT = 0;//气泡默认状态--静止
private static final int BUBBLE_STATE_CONNECT = 1;//气泡相连
private final int BUBBLE_STATE_APART = 2;//气泡分离
private final int BUBBLE_STATE_DISMISS = 3;//气泡消失
private float mBubbleRadius = 60;//气泡半径
private int mBubbleColor = Color.RED;//气泡颜色
private String mTextStr = "99+";//气泡消息文字
private int mTextColor = Color.WHITE;//气泡消息文字颜色
private float mTextSize = 60;//气泡消息文字大小
private float mBubFixedRadius = mBubbleRadius;//不动气泡的半径
private float mBubMovableRadius = mBubbleRadius;//可动气泡的半径
private PointF mBubFixedCenter;//不动气泡的圆心
private PointF mBubMovableCenter;//可动气泡的圆心
private Paint mBubblePaint;//气泡的画笔
private Path mBezierPath;//贝塞尔曲线path
private Paint mTextPaint;//文本绘制画笔
private Rect mTextRect;//文本绘制区域
private Paint mBurstPaint;//气泡爆炸消失画笔
private Rect mBurstRect;//爆炸绘制区域
private int mBubbleState = BUBBLE_STATE_DEFAULT;//气泡状态标志
private float mDist;//两气泡圆心距离
private float mMaxDist = 4 * mBubbleRadius;//气泡相连状态最大圆心距离
private float MOVE_OFFSET = 2 * mBubbleRadius;//手指触摸偏移量
private Bitmap[] mBurstBitmapsArray;//气泡爆炸的bitmap数组
private boolean mIsBurstAnimStart = false;//是否在执行气泡爆炸动画
private int mCurDrawableIndex;//当前气泡爆炸图片index
//气泡爆炸的图片id数组
private int[] mBurstDrawablesArray = {
R.drawable.burst_1,
R.drawable.burst_2,
R.drawable.burst_3,
R.drawable.burst_4,
R.drawable.burst_5
};
public DragBubbleView(Context context) {
this(context, null);
}
public DragBubbleView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public DragBubbleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
//初始化气泡画笔
mBubblePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mBubblePaint.setColor(mBubbleColor);
mBubblePaint.setStyle(Paint.Style.FILL);
//初始化文本绘制画笔
mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mTextPaint.setColor(mTextColor);
mTextPaint.setTextSize(mTextSize);
mTextRect = new Rect();
//初始化爆炸画笔
mBurstPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mBurstPaint.setFilterBitmap(true);
//初始化贝赛尔曲线path
mBezierPath = new Path();
mBurstRect = new Rect();
//将气泡爆炸的drawable转为bitmap
mBurstBitmapsArray = new Bitmap[mBurstDrawablesArray.length];
for (int i = 0; i < mBurstDrawablesArray.length; i++) {
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), mBurstDrawablesArray[i]);
mBurstBitmapsArray[i] = bitmap;
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
//不动气泡圆心
if (mBubFixedCenter == null) {
mBubFixedCenter = ne