Last active
December 14, 2022 03:08
-
-
Save EmmanuelGuther/a10cba2caa821b98fa1c965aa3022233 to your computer and use it in GitHub Desktop.
Add badge to tab layout
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?xml version="1.0" encoding="utf-8"?> | |
| <shape xmlns:android="http://schemas.android.com/apk/res/android" | |
| android:shape="rectangle"> | |
| <solid android:color="#ea5444"/> | |
| <corners android:radius="12dp"/> | |
| <stroke | |
| android:width="0.5dp" | |
| android:color="#a6a0a0"/> | |
| </shape> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package es.cityride.mantapp | |
| import android.content.Context | |
| import android.graphics.PorterDuff | |
| import android.graphics.drawable.Drawable | |
| import android.support.design.widget.TabLayout | |
| import android.util.AttributeSet | |
| import android.util.SparseArray | |
| import android.view.LayoutInflater | |
| import android.view.View | |
| import android.widget.ImageView | |
| import android.widget.TextView | |
| /** | |
| * Use: | |
| * tabLayout.with(tabPosition).badge(true).badgeCount(badgeCount).icon(R.drawable.ic_local_shipping_24dp).build() | |
| */ | |
| class BadgeTabLayout : TabLayout { | |
| private val mTabBuilders = SparseArray<Builder>() | |
| constructor(context: Context) : super(context) {} | |
| constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {} | |
| constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {} | |
| fun with(position: Int): Builder { | |
| val tab = getTabAt(position) | |
| return with(tab) | |
| } | |
| /** | |
| * Apply a builder for this tab. | |
| * | |
| * @param tab for which we create a new builder or retrieve its builder if existed. | |
| * @return the required Builder. | |
| */ | |
| fun with(tab: TabLayout.Tab?): Builder { | |
| if (tab == null) { | |
| throw IllegalArgumentException("Tab must not be null") | |
| } | |
| var builder: Builder? = mTabBuilders.get(tab.position) | |
| if (builder == null) { | |
| builder = Builder(this, tab) | |
| mTabBuilders.put(tab.position, builder) | |
| } | |
| return builder | |
| } | |
| class Builder | |
| /** | |
| * This construct take a TabLayout parent to have its context and other attributes sets. And | |
| * the tab whose icon will be updated. | |
| * | |
| * @param parent | |
| * @param tab | |
| */ | |
| (parent: TabLayout, private val mTab: TabLayout.Tab) { | |
| private val mView: View? | |
| private val mContext: Context = parent.context | |
| private var mBadgeTextView: TextView? = null | |
| private var mIconView: ImageView? = null | |
| private var mIconDrawable: Drawable? = null | |
| private var mIconColorFilter: Int? = null | |
| private var mBadgeCount = Integer.MIN_VALUE | |
| private var mHasBadge = false | |
| init { | |
| // initialize current tab's custom view. | |
| if (mTab.customView != null) { | |
| this.mView = mTab.customView | |
| } else { | |
| this.mView = LayoutInflater.from(parent.context) | |
| .inflate(R.layout.tab_custom_icon, parent, false) | |
| } | |
| if (mView != null) { | |
| this.mIconView = mView.findViewById<View>(R.id.tab_icon) as ImageView | |
| this.mBadgeTextView = mView.findViewById<View>(R.id.tab_badge) as TextView | |
| } | |
| if (this.mBadgeTextView != null) { | |
| this.mHasBadge = mBadgeTextView!!.visibility == View.VISIBLE | |
| try { | |
| this.mBadgeCount = Integer.parseInt(mBadgeTextView!!.text.toString()) | |
| } catch (er: NumberFormatException) { | |
| er.printStackTrace() | |
| this.mBadgeCount = INVALID_NUMBER | |
| } | |
| } | |
| if (this.mIconView != null) { | |
| mIconDrawable = mIconView!!.drawable | |
| } | |
| } | |
| fun imageContentDescription(contentDescription: String): Builder { | |
| mIconView?.contentDescription = contentDescription | |
| return this | |
| } | |
| /** | |
| * The related Tab is about to have a badge | |
| * | |
| * @return this builder | |
| */ | |
| fun hasBadge(): Builder { | |
| mHasBadge = true | |
| return this | |
| } | |
| /** | |
| * The related Tab is not about to have a badge | |
| * | |
| * @return this builder | |
| */ | |
| fun noBadge(): Builder { | |
| mHasBadge = false | |
| return this | |
| } | |
| /** | |
| * Dynamically set the availability of tab's badge | |
| * | |
| * @param hasBadge | |
| * @return this builder | |
| */ | |
| // This method is used for DEBUG purpose only | |
| /*hide*/ | |
| fun badge(hasBadge: Boolean): Builder { | |
| mHasBadge = hasBadge | |
| return this | |
| } | |
| /** | |
| * Set icon custom drawable by Resource ID; | |
| * | |
| * @param drawableRes | |
| * @return this builder | |
| */ | |
| fun icon(drawableRes: Int): Builder { | |
| mIconDrawable = getDrawableCompat(mContext, drawableRes) | |
| return this | |
| } | |
| /** | |
| * Set icon custom drawable by Drawable Object | |
| * | |
| * @param drawable | |
| * @return this builder | |
| */ | |
| fun icon(drawable: Drawable?): Builder { | |
| mIconDrawable = drawable | |
| return this | |
| } | |
| /** | |
| * Set drawable color. Use this when user wants to change drawable's color filter | |
| * | |
| * @param color | |
| * @return this builder | |
| */ | |
| fun iconColor(color: Int?): Builder { | |
| mIconColorFilter = color | |
| return this | |
| } | |
| /** | |
| * increase current badge by 1 | |
| * | |
| * @return this builder | |
| */ | |
| fun increase(): Builder { | |
| mBadgeCount = if (mBadgeTextView == null) | |
| INVALID_NUMBER | |
| else | |
| Integer.parseInt(mBadgeTextView!!.text.toString()) + 1 | |
| return this | |
| } | |
| /** | |
| * decrease current badge by 1 | |
| * | |
| * @return | |
| */ | |
| fun decrease(): Builder { | |
| mBadgeCount = if (mBadgeTextView == null) | |
| INVALID_NUMBER | |
| else | |
| Integer.parseInt(mBadgeTextView!!.text.toString()) - 1 | |
| return this | |
| } | |
| /** | |
| * set badge count | |
| * | |
| * @param count expected badge number | |
| * @return this builder | |
| */ | |
| public fun badgeCount(count: Int): Builder { | |
| mBadgeCount = count | |
| return this | |
| } | |
| /** | |
| * Build the current Tab icon's custom view | |
| */ | |
| fun build() { | |
| if (mView == null) { | |
| return | |
| } | |
| // update badge counter | |
| if (mBadgeTextView != null) { | |
| mBadgeTextView!!.text = formatBadgeNumber(mBadgeCount) | |
| if (mHasBadge) { | |
| mBadgeTextView!!.visibility = View.VISIBLE | |
| } else { | |
| // set to View#INVISIBLE to not screw up the layout | |
| mBadgeTextView!!.visibility = View.INVISIBLE | |
| } | |
| } | |
| // update icon drawable | |
| if (mIconView != null && mIconDrawable != null) { | |
| mIconView!!.setImageDrawable(mIconDrawable!!.mutate()) | |
| // be careful if you modify this. make sure your result matches your expectation. | |
| if (mIconColorFilter != null) { | |
| mIconDrawable!!.setColorFilter(mIconColorFilter!!, PorterDuff.Mode.MULTIPLY) | |
| } | |
| } | |
| mTab.customView = mView | |
| } | |
| companion object { | |
| /** | |
| * This badge widget must not support this value. | |
| */ | |
| private val INVALID_NUMBER = Integer.MIN_VALUE | |
| } | |
| } | |
| companion object { | |
| private fun getDrawableCompat(context: Context, drawableRes: Int): Drawable? { | |
| var drawable: Drawable? = null | |
| try { | |
| drawable = context.resources.getDrawable(drawableRes, context.theme) | |
| } catch (ex: NullPointerException) { | |
| ex.printStackTrace() | |
| } | |
| return drawable | |
| } | |
| /** | |
| * This format must follow User's badge policy. | |
| * | |
| * @param value of current badge | |
| * @return corresponding badge number. TextView need to be passed by a String/CharSequence | |
| */ | |
| private fun formatBadgeNumber(value: Int): String { | |
| if (value < 0) { | |
| return "-" + formatBadgeNumber(-value) | |
| } | |
| return if (value <= 10) { | |
| // equivalent to String#valueOf(int); | |
| Integer.toString(value) | |
| } else "10+" | |
| // my own policy | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?xml version="1.0" encoding="utf-8"?> | |
| <FrameLayout | |
| xmlns:android="http://schemas.android.com/apk/res/android" | |
| android:layout_width="@dimen/actionbar_height" | |
| android:layout_height="@dimen/actionbar_height" | |
| android:background="@android:color/transparent" | |
| android:gravity="center" | |
| android:paddingBottom="4dp" | |
| android:paddingLeft="12dp" | |
| android:paddingRight="12dp" | |
| android:paddingTop="4dp"> | |
| <ImageView | |
| android:id="@+id/tab_icon" | |
| android:layout_width="match_parent" | |
| android:layout_height="match_parent" | |
| android:layout_gravity="center" | |
| android:adjustViewBounds="true" | |
| android:background="@android:color/transparent"/> | |
| <TextView | |
| android:id="@+id/tab_badge" | |
| android:layout_width="wrap_content" | |
| android:layout_height="wrap_content" | |
| android:layout_gravity="top|end" | |
| android:layout_marginTop="4dp" | |
| android:background="@drawable/badge" | |
| android:paddingBottom="1dp" | |
| android:paddingLeft="6dp" | |
| android:paddingRight="6dp" | |
| android:paddingTop="1dp" | |
| android:textAppearance="@style/TextAppearance.AppCompat.Small" | |
| android:textColor="@android:color/white" | |
| android:textSize="12dp"/> | |
| </FrameLayout> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment