通常カレンダーピッカー
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
@OptIn(ExperimentalMaterial3Api::class) @Composable fun GrantDateItem( grantDate: LocalDate, onDateSelected: (LocalDate) -> Unit ) { var showDatePicker by remember { mutableStateOf(false) } Row( modifier = Modifier .fillMaxWidth() .padding(16.dp), verticalAlignment = Alignment.CenterVertically ) { Text( text = "次回有給付与日", style = MaterialTheme.typography.titleMedium ) Spacer(Modifier.weight(1f)) OutlinedButton( onClick = { showDatePicker = true } ) { Text( grantDate.format( DateTimeFormatter.ofPattern("yyyy/MM/dd") ) ) } } if (showDatePicker) { val datePickerState = rememberDatePickerState( initialSelectedDateMillis = grantDate .atStartOfDay(ZoneId.systemDefault()) .toInstant() .toEpochMilli() ) DatePickerDialog( onDismissRequest = { showDatePicker = false }, confirmButton = { TextButton( onClick = { datePickerState.selectedDateMillis?.let { millis -> val selectedDate = Instant.ofEpochMilli(millis) .atZone(ZoneId.systemDefault()) .toLocalDate() onDateSelected(selectedDate) } showDatePicker = false } ) { Text("OK") } }, dismissButton = { TextButton( onClick = { showDatePicker = false } ) { Text("キャンセル") } } ) { DatePicker( state = datePickerState ) } } } |
呼び出し側は
GrantDateItem(
grantDate = uiState.grantDate,
onDateSelected = {
svm.setGrantDate(it)
}
)
年と月のカスタムピッカー
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 |
package com.garcharis.adsenteeismcalender_android import androidx.compose.foundation.background import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.Button import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.Text import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import kotlin.math.abs /** * ドラム式(iOSのUIPickerView風)のホイールピッカー。 * LazyColumn + Snap(スナップ)で中央のアイテムを選択値として扱う。 */ @OptIn(ExperimentalFoundationApi::class) @Composable fun WheelPicker( items: List<String>, selectedIndex: Int, onSelectedIndexChange: (Int) -> Unit, modifier: Modifier = Modifier, visibleItemCount: Int = 5, itemHeight: Dp = 40.dp ) { val halfVisible = visibleItemCount / 2 val listState = rememberLazyListState(initialFirstVisibleItemIndex = selectedIndex) val flingBehavior = rememberSnapFlingBehavior(lazyListState = listState) // 画面中央に最も近いアイテムのインデックスを常時計算する val centerIndex by remember { derivedStateOf { val layoutInfo = listState.layoutInfo if (layoutInfo.visibleItemsInfo.isEmpty()) return@derivedStateOf selectedIndex val viewportCenter = (layoutInfo.viewportStartOffset + layoutInfo.viewportEndOffset) / 2 layoutInfo.visibleItemsInfo.minByOrNull { abs((it.offset + it.size / 2) - viewportCenter) }?.index ?: selectedIndex } } // スクロールが止まったタイミングで確定値として親に通知する LaunchedEffect(listState.isScrollInProgress, centerIndex) { if (!listState.isScrollInProgress) { onSelectedIndexChange(centerIndex) } } Box( modifier = modifier.height(itemHeight * visibleItemCount) ) { LazyColumn( state = listState, flingBehavior = flingBehavior, contentPadding = PaddingValues( vertical = itemHeight * halfVisible ), modifier = Modifier.fillMaxHeight() ) { itemsIndexed(items) { index, item -> val isSelected = index == centerIndex Box( modifier = Modifier .height(itemHeight) .fillMaxWidth(), contentAlignment = Alignment.Center ) { Text( text = item, fontSize = if (isSelected) 20.sp else 16.sp, color = if (isSelected) Color.Black else Color.Gray ) } } } // 中央の選択位置を示すハイライト帯(お好みで色や有無を調整してください) Box( modifier = Modifier .align(Alignment.Center) .fillMaxWidth() .height(itemHeight) .background(Color.Black.copy(alpha = 0.06f)) ) } } /** * 年・月をドラム式で選択するボトムシート(SwiftUIのfullScreenCoverに相当する画面遷移)。 * 決定ボタン押下時に onConfirm(year, month) が呼ばれる。 */ @OptIn(ExperimentalMaterial3Api::class) @Composable fun YearMonthPickerBottomSheet( initialYear: Int, initialMonth: Int, onDismiss: () -> Unit, onConfirm: (year: Int, month: Int) -> Unit ) { val sheetState = rememberModalBottomSheetState() val years = remember { (2000..2100).toList() } val months = remember { (1..12).toList() } var selectedYearIndex by remember { mutableStateOf(years.indexOf(initialYear).coerceAtLeast(0)) } var selectedMonthIndex by remember { mutableStateOf((initialMonth - 1).coerceIn(0, months.lastIndex)) } ModalBottomSheet( onDismissRequest = onDismiss, sheetState = sheetState ) { Column( modifier = Modifier .fillMaxWidth() .padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally ) { Text(text = "年月を選択", fontSize = 18.sp) Spacer(modifier = Modifier.height(8.dp)) Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center ) { WheelPicker( items = years.map { "${it}年" }, selectedIndex = selectedYearIndex, onSelectedIndexChange = { selectedYearIndex = it }, modifier = Modifier.width(120.dp) ) Spacer(modifier = Modifier.width(24.dp)) WheelPicker( items = months.map { "${it}月" }, selectedIndex = selectedMonthIndex, onSelectedIndexChange = { selectedMonthIndex = it }, modifier = Modifier.width(100.dp) ) } Spacer(modifier = Modifier.height(24.dp)) Button( onClick = { onConfirm(years[selectedYearIndex], months[selectedMonthIndex]) }, modifier = Modifier.fillMaxWidth() ) { Text("決定") } } } } |
1. SettingViewModel に追加する関数
previousMonth() / nextMonth() の近くに追加してください。
fun setYearMonth(year: Int, month: Int) {
val newDate = LocalDate.of(year, month, 1)
val safeDay = _today.value.dayOfMonth.coerceAtMost(newDate.lengthOfMonth())
_today.value = LocalDate.of(year, month, safeDay)
}
2. MainTopBar の該当ボタンの修正
var showYearMonthPicker by rememberSaveable { mutableStateOf(false) }
// ...
Button(
onClick = { showYearMonthPicker = true }, // ← ここを変更
contentPadding = PaddingValues(0.dp),
colors = ButtonDefaults.buttonColors(
containerColor = baseColor,
contentColor = baseColorText
)
) {
Text(text = today.format(formatter))
}
MainTopBarのComposable末尾(Columnの外側でもOK)に以下を追加:
if (showYearMonthPicker) {
YearMonthPickerBottomSheet(
initialYear = today.year,
initialMonth = today.monthValue,
onDismiss = { showYearMonthPicker = false },
onConfirm = { year, month ->
svm.setYearMonth(year, month)
showYearMonthPicker = false
}
)
}