1. Introduction
Usually, when we do the little red dot effect, we have two options:
- Customize the BadgeView and set it to the target View
- XML writes a View and sets shape
Some students may think, can not achieve it, yes, the code is not elegant, SAO SAO is not important, code and people as long as one can run on the line…
However, today, here is a different way to achieve the little red dot effect, which may surprise you
Effect of 2.
3. Introduction
- Use: Add dynamic display information to View (red dot prompt effect)
- App theme needs to be used
Theme.MaterialComponents.*
- The API request
18 +
Android 4.3 and above (API level mapping)
4. Implement disassembly
4.1 TabLayout
- 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>
Copy the code
- kotlin:
private fun initTabLayout(a) {
// Small red dots with numbers
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}}// No numeric red dots
mBinding.tabLayout.getTabAt(1)? .let { it.orCreateBadge.apply { backgroundColor = ContextCompat.getColor(this@BadgeDrawableActivity, R.color.red)
badgeTextColor = ContextCompat.getColor(this@BadgeDrawableActivity, R.color.white)
}
}
}
Copy the code
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="Little red Dot Example"
android:textAllCaps="false"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tab_layout" />
Copy the code
- kotlin:
private fun initTextView(a) {
// Change in the view tree
mBinding.tvBadge.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout(a) {
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)}}}Copy the code
4.3. The 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 red dot example" />
</FrameLayout>
Copy the code
- kotlin:
private fun initButton(a) {
mBinding.mbBadge.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
@SuppressLint("UnsafeOptInUsageError")
override fun onGlobalLayout(a) {
BadgeDrawable.create(this@BadgeDrawableActivity).apply {
badgeGravity = BadgeDrawable.TOP_START
number = 6
backgroundColor = ContextCompat.getColor(this@BadgeDrawableActivity, R.color.red)
// The MaterialButton itself has spacing. If it is not set to 0dp, you can set the offset of the badge
verticalOffset = 15
horizontalOffset = 10
BadgeUtils.attachBadgeDrawable(this, mBinding.mbBadge, mBinding.flBtn)
}
mBinding.mbBadge.viewTreeObserver.removeOnGlobalLayoutListener(this)}}}Copy the code
About the use and analysis of MaterialButton can be viewed: Android MaterialButton use details, farewell 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 red dot example"
android:src="@mipmap/ic_avatar" />
</FrameLayout>
Copy the code
- kotlin:
private fun initImageView(a) {
mBinding.sivBadge.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
@SuppressLint("UnsafeOptInUsageError")
override fun onGlobalLayout(a) {
BadgeDrawable.create(this@BadgeDrawableActivity).apply {
badgeGravity = BadgeDrawable.TOP_END
number = 99999
// Badge displays maximum number of characters, default 999+ is 4 characters (with '+' number)
maxCharacterCount = 3
backgroundColor = ContextCompat.getColor(this@BadgeDrawableActivity, R.color.red)
BadgeUtils.attachBadgeDrawable(this, mBinding.sivBadge, mBinding.flImg)
}
mBinding.sivBadge.viewTreeObserver.removeOnGlobalLayoutListener(this)}}}Copy the code
ShapeableImageView ShapeableImageView ShapeableImageView ShapeableImageView ShapeableImageView ShapeableImageView
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" />
Copy the code
- Kotlin:
private fun initNavigationView(a) {
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}}Copy the code
TabLayout and BottomNavigationView provide apis for creating BadgeDrawable directly in the source code, and use BadgeUtils for those not provided.
5. Common API sorting
API | describe |
---|---|
backgroundColor | The background color |
badgeTextColor | Text color |
alpha | transparency |
number | The prompt number displayed |
maxCharacterCount | Maximum number of characters displayed (99+ including the ‘+’ sign) |
badgeGravity | Display position |
horizontalOffset | Horizontal offset |
verticalOffset | Vertical offset |
isVisible | Whether or not shown |
6. Source code analysis
Here’s a simple code example:
BadgeDrawable.create(this@BadgeDrawableActivity).apply {
// ...
BadgeUtils.attachBadgeDrawable(this, mBinding.mbBadge, mBinding.flBtn)
}
Copy the code
It is not hard to see that there are two key points:
- BadgeDrawable.create
- BadgeUtils.attachBadgeDrawable
Continue with the following, see what is done in the source code
6.1. BadgeDrawable. Create
Create actually calls the constructor:
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);
}
Copy the code
So a line in the constructor: ThemeEnforcement checkMaterialTheme (context); Detects Material topics, if not, an exception is thrown directly
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)."); }}Copy the code
This is also why the above said subject to use Theme. MaterialComponents. *
I then created a text drawing helper class, TextDrawableHelper
Such as setting the text centered: textDrawableHelper getTextPaint () setTextAlign (Paint. The Align. CENTER);
The other thing is to get and set the text property, as we usually set it, which is easier to understand.
How do I display the text after I draw it? Continue with 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); }}Copy the code
Here first judgment badgeDrawable. GetCustomBadgeParent ()! = null, the parent view type is FrameLayout, if not null, the hierarchy is preloaded.
If the parameter is null, it checks if (USE_COMPAT_PARENT), which means API level
static {
USE_COMPAT_PARENT = VERSION.SDK_INT < 18;
}
Copy the code
The core code
:
anchor.getOverlay().add(badgeDrawable);
Copy the code
This line of code looks familiar to those of you who have done something like adding a View globally.
ViewOverlay, also known as a float layer, allows you to add and remove views without affecting their subviews. Android 4.3 added this API, which requires 18+ views.
Ok, so far about the use of BadgeDrawable and source code parsing is introduced.
7.Github
Github.com/yechaoa/Mat…
Welcome to the homepage or Github for more sharing of MaterialDesign components.
8. Related documents
- BadgeDrawable
- BadgeUtils
- ViewOverlay
9. The last
Writing is not easy, if you have a drop of help or inspiration, thank you for the like support ^ – ^