Android漲姿勢(shì)知識(shí)點(diǎn)之你沒(méi)用過(guò)的BadgeDrawable
1.前言
通常情況下,我們?cè)谧鲂〖t點(diǎn)效果的時(shí)候,會(huì)有兩種選擇:
自定義BadgeView,然后設(shè)置給目標(biāo)Viewxml寫(xiě)一個(gè)View,然后設(shè)置shape
有的同學(xué)可能會(huì)想,能實(shí)現(xiàn)不就行了嗎,是的,代碼優(yōu)不優(yōu)雅的不重要,代碼和人只要有一個(gè)能跑就行…
不過(guò),今天來(lái)介紹一種不同的方式來(lái)實(shí)現(xiàn)小紅點(diǎn)效果,或許會(huì)讓你眼前一亮~
2.效果

3.簡(jiǎn)介

- 用途:給View添加動(dòng)態(tài)顯示信息(小紅點(diǎn)提示效果)
- app主題需使用
Theme.MaterialComponents.* - api 要求
18+也就Android 4.3以上(api等級(jí)對(duì)應(yīng)關(guān)系見(jiàn)文末)
4.實(shí)現(xiàn)拆解
4.1TabLayout

xml:
<com.google.android.material.tabs.TabLayout
android:id="@+id/tab_layout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:background="#FFFAF0"
android:textAllCaps="false"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/include"
app:tabIndicator="@drawable/shape_tab_indicator"
app:tabIndicatorColor="@color/colorPrimary"
app:tabIndicatorFullWidth="false"
app:tabMaxWidth="200dp"
app:tabMinWidth="100dp"
app:tabMode="fixed"
app:tabSelectedTextColor="@color/colorPrimary"
app:tabTextColor="@color/gray">
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Android" />
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Kotlin" />
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Flutter" />
</com.google.android.material.tabs.TabLayout>
kotlin:
private fun initTabLayout() {
// 帶數(shù)字小紅點(diǎn)
mBinding.tabLayout.getTabAt(0)?.let {
it.orCreateBadge.apply {
backgroundColor = ContextCompat.getColor(this@BadgeDrawableActivity, R.color.red)
badgeTextColor = ContextCompat.getColor(this@BadgeDrawableActivity, R.color.white)
number = 6
}
}
// 不帶數(shù)字小紅點(diǎn)
mBinding.tabLayout.getTabAt(1)?.let {
it.orCreateBadge.apply {
backgroundColor = ContextCompat.getColor(this@BadgeDrawableActivity, R.color.red)
badgeTextColor = ContextCompat.getColor(this@BadgeDrawableActivity, R.color.white)
}
}
}
4.2.TextView

xml:
<TextView
android:id="@+id/tv_badge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:text="小紅點(diǎn)示例"
android:textAllCaps="false"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tab_layout" />
kotlin:
private fun initTextView() {
// 在視圖樹(shù)變化
mBinding.tvBadge.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
BadgeDrawable.create(this@BadgeDrawableActivity).apply {
badgeGravity = BadgeDrawable.TOP_END
number = 6
backgroundColor = ContextCompat.getColor(this@BadgeDrawableActivity, R.color.colorPrimary)
isVisible = true
BadgeUtils.attachBadgeDrawable(this, mBinding.tvBadge)
}
mBinding.tvBadge.viewTreeObserver.removeOnGlobalLayoutListener(this)
}
})
}
4.3.Button
xml:
<FrameLayout
android:id="@+id/fl_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:padding="10dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_badge">
<com.google.android.material.button.MaterialButton
android:id="@+id/mb_badge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button小紅點(diǎn)示例" />
</FrameLayout>
kotlin:
private fun initButton() {
mBinding.mbBadge.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
@SuppressLint("UnsafeOptInUsageError")
override fun onGlobalLayout() {
BadgeDrawable.create(this@BadgeDrawableActivity).apply {
badgeGravity = BadgeDrawable.TOP_START
number = 6
backgroundColor = ContextCompat.getColor(this@BadgeDrawableActivity, R.color.red)
// MaterialButton本身有間距,不設(shè)置為0dp的話(huà),可以設(shè)置badge的偏移量
verticalOffset = 15
horizontalOffset = 10
BadgeUtils.attachBadgeDrawable(this, mBinding.mbBadge, mBinding.flBtn)
}
mBinding.mbBadge.viewTreeObserver.removeOnGlobalLayoutListener(this)
}
})
}
關(guān)于MaterialButton的使用及解析可查看:Android MaterialButton使用詳解,告別shape、selector
4.4.ImageView
xml:
<FrameLayout
android:id="@+id/fl_img"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:padding="10dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/fl_btn">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/siv_badge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:contentDescription="Image小紅點(diǎn)示例"
android:src="@mipmap/ic_avatar" />
</FrameLayout>
kotlin:
private fun initImageView() {
mBinding.sivBadge.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
@SuppressLint("UnsafeOptInUsageError")
override fun onGlobalLayout() {
BadgeDrawable.create(this@BadgeDrawableActivity).apply {
badgeGravity = BadgeDrawable.TOP_END
number = 99999
// badge最多顯示字符,默認(rèn)999+ 是4個(gè)字符(帶'+'號(hào))
maxCharacterCount = 3
backgroundColor = ContextCompat.getColor(this@BadgeDrawableActivity, R.color.red)
BadgeUtils.attachBadgeDrawable(this, mBinding.sivBadge, mBinding.flImg)
}
mBinding.sivBadge.viewTreeObserver.removeOnGlobalLayoutListener(this)
}
})
}
關(guān)于ShapeableImageView的使用及解析可查看:Android ShapeableImageView使用詳解,告別shape、三方庫(kù)
4.5.BottomNavigationView
xml:
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/navigation_view"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
android:background="?android:attr/windowBackground"
app:itemBackground="@color/colorPrimary"
app:itemIconTint="@color/white"
app:itemTextColor="@color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:menu="@menu/navigation" />
kotlin:
private fun initNavigationView() {
mBinding.navigationView.getOrCreateBadge(R.id.navigation_home).apply {
backgroundColor = ContextCompat.getColor(this@BadgeDrawableActivity, R.color.red)
badgeTextColor = ContextCompat.getColor(this@BadgeDrawableActivity, R.color.white)
number = 9999
}
}
TabLayout和BottomNavigationView源碼中直接提供了創(chuàng)建
BadgeDrawable的api,未提供的使用BadgeUtils。
5.常用API整理
| API | 描述 |
|---|---|
| backgroundColor | 背景色 |
| badgeTextColor | 文本顏色 |
| alpha | 透明度 |
| number | 顯示的提示數(shù)字 |
| maxCharacterCount | 最多顯示字符數(shù)量(99+包括‘+’號(hào)) |
| badgeGravity | 顯示位置 |
| horizontalOffset | 水平方向偏移量 |
| verticalOffset | 垂直方向偏移量 |
| isVisible | 是否顯示 |
6.源碼解析
來(lái)一段最簡(jiǎn)單的代碼示例看看:
BadgeDrawable.create(this@BadgeDrawableActivity).apply {
// ...
BadgeUtils.attachBadgeDrawable(this, mBinding.mbBadge, mBinding.flBtn)
}
不難發(fā)現(xiàn),有兩個(gè)關(guān)鍵點(diǎn):
- BadgeDrawable.create
- BadgeUtils.attachBadgeDrawable
下面繼續(xù)跟一下,看看源碼里究竟是做了什么
6.1.BadgeDrawable.create
create實(shí)際調(diào)用的是構(gòu)造方法:
private BadgeDrawable(@NonNull Context context) {
this.contextRef = new WeakReference<>(context);
ThemeEnforcement.checkMaterialTheme(context);
Resources res = context.getResources();
badgeBounds = new Rect();
shapeDrawable = new MaterialShapeDrawable();
badgeRadius = res.getDimensionPixelSize(R.dimen.mtrl_badge_radius);
badgeWidePadding = res.getDimensionPixelSize(R.dimen.mtrl_badge_long_text_horizontal_padding);
badgeWithTextRadius = res.getDimensionPixelSize(R.dimen.mtrl_badge_with_text_radius);
textDrawableHelper = new TextDrawableHelper(/* delegate= */ this);
textDrawableHelper.getTextPaint().setTextAlign(Paint.Align.CENTER);
this.savedState = new SavedState(context);
setTextAppearanceResource(R.style.TextAppearance_MaterialComponents_Badge);
}
構(gòu)造方法里有這么一行:ThemeEnforcement.checkMaterialTheme(context); 檢測(cè)Material主題,如果不是會(huì)直接拋出異常
private static void checkTheme(
@NonNull Context context, @NonNull int[] themeAttributes, String themeName) {
if (!isTheme(context, themeAttributes)) {
throw new IllegalArgumentException(
"The style on this component requires your app theme to be "
+ themeName
+ " (or a descendant).");
}
}
這也是上面為什么說(shuō)主題要使用Theme.MaterialComponents.*
然后創(chuàng)建了一個(gè)文本繪制幫助類(lèi),TextDrawableHelper
比如設(shè)置文本居中:textDrawableHelper.getTextPaint().setTextAlign(Paint.Align.CENTER);
其他的就是text屬性的獲取和設(shè)置,跟我們平時(shí)設(shè)置一毛一樣,比較好理解。
繪制文本之后怎么顯示出來(lái)呢?繼續(xù)跟attachBadgeDrawable。
6.2.BadgeUtils.attachBadgeDrawable
public static void attachBadgeDrawable(@NonNull BadgeDrawable badgeDrawable, @NonNull View anchor, @Nullable FrameLayout customBadgeParent) {
setBadgeDrawableBounds(badgeDrawable, anchor, customBadgeParent);
if (badgeDrawable.getCustomBadgeParent() != null) {
badgeDrawable.getCustomBadgeParent().setForeground(badgeDrawable);
} else {
if (USE_COMPAT_PARENT) {
throw new IllegalArgumentException("Trying to reference null customBadgeParent");
}
anchor.getOverlay().add(badgeDrawable);
}
}
這里先是判斷badgeDrawable.getCustomBadgeParent() != null,這個(gè)parent view的類(lèi)型就是FrameLayout,不為空的情況下,層級(jí)前置。
為空的情況下先是判斷了if (USE_COMPAT_PARENT),這里其實(shí)是對(duì)api level的判斷
static {
USE_COMPAT_PARENT = VERSION.SDK_INT < 18;
}
核心代碼:
anchor.getOverlay().add(badgeDrawable);
如果有同學(xué)做過(guò)類(lèi)似全局添加View的需求,這行代碼就看著比較熟悉了。
ViewOverlay,視圖疊加,也可以理解為浮層,在不影響子view的情況下,可以添加、刪除View,這個(gè)api就是android 4.3加的,這也是為什么前面說(shuō)api 要求18+。
ok,至此關(guān)于BadgeDrawable的使用和源碼解析就介紹完了。
7.Github
https://github.com/yechaoa/MaterialDesign
8.相關(guān)文檔
附:Android開(kāi)發(fā)版本和API等級(jí)對(duì)應(yīng)關(guān)系
| Platform Version | API Level | VERSION_CODE |
|---|---|---|
| 13.0(beta) | ||
| 12.0 | 32 | S_V2 |
| 12.0 | 31 | S |
| 11.0 | 30 | R |
| 10.0 | 29 | Q |
| 9.0 | 28 | P |
| 8.1 | 27 | O_MR1 |
| 8.0 | 26 | O |
| 7.1 | 25 | N_MR1 |
| 7.0 | 24 | N |
| 6.0 | 23 | M |
| 5.1 | 22 | LOLLIPOP_MR1 |
| 5.0 | 21 | LOLLIPOP |
| 4.4w | 20 | KITKAT_WATCH |
| 4.4 | 19 | KITKAT |
| 4.3 | 18 | JELLY_BEAN_MR2 |
| 4.2 | 17 | JELLY_BEAN_MR1 |
| 4.1 | 16 | JELLY_BEAN |
| 4.0.3 | 15 | ICE_CREAM_SANDWICH_MR1 |
| 4.0 | 14 | ICE_CREAM_SANDWICH |
| 3.2 | 13 | HONEYCOMB_MR2 |
| 3.1 | 12 | HONEYCOMB_MR1 |
| 3.0 | 11 | HONEYCOMB |
| 2.3.3-2.3.4 | 10 | GINGERBREAD_MR1 |
| 2.3.0-2.3.2 | 9 | GINGERBREAD |
| 2.2 | 8 | FROYO |
| 2.1 | 7 | ECLAIR_MR1 |
| 2.0.1 | 6 | ECLAIR_0_1 |
| 2.0 | 5 | ECLAIR |
| 1.6 | 4 | DONUT |
| 1.5 | 3 | CUPCAKE |
| 1.1 | 2 | BASE_1_1 |
| 1.0 | 1 | BASE |
總結(jié)
到此這篇關(guān)于Android漲姿勢(shì)知識(shí)點(diǎn)之BadgeDrawable的文章就介紹到這了,更多相關(guān)Android BadgeDrawable詳解內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android積分簽到上移消失動(dòng)畫(huà)效果
這篇文章主要介紹了Android積分簽到上移消失動(dòng)畫(huà)效果大致思路:動(dòng)畫(huà)部分,由一個(gè)垂直的平移和一個(gè)透明度變化的兩個(gè)動(dòng)畫(huà)組成。然后通過(guò)AnimationSet將兩個(gè)動(dòng)畫(huà)添加到集合,然后開(kāi)始播放動(dòng)畫(huà)。2018-05-05
Android實(shí)現(xiàn)文字滾動(dòng)播放效果的代碼
這篇文章主要介紹了Android實(shí)現(xiàn)文字滾動(dòng)播放效果,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-07-07
Android Studio打包APK文件具體實(shí)現(xiàn)步驟解析
這篇文章主要介紹了Android Studio打包APK文件具體實(shí)現(xiàn)步驟解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-11-11
Android仿拉手網(wǎng)團(tuán)購(gòu)App我的收藏界面實(shí)例代碼
這篇文章主要介紹了Android仿拉手團(tuán)購(gòu)網(wǎng)App我的收藏界面實(shí)例代碼,需要的朋友可以參考下2017-05-05
Android中dataBinding使用的簡(jiǎn)單封裝
前面一段時(shí)間學(xué)習(xí)了一下Android中的DataBinding,但是只是很簡(jiǎn)單地實(shí)現(xiàn)了一下,DataBinding中最強(qiáng)大的地方還沒(méi)有認(rèn)真地學(xué)習(xí)過(guò),有很多地方還不理解,下面這篇文章主要給大家介紹了關(guān)于Android中dataBinding使用的簡(jiǎn)單封裝,需要的朋友可以參考下2023-06-06
Android自定義圓形View實(shí)現(xiàn)小球跟隨手指移動(dòng)效果
這篇文章主要為大家詳細(xì)介紹了Android自定義圓形View實(shí)現(xiàn)小球跟隨手指移動(dòng)效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-03-03
使用android隱藏api實(shí)現(xiàn)亮度調(diào)節(jié)的方法
使用android隱藏api實(shí)現(xiàn)亮度調(diào)節(jié)的方法,需要的朋友可以參考一下2013-05-05

