Skip to content

Commit

Permalink
Merge pull request #29 from brookmg/feat/badge
Browse files Browse the repository at this point in the history
Add badge to bottom bar item
  • Loading branch information
ibrahimsn98 authored Apr 10, 2020
2 parents 4523114 + 6ed4d92 commit d7f09cd
Show file tree
Hide file tree
Showing 8 changed files with 187 additions and 2 deletions.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,20 @@ bottomBar.setOnItemReselectedListener(object: OnItemReselectedListener {
})
```

- Add badge to a specific item
```kotlin
bottomBar.setBadge(1, Badge(
badgeSize = 20F,
badgeBoxCornerRadius = 8F,
badgeColor = ContextCompat.getColor(this , R.color.colorBadge),
badgeText = "99+",
badgeTextColor = Color.BLACK,
badgeType = BadgeType.BOX // or BadgeType.CIRCLE
))

bottomBar.removeBadge(1)
```

## Customization

```xml
Expand Down
26 changes: 26 additions & 0 deletions app/src/main/java/me/ibrahimsn/smoothbottombar/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package me.ibrahimsn.smoothbottombar

import android.graphics.Color
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import kotlinx.android.synthetic.main.activity_main.*
import me.ibrahimsn.lib.Badge
import me.ibrahimsn.lib.BadgeType

class MainActivity : AppCompatActivity(R.layout.activity_main) {

Expand All @@ -16,5 +20,27 @@ class MainActivity : AppCompatActivity(R.layout.activity_main) {
bottomBar.onItemReselected = {
status.text = "Item $it re-selected"
}

// Delay 1 second before drawing the badges ( just to notice the animation )
bottomBar.postDelayed({
bottomBar.setBadge(0, Badge(
badgeSize = 12F,
badgeColor = ContextCompat.getColor(this , R.color.colorBadge)
))

bottomBar.setBadge(1, Badge(
badgeSize = 20F,
badgeBoxCornerRadius = 8F,
badgeColor = ContextCompat.getColor(this , R.color.colorBadge),
badgeText = "99+",
badgeTextColor = Color.BLACK,
badgeType = BadgeType.BOX
))
} , 1000);

// Remove the badge from item at index 0 after 4 seconds
bottomBar.postDelayed({
bottomBar.removeBadge(0)
} , 4000);
}
}
1 change: 1 addition & 0 deletions app/src/main/res/values/colors.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@

<color name="colorTextPrimary">#ffffff</color>
<color name="colorTextSecondary">#A3FFFFFF</color>
<color name="colorBadge">#f4d415</color>
</resources>
16 changes: 16 additions & 0 deletions lib/src/main/java/me/ibrahimsn/lib/Badge.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package me.ibrahimsn.lib

import android.graphics.Color

/**
* Created by BrookMG on 4/8/2020 in me.ibrahimsn.lib
inside the project SmoothBottomBar .
*/
data class Badge (
var badgeSize: Float,
var badgeText: String = "",
var badgeColor: Int,
var badgeTextColor: Int = Color.BLACK,
var badgeBoxCornerRadius: Float = 8F,
var badgeType: BadgeType = BadgeType.CIRCLE
)
9 changes: 9 additions & 0 deletions lib/src/main/java/me/ibrahimsn/lib/BadgeType.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package me.ibrahimsn.lib

/**
* Created by BrookMG on 4/8/2020 in me.ibrahimsn.lib
inside the project SmoothBottomBar .
*/
enum class BadgeType {
CIRCLE , BOX
}
3 changes: 2 additions & 1 deletion lib/src/main/java/me/ibrahimsn/lib/BottomBarItem.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ data class BottomBarItem(
var title: String,
val icon: Drawable,
var rect: RectF = RectF(),
var alpha: Int
var alpha: Int,
var badge: Badge?
)
2 changes: 1 addition & 1 deletion lib/src/main/java/me/ibrahimsn/lib/BottomBarParser.kt
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,6 @@ class BottomBarParser(private val context: Context, @XmlRes res: Int) {
if (itemDrawable == null)
throw Throwable("Item icon can not be null!")

return BottomBarItem(itemText ?: "", itemDrawable, alpha = 0)
return BottomBarItem(itemText ?: "", itemDrawable, alpha = 0, badge = null)
}
}
118 changes: 118 additions & 0 deletions lib/src/main/java/me/ibrahimsn/lib/SmoothBottomBar.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package me.ibrahimsn.lib

import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ArgbEvaluator
import android.animation.ValueAnimator
import android.annotation.SuppressLint
Expand All @@ -11,10 +13,12 @@ import android.graphics.Paint
import android.graphics.RectF
import android.os.Build
import android.util.AttributeSet
import android.util.Log
import android.view.MotionEvent
import android.view.View
import android.view.animation.DecelerateInterpolator
import androidx.annotation.FontRes
import androidx.core.animation.addListener
import androidx.core.content.res.ResourcesCompat
import androidx.core.graphics.drawable.DrawableCompat
import me.ibrahimsn.lib.Constants.DEFAULT_ANIM_DURATION
Expand All @@ -31,6 +35,7 @@ import me.ibrahimsn.lib.Constants.OPAQUE
import me.ibrahimsn.lib.Constants.TRANSPARENT
import me.ibrahimsn.lib.Constants.WHITE_COLOR_HEX
import kotlin.math.abs
import kotlin.math.min

class SmoothBottomBar : View {

Expand Down Expand Up @@ -90,6 +95,20 @@ class SmoothBottomBar : View {
isFakeBoldText = true
}

private val paintBadge = Paint().apply {
isAntiAlias = true
style = Paint.Style.FILL
strokeWidth = 4f
}

private val paintBadgeText = Paint().apply {
isAntiAlias = true
style = Paint.Style.FILL
textSize = itemTextSize
textAlign = Paint.Align.CENTER
isFakeBoldText = true
}

constructor(context: Context) : this(context, null)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
Expand Down Expand Up @@ -181,6 +200,105 @@ class SmoothBottomBar : View {

this.paintText.alpha = item.alpha
canvas.drawText(item.title, item.rect.centerX() + itemIconSize / 2 + itemIconMargin, item.rect.centerY() - textHeight, paintText)

// Draw item badge
if (item.badge != null) drawBadge(canvas, item)
}
}

// Draw item badge
private fun drawBadge(canvas: Canvas, item: BottomBarItem) {
paintBadge.style = Paint.Style.FILL
paintBadge.color = item.badge?.badgeColor!!

val cy = (height / 2).toFloat() - itemIconSize - itemIconMargin / 2 + 10

val boxRectF = RectF(
item.rect.centerX() + itemIconSize / 2 + 4 - (item.badge?.badgeSize ?: 0F) * 1.2f,
(height / 2).toFloat() - itemIconSize - itemIconMargin / 2 - 8,
(item.rect.centerX() + itemIconSize / 2 + 4) + (item.badge?.badgeSize ?: 0F) * 1.2f,
((height / 2).toFloat() - itemIconSize - itemIconMargin / 2 - 8) + (item.badge?.badgeSize ?: 0F) * 2f
)

(item.badge)?.let {
if (it.badgeType == BadgeType.CIRCLE)
canvas.drawCircle(item.rect.centerX() + itemIconSize / 2 + 4,
cy, it.badgeSize + 2f, paintBadge)
else if (it.badgeType == BadgeType.BOX) {
canvas.drawRoundRect(boxRectF, it.badgeBoxCornerRadius, it.badgeBoxCornerRadius, paintBadge)
}
}

paintBadge.style = Paint.Style.STROKE
paintBadge.color = barBackgroundColor

(item.badge)?.let {
if (it.badgeType == BadgeType.CIRCLE)
canvas.drawCircle(item.rect.centerX() + itemIconSize / 2 + 4,
cy, it.badgeSize + 2f, paintBadge)
else if (it.badgeType == BadgeType.BOX && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
canvas.drawRoundRect(boxRectF, it.badgeBoxCornerRadius, it.badgeBoxCornerRadius, paintBadge)
}
}

(item.badge)?.let {
this.paintBadgeText.color = it.badgeTextColor

val textHeight = (paintBadgeText.descent() + paintBadgeText.ascent()) / 2
canvas.drawText(
it.badgeText,

if (it.badgeType == BadgeType.CIRCLE) item.rect.centerX() + itemIconSize / 2 + 4
else boxRectF.centerX() ,

if (it.badgeType == BadgeType.CIRCLE) {
cy - textHeight
} else boxRectF.centerY() - textHeight,

paintBadgeText)

}
}

// Add item badge
fun setBadge(pos: Int, badge: Badge) {
if (pos >= 0 && pos < items.size && badge.badgeSize > 0f) {
items[pos].badge = badge

val animator = ValueAnimator.ofFloat(0f, badge.badgeSize)
animator.duration = 100

animator.addUpdateListener { animation ->
items[pos].badge?.badgeSize = animation.animatedValue as Float
invalidate()
}

animator.start()
}
}

fun getBadge(pos: Int) : Badge? {
return if (pos >= 0 && pos < items.size) items[pos].badge else null
}

// Remove item badge
fun removeBadge(pos: Int) {
if (pos >= 0 && pos < items.size && items[pos].badge != null && items[pos].badge?.badgeSize!! > 0f) {
val animator = ValueAnimator.ofFloat(items[pos].badge?.badgeSize!!, 0f)
animator.duration = 100
animator.addUpdateListener {
animation -> items[pos].badge?.badgeSize = animation.animatedValue as Float
invalidate()
}

animator.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
super.onAnimationEnd(animation)
items[pos].badge = null
}
})

animator.start()
}
}

Expand Down

0 comments on commit d7f09cd

Please sign in to comment.