BadgeView分析

作者: NoBugException | 来源:发表于2019-04-24 16:41 被阅读118次

    在一些项目中,我们常常会看到显示未读消息数的效果,此时此刻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在一张图片的右上角显示

    <ImageView
        android:id="@+id/imageview"
        android:layout_marginTop="50dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@mipmap/ic_launcher"/>
    
        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)
    
    图片.png

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

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

    图片.png

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

    (4)setBadgeText设置文字
    .setBadgeText("中国")
    
    图片.png

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

    图片.png
    (5)setExactMode设置精准模式

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

    (6)setShowShadow设置阴影效果开关

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

    .setExactMode(true)
    
    图片.png

    画笔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

    <?xml version="1.0" encoding="utf-8"?>
    <shape xmlns:android="http://schemas.android.com/apk/res/android">
        <solid android:color="#ff03a9f4" />
        <corners android:radius="2dp" />
    </shape>
    
    .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)
    
    图片.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的角标可以被拖拽,默认是不可拖拽,只有设置了拖拽监听之后才可以被拖拽。

    拖拽效果如下:

    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
        android:id="@+id/imageview"
        android:layout_marginTop="50dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@mipmap/ic_launcher"/>
    
        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<PointF> 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

    <?xml version="1.0" encoding="utf-8"?>
    <shape xmlns:android="http://schemas.android.com/apk/res/android">
        <solid android:color="#ff03a9f4" />
        <corners android:radius="2dp" />
    </shape>
    
    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<PointF> 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<Parcelable> 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<QBadgeView> 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分析

        本文链接:https://www.haomeiwen.com/subject/zicggqtx.html