(十三)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

原创粉丝点击