在一些项目中,我们常常会看到显示未读消息数的效果,此时此刻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,那么效果图如下
图片.pngBadgeView会根据数字长度决定画圆形背景还是圆角矩形背景。
(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)setBadgeBackgroundColor
和setBadgeBackground
设置背景色
设置背景色有三个方法
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
模式,即两图相交之后取目标图,效果如下:
(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);
}
}
}
网友评论