QQ红点消除功能——自定义RedPointLayout

写在前面:

  本文原创者醉悔今朝http://blog.csdn.net/qq_38911444/article/details/77963619

  一开始又看了几篇写QQ红点消除功能的文章,基本上都差不多,这里给出其中一个的链接:http://blog.csdn.net/crazy__chen/article/details/49903475,但是这些文章都只是介绍了一下红点绘制以及拉伸、消除动画,并没有涉及到实际应用中去,也就是说,都只是在自己的这个View中演示红点消除功能动画而已,因此就有了我接下来自己写的一个Demo。

  在我的Demo中,自定义的RedPointLayout是继承自LinearLayout,可以实现像QQ消息上红点类似的功能。只需要向RedPointLayout中传入一个红点的集合,就可以绘制出想要的任意个数红点,并且各红点之间相互独立。

  效果图如下:


  Demo地址http://git.oschina.net/ZuiHuiJinZhao/RedPointLayout_Demo

开始介绍:

1、内部类RedPoint

  从名字就可以知道,这是一个红点类,每一个RedPoint实例就是一个需要绘制的红点,具体代码如下:

public  class RedPoint{
    /** 红点初始位置中心点坐标 */
    private Point centerPoint;
    /** 红点小红点中心点坐标 */
    private Point rePoint;
    /** 红点大红点中心点坐标 */
    private Point toPoint;
    /** 红点的大小(即半径) */
    private int size;
    /** 红点要展示的文字内容 */
    private String text;
    /** 文字的大小 */
    private int textSize;
    /** 文字的颜色 */
    private int textColor;
    /** 红点的颜色 */
    private int color;
    /** 红点的消除距离 */
    private int distance;

    public RedPoint(){
        centerPoint = new Point(0, 0);
        rePoint = new Point(0, 0);
        toPoint = new Point(0, 0);
        size = 20;
        text = "0";
        textSize  = 18;
        textColor = 0xffffffff;
        color = 0xffff318c;
        distance = 80;
    }

    public void setCenterPoint(Point centerPoint) {
        this.centerPoint = centerPoint;
        rePoint.set(centerPoint.x, centerPoint.y);
        toPoint.set(centerPoint.x, centerPoint.y);
        Log.i(TAG, "rePoint.x = " + rePoint.x + "rePoint.y = " + rePoint.y);
        Log.i(TAG, "toPoint.x = " + toPoint.x + "toPoint.y = " + toPoint.y);
    }

    public void setRePoint(Point redPoint) {
        this.rePoint = redPoint;
    }

    public void setTopoint(Point topoint) {
        this.toPoint = topoint;
    }

    public void setSize(int size) {
        this.size = size;
    }

    public void setText(String text) {
        this.text = text;
    }

    public void setTextSize(int textSize) {
        this.textSize = textSize;
    }

    public void setTextColor(int textColor) {
        try{
            ColorStateList csl = ColorStateList.valueOf(textColor);
            this.textColor = csl.getColorForState(getDrawableState(), 0);
        }catch (Exception e){
            this.textColor = textColor;
        }
    }
    public void setTextColor(ColorStateList textColor) {
        this.textColor = textColor.getColorForState(getDrawableState(), 0);
    }

    public void setColor(int color) {
        try{
            ColorStateList csl = ColorStateList.valueOf(color);
            this.color = csl.getColorForState(getDrawableState(), 0);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    public void setColor(ColorStateList color) {
        this.color = color.getColorForState(getDrawableState(), 0);
    }

    public void setDistance(int distance) {
        this.distance = distance;
    }

    public Point getCenterPoint() {
        return centerPoint;
    }

    public Point getRePoint() {
        return rePoint;
    }

    public Point getToPoint() {
        return toPoint;
    }

    public int getSize() {
        return size;
    }

    public String getText() {
        return text;
    }

    public int getTextSize() {
        return textSize;
    }

    public int getTextColor() {
        return textColor;
    }

    public int getColor() {
        return color;
    }

    public int getDistance() {
        return distance;
    }
}
2、红点增删关键方法setRedPoint()、addRedPoint()、clearRedPoint()、removeRedPoint()

  RedPointLayout中有一个专门用于存储红点的ArrayList,名为redPointArrayList,这几个方法都与其有关,用于添加或者删除红点

  setRedPoint()可直接传入一个ArrayList<RedPoint>赋值给redPointArrayList,RedPointLayout将会自动刷新并绘制出ArrayList<RedPoint>中包含的所有红点

/**
 * 直接传入完整的redPointArrayList并刷新显示
 * @param redPointArrayList
 */
public void setRedPoint(ArrayList<RedPoint> redPointArrayList){
    try {
        if(redPointArrayList == null){
            return;
        }
        this.redPointArrayList.clear();
        this.redPointArrayList = null;
        this.redPointArrayList = redPointArrayList;
        invalidate();
    }catch (Exception e){
        e.printStackTrace();
    }
}

  addRedPoint()可直接在redPointArrayList中添加一个红点(可重复调用添加多个),RedPointLayout同样会自动刷新并绘制出所有的的所有红点

/**
 * 添加红点
 * @param redPoint
 */
public void addRedPoint(RedPoint redPoint){
    try {
        if(redPoint == null){
            return;
        }
        redPointArrayList.add(redPoint);
        invalidate();
    }catch (Exception e){
        e.printStackTrace();
    }
}

  clearRedPoint()可清除redPointArrayList中的所有红点,并刷新界面

public void clearRedPoint(){
    try {
        if(redPointArrayList != null){
            redPointArrayList.clear();
        }
    }catch (Exception e){
        e.printStackTrace();
    }
}

  removeRedPoint()传入一个int类型的下标,可移除redPointArrayList中对应下标的红点,并刷新界面

/**
 * 根据下标移除红点
 * @param index
 */
public void removeRedPoint(int index){
    try {
        if(index > 0 && index < redPointArrayList.size()){
            redPointArrayList.remove(index);
        }
    }catch (Exception e){
        e.printStackTrace();
    }
}

3、红点及动画效果绘制

  由于RedPointLayout是继承自LinearLayout,因此绘制操作通过重写dispatchDraw()实现,在该方法重调用如下方法

/**
 * 绘制红点
 * @param canvas
 */
private void drawRedPointList(Canvas canvas){
    //绘制所有红点
    for(int i=0;i<redPointArrayList.size();i++){
        RedPoint redPoint = redPointArrayList.get(i);

        mCanvasPaint.setColor(redPoint.getColor());

        mTextPaint.setTextAlign(Paint.Align.CENTER);
        mTextPaint.setTextSize(redPoint.getTextSize());
        mTextPaint.setColor(redPoint.getTextColor());
        Paint.FontMetricsInt fontMetrics = mTextPaint.getFontMetricsInt();
        canvas.drawCircle(redPoint.getRePoint().x, redPoint.getRePoint().y, redPoint.getSize()/2, mCanvasPaint);
        canvas.drawCircle(redPoint.getToPoint().x, redPoint.getToPoint().y, redPoint.getSize(), mCanvasPaint);
        if(Math.sqrt((redPoint.getToPoint().x - redPoint.getRePoint().x)*(redPoint.getToPoint().x - redPoint.getRePoint().x)
                +(redPoint.getToPoint().y - redPoint.getRePoint().y)*(redPoint.getToPoint().y - redPoint.getRePoint().y))<redPoint.getDistance()){
            drawBeiSaierLine(canvas, redPoint);
        }
        //计算文字在红点中垂直居中时的y值
        int baseline = redPoint.getToPoint().y + ( - fontMetrics.ascent - fontMetrics.descent) / 2;
        canvas.drawText(redPoint.getText(), redPoint.getToPoint().x, baseline, mTextPaint);
    }
    if(beng){
        if(mCurExplosionIndex < mExplosionBitmaps.length){
            //设置气泡爆炸图片的位置
            Log.i(TAG, "mCurExplosionIndex = " + mCurExplosionIndex);
            mBitMapPaint.setFilterBitmap(true);
            //根据当前进行到爆炸气泡的位置index来绘制爆炸气泡bitmap
            canvas.drawBitmap(mExplosionBitmaps[mCurExplosionIndex], null, mExplosionRect, mBitMapPaint);
        }else{
            beng = false;
        }
    }
}

  其中有做判断,当大红点(即红点当前位置)与小红点(即红点原位置)不在同一位置且尚未超出消除距离时,调用以下方法绘制出粘连效果:

/**
 * 通过贝塞尔曲线绘制大小红点之间的粘连效果
 * @param canvas
 * @param redPoint
 */
private void drawBeiSaierLine(Canvas canvas, RedPoint redPoint){
    Point p1 = getQieDian(redPoint.getRePoint(), redPoint.getToPoint(), redPoint.getSize()/2);
    Point p2 = getQieDian(redPoint.getToPoint(), redPoint.getRePoint(), redPoint.getSize());
    Point p3 = getQieDian2(redPoint.getRePoint(), redPoint.getToPoint(), redPoint.getSize()/2);
    Point p4 = getQieDian2(redPoint.getToPoint(), redPoint.getRePoint(), redPoint.getSize());
    mPath.reset();
    mPath.moveTo(p1.x, p1.y);
    mPath.quadTo((redPoint.getToPoint().x + redPoint.getRePoint().x)/2, (redPoint.getToPoint().y + redPoint.getRePoint().y)/2, p4.x, p4.y);
    mPath.lineTo(p2.x, p2.y);
    mPath.quadTo((redPoint.getToPoint().x + redPoint.getRePoint().x)/2, (redPoint.getToPoint().y + redPoint.getRePoint().y)/2, p3.x, p3.y);
    canvas.drawPath(mPath, mCanvasPaint);
}

4、触摸及红点的移动判断

  因为红点是绘制在RedPointLayout上,而RedPointLayout是继承自LinearLayout,最终的表现效果是如同QQ消息的在某一条消息上显示的红点,而那条消息所在的控件又是可点击的,为了使触摸事件优先触发红点的效果,因此这里重写的是dispatchTouchEvent()而非onTouchEvent():

/**
 * 由于要使红点对触摸事件的响应优先于RedPointLayout的子控件,所以重写dispatchTouchEvent来处理触摸事件,并决定是否拦截
 * 而不是重写onTouchEvent
 * @param event
 * @return
 */
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
    switch (event.getAction()){
        case MotionEvent.ACTION_DOWN:
            //获取触摸到的红点(-1为触摸位置没有红点)
            curMoveRedPointIndex = getCurMoveRedPointIndex(event.getX(), event.getY() + getScrollY());
            if(curMoveRedPointIndex == -1){
                return super.dispatchTouchEvent(event);
            }else{
                //开始更新大红点到触摸位置,小红点留在原地
                RedPoint curMoveRedPoint = redPointArrayList.get(curMoveRedPointIndex);
                redPointArrayList.remove(curMoveRedPointIndex);
                curMoveRedPoint.setTopoint(new Point((int)event.getX(), (int)event.getY() + getScrollY()));
                redPointArrayList.add(curMoveRedPointIndex, curMoveRedPoint);
                invalidate();
                return true;
            }
        case MotionEvent.ACTION_MOVE:
            if(curMoveRedPointIndex == -1){
                return super.dispatchTouchEvent(event);
            }else{
                //更新红点位置
                RedPoint curMoveRedPoint = redPointArrayList.get(curMoveRedPointIndex);
                redPointArrayList.remove(curMoveRedPointIndex);
                curMoveRedPoint.setTopoint(new Point((int)event.getX(), (int)event.getY() + getScrollY()));
                redPointArrayList.add(curMoveRedPointIndex, curMoveRedPoint);
                invalidate();
                return true;
            }
        case MotionEvent.ACTION_UP:
            if(curMoveRedPointIndex == -1){
                return super.dispatchTouchEvent(event);
            }else{
                //更新红点位置,并判断大红点与小红点的距离,若小于消除距离(curMoveRedPoint.getDistance())则执行动画将大红点归位
                //否则,消除所有红点,并将该红点从redPointArrayList中移除
                RedPoint curMoveRedPoint = redPointArrayList.get(curMoveRedPointIndex);
                redPointArrayList.remove(curMoveRedPointIndex);
                curMoveRedPoint.setTopoint(new Point((int)event.getX(), (int)event.getY() + getScrollY()));
                if(Math.sqrt((curMoveRedPoint.getToPoint().x - curMoveRedPoint.getRePoint().x)*(curMoveRedPoint.getToPoint().x - curMoveRedPoint.getRePoint().x)+(curMoveRedPoint.getToPoint().y - curMoveRedPoint.getRePoint().y)*(curMoveRedPoint.getToPoint().y - curMoveRedPoint.getRePoint().y))<curMoveRedPoint.getDistance()){
                    Log.i(TAG, "正在执行动画···");
                    beng = false;
                    redPointArrayList.add(curMoveRedPointIndex, curMoveRedPoint);
                    startAnimator();
                } else{
                    Log.i(TAG, "正在执行爆炸···");
                    beng = true;
                    mExplosionRect.set((int)(curMoveRedPoint.getToPoint().x - Math.sqrt(curMoveRedPoint.getSize() * curMoveRedPoint.getSize())), (int)(curMoveRedPoint.getToPoint().y - Math.sqrt(curMoveRedPoint.getSize() * curMoveRedPoint.getSize()))
                            , (int)(curMoveRedPoint.getToPoint().x + Math.sqrt(curMoveRedPoint.getSize() * curMoveRedPoint.getSize())),  (int)(curMoveRedPoint.getToPoint().y + Math.sqrt(curMoveRedPoint.getSize() * curMoveRedPoint.getSize())));
                    startBubbleDismissAnim();
                }
                return true;
            }
    }
    return false;
}

附上整个自定义控件的代码:

import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.animation.LinearInterpolator;
import android.view.animation.PathInterpolator;
import android.widget.ScrollView;

import java.util.ArrayList;

import redpoint.szm.com.R;

/**
 * Created by:Sun ZhongMou on 2017/8/28 10:08
 **/

@SuppressLint("newApi")
public class RedPointLayout extends ScrollView {

    private static final int REDPOINT_VISIBLE = 0;
    private static final int REDPOINT_GONE = 1;

    private static final String TAG = "RedPointLayout";

    private Paint mTextPaint;
    private Paint mBitMapPaint;
    private Paint mCanvasPaint;
    private Path mPath;
    private Rect mRect;
    private Rect mExplosionRect;

    private ArrayList<RedPoint> redPointArrayList;
    private int curMoveRedPointIndex;
    /** 气泡爆炸动画资源 */
    private int[] mExplosionDrawables = {R.drawable.explosion_one, R.drawable.explosion_two
            , R.drawable.explosion_three, R.drawable.explosion_four, R.drawable.explosion_five};
    /** 气泡爆炸的bitmap数组 */
    private Bitmap[] mExplosionBitmaps;
    /** 当前展示的气泡图下标 */
    private int mCurExplosionIndex;
    /** 动画展示与否 */
    private boolean beng;

    public RedPointLayout(Context context){
        this(context, null);
    }
    public RedPointLayout(Context context, AttributeSet attrs){
        this(context, attrs, 0);
    }
    public RedPointLayout(Context context, AttributeSet attrs , int defStyleAttr){
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init(){
        mPath = new Path();
        mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mBitMapPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mCanvasPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mTextPaint.setAntiAlias(true);
        mBitMapPaint.setAntiAlias(true);
        mCanvasPaint.setAntiAlias(true);
        mRect = new Rect();
        mExplosionRect = new Rect();
        redPointArrayList = new ArrayList<RedPoint>();
        mExplosionBitmaps = new Bitmap[mExplosionDrawables.length];
        for (int i = 0; i < mExplosionDrawables.length; i++) {
            //将气泡爆炸的drawable转为bitmap
            Bitmap bitmap = BitmapFactory.decodeResource(getResources(), mExplosionDrawables[i]);
            mExplosionBitmaps[i] = bitmap;
        }
    }

    /**
     * 直接传入完整的redPointArrayList并刷新显示
     * @param redPointArrayList
     */
    public void setRedPoint(ArrayList<RedPoint> redPointArrayList){
        try {
            if(redPointArrayList == null){
                return;
            }
            this.redPointArrayList.clear();
            this.redPointArrayList = null;
            this.redPointArrayList = redPointArrayList;
            invalidate();
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * 添加红点
     * @param redPoint
     */
    public void addRedPoint(RedPoint redPoint){
        try {
            if(redPoint == null){
                return;
            }
            redPointArrayList.add(redPoint);
            invalidate();
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public void clearRedPoint(){
        try {
            if(redPointArrayList != null){
                redPointArrayList.clear();
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * 根据下标移除红点
     * @param index
     */
    public void removeRedPoint(int index){
        try {
            if(index > 0 && index < redPointArrayList.size()){
                redPointArrayList.remove(index);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public void reStart(){
        invalidate();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        //获取当前控件大小以及位置
        getLocalVisibleRect(mRect);
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
        drawRedPointList(canvas);
    }

    /**
     * 绘制红点
     * @param canvas
     */
    private void drawRedPointList(Canvas canvas){
        //绘制所有红点
        for(int i=0;i<redPointArrayList.size();i++){
            RedPoint redPoint = redPointArrayList.get(i);

            mCanvasPaint.setColor(redPoint.getColor());

            mTextPaint.setTextAlign(Paint.Align.CENTER);
            mTextPaint.setTextSize(redPoint.getTextSize());
            mTextPaint.setColor(redPoint.getTextColor());
            Paint.FontMetricsInt fontMetrics = mTextPaint.getFontMetricsInt();
            canvas.drawCircle(redPoint.getRePoint().x, redPoint.getRePoint().y, redPoint.getSize()/2, mCanvasPaint);
            canvas.drawCircle(redPoint.getToPoint().x, redPoint.getToPoint().y, redPoint.getSize(), mCanvasPaint);
            if(Math.sqrt((redPoint.getToPoint().x - redPoint.getRePoint().x)*(redPoint.getToPoint().x - redPoint.getRePoint().x)
                    +(redPoint.getToPoint().y - redPoint.getRePoint().y)*(redPoint.getToPoint().y - redPoint.getRePoint().y))<redPoint.getDistance()){
                drawBeiSaierLine(canvas, redPoint);
            }
            //计算文字在红点中垂直居中时的y值
            int baseline = redPoint.getToPoint().y + ( - fontMetrics.ascent - fontMetrics.descent) / 2;
            canvas.drawText(redPoint.getText(), redPoint.getToPoint().x, baseline, mTextPaint);
        }
        if(beng){
            if(mCurExplosionIndex < mExplosionBitmaps.length){
                //设置气泡爆炸图片的位置
                Log.i(TAG, "mCurExplosionIndex = " + mCurExplosionIndex);
                mBitMapPaint.setFilterBitmap(true);
                //根据当前进行到爆炸气泡的位置index来绘制爆炸气泡bitmap
                canvas.drawBitmap(mExplosionBitmaps[mCurExplosionIndex], null, mExplosionRect, mBitMapPaint);
            }else{
                beng = false;
            }
        }
    }

    /**
     * 通过贝塞尔曲线绘制大小红点之间的粘连效果
     * @param canvas
     * @param redPoint
     */
    private void drawBeiSaierLine(Canvas canvas, RedPoint redPoint){
        Point p1 = getQieDian(redPoint.getRePoint(), redPoint.getToPoint(), redPoint.getSize()/2);
        Point p2 = getQieDian(redPoint.getToPoint(), redPoint.getRePoint(), redPoint.getSize());
        Point p3 = getQieDian2(redPoint.getRePoint(), redPoint.getToPoint(), redPoint.getSize()/2);
        Point p4 = getQieDian2(redPoint.getToPoint(), redPoint.getRePoint(), redPoint.getSize());
        mPath.reset();
        mPath.moveTo(p1.x, p1.y);
        mPath.quadTo((redPoint.getToPoint().x + redPoint.getRePoint().x)/2, (redPoint.getToPoint().y + redPoint.getRePoint().y)/2, p4.x, p4.y);
        mPath.lineTo(p2.x, p2.y);
        mPath.quadTo((redPoint.getToPoint().x + redPoint.getRePoint().x)/2, (redPoint.getToPoint().y + redPoint.getRePoint().y)/2, p3.x, p3.y);
        canvas.drawPath(mPath, mCanvasPaint);
    }

    /**
     * 检查触摸点是否在红点范围内,如果再红点范围内,则返回具体的红点在redPointArrayList中的index
     * PS:此处考虑到红点一般比较小,因此设置为在红点2倍半径内都做响应
     * @param x
     * @param y
     * @return
     */
    private int getCurMoveRedPointIndex(float x, float y){
        for(int i=0;i<redPointArrayList.size();i++){
            RedPoint redPoint = redPointArrayList.get(i);
            if(Math.sqrt((redPoint.getToPoint().x - x)*(redPoint.getToPoint().x - x)+(redPoint.getToPoint().y - y)*(redPoint.getToPoint().y - y))<redPoint.getSize() * 2){
                return  i;
            }
        }
        return -1;
    }

    /**
     * 由于要使红点对触摸事件的响应优先于RedPointLayout的子控件,所以重写dispatchTouchEvent来处理触摸事件,并决定是否拦截
     * 而不是重写onTouchEvent
     * @param event
     * @return
     */
    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                //获取触摸到的红点(-1为触摸位置没有红点)
                curMoveRedPointIndex = getCurMoveRedPointIndex(event.getX(), event.getY() + getScrollY());
                if(curMoveRedPointIndex == -1){
                    return super.dispatchTouchEvent(event);
                }else{
                    //开始更新大红点到触摸位置,小红点留在原地
                    RedPoint curMoveRedPoint = redPointArrayList.get(curMoveRedPointIndex);
                    redPointArrayList.remove(curMoveRedPointIndex);
                    curMoveRedPoint.setTopoint(new Point((int)event.getX(), (int)event.getY() + getScrollY()));
                    redPointArrayList.add(curMoveRedPointIndex, curMoveRedPoint);
                    invalidate();
                    return true;
                }
            case MotionEvent.ACTION_MOVE:
                if(curMoveRedPointIndex == -1){
                    return super.dispatchTouchEvent(event);
                }else{
                    //更新红点位置
                    RedPoint curMoveRedPoint = redPointArrayList.get(curMoveRedPointIndex);
                    redPointArrayList.remove(curMoveRedPointIndex);
                    curMoveRedPoint.setTopoint(new Point((int)event.getX(), (int)event.getY() + getScrollY()));
                    redPointArrayList.add(curMoveRedPointIndex, curMoveRedPoint);
                    invalidate();
                    return true;
                }
            case MotionEvent.ACTION_UP:
                if(curMoveRedPointIndex == -1){
                    return super.dispatchTouchEvent(event);
                }else{
                    //更新红点位置,并判断大红点与小红点的距离,若小于消除距离(curMoveRedPoint.getDistance())则执行动画将大红点归位
                    //否则,消除所有红点,并将该红点从redPointArrayList中移除
                    RedPoint curMoveRedPoint = redPointArrayList.get(curMoveRedPointIndex);
                    redPointArrayList.remove(curMoveRedPointIndex);
                    curMoveRedPoint.setTopoint(new Point((int)event.getX(), (int)event.getY() + getScrollY()));
                    if(Math.sqrt((curMoveRedPoint.getToPoint().x - curMoveRedPoint.getRePoint().x)*(curMoveRedPoint.getToPoint().x - curMoveRedPoint.getRePoint().x)+(curMoveRedPoint.getToPoint().y - curMoveRedPoint.getRePoint().y)*(curMoveRedPoint.getToPoint().y - curMoveRedPoint.getRePoint().y))<curMoveRedPoint.getDistance()){
                        Log.i(TAG, "正在执行动画···");
                        beng = false;
                        redPointArrayList.add(curMoveRedPointIndex, curMoveRedPoint);
                        startAnimator();
                    } else{
                        Log.i(TAG, "正在执行爆炸···");
                        beng = true;
                        mExplosionRect.set((int)(curMoveRedPoint.getToPoint().x - Math.sqrt(curMoveRedPoint.getSize() * curMoveRedPoint.getSize())), (int)(curMoveRedPoint.getToPoint().y - Math.sqrt(curMoveRedPoint.getSize() * curMoveRedPoint.getSize()))
                                , (int)(curMoveRedPoint.getToPoint().x + Math.sqrt(curMoveRedPoint.getSize() * curMoveRedPoint.getSize())),  (int)(curMoveRedPoint.getToPoint().y + Math.sqrt(curMoveRedPoint.getSize() * curMoveRedPoint.getSize())));
                        startBubbleDismissAnim();
                    }
                    return true;
                }
        }
        return false;
    }

    private void startAnimator(){
        //通过ValueAnimator更新redPointArrayList中当前移动的红点坐标信息,并刷新
        RedPoint curMoveRedPoint = redPointArrayList.get(curMoveRedPointIndex);
        ValueAnimator va1 = ValueAnimator.ofInt(curMoveRedPoint.getToPoint().x, curMoveRedPoint.getRePoint().x);
        ValueAnimator va2 = ValueAnimator.ofInt(curMoveRedPoint.getToPoint().y, curMoveRedPoint.getRePoint().y);
        va1.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                RedPoint curMoveRedPoint = redPointArrayList.get(curMoveRedPointIndex);
                redPointArrayList.remove(curMoveRedPointIndex);
                curMoveRedPoint.getToPoint().set((int)animation.getAnimatedValue(), curMoveRedPoint.getToPoint().y);
                redPointArrayList.add(curMoveRedPointIndex, curMoveRedPoint);
                invalidate();
            }
        });
        va2.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                RedPoint curMoveRedPoint = redPointArrayList.get(curMoveRedPointIndex);
                redPointArrayList.remove(curMoveRedPointIndex);
                curMoveRedPoint.getToPoint().set(curMoveRedPoint.getToPoint().x, (int)animation.getAnimatedValue());
                redPointArrayList.add(curMoveRedPointIndex, curMoveRedPoint);
                invalidate();
            }
        });
        AnimatorSet as = new AnimatorSet();
        as.setDuration(200);
        as.play(va1).with(va2);
        as.setInterpolator(new PathInterpolator(0.45f, 1.98f, 0.85f, 0.58f));
        as.start();
    }
    /**
     * 设置气泡消失的动画
     */
    private void startBubbleDismissAnim() {
        //做一个int型属性动画,从0开始,到气泡爆炸图片数组个数结束
        ValueAnimator anim = ValueAnimator.ofInt(0, mExplosionDrawables.length);
        anim.setInterpolator(new LinearInterpolator());
        anim.setDuration(1000);
        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                //拿到当前的值并重绘
                Log.i(TAG, "mCurExplosionIndex···" + mCurExplosionIndex);
                mCurExplosionIndex = (int) animation.getAnimatedValue();
                invalidate();
            }
        });
        anim.start();
    }

    public Point getQieDian(Point ptCenter, Point ptOutside, double dbRadious)
    {
        Point E = new Point(),F = new Point(),G = new Point(),H = new Point();
        double r = dbRadious;
        //1. 坐标平移到圆心ptCenter处,求园外点的新坐标E
        E.x = ptOutside.x-ptCenter.x;
        E.y = ptOutside.y-ptCenter.y; //平移变换到E
        //2. 求园与OE的交点坐标F, 相当于E的缩放变换
        double t= r / Math.sqrt(E.x * E.x + E.y * E.y);  //得到缩放比例
        F.x = (int)(E.x * t);   F.y = (int)(E.y * t);   //缩放变换到F
        //3. 将E旋转变换角度a到切点G,其中cos(a)=r/OF=t, 所以a=arccos(t);
        double a = Math.acos(t);   //得到旋转角度
        G.x = (int)(F.x*Math.cos(a) -F.y*Math.sin(a));
        G.y = (int)(F.x*Math.sin(a) +F.y*Math.cos(a));    //旋转变换到G
        //4. 将G平移到原来的坐标下得到新坐标H
        H.x= G.x+ptCenter.x;
        H.y= G.y+ptCenter.y;             //平移变换到H
        //5. 返回H
        return H;
        //6. 实际应用过程中,只要一个中间变量E,其他F,G,H可以不用。
    }

    public Point getQieDian2(Point ptCenter, Point ptOutside, double dbRadious)
    {
        Point E = new Point(),F = new Point(),G = new Point(),H = new Point();
        double r = dbRadious;
        //1. 坐标平移到圆心ptCenter处,求园外点的新坐标E
        E.x = ptOutside.x-ptCenter.x;
        E.y = ptOutside.y-ptCenter.y; //平移变换到E
        //2. 求园与OE的交点坐标F, 相当于E的缩放变换
        double t= r / Math.sqrt(E.x * E.x + E.y * E.y);  //得到缩放比例
        F.x = (int)(E.x * t);   F.y = (int)(E.y * t);   //缩放变换到F
        //3. 将E旋转变换角度a到切点G,其中cos(a)=r/OF=t, 所以a=arccos(t);
        double a = -Math.acos(t);   //得到旋转角度
        G.x = (int)(F.x*Math.cos(a) -F.y*Math.sin(a));
        G.y = (int)(F.x*Math.sin(a) +F.y*Math.cos(a));    //旋转变换到G
        //4. 将G平移到原来的坐标下得到新坐标H
        H.x= G.x+ptCenter.x;
        H.y= G.y+ptCenter.y;             //平移变换到H
        //5. 返回H
        return H;
        //6. 实际应用过程中,只要一个中间变量E,其他F,G,H可以不用。
    }

    public  RedPoint createRedPoint(){
        return new RedPoint();
    }

    public  class RedPoint{
        /** 红点初始位置中心点坐标 */
        private Point centerPoint;
        /** 红点小红点中心点坐标 */
        private Point rePoint;
        /** 红点大红点中心点坐标 */
        private Point toPoint;
        /** 红点的大小(即半径) */
        private int size;
        /** 红点要展示的文字内容 */
        private String text;
        /** 文字的大小 */
        private int textSize;
        /** 文字的颜色 */
        private int textColor;
        /** 红点的颜色 */
        private int color;
        /** 红点的消除距离 */
        private int distance;

        public RedPoint(){
            centerPoint = new Point(0, 0);
            rePoint = new Point(0, 0);
            toPoint = new Point(0, 0);
            size = 20;
            text = "0";
            textSize  = 18;
            textColor = 0xffffffff;
            color = 0xffff318c;
            distance = 80;
        }

        public void setCenterPoint(Point centerPoint) {
            this.centerPoint = centerPoint;
            rePoint.set(centerPoint.x, centerPoint.y);
            toPoint.set(centerPoint.x, centerPoint.y);
            Log.i(TAG, "rePoint.x = " + rePoint.x + "rePoint.y = " + rePoint.y);
            Log.i(TAG, "toPoint.x = " + toPoint.x + "toPoint.y = " + toPoint.y);
        }

        public void setRePoint(Point redPoint) {
            this.rePoint = redPoint;
        }

        public void setTopoint(Point topoint) {
            this.toPoint = topoint;
        }

        public void setSize(int size) {
            this.size = size;
        }

        public void setText(String text) {
            this.text = text;
        }

        public void setTextSize(int textSize) {
            this.textSize = textSize;
        }

        public void setTextColor(int textColor) {
            try{
                ColorStateList csl = ColorStateList.valueOf(textColor);
                this.textColor = csl.getColorForState(getDrawableState(), 0);
            }catch (Exception e){
                this.textColor = textColor;
            }
        }
        public void setTextColor(ColorStateList textColor) {
            this.textColor = textColor.getColorForState(getDrawableState(), 0);
        }

        public void setColor(int color) {
            try{
                ColorStateList csl = ColorStateList.valueOf(color);
                this.color = csl.getColorForState(getDrawableState(), 0);
            }catch (Exception e){
                e.printStackTrace();
            }
        }
        public void setColor(ColorStateList color) {
            this.color = color.getColorForState(getDrawableState(), 0);
        }

        public void setDistance(int distance) {
            this.distance = distance;
        }

        public Point getCenterPoint() {
            return centerPoint;
        }

        public Point getRePoint() {
            return rePoint;
        }

        public Point getToPoint() {
            return toPoint;
        }

        public int getSize() {
            return size;
        }

        public String getText() {
            return text;
        }

        public int getTextSize() {
            return textSize;
        }

        public int getTextColor() {
            return textColor;
        }

        public int getColor() {
            return color;
        }

        public int getDistance() {
            return distance;
        }
    }
}


  Demo地址 http://git.oschina.net/ZuiHuiJinZhao/RedPointLayout_Demo


第一次写文章,描述不够清晰,欢迎提出意见与建议,觉得作者写的不好,请轻喷(*^▽^*)


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值