(十三)QQ 消息气泡
版权声明:本文为博主原创文章,未经博主允许不得转载。
本文纯个人学习笔记,由于水平有限,难免有所出错,有发现的可以交流一下。
一、效果
二、分析
我们把整个气泡做成一个自定义控件,通过实际效果,可以知道控件有下列几种状态:
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/O1O2
BF/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() 方法。
@Overridepublic 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() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {mBubMoveableCenter = (PointF) animation.getAnimatedValue();invalidate();}});anim.addListener(new AnimatorListenerAdapter() {@Overridepublic 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() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {//设置当前绘制的爆炸图片indexmCurDrawableIndex = (int) animation.getAnimatedValue();invalidate();}});anim.start();}
代码相对比较简单。(属性动画这边不讲)
六、初始化
在 onSizeChanged() 方法中调用初始化。
@Overrideprotected 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转为bitmapBitmap bitmap = BitmapFactory.decodeResource(getResources(), mBurstDrawablesArray[i]);mBurstBitmapsArray[i] = bitmap;}}@Overrideprotected 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);}}@Overridepublic 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() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {mBubMoveableCenter = (PointF) animation.getAnimatedValue();invalidate();}});anim.addListener(new AnimatorListenerAdapter() {@Overridepublic 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() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {//设置当前绘制的爆炸图片indexmCurDrawableIndex = (int) animation.getAnimatedValue();invalidate();}});anim.start();}@Overrideprotected 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群:139128168 ← 点击加群 今天带来的是仿QQ消息气泡拖拽消失特效,源码中很多地方还是有很多不足,希望大家一起齐心协力,给 ...
- c++ qt 学习笔记 2021-2-26(QLabel的对齐方式等一些属性,QLabel自适应文字大小,QLabel播放gif,动态的QQ消息气泡框)
1.QLabel的对齐方式等一些属性: (1)对齐方式: QLabel对齐的方法: Qt::Alignment alignalignment() const; //获取对齐方式 void setAli ...
- SeniorUI09_贝塞尔曲线运用(QQ消息气泡)
高级UI汇总 源码:SeniorUI09_BezierActivity 1 效果图 2 贝塞尔曲线简介 以简单的二阶贝塞尔曲线为例 在平面内任选 3 个不共线的点,依次用线段连接. 在第 ...
- Android qq消息气泡实现效果,Android 实现仿QQ拖拽气泡效果的示例
效果图: 一.实现思路 在列表中默认使用自定义的TextView控件来展示消息气泡,在自定义的TextView控件中重写onTouchEvent方法,然后在DOWN.MOVE.UP事件中分别处理拖拽效 ...
- 仿QQ消息气泡拖拽效果
此次的自定义View是仿qq消息列表,消息气泡拖拽效果. 1.原理介绍:自定义view,绘制原始点圆,touch点圆,然后将两圆用贝塞尔曲线连接并填充. 2.应用WindowManager,将自定义v ...
- Android qq消息气泡实现效果,BezierDemo源码解析-实现qq消息气泡拖拽消失的效果
这篇文章中我们比较了DraggableFlagView和BezierDemo两个项目的区别,提到将对其中一个做源码分析,那么我们就来分析BezierDemo的源码吧,因为这个项目的源码最简单,可以更直 ...
- Android自定义View——qq消息气泡
效果图: 原理: 控件源码: public class DragView extends View {private int defaultZoomSize = 8;//初始化圆的大小private ...
- java 气泡聊天消息_Html,CSS 实现类似QQ的气泡聊天
下面是效果图: 下面说下关键地方的样式设置,然后贴出html和css代码(不多). 步骤1:布局 消息采用div+float布局,每条消息用一个DIV标签包裹,里面再放两个DIV分别用来包裹用户图标和 ...
- 安卓仿手机QQ消息BadgeView气泡跟随手指移动,并实现进出动画效果。
欢迎加安卓开发交流群:308372687(博主尽可能帮助大家)#安卓仿手机QQ消息BadgeView气泡,跟随手指移动,并实现进出动画效果. 欢迎加安卓开发交流群:308372687(博主尽可能帮助大 ...
最新文章
- 如何更新你的机器学习模型?手把手带你设计一个可持续的预测模型!
- LightOJ 1093 - Ghajini 线段树
- python爬虫-初步使用Scrapy分布式爬虫(爬取mcbbs整合包保存名称及主要mod),大爱MC
- 【Python爬虫】写个爬虫爬取自己的博客,可以刷访问量
- Infinite Fraction Path UVALive - 8207
- ShellExecute 函数的用法和实例
- 为什么不能同时用const和static修饰成员函数?
- AndroidO Treble架构下HIDL服务Java框架实现
- IAR环境中实现数据或函数的定位
- DOM的利用冒泡做的一个小程序
- Python学习笔记(语句)
- GitHub下载加速
- Gxlcms有声小说系统/小说听书系统源码
- stm32无感无刷电机驱动
- 802.11-2020协议学习__$12-Security__$12.5.2-TKIP__2
- 2022年金三银四该如何规划,才能轻松拿到offer
- 红豆熟了之后会生出什么呢?
- CSS在线字体库,外部字体的引用方法@font-face
- RK3126 人体感应模块驱动
- 蚁群优化(ACO)算法与变种
热门文章
- Android 连接 MySQL 数据库教程
- Xshell导入导出会话
- centos7 firewall-cmd查看端口是否开放及开放端口
- 小游戏《塔防》开发(三)
- php中input的onclick,onkeydown、onkeyup、onclick、onchange、oninput、onpropertychange 的用法和区别...
- 10.常用操作符一览
- NVR DVR前世今生
- 奇迹世界服务器架构(2)
- 2021-11-18 adb发送广播应用收不到的问题,BroadcastQueue: Background execution not allowed: receiving Intent { act=
- mysql中还有窗口函数?这是什么东西?