Android仿QQ微信未读消息小红点BadgeHelper
Android 小红点 未读消息功能 BadgeHelper
因为最近的项目需求,翻遍github上的未读消息红点开源库, 发现大部分
不能适配不同情况的布局, 所以我写了一个能兼容全部的 !
网上的写法是 继承TextView然后生成一个小红点drawable,设置到背景中去, 然后把目标view外层加一层FrameLayout,然后把小红点添加进去
但这样做的问题来了, 小红点与目标View 会叠起来!, 挡住文字,!!! 看得我瞎了~~~ 而且 他们提供的setOffsetX setpadding 之类的没卵用,你如果想要偏移小红点让它不与下面的View重叠,那是不可能的
所以我的写法是 为了更好的性能,直接继承View然后画小红点背景, 然后把目标view外层加一层LinearLayout 让小红点View放目标的右边,这样就不会重叠
同时 我也支持设置 重叠模式和非重叠模式
这是效果图
由于github账号出问题了,没法上传, 所以写到博客上算了
这只是一个小工具类 供大家学习下我的思路, 就不多说了, 具体实现注释里写的很清楚,不太清楚的可以回复问我
#核心类:
##BadgeHelper .java
package com.truescend.gofit.views;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.support.annotation.IntDef;
import android.support.design.widget.TabLayout;
import android.util.Log;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Field;
public class BadgeHelper extends View {
private static final String TAG = "BadgeHelper";
private float density;
private Paint mTextPaint;
private Paint mBackgroundPaint;
private String text = "0";
private int number;
@Type
private int type = Type.TYPE_POINT;
private boolean isOverlap;
private final RectF rect = new RectF();
private int badgeColor = 0xFFD3321B; //默认的小红点颜色
private int textColor = 0xFFFFFFff;
private float textSize;
private int w;
private int h;
private boolean isSetup;
private boolean mIgnoreTargetPadding;
private boolean isCenterVertical;
private int leftMargin;
private int topMargin;
private int rightMargin;
private int bottomMargin;
@IntDef({Type.TYPE_POINT, Type.TYPE_TEXT})
@Retention(RetentionPolicy.SOURCE)
public @interface Type {
int TYPE_POINT = 0;
int TYPE_TEXT = 1;
}
public BadgeHelper(Context context) {
super(context);
}
private void init(@Type int type, boolean isOverlap) {
this.type = type;
this.isOverlap = isOverlap;
density = getResources().getDisplayMetrics().density;
switch (type) {
case Type.TYPE_POINT:
mBackgroundPaint = new Paint();
mBackgroundPaint.setStyle(Paint.Style.FILL);
mBackgroundPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
mBackgroundPaint.setColor(badgeColor);
//计算小红点无文本情况下的小红点大小, 按屏幕像素计算, 如果你有你自己认为更好的算法, 改这里即可
w = h = Math.round(density * 7f);
break;
case Type.TYPE_TEXT:
mBackgroundPaint = new Paint();
mBackgroundPaint.setStyle(Paint.Style.FILL);
mBackgroundPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
mBackgroundPaint.setColor(badgeColor);
mTextPaint = new Paint();
mTextPaint.setStyle(Paint.Style.FILL);
mTextPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
mTextPaint.setColor(textColor);//文本颜色
if (textSize == 0) {
mTextPaint.setTextSize(density * 10);//文本大小按屏幕像素 计算, 没写死是为了适配各种屏幕, 但如果你有你认为更合理的计算方式 你可以改这里
} else {
mTextPaint.setTextSize(textSize);//使用自定义大小
}
//计算小红点有文本情况下的小红点大小, 按文本宽高计算, 如果你有你自己认为更好的算法, 改这里即可
float textWidth = getTextWidth("99", mTextPaint);
w = h = Math.round(textWidth * 1.4f);//让背景比文本大一点
break;
}
}
public BadgeHelper setBadgeMargins(int left, int top, int right, int bottom) {
leftMargin = left;
topMargin = top;
rightMargin = right;
bottomMargin = bottom;
return this;
}
public BadgeHelper setBadgeCenterVertical( ) {
isCenterVertical = true;
return this;
}
public BadgeHelper setBadgeType(@Type int type) {
this.type = type;
return this;
}
public BadgeHelper setBadgeTextSize(int textSize) {
Context c = getContext();
Resources r;
if (c == null) {
r = Resources.getSystem();
} else {
r = c.getResources();
}
this.textSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, textSize, r.getDisplayMetrics());
return this;
}
public BadgeHelper setBadgeTextColor(int textColor) {
this.textColor = textColor;
return this;
}
public BadgeHelper setBadgeOverlap(boolean isOverlap) {
return setBadgeOverlap(isOverlap, false);
}
public BadgeHelper setBadgeOverlap(boolean isOverlap, boolean isIgnoreTargetPadding) {
this.isOverlap = isOverlap;
this.mIgnoreTargetPadding = isIgnoreTargetPadding;
if (!isOverlap && isIgnoreTargetPadding) {
Log.w(TAG, "警告:只有重叠模式isOverlap=true 设置mIgnoreTargetPadding才有意义");
}
return this;
}
public BadgeHelper setBadgeColor(int mBadgeColor) {
this.badgeColor = mBadgeColor;
return this;
}
public BadgeHelper setBadgeSize(int w, int h) {
this.w = w;
this.h = h;
return this;
}
public void setBadgeEnable(boolean enable) {
setVisibility(enable?VISIBLE:INVISIBLE);
}
public void setBadgeNumber(int number) {
this.number = number;
this.text = String.valueOf(number);
if (isSetup) {
if(number==0){
setVisibility(INVISIBLE);
}else{
setVisibility(VISIBLE);
}
invalidate();
}
}
public void bindToTargetView(TabLayout target, int tabIndex) {
TabLayout.Tab tab = target.getTabAt(tabIndex);
View targetView = null;
View tabView = null;
try {
Field viewField = TabLayout.Tab.class.getDeclaredField("mView");
viewField.setAccessible(true);
targetView = tabView = (View) viewField.get(tab);
} catch (Exception e) {
e.printStackTrace();
}
try {
if (tabView != null) {
Field mTextViewField = tabView.getClass().getDeclaredField("mTextView");//"mIconView"
mTextViewField.setAccessible(true);
targetView = (View) mTextViewField.get(tabView);
}
} catch (Exception e) {
e.printStackTrace();
}
if (targetView != null) {
bindToTargetView(targetView);
}
}
public void bindToTargetView(View target) {
init(type, isOverlap);
if (getParent() != null) {
((ViewGroup) getParent()).removeView(this);
}
if (target == null) {
return;
}
if (target.getParent() instanceof ViewGroup) {
ViewGroup parent = (ViewGroup) target.getParent();
int groupIndex = parent.indexOfChild(target);
parent.removeView(target);
if (isOverlap) {//[小红点与目标View重叠]模式
FrameLayout badgeContainer = new FrameLayout(getContext());
ViewGroup.LayoutParams targetLayoutParams = target.getLayoutParams();
badgeContainer.setLayoutParams(targetLayoutParams);
target.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
parent.addView(badgeContainer, groupIndex, targetLayoutParams);
badgeContainer.addView(target);
badgeContainer.addView(this);
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) getLayoutParams();
if(isCenterVertical) {
layoutParams.gravity = Gravity.CENTER_VERTICAL ;
}else{
layoutParams.gravity = Gravity.END | Gravity.TOP;
}
if (mIgnoreTargetPadding) {
layoutParams.rightMargin = target.getPaddingRight() - w;
layoutParams.topMargin = target.getPaddingTop() - h / 2;
}
setLayoutParams(layoutParams);
} else {//[小红点放右侧]模式
LinearLayout badgeContainer = new LinearLayout(getContext());
badgeContainer.setOrientation(LinearLayout.HORIZONTAL);
ViewGroup.LayoutParams targetLayoutParams = target.getLayoutParams();
badgeContainer.setLayoutParams(targetLayoutParams);
target.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
parent.addView(badgeContainer, groupIndex, targetLayoutParams);
badgeContainer.addView(target);
badgeContainer.addView(this);
if(isCenterVertical) {
badgeContainer.setGravity(Gravity.CENTER_VERTICAL);
}
}
boolean hasSetMargin = leftMargin>0||topMargin>0||rightMargin>0||bottomMargin>0;
if (hasSetMargin&&getLayoutParams() instanceof ViewGroup.MarginLayoutParams) {
ViewGroup.MarginLayoutParams p = (ViewGroup.MarginLayoutParams) getLayoutParams();
p.setMargins(leftMargin, topMargin, rightMargin, bottomMargin);
setLayoutParams(p);
}
isSetup = true;
} else if (target.getParent() == null) {
throw new IllegalStateException("目标View不能没有父布局!");
}
if(number==0){
setVisibility(INVISIBLE);
}else{
setVisibility(VISIBLE);
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (w > 0 && h > 0) {
setMeasuredDimension(w, h);
} else {
throw new IllegalStateException("如果你自定义了小红点的宽高,就不能设置其宽高小于0 ,否则请不要设置!");
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//这里不用解释了 很简单 就是画一个圆形和文字
rect.left = 0;
rect.top = 0;
rect.right = getWidth();
rect.bottom = getHeight();
canvas.drawRoundRect(rect, getWidth() / 2, getWidth() / 2, mBackgroundPaint);
if (type == Type.TYPE_TEXT) {
float textWidth = getTextWidth(text, mTextPaint);
float textHeight = getTextHeight(text, mTextPaint);
canvas.drawText(text, getWidth() / 2 - textWidth / 2, getHeight() / 2 + textHeight / 2, mTextPaint);
}
}
private float getTextWidth(String text, Paint p) {
return p.measureText(text, 0, text.length());
}
private float getTextHeight(String text, Paint p) {
Rect rect = new Rect();
p.getTextBounds(text, 0, text.length(), rect);
return rect.height();
}
}
#使用示例:
public class Main2Activity extends AppCompatActivity {
private TextView mVTypeA;
private TextView mVTypeB;
private TextView mVTypeC;
private TextView mVTypeD;
private ImageView mVTypeE;
private TabLayout mTabLayout;
private void initView() {
mVTypeA = (TextView) findViewById(R.id.vTypeA);
mVTypeB = (TextView) findViewById(R.id.vTypeB);
mVTypeC = (TextView) findViewById(R.id.vTypeC);
mVTypeD = (TextView) findViewById(R.id.vTypeD);
mVTypeE = (ImageView) findViewById(R.id.vTypeE);
mTabLayout = (TabLayout) findViewById(R.id.tabLayout);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
initView();
//情况A(有margin时)
new BadgeHelper(this)
.setBadgeType(BadgeHelper.Type.TYPE_POINT)
.setBadgeOverlap(false)
.bindToTargetView(mVTypeA);
//情况B(有padding时)
new BadgeHelper(this)
.setBadgeType(BadgeHelper.Type.TYPE_POINT)
.setBadgeOverlap(true, true)//重叠模式+忽略目标ViewPadding 效果, 什么意思? 你可以把第二个参数写成false看看会发生什么
.bindToTargetView(mVTypeB);
//情况C(默认情况)
BadgeHelper badgeHelperC = new BadgeHelper(this)
.setBadgeType(BadgeHelper.Type.TYPE_TEXT)
.setBadgeOverlap(false);
badgeHelperC.bindToTargetView(mVTypeC);
badgeHelperC.setBadgeNumber(6);//数字模式
//情况D(有权重时)
new BadgeHelper(this)
.setBadgeColor(0xff0f00ff)//顺便演示下 小红点颜色的设置
.setBadgeType(BadgeHelper.Type.TYPE_POINT)
.setBadgeOverlap(false)
.bindToTargetView(mVTypeD);
//情况E(小红点与图片重叠)+ 数字模式
BadgeHelper badgeHelperE = new BadgeHelper(this)
.setBadgeType(BadgeHelper.Type.TYPE_TEXT)
.setBadgeOverlap(true, true);//注意 isIgnoreTargetPadding=true时 和 setBadgeMargins 不能同时使用
//.setBadgeSize(100,100)//设置小红点的大小, 如果未设置则使用默认宽高, 一般默认就好
//.setBadgeTextSize(15) //设置文本大小, 不设置 则使用默认, 一般默认就好
badgeHelperE.bindToTargetView(mVTypeE);
//任意地方可以刷新数字
badgeHelperE.setBadgeNumber(58);//数字
//情况F(TabLayout兼容)
BadgeHelper badgeHelperF = new BadgeHelper(this)
.setBadgeType(BadgeHelper.Type.TYPE_TEXT)
.setBadgeCenterVertical()//红点居中
.setBadgeMargins(10,0,0,0)//偏移一下
.setBadgeOverlap(false);
badgeHelperF.bindToTargetView(mTabLayout, 0);
badgeHelperF.setBadgeNumber(5);
new BadgeHelper(this)
.setBadgeType(BadgeHelper.Type.TYPE_POINT)
.setBadgeCenterVertical()//红点居中
.setBadgeMargins(10,0,0,0)//偏移一下
.setBadgeOverlap(false)
.bindToTargetView(mTabLayout,1);
}
}
#布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
tools:context="com.mx.test12.Main2Activity">
<TextView
android:background="#220000ff"
android:id="@+id/vTypeA"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="20dp"
android:text="情况A(有margin时)" />
<TextView
android:background="#2200ff00"
android:id="@+id/vTypeB"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="30dp"
android:text="情况B(有padding时)" />
<TextView
android:id="@+id/vTypeC"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="情况C(默认情况)" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:background="#2200ffff"
android:id="@+id/vTypeD"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="情况D(有权重时)" />
<View
android:layout_width="0dp"
android:layout_height="0dp" />
</LinearLayout>
<ImageView
android:id="@+id/vTypeE"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="情况E(与图片重叠)"
android:padding="20dp"
android:class="lazy" data-src="@mipmap/ic_launcher" />
<android.support.design.widget.TabLayout
android:id="@+id/tabLayout"
android:layout_width="match_parent"
android:layout_height="55dp"
app:layout_scrollFlags="scroll"
app:tabIndicatorColor="#057523"
app:tabIndicatorHeight="2.0dp"
app:tabMode="fixed"
app:tabSelectedTextColor="#057523"
app:tabTextColor="#ced0d3">
<android.support.design.widget.TabItem
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="聊天列表" />
<android.support.design.widget.TabItem
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="好友列表" />
<android.support.design.widget.TabItem
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="设置" />
</android.support.design.widget.TabLayout>
</LinearLayout>
到此这篇关于Android仿QQ微信未读消息小红点BadgeHelper的文章就介绍到这了,更多相关Android小红点内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341