Android仿QQ微信未讀消息小紅點(diǎn)BadgeHelper
Android 小紅點(diǎn) 未讀消息功能 BadgeHelper
因?yàn)樽罱捻?xiàng)目需求,翻遍github上的未讀消息紅點(diǎn)開源庫, 發(fā)現(xiàn)大部分
不能適配不同情況的布局, 所以我寫了一個能兼容全部的 !
網(wǎng)上的寫法是 繼承TextView然后生成一個小紅點(diǎn)drawable,設(shè)置到背景中去, 然后把目標(biāo)view外層加一層FrameLayout,然后把小紅點(diǎn)添加進(jìn)去
但這樣做的問題來了, 小紅點(diǎn)與目標(biāo)View 會疊起來!, 擋住文字,!!! 看得我瞎了~~~ 而且 他們提供的setOffsetX setpadding 之類的沒卵用,你如果想要偏移小紅點(diǎn)讓它不與下面的View重疊,那是不可能的
所以我的寫法是 為了更好的性能,直接繼承View然后畫小紅點(diǎn)背景, 然后把目標(biāo)view外層加一層LinearLayout 讓小紅點(diǎn)View放目標(biāo)的右邊,這樣就不會重疊
同時 我也支持設(shè)置 重疊模式和非重疊模式
這是效果圖

由于github賬號出問題了,沒法上傳, 所以寫到博客上算了
這只是一個小工具類 供大家學(xué)習(xí)下我的思路, 就不多說了, 具體實(shí)現(xiàn)注釋里寫的很清楚,不太清楚的可以回復(fù)問我
#核心類:
##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;
/**
* 作者:東芝(2018/8/23).
* 支持 重疊目標(biāo)模式 和 放目標(biāo)右上角但不重疊的模式 兩種模式 通過setOverlap 設(shè)置, 默認(rèn)為false=不重疊
*/
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; //默認(rèn)的小紅點(diǎn)顏色
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);
//計(jì)算小紅點(diǎn)無文本情況下的小紅點(diǎn)大小, 按屏幕像素計(jì)算, 如果你有你自己認(rèn)為更好的算法, 改這里即可
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);//文本大小按屏幕像素 計(jì)算, 沒寫死是為了適配各種屏幕, 但如果你有你認(rèn)為更合理的計(jì)算方式 你可以改這里
} else {
mTextPaint.setTextSize(textSize);//使用自定義大小
}
//計(jì)算小紅點(diǎn)有文本情況下的小紅點(diǎn)大小, 按文本寬高計(jì)算, 如果你有你自己認(rèn)為更好的算法, 改這里即可
float textWidth = getTextWidth("99", mTextPaint);
w = h = Math.round(textWidth * 1.4f);//讓背景比文本大一點(diǎn)
break;
}
}
/**
* 設(shè)置Margin 可用于做偏移
* @param left
* @param top
* @param right
* @param bottom
* @return
*/
public BadgeHelper setBadgeMargins(int left, int top, int right, int bottom) {
leftMargin = left;
topMargin = top;
rightMargin = right;
bottomMargin = bottom;
return this;
}
/**
* 設(shè)置Gravity居中
* @return
*/
public BadgeHelper setBadgeCenterVertical( ) {
isCenterVertical = true;
return this;
}
/**
* 設(shè)置小紅點(diǎn)類型
*
* @param type
* @return
*/
public BadgeHelper setBadgeType(@Type int type) {
this.type = type;
return this;
}
/**
* 設(shè)置小紅點(diǎn)大小, 默認(rèn)自動適配
*
* @param textSize
* @return
*/
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;
}
/**
* 設(shè)置小紅點(diǎn)文字顏色, 默認(rèn)白
*
* @param textColor
* @return
*/
public BadgeHelper setBadgeTextColor(int textColor) {
this.textColor = textColor;
return this;
}
/**
* 設(shè)置重疊模式, 默認(rèn)是false(不重疊)
*
* @param isOverlap 是否把小紅點(diǎn)重疊到目標(biāo)View之上
* @return
*/
public BadgeHelper setBadgeOverlap(boolean isOverlap) {
return setBadgeOverlap(isOverlap, false);
}
/**
* 設(shè)置重疊模式, 默認(rèn)是false(不重疊)
*
* @param isOverlap 是否把小紅點(diǎn)重疊到目標(biāo)View之上
* @param isIgnoreTargetPadding 是否忽略目標(biāo)View的padding
* @return
*/
public BadgeHelper setBadgeOverlap(boolean isOverlap, boolean isIgnoreTargetPadding) {
this.isOverlap = isOverlap;
this.mIgnoreTargetPadding = isIgnoreTargetPadding;
if (!isOverlap && isIgnoreTargetPadding) {
Log.w(TAG, "警告:只有重疊模式isOverlap=true 設(shè)置mIgnoreTargetPadding才有意義");
}
return this;
}
/**
* 設(shè)置小紅點(diǎn)顏色
*
* @param mBadgeColor
* @return
*/
public BadgeHelper setBadgeColor(int mBadgeColor) {
this.badgeColor = mBadgeColor;
return this;
}
/**
* 設(shè)置小紅點(diǎn)大小
*
* @param w
* @param h
* @return
*/
public BadgeHelper setBadgeSize(int w, int h) {
this.w = w;
this.h = h;
return this;
}
/**
* 是否顯示
* @param enable
*/
public void setBadgeEnable(boolean enable) {
setVisibility(enable?VISIBLE:INVISIBLE);
}
/**
* 設(shè)置小紅點(diǎn)的文字
*
* @param number
*/
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);
}
}
/**
* 綁定小紅點(diǎn)到目標(biāo)View的右上角
*
* @param target
*/
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) {//[小紅點(diǎn)與目標(biāo)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 {//[小紅點(diǎn)放右側(cè)]模式
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("目標(biāo)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("如果你自定義了小紅點(diǎn)的寬高,就不能設(shè)置其寬高小于0 ,否則請不要設(shè)置!");
}
}
@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)//重疊模式+忽略目標(biāo)ViewPadding 效果, 什么意思? 你可以把第二個參數(shù)寫成false看看會發(fā)生什么
.bindToTargetView(mVTypeB);
//情況C(默認(rèn)情況)
BadgeHelper badgeHelperC = new BadgeHelper(this)
.setBadgeType(BadgeHelper.Type.TYPE_TEXT)
.setBadgeOverlap(false);
badgeHelperC.bindToTargetView(mVTypeC);
badgeHelperC.setBadgeNumber(6);//數(shù)字模式
//情況D(有權(quán)重時)
new BadgeHelper(this)
.setBadgeColor(0xff0f00ff)//順便演示下 小紅點(diǎn)顏色的設(shè)置
.setBadgeType(BadgeHelper.Type.TYPE_POINT)
.setBadgeOverlap(false)
.bindToTargetView(mVTypeD);
//情況E(小紅點(diǎn)與圖片重疊)+ 數(shù)字模式
BadgeHelper badgeHelperE = new BadgeHelper(this)
.setBadgeType(BadgeHelper.Type.TYPE_TEXT)
.setBadgeOverlap(true, true);//注意 isIgnoreTargetPadding=true時 和 setBadgeMargins 不能同時使用
//.setBadgeSize(100,100)//設(shè)置小紅點(diǎn)的大小, 如果未設(shè)置則使用默認(rèn)寬高, 一般默認(rèn)就好
//.setBadgeTextSize(15) //設(shè)置文本大小, 不設(shè)置 則使用默認(rèn), 一般默認(rèn)就好
badgeHelperE.bindToTargetView(mVTypeE);
//任意地方可以刷新數(shù)字
badgeHelperE.setBadgeNumber(58);//數(shù)字
//情況F(TabLayout兼容)
BadgeHelper badgeHelperF = new BadgeHelper(this)
.setBadgeType(BadgeHelper.Type.TYPE_TEXT)
.setBadgeCenterVertical()//紅點(diǎn)居中
.setBadgeMargins(10,0,0,0)//偏移一下
.setBadgeOverlap(false);
badgeHelperF.bindToTargetView(mTabLayout, 0);
badgeHelperF.setBadgeNumber(5);
new BadgeHelper(this)
.setBadgeType(BadgeHelper.Type.TYPE_POINT)
.setBadgeCenterVertical()//紅點(diǎn)居中
.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(默認(rèn)情況)" />
<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(有權(quán)重時)" />
<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: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="設(shè)置" />
</android.support.design.widget.TabLayout>
</LinearLayout>
到此這篇關(guān)于Android仿QQ微信未讀消息小紅點(diǎn)BadgeHelper的文章就介紹到這了,更多相關(guān)Android小紅點(diǎn)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android 退出應(yīng)用程序的實(shí)現(xiàn)方法
這篇文章主要介紹了Android 退出應(yīng)用程序的實(shí)現(xiàn)方法的相關(guān)資料,需要的朋友可以參考下2017-04-04
解決android studio中使用monitor工具無法打開data文件夾問題
這篇文章主要介紹了解決android studio中使用monitor工具無法打開data文件夾問題,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-04-04
Android打空包后提示沒有"android:exported"的屬性設(shè)置問題解決
這篇文章主要介紹了Android打空包后提示沒有"android:exported"的屬性設(shè)置問題的解決方法,文中通過圖文將解決的辦法介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2023-02-02
Android應(yīng)用的Material設(shè)計(jì)的布局兼容性的一些要點(diǎn)總結(jié)
這篇文章主要介紹了Android應(yīng)用的Material設(shè)計(jì)的布局兼容性的一些要點(diǎn)總結(jié),文中還給了一個RecyclerView布局管理的例子,需要的朋友可以參考下2016-04-04
詳解Android數(shù)據(jù)存儲之SQLCipher數(shù)據(jù)庫加密
對于已經(jīng)ROOT的手機(jī)來說的沒有任何安全性可以,一旦被利用將會導(dǎo)致數(shù)據(jù)庫數(shù)據(jù)的泄漏,本篇文章主要介紹了Android數(shù)據(jù)存儲之SQLCipher數(shù)據(jù)庫加密,具有一定的參考價值,有需要的可以了解一下。2016-12-12

