Android Path进阶之Path路径实现QQ拖拽气泡效果

先上效果图

先将这个过程分状态(阶段):

    1.静止状态,小球的默认状态,由红色的气泡和中间的文本组成;

    2.连接状态(拖拽半径短),拖拽过程中由随着手指一定的气泡和中间不断缩小的小气泡组成;

    3.分离状态(拖拽半径长),中间的小气泡消失,连接的中间线也消失;

    4.回弹或隐藏状态,当从连接状态松手,移动的气泡会回弹到中心,从分离状态松手,移动的气泡爆炸,整个页面隐藏。

1.实现思路

自定义一个DragBubbleView继承View,静止、连接和分离状态都是需要绘制一个大的可动气泡,只是坐标不同,连接状态时,首先随着手指的移动,改变可动气泡的坐标,同时在中心绘制固定气泡,固定气泡的圆心随拖拽半径的变大二变小,大气泡和小气泡中间连接处是两条二阶贝赛尔曲线,贝塞尔曲线可以参考上一小节总结 Android Path进阶之Path常用API,第一条二阶贝塞尔曲线的两个数据点分别是A和B,第二条二阶贝塞尔曲线的两个数据点分别是C和D,两条贝塞尔曲线的控制点都是大气泡和小气泡的圆心连线的中点G,然后两条贝塞尔曲线和两条直线BC和DA闭和,外加大圆和小圆的部分,3部分刚好形成连接状态时的图形绘制,分离状态只需要绘制大气泡,爆炸时使用5张连续的图片顺序播放形成爆炸效果。

2.初始化

对过程进行了分解之后开始实现,首先确定需要使用到的变量,并会其初始化。一只画笔绘制可动气泡中心文本,一只画笔绘制两个气泡和中心不规则图形,一只画笔绘制爆炸时效果,一个路径(Path)用来记录两个气泡中间连接不规则的图形,然后在构造方法中对画笔和路径进行初始化,在onSizeChange中对坐标点初始化。

public class DragBubbleView extends View {

    private static final int BUBBLE_STATE_DEFAULT = 0;//气泡默认状态--静止
    private static final int BUBBLE_STATE_CONNECT = 1;//气泡相连
    private final int BUBBLE_STATE_APART = 2;//气泡分离
    private final int BUBBLE_STATE_DISMISS = 3;//气泡消失
    private float mBubbleRadius = 60;//气泡半径
    private int mBubbleColor = Color.RED;//气泡颜色
    private String mTextStr = "99+";//气泡消息文字
    private int mTextColor = Color.WHITE;//气泡消息文字颜色
    private float mTextSize = 60;//气泡消息文字大小
    private float mBubFixedRadius = mBubbleRadius;//不动气泡的半径
    private float mBubMovableRadius = mBubbleRadius;//可动气泡的半径
    private PointF mBubFixedCenter;//不动气泡的圆心
    private PointF mBubMovableCenter;//可动气泡的圆心
    private Paint mBubblePaint;//气泡的画笔
    private Path mBezierPath;//贝塞尔曲线path
    private Paint mTextPaint;//文本绘制画笔
    private Rect mTextRect;//文本绘制区域
    private Paint mBurstPaint;//气泡爆炸消失画笔
    private Rect mBurstRect;//爆炸绘制区域
    private int mBubbleState = BUBBLE_STATE_DEFAULT;//气泡状态标志
    private float mDist;//两气泡圆心距离
    private float mMaxDist = 4 * mBubbleRadius;//气泡相连状态最大圆心距离
    private float MOVE_OFFSET = 2 * mBubbleRadius;//手指触摸偏移量
    private Bitmap[] mBurstBitmapsArray;//气泡爆炸的bitmap数组
    private boolean mIsBurstAnimStart = false;//是否在执行气泡爆炸动画
    private int mCurDrawableIndex;//当前气泡爆炸图片index

    //气泡爆炸的图片id数组
    private int[] mBurstDrawablesArray = {
            R.drawable.burst_1,
            R.drawable.burst_2,
            R.drawable.burst_3,
            R.drawable.burst_4,
            R.drawable.burst_5
    };

    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) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        //初始化气泡画笔
        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);
        mTextRect = new Rect();

        //初始化爆炸画笔
        mBurstPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mBurstPaint.setFilterBitmap(true);

        //初始化贝赛尔曲线path
        mBezierPath = new Path();

        mBurstRect = new Rect();

        //将气泡爆炸的drawable转为bitmap
        mBurstBitmapsArray = new Bitmap[mBurstDrawablesArray.length];
        for (int i = 0; i < mBurstDrawablesArray.length; i++) {
            Bitmap bitmap = BitmapFactory.decodeResource(getResources(), mBurstDrawablesArray[i]);
            mBurstBitmapsArray[i] = bitmap;
        }

    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        //不动气泡圆心
        if (mBubFixedCenter == null) {
            mBubFixedCenter = new PointF(w / 2, h / 2);
        } else {
            mBubFixedCenter.set(w / 2, h / 2);
        }

        //可动气泡圆心
        if (mBubMovableCenter == null) {
            mBubMovableCenter = new PointF(w / 2, h / 2);
        } else {
            mBubMovableCenter.set(w / 2, h / 2);
        }
    }
}

3.气泡状态改变与触摸事件监听

气泡的各种状态的改变都是通过手指在屏幕的状态决定的,因此需要对触摸事件进行监听。

1.DOWN事件。气泡的默认状态是静止状态,在Down事件时判断下是不是手指的坐标是否在大气泡的半径内,如果在,则将气泡状态改为连接状态,如果不是则不作处理,即保持原来的默认静止状态

2.MOVE事件。Move事件先判断如果气泡是隐藏状态则不作处理,如果不是,求出固定气泡圆心和移动坐标之间的距离,如果该距离小于我们定的临界值,则认为气泡还处于连接状态,如果该距离大于我们定的临界值,则将气泡的状态改为分离状态,同时设置移动气泡的坐标为移动的坐标,并改变固定气泡的半径,最后别忘了调用invalidate方法进行重绘。

3.UP事件。UP事件时我们只需要判断下气泡当前的状态。如果当前气泡是连接状态,我们需要将移动气泡重置到中心,并将气泡状态设置为默认状态。如果当前气泡的状态为断开状态,我们只需要执行爆炸动画,并将气泡状态设置为默认状态。气泡重置动画和爆炸动画后面讲。

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                if (mBubbleState != BUBBLE_STATE_DISMISS) {
                    mDist = (float) Math.hypot(event.getX() - mBubFixedCenter.x, event.getY() - mBubFixedCenter.y);
                    Log.d(TAG, "mDist: " + (mDist < mBubbleRadius + MOVE_OFFSET));
                    if (mDist < mBubbleRadius + MOVE_OFFSET) {
                        mBubbleState = BUBBLE_STATE_CONNECT;
                    } else {
                        mBubbleState = BUBBLE_STATE_DEFAULT;
                    }
                }
                break;
            case MotionEvent.ACTION_MOVE:
                Log.d(TAG, "mBubbleState: " + mBubbleState);
                if (mBubbleState != BUBBLE_STATE_DISMISS && mBubbleState != BUBBLE_STATE_DEFAULT) {
                    mDist = (float) Math.hypot(event.getX() - mBubFixedCenter.x, event.getY() - mBubFixedCenter.y);
                    Log.d(TAG, "mDist: " + mDist);
                    mBubMovableCenter.set(event.getX(), event.getY());//设置移动气泡的坐标
                    mBubFixedRadius = mBubbleRadius - mDist / 8;//缩小固定气泡的半径
                    if (mDist < mMaxDist + MOVE_OFFSET) {
                        mBubbleState = BUBBLE_STATE_CONNECT;
                    } else {
                        mBubbleState = BUBBLE_STATE_APART;//将气泡变为断开状态
                    }
                }
                invalidate();
                break;
            case MotionEvent.ACTION_UP:
                if (mBubbleState == BUBBLE_STATE_CONNECT) {
                    startBubbleResetAnim();//回弹效果
                } else if (mBubbleState == BUBBLE_STATE_APART) {
                    startBubbleBurstAnim();//爆炸动画
                }
                break;
        }
        return true;
    }

4.绘制

绘制是在ondraw方法中进行的,上面已经对气泡的每种状态进行了设置。接下来就是根据不同的状态绘制不同的状态。

1.默认静止状态、连接状态和分离状态大气泡,因为这三种状态都有一个大气泡,因此可以放一起绘制,因此只要不是隐藏状态,都需要绘制。

        //一个气泡加消息数据
        if (mBubbleState != BUBBLE_STATE_DISMISS) {
            canvas.drawCircle(mBubMovableCenter.x, mBubMovableCenter.y, mBubMovableRadius, mBubblePaint);
            mTextPaint.getTextBounds(mTextStr, 0, mTextStr.length(), mTextRect);
            canvas.drawText(mTextStr, mBubMovableCenter.x - mTextRect.width() / 2, mBubMovableCenter.y + mTextRect.height() / 2, mTextPaint);
        }

2.连接状态小圆和不规则图形的绘制。连接状态时移动气泡已经在上面绘制了,还剩下小圆的绘制和不规则图形的绘制,小圆的绘制简单,直接绘制。不规则图形的绘制主要是求控制点的坐标和A、B、C、D四点的坐标,然后利用Path形成闭和路径,然后绘制Path。

        if (mBubbleState == BUBBLE_STATE_CONNECT) {

            //绘制不动圆
            canvas.drawCircle(mBubFixedCenter.x, mBubFixedCenter.y, mBubFixedRadius, mBubblePaint);

            //绘制贝塞尔曲线

            //控制点坐标
            float ctrlX = (mBubFixedCenter.x + mBubMovableCenter.x) / 2;
            float ctrlY = (mBubFixedCenter.y + mBubMovableCenter.y) / 2;

            float sinTheta = (mBubMovableCenter.y - mBubFixedCenter.y) / mDist;
            float cosTheta = (mBubMovableCenter.x - mBubFixedCenter.x) / mDist;

            //B
            float iBubMovableStartX = mBubMovableCenter.x + sinTheta * mBubMovableRadius;
            float iBubMovableStartY = mBubMovableCenter.y - cosTheta * mBubMovableRadius;

            //A
            float iBubFixedEndX = mBubFixedCenter.x + mBubFixedRadius * sinTheta;
            float iBubFixedEndY = mBubFixedCenter.y - mBubFixedRadius * cosTheta;

            //D
            float iBubFixedStartX = mBubFixedCenter.x - mBubFixedRadius * sinTheta;
            float iBubFixedStartY = mBubFixedCenter.y + mBubFixedRadius * cosTheta;
            //C
            float iBubMovableEndX = mBubMovableCenter.x - mBubMovableRadius * sinTheta;
            float iBubMovableEndY = mBubMovableCenter.y + mBubMovableRadius * cosTheta;

            mBezierPath.reset();
            mBezierPath.moveTo(iBubFixedStartX, iBubFixedStartY);
            mBezierPath.quadTo(ctrlX, ctrlY, iBubMovableEndX, iBubMovableEndY);//二阶贝塞尔曲线
            mBezierPath.lineTo(iBubMovableStartX, iBubMovableStartY);//一阶贝赛尔曲线
            mBezierPath.quadTo(ctrlX, ctrlY, iBubFixedEndX, iBubFixedEndY);//二阶贝塞尔曲线
            mBezierPath.close();//闭和曲线

            canvas.drawPath(mBezierPath, mBubblePaint);//绘制两圆中间的区域

        }

3.重置回弹动画。UP事件后可能会执行重置回弹动画或者爆炸效果,先来说说回弹动画,回弹动画,实际上是移动气泡的坐标从UP之后的坐标回到固定气泡的过程,因此可以采用属性动画实现,并且在动画结束前会有回弹效果,因此我们对属性动画设置差值器设置为OvershootInterpolator,该方法在UP事件时被调用。

    private void startBubbleResetAnim() {
        ValueAnimator animator = ValueAnimator.ofObject(new PointFEvaluator(), mBubMovableCenter, mBubFixedCenter);
        animator.setDuration(300);
        animator.setInterpolator(new OvershootInterpolator());
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mBubMovableCenter = (PointF) animation.getAnimatedValue();
                invalidate();
            }
        });
        animator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                mBubbleState = BUBBLE_STATE_DEFAULT;
            }
        });
        animator.start();
    }

4.爆炸效果。在断开状态松手,会实现一个爆炸效果,爆炸效果实际上是5张静图的顺序播放,这里也采用属性动画实现,动画的变化值为int,大小从0变化到爆炸数组的length。

    private void startBubbleBurstAnim() {
        mBubbleState = BUBBLE_STATE_DISMISS;
        ValueAnimator animator = ValueAnimator.ofInt(0, mBurstBitmapsArray.length);
        animator.setDuration(1000);
        animator.setInterpolator(new LinearInterpolator());
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mCurDrawableIndex = (int) animation.getAnimatedValue();
                invalidate();
            }
        });
        animator.start();
    }

值变了之后还要重绘,因此ondraw方法中也需要做相应的处理

        if (mBubbleState == BUBBLE_STATE_DISMISS && mCurDrawableIndex < mBurstBitmapsArray.length) {
            mBurstRect.set((int) (mBubMovableCenter.x - mBubMovableRadius),
                    (int) (mBubMovableCenter.y - mBubMovableRadius),
                    (int) (mBubMovableCenter.x + mBubMovableRadius),
                    (int) (mBubMovableCenter.y + mBubMovableRadius)
            );
            canvas.drawBitmap(mBurstBitmapsArray[mCurDrawableIndex], null, mBurstRect, mBurstPaint);
        }

好了,讲完了,核心其实在连接状态的绘制,完整代码:

public class DragBubbleView extends View {

    private static final int BUBBLE_STATE_DEFAULT = 0;//气泡默认状态--静止
    private static final int BUBBLE_STATE_CONNECT = 1;//气泡相连
    private static final String TAG = "DragBubbleView";
    private final int BUBBLE_STATE_APART = 2;//气泡分离
    private final int BUBBLE_STATE_DISMISS = 3;//气泡消失
    private float mBubbleRadius = 60;//气泡半径
    private int mBubbleColor = Color.RED;//气泡颜色
    private String mTextStr = "99+";//气泡消息文字
    private int mTextColor = Color.WHITE;//气泡消息文字颜色
    private float mTextSize = 60;//气泡消息文字大小
    private float mBubFixedRadius = mBubbleRadius;//不动气泡的半径
    private float mBubMovableRadius = mBubbleRadius;//可动气泡的半径
    private PointF mBubFixedCenter;//不动气泡的圆心
    private PointF mBubMovableCenter;//可动气泡的圆心
    private Paint mBubblePaint;//气泡的画笔
    private Path mBezierPath;//贝塞尔曲线path
    private Paint mTextPaint;//文本绘制画笔
    private Rect mTextRect;//文本绘制区域
    private Paint mBurstPaint;//气泡爆炸消失画笔
    private Rect mBurstRect;//爆炸绘制区域
    private int mBubbleState = BUBBLE_STATE_DEFAULT;//气泡状态标志
    private float mDist;//两气泡圆心距离
    private float mMaxDist = 4 * mBubbleRadius;//气泡相连状态最大圆心距离
    private float MOVE_OFFSET = 2 * mBubbleRadius;//手指触摸偏移量
    private Bitmap[] mBurstBitmapsArray;//气泡爆炸的bitmap数组
    private boolean mIsBurstAnimStart = false;//是否在执行气泡爆炸动画
    private int mCurDrawableIndex;//当前气泡爆炸图片index

    //气泡爆炸的图片id数组
    private int[] mBurstDrawablesArray = {
            R.drawable.burst_1,
            R.drawable.burst_2,
            R.drawable.burst_3,
            R.drawable.burst_4,
            R.drawable.burst_5
    };

    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) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        //初始化气泡画笔
        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);
        mTextRect = new Rect();

        //初始化爆炸画笔
        mBurstPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mBurstPaint.setFilterBitmap(true);

        //初始化贝赛尔曲线path
        mBezierPath = new Path();

        mBurstRect = new Rect();

        //将气泡爆炸的drawable转为bitmap
        mBurstBitmapsArray = new Bitmap[mBurstDrawablesArray.length];
        for (int i = 0; i < mBurstDrawablesArray.length; i++) {
            Bitmap bitmap = BitmapFactory.decodeResource(getResources(), mBurstDrawablesArray[i]);
            mBurstBitmapsArray[i] = bitmap;
        }

    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        //不动气泡圆心
        if (mBubFixedCenter == null) {
            mBubFixedCenter = new PointF(w / 2, h / 2);
        } else {
            mBubFixedCenter.set(w / 2, h / 2);
        }

        //可动气泡圆心
        if (mBubMovableCenter == null) {
            mBubMovableCenter = new PointF(w / 2, h / 2);
        } else {
            mBubMovableCenter.set(w / 2, h / 2);
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //1,静止状态,一个气泡加消息数据
        //2, 连接状态,一个气泡加消息数据,贝塞尔曲线,本身位置上气泡,大小可变化
        //3,分离状态,一个气泡加消息数据
        //4,消失状态,爆炸效果
        Log.d(TAG, "onDraw mBubbleState: " + mBubbleState);
        if (mBubbleState == BUBBLE_STATE_CONNECT) {

            //绘制不动圆
            canvas.drawCircle(mBubFixedCenter.x, mBubFixedCenter.y, mBubFixedRadius, mBubblePaint);

            //绘制贝塞尔曲线

            //控制点坐标
            float ctrlX = (mBubFixedCenter.x + mBubMovableCenter.x) / 2;
            float ctrlY = (mBubFixedCenter.y + mBubMovableCenter.y) / 2;

            float sinTheta = (mBubMovableCenter.y - mBubFixedCenter.y) / mDist;
            float cosTheta = (mBubMovableCenter.x - mBubFixedCenter.x) / mDist;

            //B
            float iBubMovableStartX = mBubMovableCenter.x + sinTheta * mBubMovableRadius;
            float iBubMovableStartY = mBubMovableCenter.y - cosTheta * mBubMovableRadius;

            //A
            float iBubFixedEndX = mBubFixedCenter.x + mBubFixedRadius * sinTheta;
            float iBubFixedEndY = mBubFixedCenter.y - mBubFixedRadius * cosTheta;

            //D
            float iBubFixedStartX = mBubFixedCenter.x - mBubFixedRadius * sinTheta;
            float iBubFixedStartY = mBubFixedCenter.y + mBubFixedRadius * cosTheta;
            //C
            float iBubMovableEndX = mBubMovableCenter.x - mBubMovableRadius * sinTheta;
            float iBubMovableEndY = mBubMovableCenter.y + mBubMovableRadius * cosTheta;

            mBezierPath.reset();
            mBezierPath.moveTo(iBubFixedStartX, iBubFixedStartY);
            mBezierPath.quadTo(ctrlX, ctrlY, iBubMovableEndX, iBubMovableEndY);//二阶贝塞尔曲线
            mBezierPath.lineTo(iBubMovableStartX, iBubMovableStartY);//一阶贝赛尔曲线
            mBezierPath.quadTo(ctrlX, ctrlY, iBubFixedEndX, iBubFixedEndY);//二阶贝塞尔曲线
            mBezierPath.close();//闭和曲线

            canvas.drawPath(mBezierPath, mBubblePaint);//绘制两圆中间的区域

        }

        //一个气泡加消息数据
        if (mBubbleState != BUBBLE_STATE_DISMISS) {
            canvas.drawCircle(mBubMovableCenter.x, mBubMovableCenter.y, mBubMovableRadius, mBubblePaint);
            mTextPaint.getTextBounds(mTextStr, 0, mTextStr.length(), mTextRect);
            canvas.drawText(mTextStr, mBubMovableCenter.x - mTextRect.width() / 2, mBubMovableCenter.y + mTextRect.height() / 2, mTextPaint);
        }

        //爆炸效果
        if (mBubbleState == BUBBLE_STATE_DISMISS && mCurDrawableIndex < mBurstBitmapsArray.length) {
            mBurstRect.set((int) (mBubMovableCenter.x - mBubMovableRadius),
                    (int) (mBubMovableCenter.y - mBubMovableRadius),
                    (int) (mBubMovableCenter.x + mBubMovableRadius),
                    (int) (mBubMovableCenter.y + mBubMovableRadius)
            );
            canvas.drawBitmap(mBurstBitmapsArray[mCurDrawableIndex], null, mBurstRect, mBurstPaint);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                if (mBubbleState != BUBBLE_STATE_DISMISS) {
                    mDist = (float) Math.hypot(event.getX() - mBubFixedCenter.x, event.getY() - mBubFixedCenter.y);
                    Log.d(TAG, "mDist: " + (mDist < mBubbleRadius + MOVE_OFFSET));
                    if (mDist < mBubbleRadius + MOVE_OFFSET) {
                        mBubbleState = BUBBLE_STATE_CONNECT;
                    } else {
                        mBubbleState = BUBBLE_STATE_DEFAULT;
                    }
                }
                break;
            case MotionEvent.ACTION_MOVE:
                Log.d(TAG, "mBubbleState: " + mBubbleState);
                if (mBubbleState != BUBBLE_STATE_DISMISS && mBubbleState != BUBBLE_STATE_DEFAULT) {
                    mDist = (float) Math.hypot(event.getX() - mBubFixedCenter.x, event.getY() - mBubFixedCenter.y);
                    Log.d(TAG, "mDist: " + mDist);
                    mBubMovableCenter.set(event.getX(), event.getY());//设置移动气泡的坐标
                    mBubFixedRadius = mBubbleRadius - mDist / 8;//缩小固定气泡的半径
                    if (mDist < mMaxDist + MOVE_OFFSET) {
                        mBubbleState = BUBBLE_STATE_CONNECT;
                    } else {
                        mBubbleState = BUBBLE_STATE_APART;//将气泡变为断开状态
                    }
                }
                invalidate();
                break;
            case MotionEvent.ACTION_UP:
                if (mBubbleState == BUBBLE_STATE_CONNECT) {
                    startBubbleResetAnim();//回弹效果
                } else if (mBubbleState == BUBBLE_STATE_APART) {
                    startBubbleBurstAnim();//爆炸动画
                }
                break;
        }
        return true;
    }

    //回弹动画
    private void startBubbleResetAnim() {
        ValueAnimator animator = ValueAnimator.ofObject(new PointFEvaluator(), mBubMovableCenter, mBubFixedCenter);
        animator.setDuration(300);
        animator.setInterpolator(new OvershootInterpolator());
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mBubMovableCenter = (PointF) animation.getAnimatedValue();
                invalidate();
            }
        });
        animator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                mBubbleState = BUBBLE_STATE_DEFAULT;
            }
        });
        animator.start();
    }

    //爆炸动画
    private void startBubbleBurstAnim() {
        mBubbleState = BUBBLE_STATE_DISMISS;
        ValueAnimator animator = ValueAnimator.ofInt(0, mBurstBitmapsArray.length * 2);
        animator.setDuration(1000);
        animator.setInterpolator(new LinearInterpolator());
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mCurDrawableIndex = (int) animation.getAnimatedValue();
                invalidate();
            }
        });
        animator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                mBubMovableCenter.set(mBubFixedCenter.x, mBubFixedCenter.y);
                mBubbleState = BUBBLE_STATE_DEFAULT;
            }
        });
        animator.start();
    }

}

Demo:https://github.com/987570437/PaintDemo

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值