Android 自定义消息右上角的数字提示或红点(类似微信或QQ的未读消息提示)
支持自由定制外观、拖拽消除的MaterialDesign风格Android BadgeView,自定义消息右上角的数字提示或红点(类似微信或QQ的未读消息提示)实现消息数量大于0时显示具体消息数量,最大显示99,大于99设置99+效果
一.效果图:
二.添加依赖快速实现:
1.添加依赖:(也可以不添加依赖直接将自定义类写到自己的项目中)
implementation 'q.rorbin:badgeview:1.1.3'
2.代码实现:
new QBadgeView(context).bindTarget(textview).setBadgeNumber(6);
3.方法说明:
code | 说明 |
---|---|
setBadgeNumber | 设置Badge数字 |
setBadgeText | 设置Badge文本 |
setBadgeTextSize | 设置文本字体大小 |
setBadgeTextColor | 设置文本颜色 |
setExactMode | 设置是否显示精确模式数值 |
setBadgeGravity | 设置Badge相对于TargetView的位置 |
setGravityOffset | 设置外边距 |
setBadgePadding | 设置内边距 |
setBadgeBackgroundColor | 设置背景色 |
setBadgeBackground | 设置背景图片 |
setShowShadow | 设置是否显示阴影 |
setOnDragStateChangedListener | 打开拖拽消除模式并设置监听 |
stroke | 描边 |
hide | 隐藏Badge |
可以参考:https://github.com/qstumn/BadgeView
三.自定义BadgeView:
1.主函数代码:
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.DisplayMetrics;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.AbsListView;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.RadioButton;
import android.widget.RelativeLayout;
import android.widget.SeekBar;
import android.widget.Switch;
import android.widget.TextView;
import com.example.m1571.myapplication.badgeview.Badge;
import com.example.m1571.myapplication.badgeview.QBadgeView;
import java.util.ArrayList;
import java.util.List;
//import q.rorbin.badgeview.QBadgeView;
public class MainActivity extends AppCompatActivity {
TextView textview, tv_offsetx, tv_padding, tv_offsety, tv_numbersize, tv_dragstate;
EditText et_badgenumber, et_badgetext;
ImageView imageview, iv_badgecolor, iv_numbercolor;
Button button, btn_animation;
List radioButtons = new ArrayList<>();
CompoundButton lastRadioButton;
SeekBar seekBar_offsetx, seekBar_padding, seekBar_offsety, seekBar_numbersize;
Switch swicth_exact, swicth_draggable, swicth_shadow;
List badges;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
initListener();
initBadge();
swicth_draggable.setChecked(true);
}
private void initBadge() {
badges = new ArrayList<>();
badges.add(new QBadgeView(this).bindTarget(textview).setBadgeNumber(5));
badges.add(new QBadgeView(this).bindTarget(imageview).setBadgeText("PNG").setBadgeTextColor(0x00000000)
.setBadgeGravity(Gravity.BOTTOM | Gravity.END).setBadgeBackgroundColor(0xff03a9f4)
.setBadgeBackground(getResources().getDrawable(R.drawable.shape_round_rect)));
badges.add(new QBadgeView(this).bindTarget(button).setBadgeText("新").setBadgeTextSize(13, true)
.setBadgeBackgroundColor(0xffffeb3b).setBadgeTextColor(0xff000000)
.stroke(0xff000000, 1, true));
}
private void initView() {
textview = (TextView) findViewById(R.id.textview);
tv_offsetx = (TextView) findViewById(R.id.tv_offsetx);
tv_offsety = (TextView) findViewById(R.id.tv_offsety);
tv_padding = (TextView) findViewById(R.id.tv_padding);
tv_numbersize = (TextView) findViewById(R.id.tv_numbersize);
tv_dragstate = (TextView) findViewById(R.id.tv_dragstate);
et_badgenumber = (EditText) findViewById(R.id.et_badgenumber);
et_badgetext = (EditText) findViewById(R.id.et_badgetext);
imageview = (ImageView) findViewById(R.id.imageview);
iv_badgecolor = (ImageView) findViewById(R.id.iv_badgecolor);
iv_numbercolor = (ImageView) findViewById(R.id.iv_numbercolor);
iv_numbercolor = (ImageView) findViewById(R.id.iv_numbercolor);
button = (Button) findViewById(R.id.button);
btn_animation = (Button) findViewById(R.id.btn_animation);
radioButtons.add((RadioButton) findViewById(R.id.rb_st));
radioButtons.add((RadioButton) findViewById(R.id.rb_sb));
RadioButton rb_et = (RadioButton) findViewById(R.id.rb_et);
lastRadioButton = rb_et;
radioButtons.add(rb_et);
radioButtons.add((RadioButton) findViewById(R.id.rb_eb));
radioButtons.add((RadioButton) findViewById(R.id.rb_ct));
radioButtons.add((RadioButton) findViewById(R.id.rb_ce));
radioButtons.add((RadioButton) findViewById(R.id.rb_cb));
radioButtons.add((RadioButton) findViewById(R.id.rb_cs));
radioButtons.add((RadioButton) findViewById(R.id.rb_c));
seekBar_offsetx = (SeekBar) findViewById(R.id.seekBar_offsetx);
seekBar_offsety = (SeekBar) findViewById(R.id.seekBar_offsety);
seekBar_padding = (SeekBar) findViewById(R.id.seekBar_padding);
seekBar_numbersize = (SeekBar) findViewById(R.id.seekBar_numbersize);
swicth_exact = (Switch) findViewById(R.id.swicth_exact);
swicth_draggable = (Switch) findViewById(R.id.swicth_draggable);
swicth_shadow = (Switch) findViewById(R.id.swicth_shadow);
}
private void initListener() {
CompoundButton.OnCheckedChangeListener checkedChangeListener = new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (!isChecked) {
return;
}
if (lastRadioButton != null) {
lastRadioButton.setChecked(false);
}
lastRadioButton = buttonView;
for (Badge badge : badges) {
switch (buttonView.getId()) {
case R.id.rb_st:
badge.setBadgeGravity(Gravity.START | Gravity.TOP);
break;
case R.id.rb_sb:
badge.setBadgeGravity(Gravity.START | Gravity.BOTTOM);
break;
case R.id.rb_et:
badge.setBadgeGravity(Gravity.END | Gravity.TOP);
break;
case R.id.rb_eb:
badge.setBadgeGravity(Gravity.END | Gravity.BOTTOM);
break;
case R.id.rb_ct:
badge.setBadgeGravity(Gravity.CENTER | Gravity.TOP);
break;
case R.id.rb_ce:
badge.setBadgeGravity(Gravity.CENTER | Gravity.END);
break;
case R.id.rb_cb:
badge.setBadgeGravity(Gravity.CENTER | Gravity.BOTTOM);
break;
case R.id.rb_cs:
badge.setBadgeGravity(Gravity.CENTER | Gravity.START);
break;
case R.id.rb_c:
badge.setBadgeGravity(Gravity.CENTER);
break;
}
}
}
};
for (RadioButton rb : radioButtons) {
rb.setOnCheckedChangeListener(checkedChangeListener);
}
SeekBar.OnSeekBarChangeListener onSeekBarChangeListener = new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
for (Badge badge : badges) {
if (seekBar == seekBar_offsetx || seekBar == seekBar_offsety) {
int x = seekBar_offsetx.getProgress();
int y = seekBar_offsety.getProgress();
tv_offsetx.setText("GravityOffsetX : " + x);
tv_offsety.setText("GravityOffsetY : " + y);
badge.setGravityOffset(x, y, true);
} else if (seekBar == seekBar_padding) {
tv_padding.setText("BadgePadding : " + progress);
badge.setBadgePadding(progress, true);
} else if (seekBar == seekBar_numbersize) {
tv_numbersize.setText("TextSize : " + progress);
badge.setBadgeTextSize(progress, true);
}
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
};
seekBar_offsetx.setOnSeekBarChangeListener(onSeekBarChangeListener);
seekBar_offsety.setOnSeekBarChangeListener(onSeekBarChangeListener);
seekBar_padding.setOnSeekBarChangeListener(onSeekBarChangeListener);
seekBar_numbersize.setOnSeekBarChangeListener(onSeekBarChangeListener);
View.OnClickListener onClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
if (v == iv_badgecolor) {
selectorColor(new OnColorClickListener() {
@Override
public void onColorClick(int color) {
iv_badgecolor.setBackgroundColor(color);
for (Badge badge : badges) {
badge.setBadgeBackgroundColor(color);
}
}
});
} else if (v == iv_numbercolor) {
selectorColor(new OnColorClickListener() {
@Override
public void onColorClick(int color) {
iv_numbercolor.setBackgroundColor(color);
for (Badge badge : badges) {
badge.setBadgeTextColor(color);
}
}
});
} else if (v == btn_animation) {
for (Badge badge : badges) {
badge.hide(true);
}
}
}
};
iv_badgecolor.setOnClickListener(onClickListener);
iv_numbercolor.setOnClickListener(onClickListener);
btn_animation.setOnClickListener(onClickListener);
class MyTextWatcher implements TextWatcher {
private EditText editText;
public MyTextWatcher(EditText editText) {
this.editText = editText;
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
try {
for (Badge badge : badges) {
if (editText == et_badgenumber) {
int num = TextUtils.isEmpty(s) ? 0 : Integer.parseInt(s.toString());
badge.setBadgeNumber(num);
} else if (editText == et_badgetext) {
badge.setBadgeText(s.toString());
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void afterTextChanged(Editable s) {
}
}
et_badgenumber.addTextChangedListener(new MyTextWatcher(et_badgenumber));
et_badgetext.addTextChangedListener(new MyTextWatcher(et_badgetext));
CompoundButton.OnCheckedChangeListener onCheckedChangeListener = new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
for (Badge badge : badges) {
if (buttonView == swicth_exact) {
badge.setExactMode(isChecked);
} else if (buttonView == swicth_draggable) {
badge.setOnDragStateChangedListener(isChecked ?
new Badge.OnDragStateChangedListener() {
@Override
public void onDragStateChanged(int dragState, Badge badge, View targetView) {
switch (dragState) {
case STATE_START:
tv_dragstate.setText("STATE_START");
break;
case STATE_DRAGGING:
tv_dragstate.setText("STATE_DRAGGING");
break;
case STATE_DRAGGING_OUT_OF_RANGE:
tv_dragstate.setText("STATE_DRAGGING_OUT_OF_RANGE");
break;
case STATE_SUCCEED:
tv_dragstate.setText("STATE_SUCCEED");
break;
case STATE_CANCELED:
tv_dragstate.setText("STATE_CANCELED");
break;
}
}
} : null);
} else if (buttonView == swicth_shadow) {
badge.setShowShadow(isChecked);
}
}
}
};
swicth_exact.setOnCheckedChangeListener(onCheckedChangeListener);
swicth_draggable.setOnCheckedChangeListener(onCheckedChangeListener);
swicth_shadow.setOnCheckedChangeListener(onCheckedChangeListener);
}
private void selectorColor(final OnColorClickListener l) {
final AlertDialog dialog = new AlertDialog.Builder(this).create();
GridView gv = new GridView(this);
gv.setNumColumns(4);
gv.setAdapter(new BaseAdapter() {
int[] colors = new int[]{Color.TRANSPARENT, 0xffffffff, 0xff000000, 0xffe51c23, 0xffE84E40, 0xff9c27b0, 0xff673ab7,
0xff3f51b5, 0xff5677fc, 0xff03a9f4, 0xff00bcd4, 0xff009688, 0xff259b24, 0xff8bc34a, 0xffcddc39,
0xffffeb3b, 0xffffc107, 0xffff9800, 0xffff5722, 0xff795548};
@Override
public int getCount() {
return colors.length;
}
@Override
public Object getItem(int position) {
return colors[position];
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
View v = new View(MainActivity.this);
v.setBackgroundColor(colors[position]);
v.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
l.onColorClick(colors[position]);
dialog.dismiss();
}
});
DisplayMetrics dm = new DisplayMetrics();
WindowManager wm = (WindowManager) MainActivity.this
.getSystemService(Context.WINDOW_SERVICE);
wm.getDefaultDisplay().getMetrics(dm);
GridView.LayoutParams lp = new AbsListView.LayoutParams(AbsListView.LayoutParams.MATCH_PARENT,
(int) (dm.widthPixels / 5f));
v.setLayoutParams(lp);
return v;
}
});
dialog.setView(gv);
dialog.show();
dialog.getWindow().setBackgroundDrawable(new ColorDrawable(0x33FFFFFF));
}
interface OnColorClickListener {
void onColorClick(int color);
}
}
2.主函数布局:
3.相关属性:
dimens.xml:
16dp
16dp
string.xml:
My Application
TextView
Button
shape_round_rect.xml:
4.实现接口Badge.java:
import android.graphics.PointF;
import android.graphics.drawable.Drawable;
import android.view.View;
/**
* 接口
*/
public interface Badge {
Badge setBadgeNumber(int badgeNum);
int getBadgeNumber();
Badge setBadgeText(String badgeText);
String getBadgeText();
Badge setExactMode(boolean isExact);
boolean isExactMode();
Badge setShowShadow(boolean showShadow);
boolean isShowShadow();
Badge setBadgeBackgroundColor(int color);
Badge stroke(int color, float width, boolean isDpValue);
int getBadgeBackgroundColor();
Badge setBadgeBackground(Drawable drawable);
Badge setBadgeBackground(Drawable drawable, boolean clip);
Drawable getBadgeBackground();
Badge setBadgeTextColor(int color);
int getBadgeTextColor();
Badge setBadgeTextSize(float size, boolean isSpValue);
float getBadgeTextSize(boolean isSpValue);
Badge setBadgePadding(float padding, boolean isDpValue);
float getBadgePadding(boolean isDpValue);
boolean isDraggable();
Badge setBadgeGravity(int gravity);
int getBadgeGravity();
Badge setGravityOffset(float offset, boolean isDpValue);
Badge setGravityOffset(float offsetX, float offsetY, boolean isDpValue);
float getGravityOffsetX(boolean isDpValue);
float getGravityOffsetY(boolean isDpValue);
Badge setOnDragStateChangedListener(OnDragStateChangedListener l);
PointF getDragCenter();
Badge bindTarget(View view);
View getTargetView();
void hide(boolean 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);
}
}
5.自定义动画BadgeAnimator.java:
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PointF;
import android.util.Log;
import java.lang.ref.WeakReference;
import java.util.Random;
import static android.content.ContentValues.TAG;
/**
* 自定义动画
* Animation :https://github.com/tyrantgit/ExplosionField
*/
public class BadgeAnimator extends ValueAnimator {
private BitmapFragment[][] mFragments;
private WeakReference mWeakBadge;
public BadgeAnimator(Bitmap badgeBitmap, PointF center, QBadgeView badge) {
mWeakBadge = new WeakReference<>(badge);
setFloatValues(0f, 1f);
setDuration(500);
mFragments = getFragments(badgeBitmap, center);
addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
QBadgeView badgeView = mWeakBadge.get();
if (badgeView == null || !badgeView.isShown()) {
cancel();
} else {
badgeView.invalidate();
}
}
});
addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
QBadgeView badgeView = mWeakBadge.get();
if (badgeView != null) {
badgeView.reset();
}
}
});
}
public void draw(Canvas canvas) {
for (int i = 0; i < mFragments.length; i++) {
for (int j = 0; j < mFragments[i].length; j++) {
BitmapFragment bf = mFragments[i][j];
float value = Float.parseFloat(getAnimatedValue().toString());
bf.updata(value, canvas);
}
}
}
private BitmapFragment[][] getFragments(Bitmap badgeBitmap, PointF center) {
int width = badgeBitmap.getWidth();
int height = badgeBitmap.getHeight();
float fragmentSize = Math.min(width, height) / 6f;
float startX = center.x - badgeBitmap.getWidth() / 2f;
float startY = center.y - badgeBitmap.getHeight() / 2f;
BitmapFragment[][] fragments = new BitmapFragment[(int) (height / fragmentSize)][(int) (width / fragmentSize)];
for (int i = 0; i < fragments.length; i++) {
for (int j = 0; j < fragments[i].length; j++) {
BitmapFragment bf = new BitmapFragment();
bf.color = badgeBitmap.getPixel((int) (j * fragmentSize), (int) (i * fragmentSize));
bf.x = startX + j * fragmentSize;
bf.y = startY + i * fragmentSize;
bf.size = fragmentSize;
bf.maxSize = Math.max(width, height);
fragments[i][j] = bf;
}
}
badgeBitmap.recycle();
return fragments;
}
private class BitmapFragment {
Random random;
float x;
float y;
float size;
int color;
int maxSize;
Paint paint;
public BitmapFragment() {
paint = new Paint();
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.FILL);
random = new Random();
}
public void updata(float value, Canvas canvas) {
paint.setColor(color);
x = x + 0.1f * random.nextInt(maxSize) * (random.nextFloat() - 0.5f);
y = y + 0.1f * random.nextInt(maxSize) * (random.nextFloat() - 0.5f);
canvas.drawCircle(x, y, size - value * size, paint);
}
}
}
6.dp、px转换工具类DisplayUtil.java:
import android.content.Context;
/**
* dp px转换工具
*/
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);
}
}
7.数学计算工具类MathUtil.java:
import android.graphics.PointF;
import java.util.List;
/**
* Created by chqiu on 2017/3/20.
*/
public class MathUtil {
public static final double CIRCLE_RADIAN = 2 * Math.PI;
public static double getTanRadian(double atan, int quadrant) {
if (atan < 0) {
atan += CIRCLE_RADIAN / 4;
}
atan += CIRCLE_RADIAN / 4 * (quadrant - 1);
return atan;
}
public static double radianToAngle(double radian) {
return 360 * (radian / CIRCLE_RADIAN);
}
public static int getQuadrant(PointF p, PointF center) {
if (p.x > center.x) {
if (p.y > center.y) {
return 4;
} else if (p.y < center.y) {
return 1;
}
} else if (p.x < center.x) {
if (p.y > center.y) {
return 3;
} else if (p.y < center.y) {
return 2;
}
}
return -1;
}
public static float getPointDistance(PointF p1, PointF p2) {
return (float) Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2));
}
/**
* this formula is designed by mabeijianxi
* website : http://blog.csdn.net/mabeijianxi/article/details/50560361
*
* @param circleCenter The circle center point.
* @param radius The circle radius.
* @param slopeLine The slope of line which cross the pMiddle.
*/
public static void getInnertangentPoints(PointF circleCenter, float radius, Double slopeLine, List points) {
float radian, xOffset, yOffset;
if (slopeLine != null) {
radian = (float) Math.atan(slopeLine);
xOffset = (float) (Math.cos(radian) * radius);
yOffset = (float) (Math.sin(radian) * radius);
} else {
xOffset = radius;
yOffset = 0;
}
points.add(new PointF(circleCenter.x + xOffset, circleCenter.y + yOffset));
points.add(new PointF(circleCenter.x - xOffset, circleCenter.y - yOffset));
}
}
8.自定义QQ消息类QBadgeView.java:
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Parcelable;
import android.text.TextPaint;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
import java.util.ArrayList;
import java.util.List;
/**
* 自定义QQ消息数量工具类
*/
public class QBadgeView extends View implements Badge {
protected int mColorBackground;
protected int mColorBackgroundBorder;
protected int mColorBadgeText;
protected Drawable mDrawableBackground;
protected Bitmap mBitmapClip;
protected boolean mDrawableBackgroundClip;
protected float mBackgroundBorderWidth;
protected float mBadgeTextSize;
protected float mBadgePadding;
protected int mBadgeNumber;
protected String mBadgeText;
protected boolean mDraggable;
protected boolean mDragging;
protected boolean mExact;
protected boolean mShowShadow;
protected int mBadgeGravity;
protected float mGravityOffsetX;
protected float mGravityOffsetY;
protected float mDefalutRadius;
protected float mFinalDragDistance;
protected int mDragQuadrant;
protected boolean mDragOutOfRange;
protected RectF mBadgeTextRect;
protected RectF mBadgeBackgroundRect;
protected Path mDragPath;
protected Paint.FontMetrics mBadgeTextFontMetrics;
protected PointF mBadgeCenter;
protected PointF mDragCenter;
protected PointF mRowBadgeCenter;
protected PointF mControlPoint;
protected List mInnertangentPoints;
protected View mTargetView;
protected int mWidth;
protected int mHeight;
protected TextPaint mBadgeTextPaint;
protected Paint mBadgeBackgroundPaint;
protected Paint mBadgeBackgroundBorderPaint;
protected BadgeAnimator mAnimator;
protected OnDragStateChangedListener mDragStateChangedListener;
protected ViewGroup mActivityRoot;
public QBadgeView(Context context) {
this(context, null);
}
private QBadgeView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
private QBadgeView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
mBadgeTextRect = new RectF();
mBadgeBackgroundRect = new RectF();
mDragPath = new Path();
mBadgeCenter = new PointF();
mDragCenter = new PointF();
mRowBadgeCenter = new PointF();
mControlPoint = new PointF();
mInnertangentPoints = new ArrayList<>();
mBadgeTextPaint = new TextPaint();
mBadgeTextPaint.setAntiAlias(true);
mBadgeTextPaint.setSubpixelText(true);
mBadgeTextPaint.setFakeBoldText(true);
mBadgeTextPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
mBadgeBackgroundPaint = new Paint();
mBadgeBackgroundPaint.setAntiAlias(true);
mBadgeBackgroundPaint.setStyle(Paint.Style.FILL);
mBadgeBackgroundBorderPaint = new Paint();
mBadgeBackgroundBorderPaint.setAntiAlias(true);
mBadgeBackgroundBorderPaint.setStyle(Paint.Style.STROKE);
mColorBackground = 0xFFE84E40;
mColorBadgeText = 0xFFFFFFFF;
mBadgeTextSize = DisplayUtil.dp2px(getContext(), 11);
mBadgePadding = DisplayUtil.dp2px(getContext(), 5);
mBadgeNumber = 0;
mBadgeGravity = Gravity.END | Gravity.TOP;
mGravityOffsetX = DisplayUtil.dp2px(getContext(), 1);
mGravityOffsetY = DisplayUtil.dp2px(getContext(), 1);
mFinalDragDistance = DisplayUtil.dp2px(getContext(), 90);
mShowShadow = true;
mDrawableBackgroundClip = false;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
setTranslationZ(1000);
}
}
@Override
public Badge bindTarget(final View targetView) {
if (targetView == null) {
throw new IllegalStateException("targetView can not be null");
}
if (getParent() != null) {
((ViewGroup) getParent()).removeView(this);
}
ViewParent targetParent = targetView.getParent();
if (targetParent != null && targetParent instanceof ViewGroup) {
mTargetView = targetView;
if (targetParent instanceof BadgeContainer) {
((BadgeContainer) targetParent).addView(this);
} else {
ViewGroup targetContainer = (ViewGroup) targetParent;
int index = targetContainer.indexOfChild(targetView);
ViewGroup.LayoutParams targetParams = targetView.getLayoutParams();
targetContainer.removeView(targetView);
final BadgeContainer badgeContainer = new BadgeContainer(getContext());
if(targetContainer instanceof RelativeLayout){
badgeContainer.setId(targetView.getId());
}
targetContainer.addView(badgeContainer, index, targetParams);
badgeContainer.addView(targetView);
badgeContainer.addView(this);
}
} else {
throw new IllegalStateException("targetView must have a parent");
}
return this;
}
@Override
public View getTargetView() {
return mTargetView;
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (mActivityRoot == null) findViewRoot(mTargetView);
}
private void findViewRoot(View view) {
mActivityRoot = (ViewGroup) view.getRootView();
if (mActivityRoot == null) {
findActivityRoot(view);
}
}
private void findActivityRoot(View view) {
if (view.getParent() != null && view.getParent() instanceof View) {
findActivityRoot((View) view.getParent());
} else if (view instanceof ViewGroup) {
mActivityRoot = (ViewGroup) view;
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
float x = event.getX();
float y = event.getY();
if (mDraggable && event.getPointerId(event.getActionIndex()) == 0
&& (x > mBadgeBackgroundRect.left && x < mBadgeBackgroundRect.right &&
y > mBadgeBackgroundRect.top && y < mBadgeBackgroundRect.bottom)
&& mBadgeText != null) {
initRowBadgeCenter();
mDragging = true;
updataListener(OnDragStateChangedListener.STATE_START);
mDefalutRadius = DisplayUtil.dp2px(getContext(), 7);
getParent().requestDisallowInterceptTouchEvent(true);
screenFromWindow(true);
mDragCenter.x = event.getRawX();
mDragCenter.y = event.getRawY();
}
break;
case MotionEvent.ACTION_MOVE:
if (mDragging) {
mDragCenter.x = event.getRawX();
mDragCenter.y = event.getRawY();
invalidate();
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
case MotionEvent.ACTION_CANCEL:
if (event.getPointerId(event.getActionIndex()) == 0 && mDragging) {
mDragging = false;
onPointerUp();
}
break;
}
return mDragging || super.onTouchEvent(event);
}
private void onPointerUp() {
if (mDragOutOfRange) {
animateHide(mDragCenter);
updataListener(OnDragStateChangedListener.STATE_SUCCEED);
} else {
reset();
updataListener(OnDragStateChangedListener.STATE_CANCELED);
}
}
protected Bitmap createBadgeBitmap() {
Bitmap bitmap = Bitmap.createBitmap((int) mBadgeBackgroundRect.width() + DisplayUtil.dp2px(getContext(), 3),
(int) mBadgeBackgroundRect.height() + DisplayUtil.dp2px(getContext(), 3), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawBadge(canvas, new PointF(canvas.getWidth() / 2f, canvas.getHeight() / 2f), getBadgeCircleRadius());
return bitmap;
}
protected void screenFromWindow(boolean screen) {
if (getParent() != null) {
((ViewGroup) getParent()).removeView(this);
}
if (screen) {
mActivityRoot.addView(this, new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT));
} else {
bindTarget(mTargetView);
}
}
private void showShadowImpl(boolean showShadow) {
int x = DisplayUtil.dp2px(getContext(), 1);
int y = DisplayUtil.dp2px(getContext(), 1.5f);
switch (mDragQuadrant) {
case 1:
x = DisplayUtil.dp2px(getContext(), 1);
y = DisplayUtil.dp2px(getContext(), -1.5f);
break;
case 2:
x = DisplayUtil.dp2px(getContext(), -1);
y = DisplayUtil.dp2px(getContext(), -1.5f);
break;
case 3:
x = DisplayUtil.dp2px(getContext(), -1);
y = DisplayUtil.dp2px(getContext(), 1.5f);
break;
case 4:
x = DisplayUtil.dp2px(getContext(), 1);
y = DisplayUtil.dp2px(getContext(), 1.5f);
break;
}
mBadgeBackgroundPaint.setShadowLayer(showShadow ? DisplayUtil.dp2px(getContext(), 2f)
: 0, x, y, 0x33000000);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = w;
mHeight = h;
}
@Override
protected void onDraw(Canvas canvas) {
if (mAnimator != null && mAnimator.isRunning()) {
mAnimator.draw(canvas);
return;
}
if (mBadgeText != null) {
initPaints();
float badgeRadius = getBadgeCircleRadius();
float startCircleRadius = mDefalutRadius * (1 - MathUtil.getPointDistance
(mRowBadgeCenter, mDragCenter) / mFinalDragDistance);
if (mDraggable && mDragging) {
mDragQuadrant = MathUtil.getQuadrant(mDragCenter, mRowBadgeCenter);
showShadowImpl(mShowShadow);
if (mDragOutOfRange = startCircleRadius < DisplayUtil.dp2px(getContext(), 1.5f)) {
updataListener(OnDragStateChangedListener.STATE_DRAGGING_OUT_OF_RANGE);
drawBadge(canvas, mDragCenter, badgeRadius);
} else {
updataListener(OnDragStateChangedListener.STATE_DRAGGING);
drawDragging(canvas, startCircleRadius, badgeRadius);
drawBadge(canvas, mDragCenter, badgeRadius);
}
} else {
findBadgeCenter();
drawBadge(canvas, mBadgeCenter, badgeRadius);
}
}
}
private void initPaints() {
showShadowImpl(mShowShadow);
mBadgeBackgroundPaint.setColor(mColorBackground);
mBadgeBackgroundBorderPaint.setColor(mColorBackgroundBorder);
mBadgeBackgroundBorderPaint.setStrokeWidth(mBackgroundBorderWidth);
mBadgeTextPaint.setColor(mColorBadgeText);
mBadgeTextPaint.setTextAlign(Paint.Align.CENTER);
}
private void drawDragging(Canvas canvas, float startRadius, float badgeRadius) {
float dy = mDragCenter.y - mRowBadgeCenter.y;
float dx = mDragCenter.x - mRowBadgeCenter.x;
mInnertangentPoints.clear();
if (dx != 0) {
double k1 = dy / dx;
double k2 = -1 / k1;
MathUtil.getInnertangentPoints(mDragCenter, badgeRadius, k2, mInnertangentPoints);
MathUtil.getInnertangentPoints(mRowBadgeCenter, startRadius, k2, mInnertangentPoints);
} else {
MathUtil.getInnertangentPoints(mDragCenter, badgeRadius, 0d, mInnertangentPoints);
MathUtil.getInnertangentPoints(mRowBadgeCenter, startRadius, 0d, mInnertangentPoints);
}
mDragPath.reset();
mDragPath.addCircle(mRowBadgeCenter.x, mRowBadgeCenter.y, startRadius,
mDragQuadrant == 1 || mDragQuadrant == 2 ? Path.Direction.CCW : Path.Direction.CW);
mControlPoint.x = (mRowBadgeCenter.x + mDragCenter.x) / 2.0f;
mControlPoint.y = (mRowBadgeCenter.y + mDragCenter.y) / 2.0f;
mDragPath.moveTo(mInnertangentPoints.get(2).x, mInnertangentPoints.get(2).y);
mDragPath.quadTo(mControlPoint.x, mControlPoint.y, mInnertangentPoints.get(0).x, mInnertangentPoints.get(0).y);
mDragPath.lineTo(mInnertangentPoints.get(1).x, mInnertangentPoints.get(1).y);
mDragPath.quadTo(mControlPoint.x, mControlPoint.y, mInnertangentPoints.get(3).x, mInnertangentPoints.get(3).y);
mDragPath.lineTo(mInnertangentPoints.get(2).x, mInnertangentPoints.get(2).y);
mDragPath.close();
canvas.drawPath(mDragPath, mBadgeBackgroundPaint);
//draw dragging border
if (mColorBackgroundBorder != 0 && mBackgroundBorderWidth > 0) {
mDragPath.reset();
mDragPath.moveTo(mInnertangentPoints.get(2).x, mInnertangentPoints.get(2).y);
mDragPath.quadTo(mControlPoint.x, mControlPoint.y, mInnertangentPoints.get(0).x, mInnertangentPoints.get(0).y);
mDragPath.moveTo(mInnertangentPoints.get(1).x, mInnertangentPoints.get(1).y);
mDragPath.quadTo(mControlPoint.x, mControlPoint.y, mInnertangentPoints.get(3).x, mInnertangentPoints.get(3).y);
float startY;
float startX;
if (mDragQuadrant == 1 || mDragQuadrant == 2) {
startX = mInnertangentPoints.get(2).x - mRowBadgeCenter.x;
startY = mRowBadgeCenter.y - mInnertangentPoints.get(2).y;
} else {
startX = mInnertangentPoints.get(3).x - mRowBadgeCenter.x;
startY = mRowBadgeCenter.y - mInnertangentPoints.get(3).y;
}
float startAngle = 360 - (float) MathUtil.radianToAngle(MathUtil.getTanRadian(Math.atan(startY / startX),
mDragQuadrant - 1 == 0 ? 4 : mDragQuadrant - 1));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mDragPath.addArc(mRowBadgeCenter.x - startRadius, mRowBadgeCenter.y - startRadius,
mRowBadgeCenter.x + startRadius, mRowBadgeCenter.y + startRadius, startAngle,
180);
} else {
mDragPath.addArc(new RectF(mRowBadgeCenter.x - startRadius, mRowBadgeCenter.y - startRadius,
mRowBadgeCenter.x + startRadius, mRowBadgeCenter.y + startRadius), startAngle, 180);
}
canvas.drawPath(mDragPath, mBadgeBackgroundBorderPaint);
}
}
private void drawBadge(Canvas canvas, PointF center, float radius) {
if (center.x == -1000 && center.y == -1000) {
return;
}
if (mBadgeText.isEmpty() || mBadgeText.length() == 1) {
mBadgeBackgroundRect.left = center.x - (int) radius;
mBadgeBackgroundRect.top = center.y - (int) radius;
mBadgeBackgroundRect.right = center.x + (int) radius;
mBadgeBackgroundRect.bottom = center.y + (int) radius;
if (mDrawableBackground != null) {
drawBadgeBackground(canvas);
} else {
canvas.drawCircle(center.x, center.y, radius, mBadgeBackgroundPaint);
if (mColorBackgroundBorder != 0 && mBackgroundBorderWidth > 0) {
canvas.drawCircle(center.x, center.y, radius, mBadgeBackgroundBorderPaint);
}
}
} else {
mBadgeBackgroundRect.left = center.x - (mBadgeTextRect.width() / 2f + mBadgePadding);
mBadgeBackgroundRect.top = center.y - (mBadgeTextRect.height() / 2f + mBadgePadding * 0.5f);
mBadgeBackgroundRect.right = center.x + (mBadgeTextRect.width() / 2f + mBadgePadding);
mBadgeBackgroundRect.bottom = center.y + (mBadgeTextRect.height() / 2f + mBadgePadding * 0.5f);
radius = mBadgeBackgroundRect.height() / 2f;
if (mDrawableBackground != null) {
drawBadgeBackground(canvas);
} else {
canvas.drawRoundRect(mBadgeBackgroundRect, radius, radius, mBadgeBackgroundPaint);
if (mColorBackgroundBorder != 0 && mBackgroundBorderWidth > 0) {
canvas.drawRoundRect(mBadgeBackgroundRect, radius, radius, mBadgeBackgroundBorderPaint);
}
}
}
if (!mBadgeText.isEmpty()) {
canvas.drawText(mBadgeText, center.x,
(mBadgeBackgroundRect.bottom + mBadgeBackgroundRect.top
- mBadgeTextFontMetrics.bottom - mBadgeTextFontMetrics.top) / 2f,
mBadgeTextPaint);
}
}
private void drawBadgeBackground(Canvas canvas) {
mBadgeBackgroundPaint.setShadowLayer(0, 0, 0, 0);
int left = (int) mBadgeBackgroundRect.left;
int top = (int) mBadgeBackgroundRect.top;
int right = (int) mBadgeBackgroundRect.right;
int bottom = (int) mBadgeBackgroundRect.bottom;
if (mDrawableBackgroundClip) {
right = left + mBitmapClip.getWidth();
bottom = top + mBitmapClip.getHeight();
canvas.saveLayer(left, top, right, bottom, null, Canvas.ALL_SAVE_FLAG);
}
mDrawableBackground.setBounds(left, top, right, bottom);
mDrawableBackground.draw(canvas);
if (mDrawableBackgroundClip) {
mBadgeBackgroundPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
canvas.drawBitmap(mBitmapClip, left, top, mBadgeBackgroundPaint);
canvas.restore();
mBadgeBackgroundPaint.setXfermode(null);
if (mBadgeText.isEmpty() || mBadgeText.length() == 1) {
canvas.drawCircle(mBadgeBackgroundRect.centerX(), mBadgeBackgroundRect.centerY(),
mBadgeBackgroundRect.width() / 2f, mBadgeBackgroundBorderPaint);
} else {
canvas.drawRoundRect(mBadgeBackgroundRect,
mBadgeBackgroundRect.height() / 2, mBadgeBackgroundRect.height() / 2,
mBadgeBackgroundBorderPaint);
}
} else {
canvas.drawRect(mBadgeBackgroundRect, mBadgeBackgroundBorderPaint);
}
}
private void createClipLayer() {
if (mBadgeText == null) {
return;
}
if (!mDrawableBackgroundClip) {
return;
}
if (mBitmapClip != null && !mBitmapClip.isRecycled()) {
mBitmapClip.recycle();
}
float radius = getBadgeCircleRadius();
if (mBadgeText.isEmpty() || mBadgeText.length() == 1) {
mBitmapClip = Bitmap.createBitmap((int) radius * 2, (int) radius * 2,
Bitmap.Config.ARGB_4444);
Canvas srcCanvas = new Canvas(mBitmapClip);
srcCanvas.drawCircle(srcCanvas.getWidth() / 2f, srcCanvas.getHeight() / 2f,
srcCanvas.getWidth() / 2f, mBadgeBackgroundPaint);
} else {
mBitmapClip = Bitmap.createBitmap((int) (mBadgeTextRect.width() + mBadgePadding * 2),
(int) (mBadgeTextRect.height() + mBadgePadding), Bitmap.Config.ARGB_4444);
Canvas srcCanvas = new Canvas(mBitmapClip);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
srcCanvas.drawRoundRect(0, 0, srcCanvas.getWidth(), srcCanvas.getHeight(), srcCanvas.getHeight() / 2f,
srcCanvas.getHeight() / 2f, mBadgeBackgroundPaint);
} else {
srcCanvas.drawRoundRect(new RectF(0, 0, srcCanvas.getWidth(), srcCanvas.getHeight()),
srcCanvas.getHeight() / 2f, srcCanvas.getHeight() / 2f, mBadgeBackgroundPaint);
}
}
}
private float getBadgeCircleRadius() {
if (mBadgeText.isEmpty()) {
return mBadgePadding;
} else if (mBadgeText.length() == 1) {
return mBadgeTextRect.height() > mBadgeTextRect.width() ?
mBadgeTextRect.height() / 2f + mBadgePadding * 0.5f :
mBadgeTextRect.width() / 2f + mBadgePadding * 0.5f;
} else {
return mBadgeBackgroundRect.height() / 2f;
}
}
private void findBadgeCenter() {
float rectWidth = mBadgeTextRect.height() > mBadgeTextRect.width() ?
mBadgeTextRect.height() : mBadgeTextRect.width();
switch (mBadgeGravity) {
case Gravity.START | Gravity.TOP:
mBadgeCenter.x = mGravityOffsetX + mBadgePadding + rectWidth / 2f;
mBadgeCenter.y = mGravityOffsetY + mBadgePadding + mBadgeTextRect.height() / 2f;
break;
case Gravity.START | Gravity.BOTTOM:
mBadgeCenter.x = mGravityOffsetX + mBadgePadding + rectWidth / 2f;
mBadgeCenter.y = mHeight - (mGravityOffsetY + mBadgePadding + mBadgeTextRect.height() / 2f);
break;
case Gravity.END | Gravity.TOP:
mBadgeCenter.x = mWidth - (mGravityOffsetX + mBadgePadding + rectWidth / 2f);
mBadgeCenter.y = mGravityOffsetY + mBadgePadding + mBadgeTextRect.height() / 2f;
break;
case Gravity.END | Gravity.BOTTOM:
mBadgeCenter.x = mWidth - (mGravityOffsetX + mBadgePadding + rectWidth / 2f);
mBadgeCenter.y = mHeight - (mGravityOffsetY + mBadgePadding + mBadgeTextRect.height() / 2f);
break;
case Gravity.CENTER:
mBadgeCenter.x = mWidth / 2f;
mBadgeCenter.y = mHeight / 2f;
break;
case Gravity.CENTER | Gravity.TOP:
mBadgeCenter.x = mWidth / 2f;
mBadgeCenter.y = mGravityOffsetY + mBadgePadding + mBadgeTextRect.height() / 2f;
break;
case Gravity.CENTER | Gravity.BOTTOM:
mBadgeCenter.x = mWidth / 2f;
mBadgeCenter.y = mHeight - (mGravityOffsetY + mBadgePadding + mBadgeTextRect.height() / 2f);
break;
case Gravity.CENTER | Gravity.START:
mBadgeCenter.x = mGravityOffsetX + mBadgePadding + rectWidth / 2f;
mBadgeCenter.y = mHeight / 2f;
break;
case Gravity.CENTER | Gravity.END:
mBadgeCenter.x = mWidth - (mGravityOffsetX + mBadgePadding + rectWidth / 2f);
mBadgeCenter.y = mHeight / 2f;
break;
}
initRowBadgeCenter();
}
private void measureText() {
mBadgeTextRect.left = 0;
mBadgeTextRect.top = 0;
if (TextUtils.isEmpty(mBadgeText)) {
mBadgeTextRect.right = 0;
mBadgeTextRect.bottom = 0;
} else {
mBadgeTextPaint.setTextSize(mBadgeTextSize);
mBadgeTextRect.right = mBadgeTextPaint.measureText(mBadgeText);
mBadgeTextFontMetrics = mBadgeTextPaint.getFontMetrics();
mBadgeTextRect.bottom = mBadgeTextFontMetrics.descent - mBadgeTextFontMetrics.ascent;
}
createClipLayer();
}
private void initRowBadgeCenter() {
int[] screenPoint = new int[2];
getLocationOnScreen(screenPoint);
mRowBadgeCenter.x = mBadgeCenter.x + screenPoint[0];
mRowBadgeCenter.y = mBadgeCenter.y + screenPoint[1];
}
protected void animateHide(PointF center) {
if (mBadgeText == null) {
return;
}
if (mAnimator == null || !mAnimator.isRunning()) {
screenFromWindow(true);
mAnimator = new BadgeAnimator(createBadgeBitmap(), center, this);
mAnimator.start();
setBadgeNumber(0);
}
}
public void reset() {
mDragCenter.x = -1000;
mDragCenter.y = -1000;
mDragQuadrant = 4;
screenFromWindow(false);
getParent().requestDisallowInterceptTouchEvent(false);
invalidate();
}
@Override
public void hide(boolean animate) {
if (animate && mActivityRoot != null) {
initRowBadgeCenter();
animateHide(mRowBadgeCenter);
} else {
setBadgeNumber(0);
}
}
/**
* @param badgeNumber equal to zero badge will be hidden, less than zero show dot
*/
@Override
public Badge setBadgeNumber(int badgeNumber) {
mBadgeNumber = badgeNumber;
if (mBadgeNumber < 0) {
mBadgeText = "";
} else if (mBadgeNumber > 99) {
mBadgeText = mExact ? String.valueOf(mBadgeNumber) : "99+";
} else if (mBadgeNumber > 0 && mBadgeNumber <= 99) {
mBadgeText = String.valueOf(mBadgeNumber);
} else if (mBadgeNumber == 0) {
mBadgeText = null;
}
measureText();
invalidate();
return this;
}
@Override
public int getBadgeNumber() {
return mBadgeNumber;
}
@Override
public Badge setBadgeText(String badgeText) {
mBadgeText = badgeText;
mBadgeNumber = 1;
measureText();
invalidate();
return this;
}
@Override
public String getBadgeText() {
return mBadgeText;
}
@Override
public Badge setExactMode(boolean isExact) {
mExact = isExact;
if (mBadgeNumber > 99) {
setBadgeNumber(mBadgeNumber);
}
return this;
}
@Override
public boolean isExactMode() {
return mExact;
}
@Override
public Badge setShowShadow(boolean showShadow) {
mShowShadow = showShadow;
invalidate();
return this;
}
@Override
public boolean isShowShadow() {
return mShowShadow;
}
@Override
public Badge setBadgeBackgroundColor(int color) {
mColorBackground = color;
if (mColorBackground == Color.TRANSPARENT) {
mBadgeTextPaint.setXfermode(null);
} else {
mBadgeTextPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
}
invalidate();
return this;
}
@Override
public Badge stroke(int color, float width, boolean isDpValue) {
mColorBackgroundBorder = color;
mBackgroundBorderWidth = isDpValue ? DisplayUtil.dp2px(getContext(), width) : width;
invalidate();
return this;
}
@Override
public int getBadgeBackgroundColor() {
return mColorBackground;
}
@Override
public Badge setBadgeBackground(Drawable drawable) {
return setBadgeBackground(drawable, false);
}
@Override
public Badge setBadgeBackground(Drawable drawable, boolean clip) {
mDrawableBackgroundClip = clip;
mDrawableBackground = drawable;
createClipLayer();
invalidate();
return this;
}
@Override
public Drawable getBadgeBackground() {
return mDrawableBackground;
}
@Override
public Badge setBadgeTextColor(int color) {
mColorBadgeText = color;
invalidate();
return this;
}
@Override
public int getBadgeTextColor() {
return mColorBadgeText;
}
@Override
public Badge setBadgeTextSize(float size, boolean isSpValue) {
mBadgeTextSize = isSpValue ? DisplayUtil.dp2px(getContext(), size) : size;
measureText();
invalidate();
return this;
}
@Override
public float getBadgeTextSize(boolean isSpValue) {
return isSpValue ? DisplayUtil.px2dp(getContext(), mBadgeTextSize) : mBadgeTextSize;
}
@Override
public Badge setBadgePadding(float padding, boolean isDpValue) {
mBadgePadding = isDpValue ? DisplayUtil.dp2px(getContext(), padding) : padding;
createClipLayer();
invalidate();
return this;
}
@Override
public float getBadgePadding(boolean isDpValue) {
return isDpValue ? DisplayUtil.px2dp(getContext(), mBadgePadding) : mBadgePadding;
}
@Override
public boolean isDraggable() {
return mDraggable;
}
/**
* @param gravity only support Gravity.START | Gravity.TOP , Gravity.END | Gravity.TOP ,
* Gravity.START | Gravity.BOTTOM , Gravity.END | Gravity.BOTTOM ,
* Gravity.CENTER , Gravity.CENTER | Gravity.TOP , Gravity.CENTER | Gravity.BOTTOM ,
* Gravity.CENTER | Gravity.START , Gravity.CENTER | Gravity.END
*/
@Override
public Badge setBadgeGravity(int gravity) {
if (gravity == (Gravity.START | Gravity.TOP) ||
gravity == (Gravity.END | Gravity.TOP) ||
gravity == (Gravity.START | Gravity.BOTTOM) ||
gravity == (Gravity.END | Gravity.BOTTOM) ||
gravity == (Gravity.CENTER) ||
gravity == (Gravity.CENTER | Gravity.TOP) ||
gravity == (Gravity.CENTER | Gravity.BOTTOM) ||
gravity == (Gravity.CENTER | Gravity.START) ||
gravity == (Gravity.CENTER | Gravity.END)) {
mBadgeGravity = gravity;
invalidate();
} else {
throw new IllegalStateException("only support Gravity.START | Gravity.TOP , Gravity.END | Gravity.TOP , " +
"Gravity.START | Gravity.BOTTOM , Gravity.END | Gravity.BOTTOM , Gravity.CENTER" +
" , Gravity.CENTER | Gravity.TOP , Gravity.CENTER | Gravity.BOTTOM ," +
"Gravity.CENTER | Gravity.START , Gravity.CENTER | Gravity.END");
}
return this;
}
@Override
public int getBadgeGravity() {
return mBadgeGravity;
}
@Override
public Badge setGravityOffset(float offset, boolean isDpValue) {
return setGravityOffset(offset, offset, isDpValue);
}
@Override
public Badge setGravityOffset(float offsetX, float offsetY, boolean isDpValue) {
mGravityOffsetX = isDpValue ? DisplayUtil.dp2px(getContext(), offsetX) : offsetX;
mGravityOffsetY = isDpValue ? DisplayUtil.dp2px(getContext(), offsetY) : offsetY;
invalidate();
return this;
}
@Override
public float getGravityOffsetX(boolean isDpValue) {
return isDpValue ? DisplayUtil.px2dp(getContext(), mGravityOffsetX) : mGravityOffsetX;
}
@Override
public float getGravityOffsetY(boolean isDpValue) {
return isDpValue ? DisplayUtil.px2dp(getContext(), mGravityOffsetY) : mGravityOffsetY;
}
private void updataListener(int state) {
if (mDragStateChangedListener != null)
mDragStateChangedListener.onDragStateChanged(state, this, mTargetView);
}
@Override
public Badge setOnDragStateChangedListener(OnDragStateChangedListener l) {
mDraggable = l != null;
mDragStateChangedListener = l;
return this;
}
@Override
public PointF getDragCenter() {
if (mDraggable && mDragging) return mDragCenter;
return null;
}
private class BadgeContainer extends ViewGroup {
@Override
protected void dispatchRestoreInstanceState(SparseArray container) {
if(!(getParent() instanceof RelativeLayout)){
super.dispatchRestoreInstanceState(container);
}
}
public BadgeContainer(Context context) {
super(context);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
child.layout(0, 0, child.getMeasuredWidth(), child.getMeasuredHeight());
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
View targetView = null, badgeView = null;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
if (!(child instanceof QBadgeView)) {
targetView = child;
} else {
badgeView = child;
}
}
if (targetView == null) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
} else {
targetView.measure(widthMeasureSpec, heightMeasureSpec);
if (badgeView != null) {
badgeView.measure(MeasureSpec.makeMeasureSpec(targetView.getMeasuredWidth(), MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(targetView.getMeasuredHeight(), MeasureSpec.EXACTLY));
}
setMeasuredDimension(targetView.getMeasuredWidth(), targetView.getMeasuredHeight());
}
}
}
}
9.模拟数据类:
import java.util.ArrayList;
import java.util.List;
/**
* 模拟数据类
*/
public class DataSupport {
private List mDatas;
public DataSupport() {
mDatas = new ArrayList<>();
mDatas.add("(`・ω・´)ฅ铲屎官快来");
mDatas.add("ฅ(´-ω-`)ฅ好酥胡");
mDatas.add("~(=^・ω・^)ฅ☆ 澡桑嚎啊小婊贝");
mDatas.add("(=ฅರ﹏ರ)ฅ我要小鱼干");
mDatas.add("(ฅ‵皿′)ฅ︵┻┻ 为什么不跟我玩儿");
mDatas.add("ヽ(ฅ≧へ≦)ฅ饭不好吃");
mDatas.add("(╬ ̄皿 ̄)ฅ拒绝洗澡");
mDatas.add("ฅ(= ̄. ̄||)ฅ愚蠢的人类");
mDatas.add("☆*:.。. ฅ(≧▽≦)ฅ .。.:*☆ ");
mDatas.add("ヾ((๑˘ㅂ˘๑)ฅ^._.^ฅ我萌吗");
mDatas.add("༻ི(ؔᵒ̶̷ᵕؔᵒ̷̶)༄୭*ˈ 浪到飞起");
mDatas.add("*:ஐ٩(๑´ᵕ`)۶ஐ:* 透心凉心飞扬");
mDatas.add("˚₊*୧⃛(๑⃙⃘⁼̴̀꒳⁼̴́๑⃙⃘)୨⃛*₊˚⋆ 精神百倍");
mDatas.add("٩(ꇐ‿ꇐ)۶世界那么大我想去看看");
mDatas.add("๛꜀꒰ ˟⌂˟꜀ ꜆꒱꜆ 起来,不愿做作业的人们");
mDatas.add("˚‧·(´ฅ・ェ・ฅ‘)‧º.喵,救命啊,人家怕水");
mDatas.add("(^,,ԾェԾ,,^)这款裙子是为本宝宝定制的吗");
mDatas.add("ლ(ಠェಠ)و美图软件用的好,老公才好找");
mDatas.add("(҂`・ェ・´) <,︻╦̵̵̿╤─ ҉ - -- 让你秀恩爱!");
mDatas.add("为你加油!!!!!!\n" +
" ☆ * . ☆\n" +
" . ∧_∧ ∩ * ☆\n" +
"* ☆ ( ・∀・)/ .\n" +
" . ⊂ ノ* ☆\n" +
"☆ * (つ ノ .☆\n" +
" (ノ");
mDatas.add("老\n" +
" 板\n" +
" 说\n" +
" 今\n" +
" 天\n" +
" 放\n" +
" 假\n" +
" !\n" +
" ヽ\ //\n" +
" ∧∧ 。\n" +
" ゚ (゚∀゚)っ ゚\n" +
" (っノ\n" +
" `J");
mDatas.add("巴拉巴拉巴拉,把你变成猪!\n" +
" ∧_∧\n" +
" (。・ω・。)つ━☆・*。\n" +
" ⊂ ノ ・゜+.\n" +
" しーJ °。+ *´¨)\n" +
" .· ´¸.·*´¨) ¸.·*¨)\n" +
" (¸.·´ (¸.·’*");
}
public List getData() {
return mDatas;
}
}
四.实现QQ或微信聊天列表的消息提醒的话可以往下看:
1.ListViewActivity.java:
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import com.example.m1571.myapplication.badgeview.Badge;
import com.example.m1571.myapplication.badgeview.QBadgeView;
import java.util.List;
//实现QQ微信聊天列表信息的消息数量提示
public class ListViewActivity extends AppCompatActivity {
ListView listview;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_list_view);
listview = (ListView) findViewById(R.id.listview);
listview.setAdapter(new ListAdapter());
}
class ListAdapter extends BaseAdapter {
private List data;
public ListAdapter() {
data = new DataSupport().getData();
}
@Override
public int getCount() {
return data.size();
}
@Override
public Object getItem(int position) {
return data.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
Holder holder;
if (convertView == null) {
holder = new Holder();
convertView = LayoutInflater.from(ListViewActivity.this).inflate(R.layout.item_view, parent, false);
holder.textView = (TextView) convertView.findViewById(R.id.tv_content);
holder.badge = new QBadgeView(ListViewActivity.this).bindTarget(convertView.findViewById(R.id.imageview));
holder.badge.setBadgeTextSize(12, true);
convertView.setTag(holder);
} else {
holder = (Holder) convertView.getTag();
}
holder.textView.setText(data.get(position));
holder.badge.setBadgeNumber(position);
holder.badge.setOnDragStateChangedListener(new Badge.OnDragStateChangedListener() {
@Override
public void onDragStateChanged(int dragState, Badge badge, View targetView) {
if (dragState == STATE_SUCCEED) {
Toast.makeText(ListViewActivity.this, String.valueOf(position), Toast.LENGTH_SHORT).show();
}
}
});
return convertView;
}
class Holder {
TextView textView;
Badge badge;
}
}
}
2.布局activity_list_view.xml:
3.适配器布局item_view.xml
4.使用RecyclerView实现QQ或微信聊天列表消息数量显示:
RecyclerViewActivity.java:
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
import com.example.m1571.myapplication.badgeview.Badge;
import com.example.m1571.myapplication.badgeview.QBadgeView;
import java.util.List;
public class RecyclerViewActivity extends AppCompatActivity {
RecyclerView recyclerView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_recycler_view);
recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(new RecyclerAdapter());
}
class RecyclerAdapter extends RecyclerView.Adapter {
private List data;
public RecyclerAdapter() {
data = new DataSupport().getData();
}
@Override
public Holder onCreateViewHolder(ViewGroup parent, int viewType) {
return new Holder(LayoutInflater.from(RecyclerViewActivity.this).inflate(R.layout.item_view, parent, false));
}
@Override
public void onBindViewHolder(Holder holder, int position) {
holder.textView.setText(data.get(position));
holder.badge.setBadgeNumber(position);
}
@Override
public int getItemCount() {
return data.size();
}
class Holder extends RecyclerView.ViewHolder {
TextView textView;
Badge badge;
public Holder(View itemView) {
super(itemView);
textView = (TextView) itemView.findViewById(R.id.tv_content);
badge = new QBadgeView(RecyclerViewActivity.this).bindTarget(itemView.findViewById(R.id.root));
badge.setBadgeGravity(Gravity.CENTER | Gravity.END);
badge.setBadgeTextSize(14, true);
badge.setBadgePadding(6, true);
badge.setOnDragStateChangedListener(new Badge.OnDragStateChangedListener() {
@Override
public void onDragStateChanged(int dragState, Badge badge, View targetView) {
if (dragState == STATE_SUCCEED) {
Toast.makeText(RecyclerViewActivity.this, String.valueOf(getAdapterPosition()), Toast.LENGTH_SHORT).show();
}
}
});
}
}
}
}