(十三)QQ 消息气泡
来源:互联网 发布:菲律宾程序员招聘骗局 编辑:程序博客网 时间:2024/04/29 12:02
版权声明:本文为博主原创文章,未经博主允许不得转载。
本文纯个人学习笔记,由于水平有限,难免有所出错,有发现的可以交流一下。
一、效果
二、分析
我们把整个气泡做成一个自定义控件,通过实际效果,可以知道控件有下列几种状态:
1、气泡静止状态 --- 画气泡小球和数字 2、气泡相连状态 --- 画两个相连的气泡小球(类似橡皮筋效果)、数字 3、气泡分离状态 --- 单个气泡小球的拖动 4、气泡消失状态 --- 爆炸动画 5、气泡还原动画
在这边有一个技术点需要先提一下,两个小球相连的粘连效果,使用贝塞尔曲线实现,这样可以动态的控制粘连效果的大小。
三、DragBubbleView
气泡控件 DragBubbleView 继承 View。
1.自定义属性
给 DragBubbleView 控件添加几个自定义属性:
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>
按顺序分别是气泡半径、气泡颜色、气泡文字内容、气泡文字大小和气泡文字颜色。
2.状态值
上面分析过气泡有五个状态,由于最后一个还原跟静止是一个样的,所以这边列出四个状态值。同时设置默认初始状态为静止。
/** * 气泡默认状态--静止 */ private final int BUBBLE_STATE_DEFAUL = 0; /** * 气泡相连 */ private final int BUBBLE_STATE_CONNECT = 1; /** * 气泡分离 */ private final int BUBBLE_STATE_APART = 2; /** * 气泡消失 */ private final int BUBBLE_STATE_DISMISS = 3; /** * 气泡状态标志 */ private int mBubbleState = BUBBLE_STATE_DEFAUL;
3.两个气泡小球的半径和圆心
/** * 不动气泡的半径 */ private float mBubStillRadius; /** * 可动气泡的半径 */ private float mBubMoveableRadius; /** * 不动气泡的圆心 */ private PointF mBubStillCenter; /** * 可动气泡的圆心 */ private PointF mBubMoveableCenter;
当小球拖拽的时候,不动小球的圆心不变,半径在改变。被拖动的小球半径不变,圆心在改变。两个小球初始的时候是重合的,半径为属性设置的,圆心为控件中心。
4.两小球最大圆心距
记录小球圆心距,设置最大圆心距为初始小球半径的8倍,也可以把这个写出自定义属性进行设置。
/** * 两气泡圆心距离 */ private float mDist; /** * 气泡相连状态最大圆心距离 */ private float mMaxDist;
5.画笔
为气泡、文字以及爆炸效果各添加一把画笔。
/** * 气泡的画笔 */ private Paint mBubblePaint; /** * 文字的画笔 */ private Paint mTextPaint; /** * 爆炸的画笔 */ private Paint mBurstPaint;
到这里初步完成 DragBubbleView 控件的基本属性。
public class DragBubbleView extends View { /** * 气泡默认状态--静止 */ private final int BUBBLE_STATE_DEFAUL = 0; /** * 气泡相连 */ private final int BUBBLE_STATE_CONNECT = 1; /** * 气泡分离 */ private final int BUBBLE_STATE_APART = 2; /** * 气泡消失 */ private final int BUBBLE_STATE_DISMISS = 3; /** * 气泡状态标志 */ private int mBubbleState = BUBBLE_STATE_DEFAUL; /** * 气泡半径 */ private float mBubbleRadius; /** * 气泡颜色 */ private int mBubbleColor; /** * 气泡消息文字 */ private String mTextStr; /** * 气泡消息文字颜色 */ private int mTextColor; /** * 气泡消息文字大小 */ private float mTextSize; /** * 不动气泡的半径 */ private float mBubStillRadius; /** * 可动气泡的半径 */ private float mBubMoveableRadius; /** * 不动气泡的圆心 */ private PointF mBubStillCenter; /** * 可动气泡的圆心 */ private PointF mBubMoveableCenter; /** * 气泡的画笔 */ private Paint mBubblePaint; /** * 文字的画笔 */ private Paint mTextPaint; /** * 爆炸的画笔 */ private Paint mBurstPaint; /** * 两气泡圆心距离 */ private float mDist; /** * 气泡相连状态最大圆心距离 */ private float mMaxDist; 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) { this(context, attrs, defStyleAttr, 0); } //在这里统一做初始化操作 public DragBubbleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); 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(); mBubStillRadius = mBubbleRadius; mBubMoveableRadius = mBubStillRadius; mMaxDist = 8 * mBubbleRadius; 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); }}
四、onDraw()
先分析各个阶段需要绘制的东西:
1、气泡静止状态
1.画拖拽气泡小球 2.画数字
2、气泡相连状态
1.画静止气泡小球 2.画数字 3.画相连曲线 4.画拖拽气泡小球
3、气泡分离状态
1.画数字 2.画拖拽气泡小球
4、气泡消失状态
1.爆炸动画
在静止状态时候画拖拽气泡小球而不是静止气泡小球,是因为,这时候静止气泡小球被拖拽气泡小球给覆盖了,数字是处于拖拽气泡小球上。
可以发现,在不同的状态有一些重复的绘画,下面来一一实现。
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); }
2.画静止气泡小球与相连曲线
当处于气泡相连状态时候,还需要进行静止气泡小球与相连曲线的绘制。
静止小球的绘制相对简单,这边分析下相连曲线的绘制。
曲线 AB、CD 是两条贝塞尔曲线,控制点相同,为两个圆的圆心中点。现在需要计算出 A、B、C、D 以及控制点的坐标。控制点为 O1O2 中点,比较简单。
三角形 AEO1 、三角形 O1GO2 与三角形 BFO2 是相似三角形,(高中知识,不懂我也没办法了。)所以:
AE/O1G = EO1/GO2 = AO1/O1O2BF/O1G = FO2/GO2 = BO2/O1O2
根据这个以及点 O1、O2 的坐标,可以算出 A、B、C、D 的坐标。考虑到拖拽的气泡小球与静止的气泡小球有不同的位置关系,这里采用 sin 和 cos 进行计算(有时用加,有时用减,用 sin 和 cos 的正负表示)。
//处于气泡相连状态时候,进行静止气泡小球与相连曲线的绘制 if(mBubbleState == BUBBLE_STATE_CONNECT) { //绘制静止气泡小球 canvas.drawCircle(mBubStillCenter.x, mBubStillCenter.y, mBubStillRadius, mBubblePaint); //绘制相连曲线 // 计算控制点坐标,两个圆心的中点 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); mBezierPath.close(); canvas.drawPath(mBezierPath,mBubblePaint); }
3.画爆炸动画
当小球消失的时候,要进行一个爆炸动画的绘制。
拖拽的气泡小球爆炸的范围为小球最后的位置,爆炸动画采用图片的轮播形式进行。
// 3、画消失状态---爆炸动画 if(mBubbleState == BUBBLE_STATE_DISMISS){ 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); }
这样就完成了对各个状态进行绘制。
五、onTouchEvent()
消息气泡的拖拽主要是根据人和屏幕的触点手势进行判断,重写 onTouchEvent() 方法。
@Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_DOWN:{ //当状态为静止的时候才对“按下”事件进行响应 if (mBubbleState == BUBBLE_STATE_DEFAUL) { mDist = (float) Math.hypot(event.getX() - mBubStillCenter.x, event.getY() - mBubStillCenter.y); if (mDist < mBubbleRadius + MOVE_OFFSET) { // 加上MOVE_OFFSET是为了方便拖拽 mBubbleState = BUBBLE_STATE_CONNECT; } } break; } case MotionEvent.ACTION_MOVE:{ //当状态为相连或分离的时候才对“移动”事件进行响应 if (mBubbleState == BUBBLE_STATE_CONNECT || mBubbleState == BUBBLE_STATE_APART) { //重新记录拖拽的气泡小球圆心 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(); } } } } return true; } private void startBubbleRestAnim() { ValueAnimator anim = ValueAnimator.ofObject(new PointFEvaluator(), new PointF(mBubMoveableCenter.x,mBubMoveableCenter.y), new PointF(mBubStillCenter.x,mBubStillCenter.y)); anim.setDuration(200); 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(); } private void startBubbleBurstAnim() { //气泡改为消失状态 mBubbleState = BUBBLE_STATE_DISMISS; //做一个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.start(); }
代码相对比较简单。(属性动画这边不讲)
六、初始化
在 onSizeChanged() 方法中调用初始化。
@Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); initView(w,h); } /** * 初始化气泡位置 * @param w * @param h */ private void initView(int w, int h) { //设置两气泡圆心初始坐标 if(mBubStillCenter == null){ mBubStillCenter = new PointF(w / 2,h / 2); }else{ mBubStillCenter.set(w / 2,h / 2); } if(mBubMoveableCenter == null){ mBubMoveableCenter = new PointF(w / 2,h / 2); }else{ mBubMoveableCenter.set(w / 2,h / 2); } mBubbleState = BUBBLE_STATE_DEFAUL; }
七、DragBubbleView
public class DragBubbleView extends View { /** * 气泡默认状态--静止 */ private final int BUBBLE_STATE_DEFAUL = 0; /** * 气泡相连 */ private final int BUBBLE_STATE_CONNECT = 1; /** * 气泡分离 */ private final int BUBBLE_STATE_APART = 2; /** * 气泡消失 */ private final int BUBBLE_STATE_DISMISS = 3; /** * 气泡状态标志 */ private int mBubbleState = BUBBLE_STATE_DEFAUL; /** * 气泡半径 */ private float mBubbleRadius; /** * 气泡颜色 */ private int mBubbleColor; /** * 气泡消息文字 */ private String mTextStr; /** * 气泡消息文字颜色 */ private int mTextColor; /** * 气泡消息文字大小 */ private float mTextSize; /** * 不动气泡的半径 */ private float mBubStillRadius; /** * 可动气泡的半径 */ private float mBubMoveableRadius; /** * 不动气泡的圆心 */ private PointF mBubStillCenter; /** * 可动气泡的圆心 */ private PointF mBubMoveableCenter; /** * 气泡的画笔 */ private Paint mBubblePaint; /** * 贝塞尔曲线path */ private Path mBezierPath; /** * 文字的画笔 */ private Paint mTextPaint; /** * 文字的测量Rect */ private Rect mTextRect; /** * 爆炸的画笔 */ private Paint mBurstPaint; /** * 爆炸的范围 */ private Rect mBurstRect; /** * 两气泡圆心距离 */ private float mDist; /** * 气泡相连状态最大圆心距离 */ private float mMaxDist; /** * 气泡爆炸的bitmap数组 */ private Bitmap[] mBurstBitmapsArray; /** * 当前气泡爆炸图片index */ private int mCurDrawableIndex; /** * 气泡爆炸的图片id数组 */ private int[] mBurstDrawablesArray = {R.drawable.burst_1, R.drawable.burst_2 , R.drawable.burst_3, R.drawable.burst_4, R.drawable.burst_5}; /** * 手指触摸偏移量 */ private final float MOVE_OFFSET; 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) { this(context, attrs, defStyleAttr, 0); } //在这里统一做初始化操作 public DragBubbleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); 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(); mBubStillRadius = mBubbleRadius; mBubMoveableRadius = mBubStillRadius; //设置最长距离为8倍小球半径 mMaxDist = 8 * mBubbleRadius; //设置手指偏移量为小球半径的四分一 MOVE_OFFSET = mMaxDist / 4; mBezierPath = new Path(); mTextRect = new Rect(); mBurstRect = new Rect(); //小球的画笔 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); mBurstBitmapsArray = new Bitmap[mBurstDrawablesArray.length]; for (int i = 0; i < mBurstDrawablesArray.length; i++) { //将气泡爆炸的drawable转为bitmap Bitmap bitmap = BitmapFactory.decodeResource(getResources(), mBurstDrawablesArray[i]); mBurstBitmapsArray[i] = bitmap; } } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //不是处于消失状态,进行拖拽气泡小球与数字的绘制 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); } //处于气泡相连状态时候,进行静止气泡小球与相连曲线的绘制 if(mBubbleState == BUBBLE_STATE_CONNECT) { //绘制静止气泡小球 canvas.drawCircle(mBubStillCenter.x, mBubStillCenter.y, mBubStillRadius, mBubblePaint); //绘制相连曲线 // 计算控制点坐标,两个圆心的中点 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); mBezierPath.close(); canvas.drawPath(mBezierPath,mBubblePaint); } // 3、画消失状态---爆炸动画(mCurDrawableIndex 会等于 mBurstBitmapsArray 的长度,是为了自后让爆炸效果消失) if(mBubbleState == BUBBLE_STATE_DISMISS && mCurDrawableIndex < mBurstBitmapsArray.length){ 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); } } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_DOWN:{ //当状态为静止的时候才对“按下”事件进行响应 if (mBubbleState == BUBBLE_STATE_DEFAUL) { mDist = (float) Math.hypot(event.getX() - mBubStillCenter.x, event.getY() - mBubStillCenter.y); if (mDist < mBubbleRadius + MOVE_OFFSET) { // 加上MOVE_OFFSET是为了方便拖拽 mBubbleState = BUBBLE_STATE_CONNECT; } } break; } case MotionEvent.ACTION_MOVE:{ //当状态为相连或分离的时候才对“移动”事件进行响应 if (mBubbleState == BUBBLE_STATE_CONNECT || mBubbleState == BUBBLE_STATE_APART) { //重新记录拖拽的气泡小球圆心 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(); } } } } return true; } private void startBubbleRestAnim() { ValueAnimator anim = ValueAnimator.ofObject(new PointFEvaluator(), new PointF(mBubMoveableCenter.x,mBubMoveableCenter.y), new PointF(mBubStillCenter.x,mBubStillCenter.y)); anim.setDuration(200); 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(); } private void startBubbleBurstAnim() { //气泡改为消失状态 mBubbleState = BUBBLE_STATE_DISMISS; //做一个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.start(); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); initView(w,h); } /** * 初始化气泡位置 * @param w * @param h */ private void initView(int w, int h) { //设置两气泡圆心初始坐标 if(mBubStillCenter == null){ mBubStillCenter = new PointF(w / 2,h / 2); }else{ mBubStillCenter.set(w / 2,h / 2); } if(mBubMoveableCenter == null){ mBubMoveableCenter = new PointF(w / 2,h / 2); }else{ mBubMoveableCenter.set(w / 2,h / 2); } mBubbleState = BUBBLE_STATE_DEFAUL; } public void reset() { initView(getWidth(),getHeight()); invalidate(); }}
八、附
代码链接:http://download.csdn.net/detail/qq_18983205/9918206
- (十三)QQ 消息气泡
- 类似QQ拖动气泡删除消息的气泡实现
- 实现类似QQ气泡消息的样式
- 仿QQ消息气泡拖拽效果
- 仿照qq聊天,包含气泡消息发送
- 57.贝赛尔曲线初步(二) - 高仿QQ未读消息气泡拖拽黏连效果
- 贝赛尔曲线初步(二) - 高仿QQ未读消息气泡拖拽黏连效果
- 仿QQ拖动删除未读消息个数气泡
- 怎么做QQ、微信等消息气泡
- win32版QQ隐藏功能,气泡消息显示详细时间
- 高仿QQ未读消息气泡拖拽黏连效果
- kotlin学习之QQ消息气泡简单实现
- [Swift]iOS仿QQ消息气泡拉拽动画
- 高仿QQ未读消息气泡拖拽黏连效果
- Android自定义View仿QQ消息拖拽气泡实现
- 消息气泡
- 仿微信、短信、QQ等消息数目右上角红色小圆球气泡显示(基于Android XML布局文件实现)
- 仿微信、短信、QQ等消息数目右上角红色小圆球气泡显示(基于Android XML布局文件实现)
- [Js] Js实现继承的5种方式
- forward
- I am here!
- C/C++一些特殊关键字的作用&指针和引用的区别
- Excel 技巧百例:数据透视表的简单使用-行列转换
- (十三)QQ 消息气泡
- linux shell 之 管道符号(|)
- Ubuntu16.04安装tensorflow遇到的问题
- Spring Web MVC 关键技术点
- Vim
- 一个爬虫的自我修养之使用urllib进行网页抓取
- 系统调用
- hdu2032 杨辉三角(C语言)
- 我的常用linux小命令