はじめに
通知の件数表示などに使える BadgeDrawable
(Badge - Material Design)ですが、サクッと使用することができ非常に便利です。
↓のように書くと…
bottomNavigationView.getOrCreateBadge(R.id.navigation_home).apply {
number = 10
}
ただ、現状(material-components-android v1.2.1)だと BottomNavigationView
か TabLayout
からしかサクッと使用することができません。
例えば BottomNavigationView
で使っているBadgeと同じ見た目のものを他の箇所でも使いたいという場合にこまってしまいます。
本記事では、BadgeDrawable
を ImageView
でなんとか使ってみる方法をご紹介したいと思います。
↓ "This is home Fragment" の右にあるBadgeが BadgeDrawable
を ImageView
で表示してみたものです。
※ Material Componentsは com.google.android.material:material:1.2.1
で確認しています。
※ Badgeに関しては https://qiita.com/youmeee/items/75795f5314d852d74485 の記事が非常に参考になりました。
おもむろに使ってみるが…
BadgeDrawable
には create(Context)
というそれっぽいメソッドがあるのですが、それを使って BadgeDrawable
のインスタンスを生成し、 ImageView
にセットしてみても何も表示されません。
val badgeImageView: ImageView = ...
val badgeDrawable = BadgeDrawable.create(context).apply {
number = 10
}
badgeImageView.setImageDrawable(badgeDrawable)
表示されない理由としては、 create(Context)
しただけでは Drawable
の サイズ(bounds)がセットされていない ためです。
BottomNavigationViewのgetOrCreateBadge(int)を追ってみる
そこで、 BottomNavigationView
のBadgeを表示するメソッドである getOrCreateBadge(int)
を追ってみると、
BadgeUtilsのattachBadgeDrawable というメソッドが呼ばれ、その中でさらに
setBadgeDrawableBounds というメソッドが呼ばれているのがわかります。
この setBadgeDrawableBounds
というメソッドがサイズの計算をしているところとなります。
BadgeUtils
は @RestrictTo(Scope.LIBRARY)
アノテーションが付いているため、そのまま使うとLintの警告が出てしまいます。幸いメソッドの中身の処理は複雑ではないので、それを参考にしてサイズの計算を実現させれば良さそうです。
public static void setBadgeDrawableBounds(
@NonNull BadgeDrawable badgeDrawable,
@NonNull View anchor,
@NonNull FrameLayout compatBadgeParent) {
Rect badgeBounds = new Rect();
View badgeParent = USE_COMPAT_PARENT ? compatBadgeParent : anchor;
badgeParent.getDrawingRect(badgeBounds);
badgeDrawable.setBounds(badgeBounds);
badgeDrawable.updateBadgeCoordinates(anchor, compatBadgeParent);
}
ImageViewに表示させる
細かいworkaroundを加えつつ、 BadgeDrawable
を ImageView
で使うことができたコードが下記になります。
val badgeImageView: ImageView = ...
val badgeDrawable = BadgeDrawable.create(context).apply {
number = 10
// (1)
val badgeBounds = Rect()
badgeImageView.getDrawingRect(badgeBounds)
this.bounds = badgeBounds
this.updateBadgeCoordinates(badgeImageView, null)
// (2)
this.horizontalOffset = (-6).dp
this.verticalOffset = 8.dp
}
badgeImageView.setImageDrawable(badgeDrawable)
(1)
setBadgeDrawableBounds
の内部でやっている処理を同じように実行しています。
setBadgeDrawableBounds
ではSDKバージョンによる分岐がありますが省いています。 compatBadgeParent
もnullとしています。Badgeのアンカーに badgeImageView
を指定するのは違和感がありますが、これで動いたので問題は無いようでした。
(2)
workaround感満載ですが、描画の位置がズレてしまうようだったのでoffsetを調整しています。
offsetなし | offsetあり |
---|---|
↑ ImageView の background に色を付けた様子。 |
その他
Badgeを表示する場合、描画が欠けないように、親レイアウトにclippingの指定も必要になりました。
android:clipChildren="false"
android:clipToPadding="false"
表示された
以上の工程で、なんとか BadgeDrawable
を ImageView
で使うことができました。
ImageView
だとアニメーションなども簡単に適用できるので、いい感じです。
まとめ
BadgeDrawable
を ImageView
でなんとか使ってみる方法をご紹介しました。
workaroundなどもありつつも、 ImageView
にBadgeを表示することができ、アニメーションや細かい位置調整なども可能になるので、使用の幅が広がるのではないかと思います。
BadgeDrawable
についてアドバイスなどありましたら、コメントいただけると嬉しいです。
参考