BadgeView分析

在一些项目中,我们常常会看到显示未读消息数的效果,此时此刻github已经有了一款符合MaterialDesign风格Android BadgeView

对应的git地址是:https://github.com/qstumn/BadgeView

下面我来一点一点的分析BadgeView

(1)QBadgeView的构造方法
public QBadgeView(Context context) {
    this(context, null);
}

private QBadgeView(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
}

private QBadgeView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    init();
}

这里说明两点:

第一 采用联级构造方式,那么什么叫联级?我先来看一下传统的构造方式

public QBadgeView(Context context) {
    super(context);
    init();
}

private QBadgeView(Context context, AttributeSet attrs) {
    super(context, attrs);
    init();
}

private QBadgeView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    init();
}

我们看到,传统的构造方式同样也有三个构造方法,但是这种构造方式必须都要添加初始化代码,这样的话代码不太好看,解决这个问题可以采用联级构造方式解决。

联级构造方式:使用this()实现第一个构造方法调用第二个构造方法,第二个构造方法调用第三个构造方法,一次类推,直到最后一个构造方式使用super(),初始化代码只要写在最后一个构造方法里面即可。

第二 第一个构造方法使用public修饰,第二个和第三个构造方式使用private修饰,这是为什么?

首先我们需要了解一个常识:

  • 第一个构造方法主要作用是让程序猿利用new关键字新建对象的;
  • 一般我们常常将自定义view写在xml布局文件里面,让Andorid自动创建对象,Android默认使用第二个构造方法创建对象。

然而我们发现BadgeView的第二个和第三个构造方法采用private来修饰,这样使得程序猿不可以将BadgeView写在布局里面,程序猿必须在代码中使用new关键字创建对象。

QBadgeView qBadgeView = new QBadgeView(this)

那么为什么作者不让我们在布局中使用BadgeView呢?原因是BadgeView必须依赖一个targetview,QBadgeView默认在targetview右上角显示。

假设我们将QBadgeView在一张图片的右上角显示



    QBadgeView qBadgeView = new QBadgeView(this);
    qBadgeView.bindTarget(imageview)

使用bindTarget绑定目标view。

(2)建造者设计模式

BadgeView符合建造者设计模式,当我们配置QBadgeView属性时,可以链式配置,如:

    qBadgeView.bindTarget(imageview)
            .setBadgeNumber(1)
            .setBadgeTextColor(Color.BLACK)
            .setShowShadow(true);
(3)setBadgeNumber设置角标数字
.setBadgeNumber(1)
BadgeView分析_第1张图片
图片.png

默认背景色是红色,背景色是圆形

假如我们将角标数字设置成100,那么效果图如下

BadgeView分析_第2张图片
图片.png

BadgeView会根据数字长度决定画圆形背景还是圆角矩形背景。

(4)setBadgeText设置文字
.setBadgeText("中国")
BadgeView分析_第3张图片
图片.png

同样根据文字长度来决定绘制圆形背景和圆角矩形背景。

BadgeView分析_第4张图片
图片.png
(5)setExactMode设置精准模式

true:如果角标数字是100,则显示100 false:如果角标数字是100,则显示99+

.setExactMode(true)
图片.png
(6)setShowShadow设置阴影效果开关

true:打开阴影效果 false:关闭阴影效果 默认为false

画笔Paint有个setShadowLayer属性可以配置阴影效果。

使用阴影效果需要关闭硬件加速

setLayerType(View.LAYER_TYPE_SOFTWARE, null);//关闭硬件加速
(7)setBadgeBackgroundColorsetBadgeBackground设置背景色

设置背景色有三个方法

Badge setBadgeBackgroundColor(int color);//设置角标背景色,默认为红色

Badge setBadgeBackground(Drawable drawable);//设置角标背景色,默认为红色

Badge setBadgeBackground(Drawable drawable, boolean clip);//设置角标背景色,默认为红色,clip:是否对drawable进行修剪

第一个方法可以直接设置颜色

.setBadgeBackgroundColor(Color.parseColor("#fff000"))
图片.png

第二个方法可以设置一个Drawable



    
    


.setBadgeBackground(getResources().getDrawable(R.drawable.shape_round_rect))
图片.png

如上图,BadgeView的背景和预想中的不太一样,那么我们要求对这个背景进行修剪

.setBadgeBackground(getResources().getDrawable(R.drawable.shape_round_rect), true)

第二个参数设置为true,表明角标背景需要修剪,BadgeView会将背景形状改变。

怎么修剪?我们看一下源码

 //设置Xfermode为DST_IN,即只显示两图相交的地方,并且只显示目标图(目标图为背景)
mBadgeBackgroundPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));

这句代码我加上了注释,我想理解xfermode的猿都能看懂吧,其中目标图是

图片.png

源图为与一个圆,一个以BadgeView宽度为直径的圆

canvas.drawCircle(mBadgeBackgroundRect.centerX(), mBadgeBackgroundRect.centerY(),mBadgeBackgroundRect.width() / 2f, mBadgeBackgroundBorderPaint);

DST_IN模式,即两图相交之后取目标图,效果如下:

图片.png
(8)setBadgeTextColor设置角标文字颜色,默认为白色
.setBadgeTextColor(Color.BLACK)
图片.png
(9)stroke添加描边
.stroke(Color.parseColor("#fff000"), 10, false)
图片.png
(10)setBadgeTextSize设置角标文字的大小
.setBadgeTextSize(10, false)
BadgeView分析_第5张图片
图片.png
(11)setBadgePadding设置padding
.setBadgePadding(10, false)

这个没什么好说的,就是设置padding

(12)setBadgeGravity设置角标的位置

设置角标位置要严格按照源码的说明来写,否则会抛出异常

/**
 * @param gravity only support Gravity.START | Gravity.TOP , Gravity.END | Gravity.TOP ,
 *                Gravity.START | Gravity.BOTTOM , Gravity.END | Gravity.BOTTOM ,
 *                Gravity.CENTER , Gravity.CENTER | Gravity.TOP , Gravity.CENTER | Gravity.BOTTOM ,
 *                Gravity.CENTER | Gravity.START , Gravity.CENTER | Gravity.END
 */
@Override
public Badge setBadgeGravity(int gravity) {
    if (gravity == (Gravity.START | Gravity.TOP) ||
            gravity == (Gravity.END | Gravity.TOP) ||
            gravity == (Gravity.START | Gravity.BOTTOM) ||
            gravity == (Gravity.END | Gravity.BOTTOM) ||
            gravity == (Gravity.CENTER) ||
            gravity == (Gravity.CENTER | Gravity.TOP) ||
            gravity == (Gravity.CENTER | Gravity.BOTTOM) ||
            gravity == (Gravity.CENTER | Gravity.START) ||
            gravity == (Gravity.CENTER | Gravity.END)) {
        mBadgeGravity = gravity;
        invalidate();
    } else {
        throw new IllegalStateException("only support Gravity.START | Gravity.TOP , Gravity.END | Gravity.TOP , " +
                "Gravity.START | Gravity.BOTTOM , Gravity.END | Gravity.BOTTOM , Gravity.CENTER" +
                " , Gravity.CENTER | Gravity.TOP , Gravity.CENTER | Gravity.BOTTOM ," +
                "Gravity.CENTER | Gravity.START , Gravity.CENTER | Gravity.END");
    }
    return this;
}
(13)setGravityOffset

设置角标的偏移量

(14)getGravityOffsetX

获取角标x轴方向的偏移量

(15)getGravityOffsetY

获取角标y轴方向的偏移量

(16)getDragCenter

获取拖拽view的触发点位置

(17)setOnDragStateChangedListener

设置拖拽监听

            .setOnDragStateChangedListener(new Badge.OnDragStateChangedListener() {
        @Override
        public void onDragStateChanged(int dragState, Badge badge, View targetView) {
            switch (dragState){
                case Badge.OnDragStateChangedListener.STATE_START:
                    Log.d("aaa", "开始拖拽");
                    break;
                case Badge.OnDragStateChangedListener.STATE_DRAGGING:
                    Log.d("aaa", "拖拽中...");
                    break;
                case Badge.OnDragStateChangedListener.STATE_DRAGGING_OUT_OF_RANGE:
                    Log.d("aaa", "拖拽出view范围");
                    break;
                case Badge.OnDragStateChangedListener.STATE_CANCELED:
                    Log.d("aaa", "取消拖拽");
                    break;
                case Badge.OnDragStateChangedListener.STATE_SUCCEED:
                    Log.d("aaa", "成功拖拽");
                    break;
            }
        }
    });

BadgeView的角标可以被拖拽,默认是不可拖拽,只有设置了拖拽监听之后才可以被拖拽。

拖拽效果如下:

BadgeView分析_第6张图片
45.gif

拖拽效果的核心是通过Path(路径)来绘制的。

(18)hide

BadgeView提供了很多可被调用的方法,但是唯独hide这个方法不符合建造者设计模式

下面我们点击目标图片实现隐藏角标。

    imageview.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            qBadgeView.hide(true);
        }
    });

隐藏角标的方式有两种,第一种采用拖拽的方式隐藏角标,当角标被拖拽到image外面的时候被隐藏,同样hide方法也可以实现隐藏角标功能。

hide 只有一个参数,true:开启隐藏动画 false:关闭隐藏动画

隐藏动画采用属性动画来实现。

(19)具体代码


    imageview = findViewById(R.id.imageview);

    final QBadgeView qBadgeView = new QBadgeView(this);
    qBadgeView.bindTarget(imageview)
            .setBadgeNumber(100)
            //.setExactMode(true)
            //.setBadgeBackgroundColor(Color.parseColor("#fff000"))
            //.setBadgeText("中国")
            //.setBadgeBackground(getResources().getDrawable(R.drawable.shape_round_rect))
            //.setBadgeTextColor(Color.BLACK)
            //.setShowShadow(true)
            .setBadgeBackground(getResources().getDrawable(R.drawable.shape_round_rect), true)
            .stroke(Color.parseColor("#fff000"), 10, false)
            //.setBadgeTextSize(30, false)
            //.setBadgePadding(10, false)
            //.setBadgeGravity(Gravity.TOP)
           // .setGravityOffset(10 , false)
            .setOnDragStateChangedListener(new Badge.OnDragStateChangedListener() {
        @Override
        public void onDragStateChanged(int dragState, Badge badge, View targetView) {
            switch (dragState){
                case Badge.OnDragStateChangedListener.STATE_START:
                    Log.d("aaa", "开始拖拽");
                    break;
                case Badge.OnDragStateChangedListener.STATE_DRAGGING:
                    Log.d("aaa", "拖拽中...");
                    break;
                case Badge.OnDragStateChangedListener.STATE_DRAGGING_OUT_OF_RANGE:
                    Log.d("aaa", "拖拽出view范围");
                    break;
                case Badge.OnDragStateChangedListener.STATE_CANCELED:
                    Log.d("aaa", "取消拖拽");
                    break;
                case Badge.OnDragStateChangedListener.STATE_SUCCEED:
                    Log.d("aaa", "成功拖拽");
                    break;
            }
        }
    });


    imageview.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            qBadgeView.hide(true);
        }
    });


public class DisplayUtil {
    public static int dp2px(Context context, float dp) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dp * scale + 0.5f);
    }

    public static int px2dp(Context context, float pxValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (pxValue / scale + 0.5f);
    }
}


public class MathUtil {
    public static final double CIRCLE_RADIAN = 2 * Math.PI;

    public static double getTanRadian(double atan, int quadrant) {
        if (atan < 0) {
            atan += CIRCLE_RADIAN / 4;
        }
        atan += CIRCLE_RADIAN / 4 * (quadrant - 1);
        return atan;
    }

    public static double radianToAngle(double radian) {
        return 360 * (radian / CIRCLE_RADIAN);
    }

    public static int getQuadrant(PointF p, PointF center) {
        if (p.x > center.x) {
            if (p.y > center.y) {
                return 4;
            } else if (p.y < center.y) {
                return 1;
            }
        } else if (p.x < center.x) {
            if (p.y > center.y) {
                return 3;
            } else if (p.y < center.y) {
                return 2;
            }
        }
        return -1;
    }

    public static float getPointDistance(PointF p1, PointF p2) {
        return (float) Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2));
    }

    /**
     * this formula is designed by mabeijianxi
     * website : http://blog.csdn.net/mabeijianxi/article/details/50560361
     *
     * @param circleCenter The circle center point.
     * @param radius       The circle radius.
     * @param slopeLine    The slope of line which cross the pMiddle.
     */
    public static void getInnertangentPoints(PointF circleCenter, float radius, Double slopeLine, List points) {
        float radian, xOffset, yOffset;
        if (slopeLine != null) {
            radian = (float) Math.atan(slopeLine);
            xOffset = (float) (Math.cos(radian) * radius);
            yOffset = (float) (Math.sin(radian) * radius);
        } else {
            xOffset = radius;
            yOffset = 0;
        }
        points.add(new PointF(circleCenter.x + xOffset, circleCenter.y + yOffset));
        points.add(new PointF(circleCenter.x - xOffset, circleCenter.y - yOffset));
    }
}



shape_round_rect.xml



    
    


public interface Badge {

    Badge setBadgeNumber(int badgeNum);//设置角标数字

    int getBadgeNumber();//获取角标数字

    Badge setBadgeText(String badgeText);//设置角标文字

    String getBadgeText();//获取角标文字

    Badge setExactMode(boolean isExact);//设置精准模式,true:如果角标数字是100,则显示100  false:如果角标数字是100,则显示99+

    boolean isExactMode();//获取精准模式,默认为false

    Badge setShowShadow(boolean showShadow);//设置阴影效果开关 true:打开阴影效果 false:关闭阴影效果  默认为false

    boolean isShowShadow();//获取阴影效果开关

    Badge setBadgeBackgroundColor(int color);//设置角标背景色,默认为红色

    int getBadgeBackgroundColor();//获取背景色

    Badge setBadgeBackground(Drawable drawable);//设置角标背景色,默认为红色

    Badge setBadgeBackground(Drawable drawable, boolean clip);//设置角标背景色,默认为红色,clip:是否对drawable进行修剪

    Drawable getBadgeBackground();//获取背景色

    Badge setBadgeTextColor(int color);//设置角标文字颜色,默认为白色

    int getBadgeTextColor();//获取角标文字颜色

    Badge stroke(int color, float width, boolean isDpValue);//添加描边 color:描边颜色 width:描边宽度 isDpValue:宽度单位是否是db

    Badge setBadgeTextSize(float size, boolean isSpValue);//设置字体大小 isSpValue:字体大小单位是否是sp

    float getBadgeTextSize(boolean isSpValue);//获取字体大小

    Badge setBadgePadding(float padding, boolean isDpValue);//设置padding

    float getBadgePadding(boolean isDpValue);//获取padding

    Badge setOnDragStateChangedListener(OnDragStateChangedListener l);//设置拖拽监听,默认拖拽无效,只有设置了监听拖拽功能才生效

    boolean isDraggable();//是否设置了拖拽监听,如果设置了setOnDragStateChangedListener监听方法,则默认可以拖拽

    Badge setBadgeGravity(int gravity);//设置角标的位置,默认为Gravity.END | Gravity.TOP

    int getBadgeGravity();//获取角标的位置

    Badge setGravityOffset(float offset, boolean isDpValue);//设置角标的偏移量 offset:x、y轴方向偏移量  isDpValue:是否是dp单位

    Badge setGravityOffset(float offsetX, float offsetY, boolean isDpValue);//设置角标的偏移量 offsetX:x轴方向偏移量 offsetY:y轴方向偏移量 isDpValue:是否是dp单位

    float getGravityOffsetX(boolean isDpValue);//获取x轴方向的偏移量

    float getGravityOffsetY(boolean isDpValue);//获取y轴方向的偏移量

    PointF getDragCenter();//获取拖拽事件的触摸位置

    Badge bindTarget(View view);//角标的存在必须依赖view,如果不设置bindTarget,则BadgeView无法使用

    View getTargetView();//获取角标依赖的view

    void hide(boolean animate);//将角标隐藏 animate:是否开启隐藏时动画

    /**
     * 拖拽状态
     */
    interface OnDragStateChangedListener {
        int STATE_START = 1;
        int STATE_DRAGGING = 2;
        int STATE_DRAGGING_OUT_OF_RANGE = 3;
        int STATE_CANCELED = 4;
        int STATE_SUCCEED = 5;

        void onDragStateChanged(int dragState, Badge badge, View targetView);
    }
}


public class QBadgeView extends View implements Badge {
    protected int mColorBackground;
    protected int mColorBackgroundBorder;
    protected int mColorBadgeText;
    protected Drawable mDrawableBackground;
    protected Bitmap mBitmapClip;
    protected boolean mDrawableBackgroundClip;
    protected float mBackgroundBorderWidth;
    protected float mBadgeTextSize;
    protected float mBadgePadding;
    protected int mBadgeNumber;
    protected String mBadgeText;
    protected boolean mDraggable;
    protected boolean mDragging;
    protected boolean mExact;
    protected boolean mShowShadow;
    protected int mBadgeGravity;
    protected float mGravityOffsetX;
    protected float mGravityOffsetY;

    protected float mDefalutRadius;
    protected float mFinalDragDistance;
    protected int mDragQuadrant;
    protected boolean mDragOutOfRange;

    protected RectF mBadgeTextRect;
    protected RectF mBadgeBackgroundRect;
    protected Path mDragPath;

    protected Paint.FontMetrics mBadgeTextFontMetrics;

    protected PointF mBadgeCenter;
    protected PointF mDragCenter;
    protected PointF mRowBadgeCenter;
    protected PointF mControlPoint;

    protected List mInnertangentPoints;

    protected View mTargetView;

    protected int mWidth;
    protected int mHeight;

    protected TextPaint mBadgeTextPaint;
    protected Paint mBadgeBackgroundPaint;
    protected Paint mBadgeBackgroundBorderPaint;

    protected BadgeAnimator mAnimator;

    protected OnDragStateChangedListener mDragStateChangedListener;

    protected ViewGroup mActivityRoot;

    public QBadgeView(Context context) {
        this(context, null);
    }

    private QBadgeView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    private QBadgeView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        setLayerType(View.LAYER_TYPE_SOFTWARE, null);//关闭硬件加速
        mBadgeTextRect = new RectF();
        mBadgeBackgroundRect = new RectF();
        mDragPath = new Path();
        mBadgeCenter = new PointF();
        mDragCenter = new PointF();
        mRowBadgeCenter = new PointF();
        mControlPoint = new PointF();
        mInnertangentPoints = new ArrayList<>();
        mBadgeTextPaint = new TextPaint();
        mBadgeTextPaint.setAntiAlias(true);
        mBadgeTextPaint.setSubpixelText(true);
        mBadgeTextPaint.setFakeBoldText(true);
        mBadgeTextPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
        mBadgeBackgroundPaint = new Paint();
        mBadgeBackgroundPaint.setAntiAlias(true);
        mBadgeBackgroundPaint.setStyle(Paint.Style.FILL);
        mBadgeBackgroundBorderPaint = new Paint();
        mBadgeBackgroundBorderPaint.setAntiAlias(true);
        mBadgeBackgroundBorderPaint.setStyle(Paint.Style.STROKE);
        mColorBackground = 0xFFE84E40;
        mColorBadgeText = 0xFFFFFFFF;
        mBadgeTextSize = DisplayUtil.dp2px(getContext(), 11);
        mBadgePadding = DisplayUtil.dp2px(getContext(), 5);
        mBadgeNumber = 0;
        mBadgeGravity = Gravity.END | Gravity.TOP;
        mGravityOffsetX = DisplayUtil.dp2px(getContext(), 1);
        mGravityOffsetY = DisplayUtil.dp2px(getContext(), 1);
        mFinalDragDistance = DisplayUtil.dp2px(getContext(), 90);
        mShowShadow = true;
        mDrawableBackgroundClip = false;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            setTranslationZ(1000);
        }
    }

    @Override
    public Badge bindTarget(final View targetView) {
        if (targetView == null) {
            throw new IllegalStateException("targetView can not be null");
        }
        if (getParent() != null) {
            ((ViewGroup) getParent()).removeView(this);
        }
        ViewParent targetParent = targetView.getParent();
        if (targetParent != null && targetParent instanceof ViewGroup) {
            mTargetView = targetView;
            if (targetParent instanceof BadgeContainer) {
                ((BadgeContainer) targetParent).addView(this);
            } else {
                ViewGroup targetContainer = (ViewGroup) targetParent;
                int index = targetContainer.indexOfChild(targetView);
                ViewGroup.LayoutParams targetParams = targetView.getLayoutParams();
                targetContainer.removeView(targetView);
                final BadgeContainer badgeContainer = new BadgeContainer(getContext());
                if(targetContainer instanceof RelativeLayout){
                    badgeContainer.setId(targetView.getId());
                }
                targetContainer.addView(badgeContainer, index, targetParams);
                badgeContainer.addView(targetView);
                badgeContainer.addView(this);
            }
        } else {
            throw new IllegalStateException("targetView must have a parent");
        }
        return this;
    }

    @Override
    public View getTargetView() {
        return mTargetView;
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        if (mActivityRoot == null) findViewRoot(mTargetView);
    }

    private void findViewRoot(View view) {
        mActivityRoot = (ViewGroup) view.getRootView();
        if (mActivityRoot == null) {
            findActivityRoot(view);
        }
    }

    private void findActivityRoot(View view) {
        if (view.getParent() != null && view.getParent() instanceof View) {
            findActivityRoot((View) view.getParent());
        } else if (view instanceof ViewGroup) {
            mActivityRoot = (ViewGroup) view;
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:
            case MotionEvent.ACTION_POINTER_DOWN:
                float x = event.getX();
                float y = event.getY();
                if (mDraggable && event.getPointerId(event.getActionIndex()) == 0
                        && (x > mBadgeBackgroundRect.left && x < mBadgeBackgroundRect.right &&
                        y > mBadgeBackgroundRect.top && y < mBadgeBackgroundRect.bottom)
                        && mBadgeText != null) {
                    initRowBadgeCenter();
                    mDragging = true;
                    updataListener(OnDragStateChangedListener.STATE_START);
                    mDefalutRadius = DisplayUtil.dp2px(getContext(), 7);
                    getParent().requestDisallowInterceptTouchEvent(true);
                    screenFromWindow(true);
                    mDragCenter.x = event.getRawX();
                    mDragCenter.y = event.getRawY();
                }
                break;
            case MotionEvent.ACTION_MOVE:
                if (mDragging) {
                    mDragCenter.x = event.getRawX();
                    mDragCenter.y = event.getRawY();
                    invalidate();
                }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_POINTER_UP:
            case MotionEvent.ACTION_CANCEL:
                if (event.getPointerId(event.getActionIndex()) == 0 && mDragging) {
                    mDragging = false;
                    onPointerUp();
                }
                break;
        }
        return mDragging || super.onTouchEvent(event);
    }

    private void onPointerUp() {
        if (mDragOutOfRange) {
            animateHide(mDragCenter);
            updataListener(OnDragStateChangedListener.STATE_SUCCEED);
        } else {
            reset();
            updataListener(OnDragStateChangedListener.STATE_CANCELED);
        }
    }

    protected Bitmap createBadgeBitmap() {
        Bitmap bitmap = Bitmap.createBitmap((int) mBadgeBackgroundRect.width() + DisplayUtil.dp2px(getContext(), 3),
                (int) mBadgeBackgroundRect.height() + DisplayUtil.dp2px(getContext(), 3), Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        drawBadge(canvas, new PointF(canvas.getWidth() / 2f, canvas.getHeight() / 2f), getBadgeCircleRadius());
        return bitmap;
    }

    protected void screenFromWindow(boolean screen) {
        if (getParent() != null) {
            ((ViewGroup) getParent()).removeView(this);
        }
        if (screen) {
            mActivityRoot.addView(this, new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,
                    FrameLayout.LayoutParams.MATCH_PARENT));
        } else {
            bindTarget(mTargetView);
        }
    }

    private void showShadowImpl(boolean showShadow) {
        int x = DisplayUtil.dp2px(getContext(), 1);
        int y = DisplayUtil.dp2px(getContext(), 1.5f);
        switch (mDragQuadrant) {
            case 1:
                x = DisplayUtil.dp2px(getContext(), 1);
                y = DisplayUtil.dp2px(getContext(), -1.5f);
                break;
            case 2:
                x = DisplayUtil.dp2px(getContext(), -1);
                y = DisplayUtil.dp2px(getContext(), -1.5f);
                break;
            case 3:
                x = DisplayUtil.dp2px(getContext(), -1);
                y = DisplayUtil.dp2px(getContext(), 1.5f);
                break;
            case 4:
                x = DisplayUtil.dp2px(getContext(), 1);
                y = DisplayUtil.dp2px(getContext(), 1.5f);
                break;
        }
        mBadgeBackgroundPaint.setShadowLayer(showShadow ? DisplayUtil.dp2px(getContext(), 2f)
                : 0, x, y, 0x33000000);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = w;
        mHeight = h;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (mAnimator != null && mAnimator.isRunning()) {
            mAnimator.draw(canvas);
            return;
        }
        if (mBadgeText != null) {
            initPaints();
            float badgeRadius = getBadgeCircleRadius();
            float startCircleRadius = mDefalutRadius * (1 - MathUtil.getPointDistance
                    (mRowBadgeCenter, mDragCenter) / mFinalDragDistance);
            if (mDraggable && mDragging) {
                mDragQuadrant = MathUtil.getQuadrant(mDragCenter, mRowBadgeCenter);
                showShadowImpl(mShowShadow);
                if (mDragOutOfRange = startCircleRadius < DisplayUtil.dp2px(getContext(), 1.5f)) {
                    updataListener(OnDragStateChangedListener.STATE_DRAGGING_OUT_OF_RANGE);
                    drawBadge(canvas, mDragCenter, badgeRadius);
                } else {
                    updataListener(OnDragStateChangedListener.STATE_DRAGGING);
                    drawDragging(canvas, startCircleRadius, badgeRadius);
                    drawBadge(canvas, mDragCenter, badgeRadius);
                }
            } else {
                findBadgeCenter();
                drawBadge(canvas, mBadgeCenter, badgeRadius);
            }
        }
    }

    private void initPaints() {
        showShadowImpl(mShowShadow);
        mBadgeBackgroundPaint.setColor(mColorBackground);
        mBadgeBackgroundBorderPaint.setColor(mColorBackgroundBorder);
        mBadgeBackgroundBorderPaint.setStrokeWidth(mBackgroundBorderWidth);
        mBadgeTextPaint.setColor(mColorBadgeText);
        mBadgeTextPaint.setTextAlign(Paint.Align.CENTER);
    }

    private void drawDragging(Canvas canvas, float startRadius, float badgeRadius) {
        float dy = mDragCenter.y - mRowBadgeCenter.y;
        float dx = mDragCenter.x - mRowBadgeCenter.x;
        mInnertangentPoints.clear();
        if (dx != 0) {
            double k1 = dy / dx;
            double k2 = -1 / k1;
            MathUtil.getInnertangentPoints(mDragCenter, badgeRadius, k2, mInnertangentPoints);
            MathUtil.getInnertangentPoints(mRowBadgeCenter, startRadius, k2, mInnertangentPoints);
        } else {
            MathUtil.getInnertangentPoints(mDragCenter, badgeRadius, 0d, mInnertangentPoints);
            MathUtil.getInnertangentPoints(mRowBadgeCenter, startRadius, 0d, mInnertangentPoints);
        }
        mDragPath.reset();
        mDragPath.addCircle(mRowBadgeCenter.x, mRowBadgeCenter.y, startRadius,
                mDragQuadrant == 1 || mDragQuadrant == 2 ? Path.Direction.CCW : Path.Direction.CW);
        mControlPoint.x = (mRowBadgeCenter.x + mDragCenter.x) / 2.0f;
        mControlPoint.y = (mRowBadgeCenter.y + mDragCenter.y) / 2.0f;
        mDragPath.moveTo(mInnertangentPoints.get(2).x, mInnertangentPoints.get(2).y);
        mDragPath.quadTo(mControlPoint.x, mControlPoint.y, mInnertangentPoints.get(0).x, mInnertangentPoints.get(0).y);
        mDragPath.lineTo(mInnertangentPoints.get(1).x, mInnertangentPoints.get(1).y);
        mDragPath.quadTo(mControlPoint.x, mControlPoint.y, mInnertangentPoints.get(3).x, mInnertangentPoints.get(3).y);
        mDragPath.lineTo(mInnertangentPoints.get(2).x, mInnertangentPoints.get(2).y);
        mDragPath.close();
        canvas.drawPath(mDragPath, mBadgeBackgroundPaint);

        //draw dragging border
        if (mColorBackgroundBorder != 0 && mBackgroundBorderWidth > 0) {
            mDragPath.reset();
            mDragPath.moveTo(mInnertangentPoints.get(2).x, mInnertangentPoints.get(2).y);
            mDragPath.quadTo(mControlPoint.x, mControlPoint.y, mInnertangentPoints.get(0).x, mInnertangentPoints.get(0).y);
            mDragPath.moveTo(mInnertangentPoints.get(1).x, mInnertangentPoints.get(1).y);
            mDragPath.quadTo(mControlPoint.x, mControlPoint.y, mInnertangentPoints.get(3).x, mInnertangentPoints.get(3).y);
            float startY;
            float startX;
            if (mDragQuadrant == 1 || mDragQuadrant == 2) {
                startX = mInnertangentPoints.get(2).x - mRowBadgeCenter.x;
                startY = mRowBadgeCenter.y - mInnertangentPoints.get(2).y;
            } else {
                startX = mInnertangentPoints.get(3).x - mRowBadgeCenter.x;
                startY = mRowBadgeCenter.y - mInnertangentPoints.get(3).y;
            }
            float startAngle = 360 - (float) MathUtil.radianToAngle(MathUtil.getTanRadian(Math.atan(startY / startX),
                    mDragQuadrant - 1 == 0 ? 4 : mDragQuadrant - 1));
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                mDragPath.addArc(mRowBadgeCenter.x - startRadius, mRowBadgeCenter.y - startRadius,
                        mRowBadgeCenter.x + startRadius, mRowBadgeCenter.y + startRadius, startAngle,
                        180);
            } else {
                mDragPath.addArc(new RectF(mRowBadgeCenter.x - startRadius, mRowBadgeCenter.y - startRadius,
                        mRowBadgeCenter.x + startRadius, mRowBadgeCenter.y + startRadius), startAngle, 180);
            }
            canvas.drawPath(mDragPath, mBadgeBackgroundBorderPaint);
        }
    }

    private void drawBadge(Canvas canvas, PointF center, float radius) {
        if (center.x == -1000 && center.y == -1000) {
            return;
        }
        if (mBadgeText.isEmpty() || mBadgeText.length() == 1) {
            mBadgeBackgroundRect.left = center.x - (int) radius;
            mBadgeBackgroundRect.top = center.y - (int) radius;
            mBadgeBackgroundRect.right = center.x + (int) radius;
            mBadgeBackgroundRect.bottom = center.y + (int) radius;
            if (mDrawableBackground != null) {
                drawBadgeBackground(canvas);
            } else {
                canvas.drawCircle(center.x, center.y, radius, mBadgeBackgroundPaint);
                if (mColorBackgroundBorder != 0 && mBackgroundBorderWidth > 0) {
                    canvas.drawCircle(center.x, center.y, radius, mBadgeBackgroundBorderPaint);
                }
            }
        } else {
            mBadgeBackgroundRect.left = center.x - (mBadgeTextRect.width() / 2f + mBadgePadding);
            mBadgeBackgroundRect.top = center.y - (mBadgeTextRect.height() / 2f + mBadgePadding * 0.5f);
            mBadgeBackgroundRect.right = center.x + (mBadgeTextRect.width() / 2f + mBadgePadding);
            mBadgeBackgroundRect.bottom = center.y + (mBadgeTextRect.height() / 2f + mBadgePadding * 0.5f);
            radius = mBadgeBackgroundRect.height() / 2f;
            if (mDrawableBackground != null) {
                drawBadgeBackground(canvas);
            } else {
                canvas.drawRoundRect(mBadgeBackgroundRect, radius, radius, mBadgeBackgroundPaint);
                if (mColorBackgroundBorder != 0 && mBackgroundBorderWidth > 0) {
                    canvas.drawRoundRect(mBadgeBackgroundRect, radius, radius, mBadgeBackgroundBorderPaint);
                }
            }
        }
        if (!mBadgeText.isEmpty()) {
            canvas.drawText(mBadgeText, center.x,
                    (mBadgeBackgroundRect.bottom + mBadgeBackgroundRect.top
                            - mBadgeTextFontMetrics.bottom - mBadgeTextFontMetrics.top) / 2f,
                    mBadgeTextPaint);
        }
    }

    private void drawBadgeBackground(Canvas canvas) {
        mBadgeBackgroundPaint.setShadowLayer(0, 0, 0, 0);
        int left = (int) mBadgeBackgroundRect.left;
        int top = (int) mBadgeBackgroundRect.top;
        int right = (int) mBadgeBackgroundRect.right;
        int bottom = (int) mBadgeBackgroundRect.bottom;
        if (mDrawableBackgroundClip) {
            right = left + mBitmapClip.getWidth();
            bottom = top + mBitmapClip.getHeight();
            canvas.saveLayer(left, top, right, bottom, null, Canvas.ALL_SAVE_FLAG);
        }
        mDrawableBackground.setBounds(left, top, right, bottom);
        mDrawableBackground.draw(canvas);
        if (mDrawableBackgroundClip) {
            //设置Xfermode为DST_IN,即只显示两图相交的地方,并且只显示目标图(目标图为背景)
            mBadgeBackgroundPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
            canvas.drawBitmap(mBitmapClip, left, top, mBadgeBackgroundPaint);
            canvas.restore();
            mBadgeBackgroundPaint.setXfermode(null);
            if (mBadgeText.isEmpty() || mBadgeText.length() == 1) {
                canvas.drawCircle(mBadgeBackgroundRect.centerX(), mBadgeBackgroundRect.centerY(),
                        mBadgeBackgroundRect.width() / 2f, mBadgeBackgroundBorderPaint);
            } else {
                canvas.drawRoundRect(mBadgeBackgroundRect,
                        mBadgeBackgroundRect.height() / 2, mBadgeBackgroundRect.height() / 2,
                        mBadgeBackgroundBorderPaint);
            }
        } else {
            canvas.drawRect(mBadgeBackgroundRect, mBadgeBackgroundBorderPaint);
        }
    }

    private void createClipLayer() {
        if (mBadgeText == null) {
            return;
        }
        if (!mDrawableBackgroundClip) {
            return;
        }
        if (mBitmapClip != null && !mBitmapClip.isRecycled()) {
            mBitmapClip.recycle();
        }
        float radius = getBadgeCircleRadius();
        if (mBadgeText.isEmpty() || mBadgeText.length() == 1) {
            mBitmapClip = Bitmap.createBitmap((int) radius * 2, (int) radius * 2,
                    Bitmap.Config.ARGB_4444);
            Canvas srcCanvas = new Canvas(mBitmapClip);
            srcCanvas.drawCircle(srcCanvas.getWidth() / 2f, srcCanvas.getHeight() / 2f,
                    srcCanvas.getWidth() / 2f, mBadgeBackgroundPaint);
        } else {
            mBitmapClip = Bitmap.createBitmap((int) (mBadgeTextRect.width() + mBadgePadding * 2),
                    (int) (mBadgeTextRect.height() + mBadgePadding), Bitmap.Config.ARGB_4444);
            Canvas srcCanvas = new Canvas(mBitmapClip);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                srcCanvas.drawRoundRect(0, 0, srcCanvas.getWidth(), srcCanvas.getHeight(), srcCanvas.getHeight() / 2f,
                        srcCanvas.getHeight() / 2f, mBadgeBackgroundPaint);
            } else {
                srcCanvas.drawRoundRect(new RectF(0, 0, srcCanvas.getWidth(), srcCanvas.getHeight()),
                        srcCanvas.getHeight() / 2f, srcCanvas.getHeight() / 2f, mBadgeBackgroundPaint);
            }
        }
    }

    private float getBadgeCircleRadius() {
        if (mBadgeText.isEmpty()) {
            return mBadgePadding;
        } else if (mBadgeText.length() == 1) {
            return mBadgeTextRect.height() > mBadgeTextRect.width() ?
                    mBadgeTextRect.height() / 2f + mBadgePadding * 0.5f :
                    mBadgeTextRect.width() / 2f + mBadgePadding * 0.5f;
        } else {
            return mBadgeBackgroundRect.height() / 2f;
        }
    }

    private void findBadgeCenter() {
        float rectWidth = mBadgeTextRect.height() > mBadgeTextRect.width() ?
                mBadgeTextRect.height() : mBadgeTextRect.width();
        switch (mBadgeGravity) {
            case Gravity.START | Gravity.TOP:
                mBadgeCenter.x = mGravityOffsetX + mBadgePadding + rectWidth / 2f;
                mBadgeCenter.y = mGravityOffsetY + mBadgePadding + mBadgeTextRect.height() / 2f;
                break;
            case Gravity.START | Gravity.BOTTOM:
                mBadgeCenter.x = mGravityOffsetX + mBadgePadding + rectWidth / 2f;
                mBadgeCenter.y = mHeight - (mGravityOffsetY + mBadgePadding + mBadgeTextRect.height() / 2f);
                break;
            case Gravity.END | Gravity.TOP:
                mBadgeCenter.x = mWidth - (mGravityOffsetX + mBadgePadding + rectWidth / 2f);
                mBadgeCenter.y = mGravityOffsetY + mBadgePadding + mBadgeTextRect.height() / 2f;
                break;
            case Gravity.END | Gravity.BOTTOM:
                mBadgeCenter.x = mWidth - (mGravityOffsetX + mBadgePadding + rectWidth / 2f);
                mBadgeCenter.y = mHeight - (mGravityOffsetY + mBadgePadding + mBadgeTextRect.height() / 2f);
                break;
            case Gravity.CENTER:
                mBadgeCenter.x = mWidth / 2f;
                mBadgeCenter.y = mHeight / 2f;
                break;
            case Gravity.CENTER | Gravity.TOP:
                mBadgeCenter.x = mWidth / 2f;
                mBadgeCenter.y = mGravityOffsetY + mBadgePadding + mBadgeTextRect.height() / 2f;
                break;
            case Gravity.CENTER | Gravity.BOTTOM:
                mBadgeCenter.x = mWidth / 2f;
                mBadgeCenter.y = mHeight - (mGravityOffsetY + mBadgePadding + mBadgeTextRect.height() / 2f);
                break;
            case Gravity.CENTER | Gravity.START:
                mBadgeCenter.x = mGravityOffsetX + mBadgePadding + rectWidth / 2f;
                mBadgeCenter.y = mHeight / 2f;
                break;
            case Gravity.CENTER | Gravity.END:
                mBadgeCenter.x = mWidth - (mGravityOffsetX + mBadgePadding + rectWidth / 2f);
                mBadgeCenter.y = mHeight / 2f;
                break;
        }
        initRowBadgeCenter();
    }

    private void measureText() {
        mBadgeTextRect.left = 0;
        mBadgeTextRect.top = 0;
        if (TextUtils.isEmpty(mBadgeText)) {
            mBadgeTextRect.right = 0;
            mBadgeTextRect.bottom = 0;
        } else {
            mBadgeTextPaint.setTextSize(mBadgeTextSize);
            mBadgeTextRect.right = mBadgeTextPaint.measureText(mBadgeText);
            mBadgeTextFontMetrics = mBadgeTextPaint.getFontMetrics();
            mBadgeTextRect.bottom = mBadgeTextFontMetrics.descent - mBadgeTextFontMetrics.ascent;
        }
        createClipLayer();
    }

    private void initRowBadgeCenter() {
        int[] screenPoint = new int[2];
        getLocationOnScreen(screenPoint);
        mRowBadgeCenter.x = mBadgeCenter.x + screenPoint[0];
        mRowBadgeCenter.y = mBadgeCenter.y + screenPoint[1];
    }

    protected void animateHide(PointF center) {
        if (mBadgeText == null) {
            return;
        }
        if (mAnimator == null || !mAnimator.isRunning()) {
            screenFromWindow(true);
            mAnimator = new BadgeAnimator(createBadgeBitmap(), center, this);
            mAnimator.start();
            setBadgeNumber(0);
        }
    }

    public void reset() {
        mDragCenter.x = -1000;
        mDragCenter.y = -1000;
        mDragQuadrant = 4;
        screenFromWindow(false);
        getParent().requestDisallowInterceptTouchEvent(false);
        invalidate();
    }

    @Override
    public void hide(boolean animate) {
        if (animate && mActivityRoot != null) {
            initRowBadgeCenter();
            animateHide(mRowBadgeCenter);
        } else {
            setBadgeNumber(0);
        }
    }

    /**
     * @param badgeNumber equal to zero badge will be hidden, less than zero show dot
     */
    @Override
    public Badge setBadgeNumber(int badgeNumber) {
        mBadgeNumber = badgeNumber;
        if (mBadgeNumber < 0) {
            mBadgeText = "";
        } else if (mBadgeNumber > 99) {
            mBadgeText = mExact ? String.valueOf(mBadgeNumber) : "99+";
        } else if (mBadgeNumber > 0 && mBadgeNumber <= 99) {
            mBadgeText = String.valueOf(mBadgeNumber);
        } else if (mBadgeNumber == 0) {
            mBadgeText = null;
        }
        measureText();
        invalidate();
        return this;
    }

    @Override
    public int getBadgeNumber() {
        return mBadgeNumber;
    }

    @Override
    public Badge setBadgeText(String badgeText) {
        mBadgeText = badgeText;
        mBadgeNumber = 1;
        measureText();
        invalidate();
        return this;
    }

    @Override
    public String getBadgeText() {
        return mBadgeText;
    }

    @Override
    public Badge setExactMode(boolean isExact) {
        mExact = isExact;
        if (mBadgeNumber > 99) {
            setBadgeNumber(mBadgeNumber);
        }
        return this;
    }

    @Override
    public boolean isExactMode() {
        return mExact;
    }

    @Override
    public Badge setShowShadow(boolean showShadow) {
        mShowShadow = showShadow;
        invalidate();
        return this;
    }

    @Override
    public boolean isShowShadow() {
        return mShowShadow;
    }

    @Override
    public Badge setBadgeBackgroundColor(int color) {
        mColorBackground = color;
        if (mColorBackground == Color.TRANSPARENT) {
            mBadgeTextPaint.setXfermode(null);
        } else {
            mBadgeTextPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
        }
        invalidate();
        return this;
    }

    @Override
    public Badge stroke(int color, float width, boolean isDpValue) {
        mColorBackgroundBorder = color;
        mBackgroundBorderWidth = isDpValue ? DisplayUtil.dp2px(getContext(), width) : width;
        invalidate();
        return this;
    }

    @Override
    public int getBadgeBackgroundColor() {
        return mColorBackground;
    }

    @Override
    public Badge setBadgeBackground(Drawable drawable) {
        return setBadgeBackground(drawable, false);
    }

    @Override
    public Badge setBadgeBackground(Drawable drawable, boolean clip) {
        mDrawableBackgroundClip = clip;
        mDrawableBackground = drawable;
        createClipLayer();
        invalidate();
        return this;
    }

    @Override
    public Drawable getBadgeBackground() {
        return mDrawableBackground;
    }

    @Override
    public Badge setBadgeTextColor(int color) {
        mColorBadgeText = color;
        invalidate();
        return this;
    }

    @Override
    public int getBadgeTextColor() {
        return mColorBadgeText;
    }

    @Override
    public Badge setBadgeTextSize(float size, boolean isSpValue) {
        mBadgeTextSize = isSpValue ? DisplayUtil.dp2px(getContext(), size) : size;
        measureText();
        invalidate();
        return this;
    }

    @Override
    public float getBadgeTextSize(boolean isSpValue) {
        return isSpValue ? DisplayUtil.px2dp(getContext(), mBadgeTextSize) : mBadgeTextSize;
    }

    @Override
    public Badge setBadgePadding(float padding, boolean isDpValue) {
        mBadgePadding = isDpValue ? DisplayUtil.dp2px(getContext(), padding) : padding;
        createClipLayer();
        invalidate();
        return this;
    }

    @Override
    public float getBadgePadding(boolean isDpValue) {
        return isDpValue ? DisplayUtil.px2dp(getContext(), mBadgePadding) : mBadgePadding;
    }

    @Override
    public boolean isDraggable() {
        return mDraggable;
    }

    /**
     * @param gravity only support Gravity.START | Gravity.TOP , Gravity.END | Gravity.TOP ,
     *                Gravity.START | Gravity.BOTTOM , Gravity.END | Gravity.BOTTOM ,
     *                Gravity.CENTER , Gravity.CENTER | Gravity.TOP , Gravity.CENTER | Gravity.BOTTOM ,
     *                Gravity.CENTER | Gravity.START , Gravity.CENTER | Gravity.END
     */
    @Override
    public Badge setBadgeGravity(int gravity) {
        if (gravity == (Gravity.START | Gravity.TOP) ||
                gravity == (Gravity.END | Gravity.TOP) ||
                gravity == (Gravity.START | Gravity.BOTTOM) ||
                gravity == (Gravity.END | Gravity.BOTTOM) ||
                gravity == (Gravity.CENTER) ||
                gravity == (Gravity.CENTER | Gravity.TOP) ||
                gravity == (Gravity.CENTER | Gravity.BOTTOM) ||
                gravity == (Gravity.CENTER | Gravity.START) ||
                gravity == (Gravity.CENTER | Gravity.END)) {
            mBadgeGravity = gravity;
            invalidate();
        } else {
            throw new IllegalStateException("only support Gravity.START | Gravity.TOP , Gravity.END | Gravity.TOP , " +
                    "Gravity.START | Gravity.BOTTOM , Gravity.END | Gravity.BOTTOM , Gravity.CENTER" +
                    " , Gravity.CENTER | Gravity.TOP , Gravity.CENTER | Gravity.BOTTOM ," +
                    "Gravity.CENTER | Gravity.START , Gravity.CENTER | Gravity.END");
        }
        return this;
    }

    @Override
    public int getBadgeGravity() {
        return mBadgeGravity;
    }

    @Override
    public Badge setGravityOffset(float offset, boolean isDpValue) {
        return setGravityOffset(offset, offset, isDpValue);
    }

    @Override
    public Badge setGravityOffset(float offsetX, float offsetY, boolean isDpValue) {
        mGravityOffsetX = isDpValue ? DisplayUtil.dp2px(getContext(), offsetX) : offsetX;
        mGravityOffsetY = isDpValue ? DisplayUtil.dp2px(getContext(), offsetY) : offsetY;
        invalidate();
        return this;
    }

    @Override
    public float getGravityOffsetX(boolean isDpValue) {
        return isDpValue ? DisplayUtil.px2dp(getContext(), mGravityOffsetX) : mGravityOffsetX;
    }

    @Override
    public float getGravityOffsetY(boolean isDpValue) {
        return isDpValue ? DisplayUtil.px2dp(getContext(), mGravityOffsetY) : mGravityOffsetY;
    }


    private void updataListener(int state) {
        if (mDragStateChangedListener != null)
            mDragStateChangedListener.onDragStateChanged(state, this, mTargetView);
    }

    @Override
    public Badge setOnDragStateChangedListener(OnDragStateChangedListener l) {
        mDraggable = l != null;
        mDragStateChangedListener = l;
        return this;
    }

    @Override
    public PointF getDragCenter() {
        if (mDraggable && mDragging) return mDragCenter;
        return null;
    }

    private class BadgeContainer extends ViewGroup {

        @Override
        protected void dispatchRestoreInstanceState(SparseArray container) {
            if(!(getParent() instanceof RelativeLayout)){
                super.dispatchRestoreInstanceState(container);
            }
        }

        public BadgeContainer(Context context) {
            super(context);
        }

        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            for (int i = 0; i < getChildCount(); i++) {
                View child = getChildAt(i);
                child.layout(0, 0, child.getMeasuredWidth(), child.getMeasuredHeight());
            }
        }

        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            View targetView = null, badgeView = null;
            for (int i = 0; i < getChildCount(); i++) {
                View child = getChildAt(i);
                if (!(child instanceof QBadgeView)) {
                    targetView = child;
                } else {
                    badgeView = child;
                }
            }
            if (targetView == null) {
                super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            } else {
                targetView.measure(widthMeasureSpec, heightMeasureSpec);
                if (badgeView != null) {
                    badgeView.measure(MeasureSpec.makeMeasureSpec(targetView.getMeasuredWidth(), MeasureSpec.EXACTLY),
                            MeasureSpec.makeMeasureSpec(targetView.getMeasuredHeight(), MeasureSpec.EXACTLY));
                }
                setMeasuredDimension(targetView.getMeasuredWidth(), targetView.getMeasuredHeight());
            }
        }
    }
}


public class BadgeAnimator extends ValueAnimator {
    private BitmapFragment[][] mFragments;
    private WeakReference mWeakBadge;

    public BadgeAnimator(Bitmap badgeBitmap, PointF center, QBadgeView badge) {
        mWeakBadge = new WeakReference<>(badge);
        setFloatValues(0f, 1f);
        setDuration(500);
        mFragments = getFragments(badgeBitmap, center);
        addUpdateListener(new AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                QBadgeView badgeView = mWeakBadge.get();
                if (badgeView == null || !badgeView.isShown()) {
                    cancel();
                } else {
                    badgeView.invalidate();
                }
            }
        });
        addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                QBadgeView badgeView = mWeakBadge.get();
                if (badgeView != null) {
                    badgeView.reset();
                }
            }
        });
    }

    public void draw(Canvas canvas) {
        for (int i = 0; i < mFragments.length; i++) {
            for (int j = 0; j < mFragments[i].length; j++) {
                BitmapFragment bf = mFragments[i][j];
                float value = Float.parseFloat(getAnimatedValue().toString());
                bf.updata(value, canvas);
            }
        }
    }


    private BitmapFragment[][] getFragments(Bitmap badgeBitmap, PointF center) {
        int width = badgeBitmap.getWidth();
        int height = badgeBitmap.getHeight();
        float fragmentSize = Math.min(width, height) / 6f;
        float startX = center.x - badgeBitmap.getWidth() / 2f;
        float startY = center.y - badgeBitmap.getHeight() / 2f;
        BitmapFragment[][] fragments = new BitmapFragment[(int) (height / fragmentSize)][(int) (width / fragmentSize)];
        for (int i = 0; i < fragments.length; i++) {
            for (int j = 0; j < fragments[i].length; j++) {
                BitmapFragment bf = new BitmapFragment();
                bf.color = badgeBitmap.getPixel((int) (j * fragmentSize), (int) (i * fragmentSize));
                bf.x = startX + j * fragmentSize;
                bf.y = startY + i * fragmentSize;
                bf.size = fragmentSize;
                bf.maxSize = Math.max(width, height);
                fragments[i][j] = bf;
            }
        }
        badgeBitmap.recycle();
        return fragments;
    }

    private class BitmapFragment {
        Random random;
        float x;
        float y;
        float size;
        int color;
        int maxSize;
        Paint paint;

        public BitmapFragment() {
            paint = new Paint();
            paint.setAntiAlias(true);
            paint.setStyle(Paint.Style.FILL);
            random = new Random();
        }

        public void updata(float value, Canvas canvas) {
            paint.setColor(color);
            x = x + 0.1f * random.nextInt(maxSize) * (random.nextFloat() - 0.5f);
            y = y + 0.1f * random.nextInt(maxSize) * (random.nextFloat() - 0.5f);
            canvas.drawCircle(x, y, size - value * size, paint);
        }
    }
}

你可能感兴趣的:(BadgeView分析)