본문 바로가기

ANDROID/UI - UX

[안드로이드/UI-UX] Material BadgeDrawable 로 Badge를 원하는 뷰에 적용하기

 

 

Material Design의 BottomNavigationView를 사용해본 적이 있다면 매우 간편하게 Badge를 업데이트 할 수 있음을 경험한 적이 있을 것이다. 

 

금번에 뱃지를 BottomNavigationView가 아닌 별도 View(이를테면 TextView 라던지)에 달아야 하는 경우가 생겼는데,

 

쓸만하고 예쁘다 싶은 라이브러리가 잘 없었기에, Material의 Badge가 떠오르는 건 어쩔 수가 없었다.

 

그런데 다행스럽게도 구글에서 Material Components 1.1.0-alpha09 버전부터 Badge를 지원하고 있다는 반가운 소식!

 

 

Release 1.1.0-alpha09 · material-components/material-components-android

Dependency Updates: Updated to androidx.viewpager2:viewpager2:1.0.0-beta02 Removed androidx.legacy:legacy-support-core-ui:1.0.0 Removed androidx.legacy:legacy-support-core-utils:1.0.0 Java 7: This...

github.com

기쁜 마음으로 텍스트 뷰 옆에 뱃지를 붙이고 싶어 공식 문서를 들여다 보았으나..

 

여타 잘 정리되지 않은 구글 문서처럼 해당 페이지만을 기반으로 적용을 하는 건 쉽지 않았다 

 

요즘 들어서 눈이 좀 아프다. 왜일까?

결국 참을인 세 번에 검색 세 번을 해서 참조하기 좋은 소스를 좀 찾아낼 수 있었는데 핵심은

 

FrameLayout과 FrameLayout 안에 nested layout을 사용하는 것이었다. (이유에 대해선 Material Design 공식 문서를 참조)

 

< BadgeDrawable 일반 View에 적용하는 법 >

 

1. MaterialDesign dependency를 gradle에 추가한다. (20년 10월 14일자 최신 버전 : 1.2.1)

implementation "com.google.android.material:material:1.2.1"

2. BadgeDrawable을 적용하고자 하는 layout을 FrameLayout으로 감싼다.

 

뱃지는 FrameLayout 내부에서만 보여지기 때문에, FrameLayout을 작게 잡으면 Badge가 잘리는 현상이 생긴다.

 

3. BadgeDrawable을 적용하고자 하는 layout을 아래의 스니펫을 참조해 적용한다.

 

본인의 경우 Fragment 내에서 뱃지를 적용했기 때문에 requireContext()를 이용해 context를 넘겨주었다. color를 넘겨줄 때도 Fragment에서 사용하던 방식을 썼다.

 

  • BadgeDrawable을 create할 때 원하는 디자인 요소와 설정을 적용한다. (테스트를 위해 별도로 변수로 지정 안했지만, 지정하게 되면 이후에 조작도 가능할 듯 하다)
  • BadgeDrawable을 감싸주었던 frameLayout의 foreground에 설정한다.
  • BadgeDrawable을 감싸준 frameLayout에 addOnLayoutChangeListener를 지정해, 내부에서 attatchBadgeDrawable을 수행한다.
  • attatchBadgeDrawable(뱃지 Drawable, 뱃지를 달고자 하는 target, 먼저 지정해준 frameLayout)
< 추가 >

참조로 badgeGravity에는 네 개의 옵션이 있으며, 이에 따라 Badge가 달리는 지점을 설정해 줄 수 있음.

Badge는 붙이고자 하는 대상 뷰 영역에 붙기 때문에, 공간을 두고자 하는 경우 해당 뷰의 padding을 주는 등의 처리를 하면 간격 처리를 손쉽게 가능.

뱃지의 Visibility는 BadgeDrawable의 visiblity를 조정한 뒤, BadgeDrawable이 포함된 FrameLayout을 invalidate해 주어야 최종 반영되는 것을 확인했다.

BadgeDrawable.create(requireContext()).apply {
	number = 5
    backgroundColor = ContextCompat.getColor(requireContext(), R.color.bgColor)
    badgeTextColor = ContextCompat.getColor(requireContext(), R.color.textColor)
    badgeGravity = BadgeDrawable.TOP_END
    }.let {
    	binding.frameLayout.foreground = it
        binding.frameLayout.addOnLayoutChangeListener {v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom ->
                BadgeUtils.attachBadgeDrawable(it, binding.target, binding.frameLayout)
    }
    

 

마지막으로 Material Component들은 해당 레이아웃이 사용되는 Activity의 테마가 Material이 달린 것 중에 하나로 지정되야 하는 걸 유의하자.

 

그리고 혹 적용 중 ... can only be called from within the same library group 식의 오류가 발생한다면, 프로젝트 레벨의 gradle 파일에 아래 lint 옵션을 적용하자.

 

android {
  lintOptions {
    disable 'RestrictedApi'
  }
}

 

문서는 아쉽지만 BadgeDrawable의 클래스 내부에 도큐먼트가 잘 정리되어 있으니, 사용하고 싶은 요소들, 제어하고 싶은 요소들을 찾아서 적용한다면 어렵지 않게 응용이 가능할 것이다!

 

여타 커스텀 뱃지들보다 깔끔하지 않은가!

 

 

< 참조 >

 

 

Material Design

Build beautiful, usable products faster. Material Design is an adaptable system—backed by open-source code—that helps teams build high quality digital experiences.

material.io

 

 

AppCompatActivity.onCreate can only be called from within the same library group

After upgrading to appcompat 25.1.0 I've started getting wired errors. In my code: @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); I get lint

stackoverflow.com

 

Does BadgeDrawable not work for views within a FrameLayout such as buttons, images, textviews etc.?

I have this function in my main activity which doesn't display a badge: private fun setFindShiftBadge(state: HomeState) { val findShiftsBadge = BadgeDrawable.create(this)

stackoverflow.com