カレンダーをスクロール
何をやったか(結論)
家計簿アプリを作っていて、カレンダーをUIに入れたかった。カレンダーを簡単に作れるライブラリがあったので使おうとした。しかし、横にスクロールして前後の月のカレンダーを表示することもデフォルトで可能だったが、現在表示されている月を取得する方法がわからなかった。そこで、HorizontalPagerを使って解決した。
環境
- OS:Windows 11 Home
- Android Studio Koala Feature Drop | 2024.1.2 Patch 1
Build #AI-241.19072.14.2412.12360217, built on September 13, 2024
Runtime version: 17.0.11+0--11852314 amd64
VM: OpenJDK 64-Bit Server VM by JetBrains s.r.o.
手順
ライブラリのimport
koalaの場合、kizitonwoseのREADME.mdに表示されているjitpack.ioの書き方だとうまくいかなかった。 次のように書かないといけない。
settings.gradle.kts
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
//ここに自分で追加する
maven {
url = uri("https://jitpack.io")
}
}
}
参考 : Adding maven { url "https://jitpack.io" }
build.gradle.ktsのdependencies内には次を追加する
build.gradle.kts
dependencies {
val calendar_version="2.6.0"
// The compose calendar library for Android
implementation("com.kizitonwose.calendar:compose:${calendar_version}")
..
..
}
カレンダーの表示
元々のコード
@Composable
fun CalendarDisplay() {
// 現在の年月
val currentMonth = remember { YearMonth.now() }
// 現在より前の年月
val startMonth = remember { currentMonth.minusMonths(100) }
// 現在より後の年月
val endMonth = remember { currentMonth.plusMonths(100) }
// 曜日
val daysOfWeek = remember { daysOfWeek() }
// カレンダーの状態を持つ
val state = rememberCalendarState(
startMonth = startMonth,
endMonth = endMonth,
firstVisibleMonth = currentMonth,
firstDayOfWeek = daysOfWeek.first(),
outDateStyle = OutDateStyle.EndOfGrid
)
// 横スクロールのカレンダーを作成するためのComposable関数
// 縦スクロールのVerticalなどもある
HorizontalCalendar(
state = state,
// 日付を表示する部分
dayContent = {Day(it)},
// カレンダーのヘッダー
monthHeader = {DaysOfWeekTitle(daysOfWeek = daysOfWeek)}
)
}
@Composable
fun DaysOfWeekTitle(daysOfWeek: List<DayOfWeek>) {
Row(
modifier = Modifier
.fillMaxWidth()
) {
for (dayOfWeek in daysOfWeek) {
Text(
modifier = Modifier.weight(1f),
textAlign = TextAlign.Center,
text = dayOfWeek.getDisplayName(TextStyle.SHORT, Locale.getDefault())
)
}
}
}
@Composable
fun Day(day: CalendarDay) {
Box(
modifier = Modifier
.aspectRatio(1f),
contentAlignment = Alignment.Center
) {
Text(
text = day.date.dayOfMonth.toString(),
// ここで今月でないものの日付をグレーアウトさせている
color = if (day.position == DayPosition.MonthDate) Color.Black else Color.Gray
)
}
}
カレンダーの修正
yearとmonthを渡すことで特定の月のカレンダーを作るように変更した。また横にスクロールするのはPagerを使って行うので、startMonthとendMonthを一致させた。
@Composable
fun CalendarDisplay(calendarYear:Int, calendarMonth:Int ) {
// 現在の年月
val calendarYearMonth = YearMonth.of(calendarYear,calendarMonth)
// 現在より前の年月
val startMonth = calendarYearMonth.minusMonths(0)
// 現在より後の年月
val endMonth = calendarYearMonth.plusMonths(0)
// 曜日
val daysOfWeek = daysOfWeek()
// カレンダーの状態を持つ
val state = rememberCalendarState(
startMonth = startMonth,
endMonth = endMonth,
firstVisibleMonth = calendarYearMonth,
firstDayOfWeek = daysOfWeek.first(),
outDateStyle = OutDateStyle.EndOfGrid
)
// 横スクロールのカレンダーを作成するためのComposable関数
// 縦スクロールのVerticalなどもある
HorizontalCalendar(
state = state,
// 日付を表示する部分
dayContent = {Day(it)},
// カレンダーのヘッダー
monthHeader = {DaysOfWeekTitle(daysOfWeek = daysOfWeek)},
//ユーザーのスクロール
userScrollEnabled = false
)
}
HorizontalPagerの基本的な使い方
参考 : Composeでのページャー
サンプルコード
// Display 10 items
val pagerState = rememberPagerState(pageCount = {
10
})
VerticalPager(state = pagerState) { page ->
// Our page content
Text(
text = "Page: $page",
modifier = Modifier.fillMaxWidth()
)
}
- 特にページの状態が変更したとき(通知を受け取る)
サンプルコード
val pagerState = rememberPagerState(pageCount = {
10
})
LaunchedEffect(pagerState) {
// Collect from the a snapshotFlow reading the currentPage
snapshotFlow { pagerState.currentPage }.collect { page ->
// Do something with each page change, for example:
// viewModel.sendPageSelectedEvent(page)
Log.d("Page change", "Page changed to $page")
}
}
VerticalPager(
state = pagerState,
) { page ->
Text(text = "Page: $page")
}
HorizontalPagerを使ってCalendarをスクロールする
viewModelの中身は以下のようになっていて、calendarDateを保存している。
ExpenseViewModel.kt
class ExpenseViewModel():ViewModel() {
private val calendarDate = mutableStateOf(LocalDate.now())
fun getCalendarYear(): Int {
return calendarDate.value.year
}
fun getCalendarMonth():Int{
return calendarDate.value.monthValue
}
fun incrementMonth(){
calendarDate.value = calendarDate.value.plusMonths(1)
}
fun decrementMonth(){
calendarDate.value = calendarDate.value.minusMonths(1)
}
//MainViewもLazyColumnに表示する
fun getMonthExpenses():List<Expense>{
return DummyExpenses.expensesList.filter {
it.datetime.year == calendarDate.value.year &&
it.datetime.month == calendarDate.value.month
}
}
}
スクロール部分の実装
MainView.kt
//カレンダー横スクロールのため
val calendarPagerState= rememberPagerState(initialPage = 1){ 3 }
var previousCalendarPage by remember { mutableStateOf(calendarPagerState.currentPage) }
....
....
Row(
modifier = Modifier.fillMaxWidth(),
){
HorizontalPager(
state = calendarPagerState,
modifier = Modifier.weight(1f)
) {
//viewModelからCalendarの年と月を渡す
CalendarDisplay(viewModel.getCalendarYear(),viewModel.getCalendarMonth())
}
}
/**************************************************/
/*カレンダーをスクロールしたときにviewModel内の日付を変更する*/
/**************************************************/
LaunchedEffect(calendarPagerState) {
snapshotFlow { calendarPagerState.currentPage }
.distinctUntilChanged()
.collect{
currentPage->
if(currentPage>previousCalendarPage){
//右にスクロールしたとき
//incrementすることによって次の月に切り替える
viewModel.incrementMonth()
}else if (currentPage<previousCalendarPage) {
//左にスクロールしたとき
//decrementすることによって前の月に切り替える
viewModel.decrementMonth()
}
previousCalendarPage=currentPage
}
}
....
....
これでviewModel内のcalendarDateと表示されているカレンダーの年月が一致するようになった。
質問あればお願いしますm(__)m
参考文献