기존 xml로 작업한 PWFB 프로젝트를 Compose로 변경하면서 기존에 구현했던 캘린더 UI를 어떻게 구현해야 할까 고민이 많았다. 캘린더는 MaterialCalendarView를 사용하고 있었는데, 다른 캘린더 라이브러리나 직접 구현하는 것보다는 기존 코드를 덜 수정하는 방향으로 진행하고 싶었다.
Compose에는 AndroidView라는 컴포저블이 있는데, Compose에서 아직 사용할 수 없는 UI 요소를 사용하는 경우에 쓰인다. AndroidView 컴포저블을 사용해서 MaterialCalendarView를 사용할 수 있었다.
AndroidView
@Composable
@UiComposable
fun <T : View> AndroidView(
factory: (Context) -> T,
modifier: Modifier = Modifier,
update: (T) -> Unit = NoOpUpdate
) {
AndroidView(
factory = factory,
modifier = modifier,
update = update,
onRelease = NoOpUpdate
)
}
AndroindView는 매개변수로 factory, modifier, update를 받는다.
◾ factory
factory 블록은 View를 반환하는 블록을 생성하여 View 요소 또는 계층 구조를 Compose UI에 포함할 수 있게 한다.
◾ update
View가 확장되었을 때 호출되는 콜백으로 View의 정보와 상태를 업데이트를 할 수 있다.
◾ modifier
상위 Composable에서 위치를 설정하는 등의 목적으로 사용한다.
AndroidView(
factory = { MaterialCalendarView(it) },
modifier = Modifier,
update = { calendarView ->
calendarView.setHeaderTextAppearance(R.style.CalendarWidgetHeader) // 연, 월 헤더 스타일
calendarView.setWeekDayTextAppearance(R.style.CalenderViewWeekCustomText) // 1~12월, 월간 표시
calendarView.setDateTextAppearance(R.style.CalenderViewDateCustomText) // 일~토, 주간 표시
// '년' '월'로 표시
calendarView.setTitleFormatter { day ->
val inputText = day.date
val calendarHeaderElements = inputText.toString().split("-")
val calendarHeaderBuilder = StringBuilder()
calendarHeaderBuilder.append(calendarHeaderElements[0]).append("년 ")
.append(calendarHeaderElements[1]).append("월")
calendarHeaderBuilder.toString()
}
...
}
)
factory에 MaterialCalendarView를 전달하고, update에서 해당 view를 사용하여 캘린더의 스타일을 변경했다.
view를 통해 대부분 기존 코드를 유지할 수 있었지만 Decorator가 필요한 수정이 필요했다.
Decorator 활용
/**
* themes.xml
*/
<style name="CalenderViewCustom" parent="Theme.AppCompat">
<item name="colorAccent">@color/c_31caab3f</item>
<item name="android:textStyle">bold</item>
</style>
/**
* activity_day.xml
*/
<com.prolificinteractive.materialcalendarview.MaterialCalendarView
...
android:theme="@style/CalenderViewCustom" />
MaterialCalendarView에서 날짜를 클릭했을 때 서클의 색 변경은 기존 코드에선 theme를 적용하여 쉽게 적용할 수 있다.
![[Android][Compose] MaterialCalendarView를 Compose에서 사용하기 - undefined - undefined - Decorator 활용 [Android][Compose] MaterialCalendarView를 Compose에서 사용하기 - undefined - undefined - Decorator 활용](http://t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png)
하지만 Compose로 변경하면서 xml을 사용할 수 없기 때문에 다른 방법을 적용해야만 했다. MaterialCalendarView는
Decorator를 사용해서 특정 날짜에 대한 커스텀 스타일을 지정할 수 있다.
/**
* MainActivity
*/
val drawableList: List<Drawable?> = listOf(
ResourcesCompat.getDrawable(
this.resources,
R.drawable.shape_calendar_today,
null
),
ResourcesCompat.getDrawable(
this.resources,
R.drawable.selector_calendar_day,
null
)
)
NavHost(
navController = navController,
startDestination = if(isFirstInit) SCREEN_NAME else SCREEN_HOME
) {
composable(route = SCREEN_DAY) { DdayScreen(navController, drawableList) }
...
}
MainActivity에서 Decorator로 사용할 drawable 리소스를 List로 묶어 Compose에 전달했다. Compose에서는 drawable 리소스에 접근하지 못해서 해당 과정이 필요했다.
/**
* DdayScreen
*/
@Composable
fun CalendarView(
dDayViewModel: DdayViewModel,
datePrefState: MutableState<String>,
drawableList: List<Drawable?>
) {
AndroidView(
factory = { MaterialCalendarView(it) },
modifier = Modifier,
update = { calendarView ->
...
val dayDisableDecorator = DayDisableDecorator(disabledDates, today, Gray.toArgb())
val todayDecorator = drawableList[0]?.let { TodayDecorator(Yellow40.toArgb(), it) }
calendarView.addDecorators(dayDisableDecorator, todayDecorator)
calendarView.setOnDateChangedListener { _, date, _ ->
calendarView.addDecorators(
ClearDecorator(White.toArgb(), date),
dayDisableDecorator,
todayDecorator,
drawableList[1]?.let { SelectDecorator(Yellow40.toArgb(), it, date) }
)
...
}
}
)
}
/**
* 오늘 날짜 표시 데코
*/
class TodayDecorator(
private var color: Int,
private var drawable: Drawable
) :DayViewDecorator {
private var date = CalendarDay.today()
override fun shouldDecorate(day: CalendarDay?): Boolean {
return day?.equals(date)!!
}
override fun decorate(view: DayViewFacade?) {
view?.addSpan(object: ForegroundColorSpan(color){})
view?.setBackgroundDrawable(drawable)
}
}
/**
* 날짜 선택 시 데코
*/
class SelectDecorator(
private var color: Int,
private var drawable: Drawable,
private var date: CalendarDay
) : DayViewDecorator {
override fun shouldDecorate(day: CalendarDay): Boolean {
return day == date
}
override fun decorate(view: DayViewFacade) {
view.addSpan(object: ForegroundColorSpan(color){})
view.setSelectionDrawable(drawable)
}
}
현재 날짜인 경우 setBackgroundDrawable()에 shape drawable,
날짜를 선택했을 때는 setSelectionDrawable()에 select drawable을 전달하면서 해결하였다.
![[Android][Compose] MaterialCalendarView를 Compose에서 사용하기 - undefined - undefined - Decorator 활용 [Android][Compose] MaterialCalendarView를 Compose에서 사용하기 - undefined - undefined - Decorator 활용](https://blog.kakaocdn.net/dn/cBK716/btsHun6Kw7U/YhHT2T08jZGjPEw1dfAWM1/img.gif)
'작업 일지' 카테고리의 다른 글
[Android] 내비게이션의 다음 경로 정보 구하기#2 (0) | 2024.03.07 |
---|---|
[Android] 내비게이션의 다음 경로 정보 구하기#1 (5) | 2024.03.05 |
[Android][Compose] Scaffold-topBar가 UI를 가리는 현상 (0) | 2023.05.31 |
[Android] 리스트에서 아이템 삭제 후 페이징할 때 조심할 점 (0) | 2023.01.02 |
[Android] Mqtt, subjectAltNames에 대한 고민 (0) | 2022.12.21 |
댓글