请选择 进入手机版 | 继续访问电脑版

[Android] Android仿QQ微信未读消息小红点BadgeHelper

[复制链接]
查看109 | 回复7 | 2021-9-14 06:37:55 | 显示全部楼层 |阅读模式

Android 小红点 未读消息功能 BadgeHelper

由于 近来 的项目需求,翻遍github上的未读消息红点开源库, 发现大部分
不能适配不恻隐 况的布局, 以是 我写了一个能兼容全部的 !
网上的写法是 继承TextView然后天生 一个小红点drawable,设置到背景中去, 然后把目标 view外层加一层FrameLayout,然后把小红点添加进去
但如许 做的标题 来了, 小红点与目标 View 会叠起来!, 挡住笔墨 ,!!! 看得我瞎了~~~ 而且 他们提供的setOffsetX setpadding 之类的没卵用,你假如 想要偏移小红点让它不与下面的View重叠,那是不大概 的
以是 我的写法是 为了更好的性能,直接继承View然后画小红点背景, 然后把目标 view外层加一层LinearLayout 让小红点View放目标 的右边,如许 就不会重叠
同时 我也支持设置 重叠模式和非重叠模式

这是结果 图

这里写图片形貌

由于github账号出标题 了,没法上传, 以是 写到博客上算了

这只是一个小工具类 供大家学习下我的思绪 , 就不多说了, 具体 实现表明 里写的很清楚 ,不太清楚 的可以回复问我

#核心类:
##BadgeHelper .java

  1. package com.truescend.gofit.views;
  2. import android.content.Context;
  3. import android.content.res.Resources;
  4. import android.graphics.Canvas;
  5. import android.graphics.Paint;
  6. import android.graphics.Rect;
  7. import android.graphics.RectF;
  8. import android.support.annotation.IntDef;
  9. import android.support.design.widget.TabLayout;
  10. import android.util.Log;
  11. import android.util.TypedValue;
  12. import android.view.Gravity;
  13. import android.view.View;
  14. import android.view.ViewGroup;
  15. import android.widget.FrameLayout;
  16. import android.widget.LinearLayout;
  17. import java.lang.annotation.Retention;
  18. import java.lang.annotation.RetentionPolicy;
  19. import java.lang.reflect.Field;
  20. /**
  21. * 作者:东芝(2018/8/23).
  22. * 支持 重叠目标模式 和 放目标右上角但不重叠的模式 两种模式 通过setOverlap 设置, 默认为false=不重叠
  23. */
  24. public class BadgeHelper extends View {
  25. private static final String TAG = "BadgeHelper";
  26. private float density;
  27. private Paint mTextPaint;
  28. private Paint mBackgroundPaint;
  29. private String text = "0";
  30. private int number;
  31. @Type
  32. private int type = Type.TYPE_POINT;
  33. private boolean isOverlap;
  34. private final RectF rect = new RectF();
  35. private int badgeColor = 0xFFD3321B; //默认的小红点颜色
  36. private int textColor = 0xFFFFFFff;
  37. private float textSize;
  38. private int w;
  39. private int h;
  40. private boolean isSetup;
  41. private boolean mIgnoreTargetPadding;
  42. private boolean isCenterVertical;
  43. private int leftMargin;
  44. private int topMargin;
  45. private int rightMargin;
  46. private int bottomMargin;
  47. @IntDef({Type.TYPE_POINT, Type.TYPE_TEXT})
  48. @Retention(RetentionPolicy.SOURCE)
  49. public @interface Type {
  50. int TYPE_POINT = 0;
  51. int TYPE_TEXT = 1;
  52. }
  53. public BadgeHelper(Context context) {
  54. super(context);
  55. }
  56. private void init(@Type int type, boolean isOverlap) {
  57. this.type = type;
  58. this.isOverlap = isOverlap;
  59. density = getResources().getDisplayMetrics().density;
  60. switch (type) {
  61. case Type.TYPE_POINT:
  62. mBackgroundPaint = new Paint();
  63. mBackgroundPaint.setStyle(Paint.Style.FILL);
  64. mBackgroundPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
  65. mBackgroundPaint.setColor(badgeColor);
  66. //计算小红点无文本情况下的小红点大小, 按屏幕像素计算, 如果你有你自己认为更好的算法, 改这里即可
  67. w = h = Math.round(density * 7f);
  68. break;
  69. case Type.TYPE_TEXT:
  70. mBackgroundPaint = new Paint();
  71. mBackgroundPaint.setStyle(Paint.Style.FILL);
  72. mBackgroundPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
  73. mBackgroundPaint.setColor(badgeColor);
  74. mTextPaint = new Paint();
  75. mTextPaint.setStyle(Paint.Style.FILL);
  76. mTextPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
  77. mTextPaint.setColor(textColor);//文本颜色
  78. if (textSize == 0) {
  79. mTextPaint.setTextSize(density * 10);//文本大小按屏幕像素 计算, 没写死是为了适配各种屏幕, 但如果你有你认为更合理的计算方式 你可以改这里
  80. } else {
  81. mTextPaint.setTextSize(textSize);//使用自定义大小
  82. }
  83. //计算小红点有文本情况下的小红点大小, 按文本宽高计算, 如果你有你自己认为更好的算法, 改这里即可
  84. float textWidth = getTextWidth("99", mTextPaint);
  85. w = h = Math.round(textWidth * 1.4f);//让背景比文本大一点
  86. break;
  87. }
  88. }
  89. /**
  90. * 设置Margin 可用于做偏移
  91. * @param left
  92. * @param top
  93. * @param right
  94. * @param bottom
  95. * @return
  96. */
  97. public BadgeHelper setBadgeMargins(int left, int top, int right, int bottom) {
  98. leftMargin = left;
  99. topMargin = top;
  100. rightMargin = right;
  101. bottomMargin = bottom;
  102. return this;
  103. }
  104. /**
  105. * 设置Gravity居中
  106. * @return
  107. */
  108. public BadgeHelper setBadgeCenterVertical( ) {
  109. isCenterVertical = true;
  110. return this;
  111. }
  112. /**
  113. * 设置小红点类型
  114. *
  115. * @param type
  116. * @return
  117. */
  118. public BadgeHelper setBadgeType(@Type int type) {
  119. this.type = type;
  120. return this;
  121. }
  122. /**
  123. * 设置小红点大小, 默认自动适配
  124. *
  125. * @param textSize
  126. * @return
  127. */
  128. public BadgeHelper setBadgeTextSize(int textSize) {
  129. Context c = getContext();
  130. Resources r;
  131. if (c == null) {
  132. r = Resources.getSystem();
  133. } else {
  134. r = c.getResources();
  135. }
  136. this.textSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, textSize, r.getDisplayMetrics());
  137. return this;
  138. }
  139. /**
  140. * 设置小红点文字颜色, 默认白
  141. *
  142. * @param textColor
  143. * @return
  144. */
  145. public BadgeHelper setBadgeTextColor(int textColor) {
  146. this.textColor = textColor;
  147. return this;
  148. }
  149. /**
  150. * 设置重叠模式, 默认是false(不重叠)
  151. *
  152. * @param isOverlap 是否把小红点重叠到目标View之上
  153. * @return
  154. */
  155. public BadgeHelper setBadgeOverlap(boolean isOverlap) {
  156. return setBadgeOverlap(isOverlap, false);
  157. }
  158. /**
  159. * 设置重叠模式, 默认是false(不重叠)
  160. *
  161. * @param isOverlap 是否把小红点重叠到目标View之上
  162. * @param isIgnoreTargetPadding 是否忽略目标View的padding
  163. * @return
  164. */
  165. public BadgeHelper setBadgeOverlap(boolean isOverlap, boolean isIgnoreTargetPadding) {
  166. this.isOverlap = isOverlap;
  167. this.mIgnoreTargetPadding = isIgnoreTargetPadding;
  168. if (!isOverlap && isIgnoreTargetPadding) {
  169. Log.w(TAG, "警告:只有重叠模式isOverlap=true 设置mIgnoreTargetPadding才有意义");
  170. }
  171. return this;
  172. }
  173. /**
  174. * 设置小红点颜色
  175. *
  176. * @param mBadgeColor
  177. * @return
  178. */
  179. public BadgeHelper setBadgeColor(int mBadgeColor) {
  180. this.badgeColor = mBadgeColor;
  181. return this;
  182. }
  183. /**
  184. * 设置小红点大小
  185. *
  186. * @param w
  187. * @param h
  188. * @return
  189. */
  190. public BadgeHelper setBadgeSize(int w, int h) {
  191. this.w = w;
  192. this.h = h;
  193. return this;
  194. }
  195. /**
  196. * 是否显示
  197. * @param enable
  198. */
  199. public void setBadgeEnable(boolean enable) {
  200. setVisibility(enable?VISIBLE:INVISIBLE);
  201. }
  202. /**
  203. * 设置小红点的文字
  204. *
  205. * @param number
  206. */
  207. public void setBadgeNumber(int number) {
  208. this.number = number;
  209. this.text = String.valueOf(number);
  210. if (isSetup) {
  211. if(number==0){
  212. setVisibility(INVISIBLE);
  213. }else{
  214. setVisibility(VISIBLE);
  215. }
  216. invalidate();
  217. }
  218. }
  219. public void bindToTargetView(TabLayout target, int tabIndex) {
  220. TabLayout.Tab tab = target.getTabAt(tabIndex);
  221. View targetView = null;
  222. View tabView = null;
  223. try {
  224. Field viewField = TabLayout.Tab.class.getDeclaredField("mView");
  225. viewField.setAccessible(true);
  226. targetView = tabView = (View) viewField.get(tab);
  227. } catch (Exception e) {
  228. e.printStackTrace();
  229. }
  230. try {
  231. if (tabView != null) {
  232. Field mTextViewField = tabView.getClass().getDeclaredField("mTextView");//"mIconView"
  233. mTextViewField.setAccessible(true);
  234. targetView = (View) mTextViewField.get(tabView);
  235. }
  236. } catch (Exception e) {
  237. e.printStackTrace();
  238. }
  239. if (targetView != null) {
  240. bindToTargetView(targetView);
  241. }
  242. }
  243. /**
  244. * 绑定小红点到目标View的右上角
  245. *
  246. * @param target
  247. */
  248. public void bindToTargetView(View target) {
  249. init(type, isOverlap);
  250. if (getParent() != null) {
  251. ((ViewGroup) getParent()).removeView(this);
  252. }
  253. if (target == null) {
  254. return;
  255. }
  256. if (target.getParent() instanceof ViewGroup) {
  257. ViewGroup parent = (ViewGroup) target.getParent();
  258. int groupIndex = parent.indexOfChild(target);
  259. parent.removeView(target);
  260. if (isOverlap) {//[小红点与目标View重叠]模式
  261. FrameLayout badgeContainer = new FrameLayout(getContext());
  262. ViewGroup.LayoutParams targetLayoutParams = target.getLayoutParams();
  263. badgeContainer.setLayoutParams(targetLayoutParams);
  264. target.setLayoutParams(new ViewGroup.LayoutParams(
  265. ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
  266. parent.addView(badgeContainer, groupIndex, targetLayoutParams);
  267. badgeContainer.addView(target);
  268. badgeContainer.addView(this);
  269. FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) getLayoutParams();
  270. if(isCenterVertical) {
  271. layoutParams.gravity = Gravity.CENTER_VERTICAL ;
  272. }else{
  273. layoutParams.gravity = Gravity.END | Gravity.TOP;
  274. }
  275. if (mIgnoreTargetPadding) {
  276. layoutParams.rightMargin = target.getPaddingRight() - w;
  277. layoutParams.topMargin = target.getPaddingTop() - h / 2;
  278. }
  279. setLayoutParams(layoutParams);
  280. } else {//[小红点放右侧]模式
  281. LinearLayout badgeContainer = new LinearLayout(getContext());
  282. badgeContainer.setOrientation(LinearLayout.HORIZONTAL);
  283. ViewGroup.LayoutParams targetLayoutParams = target.getLayoutParams();
  284. badgeContainer.setLayoutParams(targetLayoutParams);
  285. target.setLayoutParams(new ViewGroup.LayoutParams(
  286. ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
  287. parent.addView(badgeContainer, groupIndex, targetLayoutParams);
  288. badgeContainer.addView(target);
  289. badgeContainer.addView(this);
  290. if(isCenterVertical) {
  291. badgeContainer.setGravity(Gravity.CENTER_VERTICAL);
  292. }
  293. }
  294. boolean hasSetMargin = leftMargin>0||topMargin>0||rightMargin>0||bottomMargin>0;
  295. if (hasSetMargin&&getLayoutParams() instanceof ViewGroup.MarginLayoutParams) {
  296. ViewGroup.MarginLayoutParams p = (ViewGroup.MarginLayoutParams) getLayoutParams();
  297. p.setMargins(leftMargin, topMargin, rightMargin, bottomMargin);
  298. setLayoutParams(p);
  299. }
  300. isSetup = true;
  301. } else if (target.getParent() == null) {
  302. throw new IllegalStateException("目标View不能没有父布局!");
  303. }
  304. if(number==0){
  305. setVisibility(INVISIBLE);
  306. }else{
  307. setVisibility(VISIBLE);
  308. }
  309. }
  310. @Override
  311. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  312. if (w > 0 && h > 0) {
  313. setMeasuredDimension(w, h);
  314. } else {
  315. throw new IllegalStateException("如果你自定义了小红点的宽高,就不能设置其宽高小于0 ,否则请不要设置!");
  316. }
  317. }
  318. @Override
  319. protected void onDraw(Canvas canvas) {
  320. super.onDraw(canvas);
  321. //这里不用解释了 很简单 就是画一个圆形和文字
  322. rect.left = 0;
  323. rect.top = 0;
  324. rect.right = getWidth();
  325. rect.bottom = getHeight();
  326. canvas.drawRoundRect(rect, getWidth() / 2, getWidth() / 2, mBackgroundPaint);
  327. if (type == Type.TYPE_TEXT) {
  328. float textWidth = getTextWidth(text, mTextPaint);
  329. float textHeight = getTextHeight(text, mTextPaint);
  330. canvas.drawText(text, getWidth() / 2 - textWidth / 2, getHeight() / 2 + textHeight / 2, mTextPaint);
  331. }
  332. }
  333. private float getTextWidth(String text, Paint p) {
  334. return p.measureText(text, 0, text.length());
  335. }
  336. private float getTextHeight(String text, Paint p) {
  337. Rect rect = new Rect();
  338. p.getTextBounds(text, 0, text.length(), rect);
  339. return rect.height();
  340. }
  341. }
复制代码

#利用 示例:

  1. public class Main2Activity extends AppCompatActivity {
  2. private TextView mVTypeA;
  3. private TextView mVTypeB;
  4. private TextView mVTypeC;
  5. private TextView mVTypeD;
  6. private ImageView mVTypeE;
  7. private TabLayout mTabLayout;
  8. private void initView() {
  9. mVTypeA = (TextView) findViewById(R.id.vTypeA);
  10. mVTypeB = (TextView) findViewById(R.id.vTypeB);
  11. mVTypeC = (TextView) findViewById(R.id.vTypeC);
  12. mVTypeD = (TextView) findViewById(R.id.vTypeD);
  13. mVTypeE = (ImageView) findViewById(R.id.vTypeE);
  14. mTabLayout = (TabLayout) findViewById(R.id.tabLayout);
  15. }
  16. @Override
  17. protected void onCreate(Bundle savedInstanceState) {
  18. super.onCreate(savedInstanceState);
  19. setContentView(R.layout.activity_main2);
  20. initView();
  21. //情况A(有margin时)
  22. new BadgeHelper(this)
  23. .setBadgeType(BadgeHelper.Type.TYPE_POINT)
  24. .setBadgeOverlap(false)
  25. .bindToTargetView(mVTypeA);
  26. //情况B(有padding时)
  27. new BadgeHelper(this)
  28. .setBadgeType(BadgeHelper.Type.TYPE_POINT)
  29. .setBadgeOverlap(true, true)//重叠模式+忽略目标ViewPadding 效果, 什么意思? 你可以把第二个参数写成false看看会发生什么
  30. .bindToTargetView(mVTypeB);
  31. //情况C(默认情况)
  32. BadgeHelper badgeHelperC = new BadgeHelper(this)
  33. .setBadgeType(BadgeHelper.Type.TYPE_TEXT)
  34. .setBadgeOverlap(false);
  35. badgeHelperC.bindToTargetView(mVTypeC);
  36. badgeHelperC.setBadgeNumber(6);//数字模式
  37. //情况D(有权重时)
  38. new BadgeHelper(this)
  39. .setBadgeColor(0xff0f00ff)//顺便演示下 小红点颜色的设置
  40. .setBadgeType(BadgeHelper.Type.TYPE_POINT)
  41. .setBadgeOverlap(false)
  42. .bindToTargetView(mVTypeD);
  43. //情况E(小红点与图片重叠)+ 数字模式
  44. BadgeHelper badgeHelperE = new BadgeHelper(this)
  45. .setBadgeType(BadgeHelper.Type.TYPE_TEXT)
  46. .setBadgeOverlap(true, true);//注意 isIgnoreTargetPadding=true时 和 setBadgeMargins 不能同时使用
  47. //.setBadgeSize(100,100)//设置小红点的大小, 如果未设置则使用默认宽高, 一般默认就好
  48. //.setBadgeTextSize(15) //设置文本大小, 不设置 则使用默认, 一般默认就好
  49. badgeHelperE.bindToTargetView(mVTypeE);
  50. //任意地方可以刷新数字
  51. badgeHelperE.setBadgeNumber(58);//数字
  52. //情况F(TabLayout兼容)
  53. BadgeHelper badgeHelperF = new BadgeHelper(this)
  54. .setBadgeType(BadgeHelper.Type.TYPE_TEXT)
  55. .setBadgeCenterVertical()//红点居中
  56. .setBadgeMargins(10,0,0,0)//偏移一下
  57. .setBadgeOverlap(false);
  58. badgeHelperF.bindToTargetView(mTabLayout, 0);
  59. badgeHelperF.setBadgeNumber(5);
  60. new BadgeHelper(this)
  61. .setBadgeType(BadgeHelper.Type.TYPE_POINT)
  62. .setBadgeCenterVertical()//红点居中
  63. .setBadgeMargins(10,0,0,0)//偏移一下
  64. .setBadgeOverlap(false)
  65. .bindToTargetView(mTabLayout,1);
  66. }
  67. }
复制代码

#布局:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. xmlns:app="http://schemas.android.com/apk/res-auto"
  4. xmlns:tools="http://schemas.android.com/tools"
  5. android:layout_width="match_parent"
  6. android:layout_height="match_parent"
  7. android:orientation="vertical"
  8. android:padding="16dp"
  9. tools:context="com.mx.test12.Main2Activity">
  10. <TextView
  11. android:background="#220000ff"
  12. android:id="@+id/vTypeA"
  13. android:layout_width="wrap_content"
  14. android:layout_height="wrap_content"
  15. android:layout_margin="20dp"
  16. android:text="情况A(有margin时)" />
  17. <TextView
  18. android:background="#2200ff00"
  19. android:id="@+id/vTypeB"
  20. android:layout_width="wrap_content"
  21. android:layout_height="wrap_content"
  22. android:padding="30dp"
  23. android:text="情况B(有padding时)" />
  24. <TextView
  25. android:id="@+id/vTypeC"
  26. android:layout_width="wrap_content"
  27. android:layout_height="wrap_content"
  28. android:text="情况C(默认情况)" />
  29. <LinearLayout
  30. android:layout_width="match_parent"
  31. android:layout_height="wrap_content"
  32. android:layout_gravity="center"
  33. android:gravity="center_vertical"
  34. android:orientation="horizontal">
  35. <TextView
  36. android:background="#2200ffff"
  37. android:id="@+id/vTypeD"
  38. android:layout_width="0dp"
  39. android:layout_height="wrap_content"
  40. android:layout_weight="1"
  41. android:text="情况D(有权重时)" />
  42. <View
  43. android:layout_width="0dp"
  44. android:layout_height="0dp" />
  45. </LinearLayout>
  46. <ImageView
  47. android:id="@+id/vTypeE"
  48. android:layout_width="wrap_content"
  49. android:layout_height="wrap_content"
  50. android:contentDescription="情况E(与图片重叠)"
  51. android:padding="20dp"
  52. android:src="@mipmap/ic_launcher" />
  53. <android.support.design.widget.TabLayout
  54. android:id="@+id/tabLayout"
  55. android:layout_width="match_parent"
  56. android:layout_height="55dp"
  57. app:layout_scrollFlags="scroll"
  58. app:tabIndicatorColor="#057523"
  59. app:tabIndicatorHeight="2.0dp"
  60. app:tabMode="fixed"
  61. app:tabSelectedTextColor="#057523"
  62. app:tabTextColor="#ced0d3">
  63. <android.support.design.widget.TabItem
  64. android:layout_width="match_parent"
  65. android:layout_height="match_parent"
  66. android:text="聊天列表" />
  67. <android.support.design.widget.TabItem
  68. android:layout_width="match_parent"
  69. android:layout_height="match_parent"
  70. android:text="好友列表" />
  71. <android.support.design.widget.TabItem
  72. android:layout_width="match_parent"
  73. android:layout_height="match_parent"
  74. android:text="设置" />
  75. </android.support.design.widget.TabLayout>
  76. </LinearLayout>
复制代码

到此这篇关于Android仿QQ微信未读消息小红点BadgeHelper的文章就先容 到这了,更多相干 Android小红点内容请搜索 脚本之家从前 的文章或继续欣赏 下面的相干 文章渴望 大家以后多多支持脚本之家!


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复

使用道具 举报

avatar 李焕发 | 2021-9-16 08:04:29 | 显示全部楼层
系统居然说我是在灌水,我有吗?
回复

使用道具 举报

avatar 绣绣仙女酌 | 2021-9-18 11:49:33 | 显示全部楼层
脑残片admin楼主今天吃了么?
回复

使用道具 举报

avatar 夜昙SS | 2021-9-22 05:22:31 | 显示全部楼层
admin楼主好聪明啊!
回复

使用道具 举报

avatar 屎壳郎秧 | 2021-10-14 12:59:15 | 显示全部楼层
论坛的帖子越来越有深度了!
回复

使用道具 举报

怎么我回帖都没人理我呢?
回复

使用道具 举报

admin楼主,我告诉你一个你不知道的的秘密,有一个牛逼的网站,运动刷步数还是免费刷的,QQ和微信都可以刷,特别好用。访问地址:http://yd.mxswl.com 猫先森网络
回复

使用道具 举报

admin楼主的帖子提神醒脑啊!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则