版权声明:本文为博主原创文章,未经博主允许不得转载。

本文纯个人学习笔记,由于水平有限,难免有所出错,有发现的可以交流一下。

一、效果

二、分析

我们把整个气泡做成一个自定义控件,通过实际效果,可以知道控件有下列几种状态:

 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 消息气泡相关推荐

  1. 微信小程序之『仿 QQ 消息气泡拖拽消失』

    转载:请写明掘金原文链接及作者名 '小小小' 一个潜心研究小程序QQ群:139128168 ← 点击加群 今天带来的是仿QQ消息气泡拖拽消失特效,源码中很多地方还是有很多不足,希望大家一起齐心协力,给 ...

  2. c++ qt 学习笔记 2021-2-26(QLabel的对齐方式等一些属性,QLabel自适应文字大小,QLabel播放gif,动态的QQ消息气泡框)

    1.QLabel的对齐方式等一些属性: (1)对齐方式: QLabel对齐的方法: Qt::Alignment alignalignment() const; //获取对齐方式 void setAli ...

  3. SeniorUI09_贝塞尔曲线运用(QQ消息气泡)

    高级UI汇总​​​​​​​ 源码:SeniorUI09_BezierActivity 1 效果图 2 贝塞尔曲线简介 以简单的二阶贝塞尔曲线为例 在平面内任选 3 个不共线的点,依次用线段连接. 在第 ...

  4. Android qq消息气泡实现效果,Android 实现仿QQ拖拽气泡效果的示例

    效果图: 一.实现思路 在列表中默认使用自定义的TextView控件来展示消息气泡,在自定义的TextView控件中重写onTouchEvent方法,然后在DOWN.MOVE.UP事件中分别处理拖拽效 ...

  5. 仿QQ消息气泡拖拽效果

    此次的自定义View是仿qq消息列表,消息气泡拖拽效果. 1.原理介绍:自定义view,绘制原始点圆,touch点圆,然后将两圆用贝塞尔曲线连接并填充. 2.应用WindowManager,将自定义v ...

  6. Android qq消息气泡实现效果,BezierDemo源码解析-实现qq消息气泡拖拽消失的效果

    这篇文章中我们比较了DraggableFlagView和BezierDemo两个项目的区别,提到将对其中一个做源码分析,那么我们就来分析BezierDemo的源码吧,因为这个项目的源码最简单,可以更直 ...

  7. Android自定义View——qq消息气泡

    效果图: 原理: 控件源码: public class DragView extends View {private int defaultZoomSize = 8;//初始化圆的大小private ...

  8. java 气泡聊天消息_Html,CSS 实现类似QQ的气泡聊天

    下面是效果图: 下面说下关键地方的样式设置,然后贴出html和css代码(不多). 步骤1:布局 消息采用div+float布局,每条消息用一个DIV标签包裹,里面再放两个DIV分别用来包裹用户图标和 ...

  9. 安卓仿手机QQ消息BadgeView气泡跟随手指移动,并实现进出动画效果。

    欢迎加安卓开发交流群:308372687(博主尽可能帮助大家)#安卓仿手机QQ消息BadgeView气泡,跟随手指移动,并实现进出动画效果. 欢迎加安卓开发交流群:308372687(博主尽可能帮助大 ...

最新文章

  1. 如何更新你的机器学习模型?手把手带你设计一个可持续的预测模型!
  2. LightOJ 1093 - Ghajini 线段树
  3. python爬虫-初步使用Scrapy分布式爬虫(爬取mcbbs整合包保存名称及主要mod),大爱MC
  4. 【Python爬虫】写个爬虫爬取自己的博客,可以刷访问量
  5. Infinite Fraction Path UVALive - 8207
  6. ShellExecute 函数的用法和实例
  7. 为什么不能同时用const和static修饰成员函数?
  8. AndroidO Treble架构下HIDL服务Java框架实现
  9. IAR环境中实现数据或函数的定位
  10. DOM的利用冒泡做的一个小程序
  11. Python学习笔记(语句)
  12. GitHub下载加速
  13. Gxlcms有声小说系统/小说听书系统源码
  14. stm32无感无刷电机驱动
  15. 802.11-2020协议学习__$12-Security__$12.5.2-TKIP__2
  16. 2022年金三银四该如何规划,才能轻松拿到offer
  17. 红豆熟了之后会生出什么呢?
  18. CSS在线字体库,外部字体的引用方法@font-face
  19. RK3126 人体感应模块驱动
  20. 蚁群优化(ACO)算法与变种

热门文章

  1. Android 连接 MySQL 数据库教程
  2. Xshell导入导出会话
  3. centos7 firewall-cmd查看端口是否开放及开放端口
  4. 小游戏《塔防》开发(三)
  5. php中input的onclick,onkeydown、onkeyup、onclick、onchange、oninput、onpropertychange 的用法和区别...
  6. 10.常用操作符一览
  7. NVR DVR前世今生
  8. 奇迹世界服务器架构(2)
  9. 2021-11-18 adb发送广播应用收不到的问题,BroadcastQueue: Background execution not allowed: receiving Intent { act=
  10. mysql中还有窗口函数?这是什么东西?