1. Introduction

Usually, when we do the little red dot effect, we have two options:

  1. Customize the BadgeView and set it to the target View
  2. 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 usedTheme.MaterialComponents.*
  • The API request18 +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:

  1. BadgeDrawable.create
  2. 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 ^ – ^