Jetpack Compose 实现的时间选择组件

Jetpack Compose 实现的时间选择组件,第1张

前言

我们公司UI设计的界面中要用到一个比较简约的时间选择界面如下图

好像还挺常见的,我在网上搜索,其中发现了一个特别好看的实现 https://github.com/loperSeven/DateTimePicker. 但是是使用AndroidView实现的。而且网上大部分的实现都是使用androidview,难道就没有人用compose实现吗??!!既然没有!!那么就由我来实现吧!

思路

本来想写的,但是有好多,如果有时间之后再补充吧,直接看代码的注释吧(

代码

不出意外,直接全部复制过去再修改一下就可以使用了

组件中每一列的实现
@Composable
fun DatePickerColumn(
	//	列表	
    pairList: List<Pair<Int, String>>,
    itemHeight: Dp,
    itemWidth: Dp? = null,
    valueState: MutableState<Int>,
    focusColor: Color = MaterialTheme.colors.primary,
    unfocusColor: Color = Color(0xFFC5C7CF)
) {
    var isInit = false

    val dataPickerCoroutineScope = rememberCoroutineScope()
    val listState = rememberLazyListState()
    var value by valueState
    LazyColumn(
        state = listState,
        modifier = Modifier
            .height(itemHeight * 6)
            .padding(top = itemHeight / 2, bottom = itemHeight / 2)
    ) {
        item {
            Surface(Modifier.height(itemHeight)) {}
        }
        item {
            Surface(Modifier.height(itemHeight)) {}
        }
        itemsIndexed(items = pairList, key = { index, pair -> pair.first }) { index, pair ->

            val widthModifier = itemWidth?.let { Modifier.width(itemWidth) } ?: Modifier
            Box(
                modifier = Modifier
                    .height(itemHeight)
                    .then(widthModifier)
                    .clickable {
                        dataPickerCoroutineScope.launch {
                            listState.animateScrollToItem(index = index)
                        }
                    }
                    .padding(start = 5.dp, end = 5.dp), Alignment.Center
            ) {
                Text(
                    text = pair.second, color =
                    if (listState.firstVisibleItemIndex == index) focusColor
                    else unfocusColor
                )
            }
        }
        item {
            Surface(Modifier.height(itemHeight)) {}
        }
        item {
            Surface(Modifier.height(itemHeight)) {}
        }
    }


    /**
     * Jetpack Compose LazyColumn的滑动开始、结束及进行中事件
     * 参考文章 https://blog.csdn.net/asd912756674/article/details/122544808
     */
    if (listState.isScrollInProgress) {

        LaunchedEffect(Unit) {
            //只会调用一次,相当于滚动开始
        }
        //当state处于滚动时,preScrollStartOffset会被初始化并记忆,不会再被更改
        val preScrollStartOffset by remember { mutableStateOf(listState.firstVisibleItemScrollOffset) }
        val preItemIndex by remember { mutableStateOf(listState.firstVisibleItemIndex) }
        val isScrollDown = if (listState.firstVisibleItemIndex > preItemIndex) {
            //第一个可见item的index大于开始滚动时第一个可见item的index,说明往下滚动了
            true
        } else if (listState.firstVisibleItemIndex < preItemIndex) {
            //第一个可见item的index小于开始滚动时第一个可见item的index,说明往上滚动了
            false
        } else {
            //第一个可见item的index等于开始滚动时第一个可见item的index,对比item offset
            listState.firstVisibleItemScrollOffset > preScrollStartOffset
        }

        DisposableEffect(Unit) {
            onDispose {
            //	滑动结束时给状态赋值,并自动对齐
                value = pairList[listState.firstVisibleItemIndex].first
                dataPickerCoroutineScope.launch {
                    listState.animateScrollToItem(listState.firstVisibleItemIndex)
                }
            }
        }
    }

    //  选择初始值
    LaunchedEffect(Unit) {

        var initIndex = 0

        for (index in pairList.indices) {
            if (value == pairList[index].first) {
                initIndex = index
                break
            }
        }
        dataPickerCoroutineScope.launch {
            listState.animateScrollToItem(initIndex)
        }
    }
}
组件的实现
@Preview(heightDp = 500)
@Composable
fun DataTimePicker(
    date: Date = Date()
) {

    val itemHeight = 50.dp

    Box(
        modifier = Modifier
            .wrapContentHeight()
            .fillMaxWidth(),
        Alignment.Center
    ) {
        Row(
            Modifier
                .wrapContentHeight()
                .fillMaxWidth()
                .background(MaterialTheme.colors.surface),
            Arrangement.SpaceEvenly,
            Alignment.CenterVertically
        ) {
            val year = date.getYearr()
            val selectYear = rememberSaveable { mutableStateOf(year) }
            val years = LinkedList<Pair<Int, String>>().apply {
                for (i in year downTo 1980) {
                    add(Pair(i, "${i}年"))
                }
            }
            DatePickerColumn(years, itemHeight, 70.dp, selectYear)

            //  月份
            val month = date.getMonthh()
            val selectMonth = rememberSaveable { mutableStateOf(month) }
            val months = ArrayList<Pair<Int, String>>(12).apply {
                for (i in 1..12) {
                    add(Pair(i, "${i}月"))
                }
            }
            DatePickerColumn(months, itemHeight, 50.dp, selectMonth)

            //  月份的天数
            val dayOfMon = date.getDayOfMonth()
            val selectDay = rememberSaveable { mutableStateOf(dayOfMon) }
            val dayCountOfMonth = DateUtil.getDayCountOfMonth(selectYear.value, selectMonth.value)

            //  提前定义好
            val day31 = ArrayList<Pair<Int, String>>().apply {
                for (i in 1..31)
                    add(Pair(i, "${i}日"))
            }
            val day30 = ArrayList<Pair<Int, String>>().apply {
                for (i in 1..30)
                    add(Pair(i, "${i}日"))
            }
            val day29 = ArrayList<Pair<Int, String>>().apply {
                for (i in 1..29)
                    add(Pair(i, "${i}日"))
            }
            val day28 = ArrayList<Pair<Int, String>>().apply {
                for (i in 1..28)
                    add(Pair(i, "${i}日"))
            }

            //  快速切换
            val dayOfMonList = when (dayCountOfMonth) {
                28 -> day28
                29 -> day29
                30 -> day30
                else -> day31
            }

            DatePickerColumn(
                pairList = dayOfMonList,
                itemHeight = itemHeight,
                valueState = selectDay
            )

            //  小时
            val hour = date.getHour()
            val selectHour = rememberSaveable { mutableStateOf(hour) }
            val hours = ArrayList<Pair<Int, String>>(24).apply {
                for (i in 0..23) {
                    add(Pair(i, "${i}时"))
                }
            }
            DatePickerColumn(hours, itemHeight, 50.dp, selectHour)

            //  分
            val minute = date.getMinute()
            val selectMinute = rememberSaveable { mutableStateOf(minute) }
            val minutes = ArrayList<Pair<Int, String>>(60).apply {
                for (i in 0..59) {
                    add(Pair(i, "${i}分"))
                }
            }
            DatePickerColumn(minutes, itemHeight, 50.dp, selectMinute)

            //  秒
            val second = date.getSecond()
            val selectSecond = rememberSaveable { mutableStateOf(second) }
            val seconds = ArrayList<Pair<Int, String>>(60).apply {
                for (i in 0..59) {
                    add(Pair(i, "${i}秒"))
                }
            }
            DatePickerColumn(seconds, itemHeight, 50.dp, selectSecond)
        }

        //  放在后面使得不会被遮住
        Column {
            Divider(
                Modifier.padding(
                    start = 15.dp,
                    end = 15.dp,
                    bottom = itemHeight
                ),
                thickness = 1.dp
            )
            Divider(
                Modifier.padding(
                    start = 15.dp,
                    end = 15.dp
                ),
                thickness = 1.dp
            )
        }
    }
}
DateUtil工具类实现

import java.text.SimpleDateFormat
import java.util.*


fun Date.getFormatString(pattern: String = "yyyy-MM-dd HH:mm:ss"): String {
    val sdf = SimpleDateFormat(pattern, Locale.CHINA)
    return sdf.format(this)
}


fun Date.getYearr(): Int {
    val calendar = GregorianCalendar()
    calendar.time = this
    return calendar.get(Calendar.YEAR)
}

fun Date.getMonthh(): Int {
    val calendar = GregorianCalendar()
    calendar.time = this
    //  月份从0开始,需要手动 +1
    return calendar.get(Calendar.MONTH) + 1
}

fun Date.getDayOfMonth(): Int {
    val calendar = GregorianCalendar()
    calendar.time = this
    return calendar.get(Calendar.DAY_OF_MONTH)
}

fun Date.getHour(): Int {
    val calendar = GregorianCalendar()
    calendar.time = this
    return calendar.get(Calendar.HOUR_OF_DAY)
}

fun Date.getMinute(): Int {
    val calendar = GregorianCalendar()
    calendar.time = this
    return calendar.get(Calendar.MINUTE)
}

fun Date.getSecond(): Int {
    val calendar = GregorianCalendar()
    calendar.time = this
    return calendar.get(Calendar.SECOND)
}

fun Date.plusDays(days: Int = 1): Date {
    val calendar = GregorianCalendar()
    calendar.time = this
    calendar.add(Calendar.DATE, days)
    return calendar.time
}

fun Date.minusDays(days: Int = 1): Date {
    val calendar = GregorianCalendar()
    calendar.time = this
    calendar.add(Calendar.DATE, -days)
    return calendar.time
}

class DateUtil {
    companion object {

        fun getDayCountOfMonth(year: Int, month: Int): Int {

            if (month == 2) {
                //判断年是不是闰年
                if (year % 4 == 0 && year % 100 != 0 || year % 400 == 0) {
                    return 29
                } else {
                    return 28
                }
            } else if (month == 4 || month == 6 || month == 9 || month == 11) {
                return 30
            } else
                return 31
        }

    }
}
实现效果


不好意思,我不知道怎么录制GIF图(X,想看效果直接复制代码看看吧

存在的问题

不知道为什么,点击lazyColumn中子项的滑动事件有时候不生效,求解答

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/web/992448.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-21
下一篇 2022-05-21

发表评论

登录后才能评论

评论列表(0条)

保存