期初,对贝塞尔曲线都保持一定的距离,后来需要实现一定的效果,在简单了解贝塞尔曲线之后,自己试一下
- 首先分析一下它有几种状态
- 气泡静止状态 -- 画气泡小球和数字
- 气泡相连状态 -- 画两个相连的气泡小球(类似橡皮筋效果)、数字
- 气泡分离状态 -- 单个气泡小球的拖动
- 气泡消失状态 -- 爆炸动画
- 需要用到的知识
- 跟path、绘图相关---相连的粘连效果(贝塞尔曲线)
- 监听拖拽事件
- 动画
经过分析,我们先完成画的工作,步骤如下
//1. 气泡静止状态
//1.画禁止气泡
//2.画文字
//2. 气泡相连状态
//1.画静止气泡
//2.画文字
//3.画相连的曲线
//4.画拖拽的气泡
//3. 气泡分离状态
//1.画文字
//2.画拖拽的气泡
//4. 气泡消失状态
这是最直接的画法,但是发现有很多重复的地方,在1.1静禁止气泡,其实可以看做需要拖拽的气泡,所以有了如下思路
//1.当状态不为消失状态时,画 拖拽气泡和文字
//2.当状态为相连是,画禁止气泡 画曲线
//3.消失状态,画爆炸动画
这样就巧妙的避开了重复的地方~ 就像本市(天津)巧妙的避开下雪一样~
回到正题。。。
// 1、不为消失状态时 画拖拽的气泡 和 文字
if(mBubbleState != BUBBLE_STATE_DISMISS){}
// 2、当状态为相连是,画相连的气泡状态
if(mBubbleState == BUBBLE_STATE_CONNECT) { }
// 3、画消失状态---爆炸动画
if(mIsBurstAnimStart){ }
这就是最终的绘画思路,但是这样画有一个问题,我们最后再说~
然后开始画气泡和文字
// 1、画拖拽的气泡 和 文字
if(mBubbleState != BUBBLE_STATE_DISMISS){
canvas.drawCircle(mBubMoveableCenter.x,mBubMoveableCenter.y,
mBubMoveableRadius,mBubblePaint);
mTextPaint.getTextBounds(mTextStr,0,mTextStr.length(),mTextRect);
canvas.drawText(mTextStr,mBubMoveableCenter.x - mTextRect.width() / 2,
mBubMoveableCenter.y + mTextRect.height() / 2,mTextPaint);
}
PointF mBubMoveableCenter 可动气泡的圆心,画一个圆,然后拿到Text的Bound计算文字的位置。
然后是第二个状态
// 2、当状态为相连时
if(mBubbleState == BUBBLE_STATE_CONNECT) {
//画禁止气泡 画曲线
// 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;
//D
float iBubStillStartX = mBubStillCenter.x - mBubStillRadius * sinTheta;
float iBubStillStartY = mBubStillCenter.y + mBubStillRadius * cosTheta;
//C
float iBubMoveableEndX = mBubMoveableCenter.x - mBubMoveableRadius * sinTheta;
float iBubMoveableEndY = mBubMoveableCenter.y + mBubMoveableRadius * cosTheta;
//B
float iBubMoveableStartX = mBubMoveableCenter.x + mBubMoveableRadius * sinTheta;
float iBubMoveableStartY = mBubMoveableCenter.y - mBubMoveableRadius * cosTheta;
//A
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);
mBezierPath.close();
canvas.drawPath(mBezierPath,mBubblePaint);
}
图中绿色标注的角度都是一样的,已知 两圆心坐标,半径,求ABCD
然后通过二阶贝塞尔曲线连上四个点,因为之前忽略了 正负值,所以这里从图中D点开始画的。
如 图中 D 点 图中的sinTheta 其实是一个负值,所以在求不动圆心(mBubStillCenter)
的时候,其实是X值变大,Y值变大,就到了图中的D点~ 其他点类似
这样就把主要用到点都写上了,然后就是如何给它们赋值,赋值就需要在触摸事件里对他们进行处理。
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
//手指下落
case MotionEvent.ACTION_DOWN:
{
//当状态不为消失的时候
if(mBubbleState != BUBBLE_STATE_DISMISS){
//获取下落点到小圆的距离
mDist = (float) Math.hypot(event.getX() - mBubStillCenter.x,
event.getY() - mBubStillCenter.y);
//如果距离小于 圆半径+偏移量
if(mDist < mBubbleRadius + MOVE_OFFSET){
// 加上MOVE_OFFSET是为了方便拖拽
mBubbleState = BUBBLE_STATE_CONNECT;
}else{
mBubbleState = BUBBLE_STATE_DEFAUL;
}
}
}
break;
case MotionEvent.ACTION_MOVE:
{
if(mBubbleState != BUBBLE_STATE_DEFAUL && mBubbleState != BUBBLE_STATE_DISMISS){
//改变大圆圆心
mBubMoveableCenter.x = event.getX();
mBubMoveableCenter.y = event.getY();
//计算圆心距
mDist = (float) Math.hypot(event.getX() - mBubStillCenter.x,
event.getY() - mBubStillCenter.y);
if(mBubbleState == BUBBLE_STATE_CONNECT){
// 减去MOVE_OFFSET是为了让不动气泡半径到一个较小值时就直接消失
// 或者说是进入分离状态
if(mDist < mMaxDist - MOVE_OFFSET){
//根据圆心距缩小 小圆的半径
mBubStillRadius = mBubbleRadius - mDist / 8;
}else{
mBubbleState = BUBBLE_STATE_APART;
}
}
invalidate();
}
}
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;
}
手指下落时(ACTION_DOWN)
判断状态,如果是消失(爆炸之后为消失)
状态不进行处理,如果不是消失状态,需要算下落点到圆心的距离,如果距离小于 半径加偏移量,就改变状态。
手指移动时(ACTION_MOVE)
,正常情况下基本手指下落了就会触发手指移动,当状态不为消失(爆炸之后为消失)
或默认(初始化就是默认的)
的时候,证明在 Down的收手指下落在了指定范围内,需要对 动圆心(mBubMoveableCenter)
进行变化,然后计算圆心距(mDist)
,之前在画相连曲线的时候,需要用到 不动圆心(mBubStillCenter) 固定已知
, 动圆心(mBubMoveableCenter) 初始和不动一样,手指滑动时变化
,圆心距(mDist) 默认0手指滑动计算具体值
分别对下落,移动,抬起 做了状态的变化和相对的处理,注释都说明了~
path相关,事件监听完成了,这样可以基本的拖拽了~ 但是回不去也,爆炸不了,剩下两个动画了
/**
* 爆炸动画
*/
private void startBubbleBurstAnim() {
//气泡改为消失状态
mBubbleState = BUBBLE_STATE_DISMISS;
//是否在执行气泡爆炸动画
mIsBurstAnimStart = true;
//做一个int型属性动画,从0~mBurstDrawablesArray.length结束
ValueAnimator anim = ValueAnimator.ofInt(0, mBurstDrawablesArray.length);
anim.setInterpolator(new LinearInterpolator());
anim.setDuration(500);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//设置当前绘制的爆炸图片index
mCurDrawableIndex = (int) animation.getAnimatedValue();
invalidate();
}
});
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
//修改动画执行标志
mIsBurstAnimStart = false;
}
});
anim.start();
}
/**
* 回到原点动画
*/
private void startBubbleRestAnim() {
mBubbleState = BUBBLE_STATE_APART;
//PointFEvaluator 简单理解为 两点之间 值的变化
ValueAnimator anim = ValueAnimator.ofObject(new PointFEvaluator(),
new PointF(mBubMoveableCenter.x,mBubMoveableCenter.y),
new PointF(mBubStillCenter.x,mBubStillCenter.y));
anim.setDuration(100);
anim.setInterpolator(new OvershootInterpolator(5f));
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mBubMoveableCenter = (PointF) animation.getAnimatedValue();
invalidate();
}
});
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mBubbleState = BUBBLE_STATE_DEFAUL;
}
});
anim.start();
}
这里 在我之前 属性动画基础 上又多了一个PointFEvaluator 点估值器(可以这么说吧 = =)
加上插值器完成了动圆回弹,爆炸动画就是在换图片~
// 3、是否执行爆炸动画
if(mIsBurstAnimStart){
//画爆炸动画
mBurstRect.set((int) (mBubMoveableCenter.x - mBubMoveableRadius),
(int) (mBubMoveableCenter.y - mBubMoveableRadius),
(int) (mBubMoveableCenter.x + mBubMoveableRadius),
(int) (mBubMoveableCenter.y + mBubMoveableRadius));
canvas.drawBitmap(mBurstBitmapsArray[mCurDrawableIndex], null,
mBurstRect, mBubblePaint);
}
最后看一下效果~
什么鬼,我的字明明写的1001,这么字还被挡住了,爆炸效果也看不清
(背景颜色原因~)
,这这这。。。肯定是画的顺序问题 = = ,之前我说有问题的地方只要把第一步放到第二步下就会好了 但是这个实现的也太粗糙了,而且这项目中这么用????莫着急这只是基础思路,具体优化请看
贝塞尔曲线之聊天未读数气泡-进化
有没有超进化???
具体初始化情况,请移步源码,看完代码是不是感觉代码都能看懂???就是想不到思路???这就是思路~ 举一反三,小学就得会