Android(安卓)一个可以自由定制外观、支持拖拽消除的MaterialDesign风格Android(安卓)BadgeView
16lz
2021-01-26
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 |
- 请不要在xml中创建Badge
- Badge和TargetView绑定是采用替换TargetView的Parent方式实现的,同时将Parent的Id和TargetView的Id设置成一样来保证不会在RelativeLayout中出现位置错乱问题,所以在bindTarget后再次使用findViewById(TargetViewId)得到的会是Parent而不是TargetView,此时建议使用Badge.getTargetView方法来获取TargetView
可以参考: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.主函数布局:
<?xml version="1.0" encoding="utf-8"?>
3.相关属性:
dimens.xml:
16dp 16dp
string.xml:
My Application TextView Button
shape_round_rect.xml:
<?xml version="1.0" encoding="utf-8"?>
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:
<?xml version="1.0" encoding="utf-8"?>
3.适配器布局item_view.xml
<?xml version="1.0" encoding="utf-8"?>
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(); } } }); } } }}
更多相关文章
- Android(安卓)Handler 四个使用实例 及HandlerThread的使用
- WEEX-EEUI 页面的高度问题(页面高度设置为多少才对?)
- Android(安卓)Studio适配利器——如何设置不同机型的预览界面
- 如何为您的Android应用更新启动器图标
- Android(安卓)TextView富文本的使用
- 在Android中为啥建议你用Message.obtain()方法获取Message对象,而
- android 顶部Tab实现及原理
- android listview adapter中设置点击直接position被重用问题解决
- Android(安卓)launcher动态Icon的实现方法