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的話,可以設(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 實(shí)現(xiàn)列表倒計(jì)時(shí)功能
這篇文章主要介紹了Android 實(shí)現(xiàn)列表倒計(jì)時(shí)功能,代碼很簡(jiǎn)單,沒(méi)有任何難度,使用RecyclerView+BaseRecyclerViewAdapterHelper列表實(shí)現(xiàn),需要的朋友可以參考下2020-03-03Android編程基礎(chǔ)之Menu功能菜單設(shè)計(jì)實(shí)例
這篇文章主要介紹了Android編程基礎(chǔ)之Menu功能菜單,結(jié)合實(shí)例形式分析了基本的Menu功能菜單原理、定義與響應(yīng)機(jī)制,需要的朋友可以參考下2016-10-10Android中的深度鏈接技術(shù)實(shí)戰(zhàn)
本文主要介紹了Android中的深度鏈接技術(shù)實(shí)戰(zhàn),文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03在Android開(kāi)發(fā)中替換資源圖片不起作用的解決方法
這篇文章主要介紹了在Android開(kāi)發(fā)中替換資源圖片不起作用的解決方法,需要的朋友可以參考下2014-07-07Android開(kāi)發(fā)獲取傳感器數(shù)據(jù)的方法示例【加速度傳感器,磁場(chǎng)傳感器,光線傳感器,方向傳感器】
這篇文章主要介紹了Android開(kāi)發(fā)獲取傳感器數(shù)據(jù)的方法,結(jié)合實(shí)例形式分析了Android獲取加速度傳感器、磁場(chǎng)傳感器、光線傳感器及方向傳感器數(shù)據(jù)的相關(guān)操作技巧,需要的朋友可以參考下2017-11-11appium運(yùn)行各種坑爹報(bào)錯(cuò)問(wèn)題及解決方法【推薦】
這篇文章主要介紹了 appium運(yùn)行各種坑爹報(bào)錯(cuò)問(wèn)題及解決方法,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-06-06Android ActivityManagerService啟動(dòng)流程詳解
這篇文章主要介紹了Android ActivityManagerService啟動(dòng)流程,AMS,即ActivityManagerService,是安卓java framework的一個(gè)服務(wù),運(yùn)行在system_server進(jìn)程。此服務(wù)十分重要,因?yàn)樗芾碇沧康乃拇蠼M件,是安卓APP開(kāi)發(fā)者最常接觸到的一個(gè)服務(wù)2023-02-02Android編程實(shí)現(xiàn)下載時(shí)主界面與詳細(xì)界面一致更新的方法
這篇文章主要介紹了Android編程實(shí)現(xiàn)下載時(shí)主界面與詳細(xì)界面一致更新的方法,涉及Android事件監(jiān)聽(tīng)及界面動(dòng)態(tài)更新相關(guān)操作技巧,需要的朋友可以參考下2017-11-11