🎨
【Android】ライブラリCalendarのカスタマイズ
はじめに
前回試した Calendar の見た目や挙動を調整した際のものになります。
今回カスタマイズしたBefore → Afterは以下になります。
-
Before
-
After
環境構築や準備
各バージョンや環境
- Android Studio Giraffe | 2022.3.1 Patch 1
- com.kizitonwose.calendar:compose: 2.4.0-beta01
$ sw_vers
ProductName: macOS
ProductVersion: 13.4
BuildVersion: 22F66
ベースとなる実装
@Composable
fun CalendarCompose() {
val currentMonth = remember { YearMonth.now() }
val startMonth = remember { currentMonth.minusMonths(100) } // Adjust as needed
val endMonth = remember { currentMonth.plusMonths(100) } // Adjust as needed
val firstDayOfWeek = remember { firstDayOfWeekFromLocale() } // Available from the library
val state = rememberCalendarState(
startMonth = startMonth,
endMonth = endMonth,
firstVisibleMonth = currentMonth,
firstDayOfWeek = firstDayOfWeek
)
Column(modifier = Modifier.fillMaxSize()) {
HorizontalCalendar(
state = state,
dayContent = { Day(it) }
)
}
}
@Composable
private fun Day(day: CalendarDay) {
Box(
modifier = Modifier
.aspectRatio(1f), // This is important for square sizing!
contentAlignment = Alignment.Center
) {
Text(text = day.date.dayOfMonth.toString())
}
}
前回実装した最低限の実装になります。動作としては↓の様に左右スワイプで月変更ができるだけのものです。
実装
1. 曜日を表示する
MonthHeader
を追加
1-1. fun DayOfWeek.displayText(uppercase: Boolean = false): String {
return getDisplayName(TextStyle.SHORT, Locale.getDefault()).let { value ->
if (uppercase) value.uppercase(Locale.getDefault()) else value
}
}
@Composable
private fun MonthHeader(daysOfWeek: List<DayOfWeek>) {
Row(
modifier = Modifier
.fillMaxWidth()
.testTag("MonthHeader"),
) {
for (dayOfWeek in daysOfWeek) {
Text(
modifier = Modifier.weight(1f),
textAlign = TextAlign.Center,
fontSize = 15.sp,
text = dayOfWeek.displayText(),
fontWeight = FontWeight.Medium,
)
}
}
}
HorizontalCalendar
に反映する
1-2. Column(modifier = Modifier.fillMaxSize()) {
HorizontalCalendar(
state = state,
dayContent = { Day(it) },
monthHeader = { month ->
val daysOfWeek = month.weekDays.first().map { it.date.dayOfWeek }
MonthHeader(daysOfWeek = daysOfWeek)
}
)
}
2. 表示月に含まれない日のTextColorを変更する
Day Composable で day.position
による判定を追加します。
@Composable
private fun Day(day: CalendarDay) {
val textColor = when (day.position) {
DayPosition.MonthDate -> Color.Unspecified
DayPosition.InDate, DayPosition.OutDate -> Color.LightGray
}
Box(
modifier = Modifier
.aspectRatio(1f),
contentAlignment = Alignment.Center
) {
Text(text = day.date.dayOfMonth.toString(), color = textColor)
}
}
3. body部分の背景色を変更する
HorizontalCalendar
に monthBody
を追加
monthBody = { _, content ->
Box(
modifier = Modifier.background(
brush = Brush.verticalGradient(
colors = listOf(
MaterialTheme.colorScheme.primary.copy(alpha = 0.2f),
MaterialTheme.colorScheme.tertiary.copy(alpha = 0.2f)
)
)
)
) {
content()
}
},
4. 日付に枠線を追加する
HorizontalCalendar(
...
monthBody = { _, content ->
Box(
modifier = Modifier
.background(
brush = Brush.verticalGradient(
colors = listOf(
MaterialTheme.colorScheme.primary.copy(alpha = 0.2f), // TODO: 着せ替え考慮
MaterialTheme.colorScheme.tertiary.copy(alpha = 0.2f) // TODO: 着せ替え考慮
)
)
)
.border(width = 0.5.dp, color = Color.LightGray) // 追加
) {
content()
}
},
)
@Composable
private fun Day(day: CalendarDay) {
val textColor = when (day.position) {
DayPosition.MonthDate -> Color.Unspecified
DayPosition.InDate, DayPosition.OutDate -> Color.LightGray
}
Box(
modifier = Modifier
.aspectRatio(1f)
.border(width = 0.5.dp, color = Color.LightGray) // 追加
.padding(1.dp),
contentAlignment = Alignment.Center
) {
Text(text = day.date.dayOfMonth.toString(), color = textColor)
}
}
5. 日付を左上に寄せて土日の色を変える
@Composable
private fun Day(day: CalendarDay) {
val textColor = when (day.position) {
DayPosition.MonthDate -> when (day.date.dayOfWeek) {
DayOfWeek.SATURDAY -> Color.Blue
DayOfWeek.SUNDAY -> Color.Red
else -> Color.Unspecified
}
DayPosition.InDate, DayPosition.OutDate -> Color.LightGray
}
Box(
modifier = Modifier
.aspectRatio(1f)
.border(width = 0.5.dp, color = Color.LightGray)
.padding(1.dp),
contentAlignment = Alignment.Center
) {
Text(
modifier = Modifier
.align(Alignment.TopStart)
.padding(top = 3.dp, start = 4.dp),
text = day.date.dayOfMonth.toString(), color = textColor
)
}
}
6. 選択できるようにする
まずは選択された日付を保持する以下変数を追加します。
var selection by remember { mutableStateOf<CalendarDay?>(null) }
次に Day
Composableがクリックできるように Modifier
に clickable
を追します。
@Composable
private fun Day(day: CalendarDay, onClick: (CalendarDay) -> Unit = {}) {
...
Box(
modifier = Modifier
.aspectRatio(1f)
.border(width = 0.5.dp, color = Color.LightGray)
.padding(1.dp)
.clickable { // ←追加
onClick(day)
},
contentAlignment = Alignment.Center
) {
...
}
}
この状態でクリックされた時にログが出る様にしてみます。
HorizontalCalendar(
state = state,
dayContent = {
Day(it) { clickDay ->
Timber.d("clickDay: $clickDay")
}
},
クリックした日付がログに出力されています↓
また、表示している月以外の月の日付をクリックしても反応しないようにするには clickable
の enabled
に以下を設定します。
.clickable(enabled = day.position == DayPosition.MonthDate) { // ←追加
onClick(day)
},
最後に選択日付を反映します。
Day(it, isSelected = selection == it) { clickDay ->
selection = clickDay
}
...
@Composable
private fun Day(
day: CalendarDay,
isSelected: Boolean = false, // ← 追加
onClick: (CalendarDay) -> Unit = {}
) {
...
Box(
modifier = Modifier
.aspectRatio(1f)
.background(color = if (isSelected) Color.Cyan else Color.Transparent) // ← 追加
.border(width = 0.5.dp, color = Color.LightGray)
.padding(1.dp)
.clickable(enabled = day.position == DayPosition.MonthDate) {
onClick(day)
},
contentAlignment = Alignment.Center
) {
...
}
}
以上になります。
Discussion