观心静

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

前言

  此篇博客讲解LazyColumn 与 LazyRow、LazyVerticalGrid、LazyHorizontalGrid、LazyHorizontalGrid、LazyVerticalStaggeredGrid,在compose里LazyColumn与LazyRow与是用来延迟加载数据的,它对标原来xml里的ListView与RecyclerView。

而LazyVerticalGrid、LazyHorizontalGrid对标的是原来xml的GridView,LazyHorizontalGrid、LazyVerticalStaggeredGrid对标是原来的瀑布流列表。

  此外此博客还会讲解滚动状态监听、下拉刷新、自定义下拉刷新与上拉加载、侧滑Item删除等等例子,这里只用LazyColumn来实现举例,与其他列表大差不差,希望都能举一反三。

LazyColumn 纵向列表

效果图

代码

@Composable
fun APage() {
    val listData = remember {
        //mutableStateListOf 这个很关键,需要动态新增数据一定需要使用mutableStateListOf
        mutableStateListOf(
            "苹果" to R.mipmap.ic_fruit_apple,
            "香蕉" to R.mipmap.ic_fruit_banana,
            "牛油果" to R.mipmap.ic_fruit_avocado,
            "蓝莓" to R.mipmap.ic_fruit_blueberry,
            "椰子" to R.mipmap.ic_fruit_coconut,
            "葡萄" to R.mipmap.ic_fruit_grape,
            "哈密瓜" to R.mipmap.ic_fruit_hami_melon,
            "猕猴桃" to R.mipmap.ic_fruit_kiwifruit,
            "柠檬" to R.mipmap.ic_fruit_lemon,
            "荔枝" to R.mipmap.ic_fruit_litchi,
            "芒果" to R.mipmap.ic_fruit_mango,
        )
    }
    Column {
        Button(modifier = Modifier.fillMaxWidth(), onClick = { listData.add("山竹" to R.mipmap.ic_fruit_mangosteen) }) {
            Text(text = "新增数据")
        }
        //LazyColumn
        LazyColumn(
            Modifier.fillMaxSize(),
            verticalArrangement = Arrangement.Center
        ) {
            //这个items很关键,LazyColumn一定需要使用这个
            items(listData.size){
                Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(20.dp)) {
                    Image(painter = painterResource(id = listData[it].second), contentDescription = null, modifier = Modifier
                        .width(100.dp)
                        .height(100.dp))
                    Text(text = listData[it].first, fontSize = 25.sp, modifier = Modifier.padding(start = 80.dp))
                }
            }
        }
    }
}

LazyRow 横向列表

效果图

代码

@Composable
fun APage() {
    val listData = remember {
        //mutableStateListOf 这个很关键,需要动态新增数据一定需要使用mutableStateListOf
        mutableStateListOf(
            "苹果" to R.mipmap.ic_fruit_apple,
            "香蕉" to R.mipmap.ic_fruit_banana,
            "牛油果" to R.mipmap.ic_fruit_avocado,
            "蓝莓" to R.mipmap.ic_fruit_blueberry,
            "椰子" to R.mipmap.ic_fruit_coconut,
            "葡萄" to R.mipmap.ic_fruit_grape,
            "哈密瓜" to R.mipmap.ic_fruit_hami_melon,
            "猕猴桃" to R.mipmap.ic_fruit_kiwifruit,
            "柠檬" to R.mipmap.ic_fruit_lemon,
            "荔枝" to R.mipmap.ic_fruit_litchi,
            "芒果" to R.mipmap.ic_fruit_mango,
        )
    }
    Column {
        Button(modifier = Modifier.fillMaxWidth(), onClick = { listData.add("山竹" to R.mipmap.ic_fruit_mangosteen) }) {
            Text(text = "新增数据")
        }
        //LazyRow
        LazyRow(
            Modifier.fillMaxSize(),
        ) {
            //这个items很关键
            items(listData.size){
                Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.padding(20.dp)) {
                    Image(painter = painterResource(id = listData[it].second), contentDescription = null, modifier = Modifier
                        .width(100.dp)
                        .height(100.dp))
                    Text(text = listData[it].first, fontSize = 25.sp)
                }
            }
        }
    }
}

LazyVerticalGrid 纵向宫格列表

效果图

代码

@Composable
fun APage() {
    val listData = remember {
        //mutableStateListOf 这个很关键,需要动态新增数据一定需要使用mutableStateListOf
        mutableStateListOf(
            "苹果" to R.mipmap.ic_fruit_apple,
            "香蕉" to R.mipmap.ic_fruit_banana,
            "牛油果" to R.mipmap.ic_fruit_avocado,
            "蓝莓" to R.mipmap.ic_fruit_blueberry,
            "椰子" to R.mipmap.ic_fruit_coconut,
            "葡萄" to R.mipmap.ic_fruit_grape,
            "哈密瓜" to R.mipmap.ic_fruit_hami_melon,
            "猕猴桃" to R.mipmap.ic_fruit_kiwifruit,
            "柠檬" to R.mipmap.ic_fruit_lemon,
            "荔枝" to R.mipmap.ic_fruit_litchi,
            "芒果" to R.mipmap.ic_fruit_mango,
            "橘子" to R.mipmap.ic_fruit_orange,
            "梨子" to R.mipmap.ic_fruit_pear,
        )
    }
    Column {
        Button(modifier = Modifier.fillMaxWidth(), onClick = { listData.add("山竹" to R.mipmap.ic_fruit_mangosteen) }) {
            Text(text = "新增数据")
        }
        //LazyVerticalGrid
        //columns 设置显示几列
        LazyVerticalGrid(columns = GridCells.Fixed(3)) {
            items(listData.size){
                Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.padding(20.dp)) {
                    Image(painter = painterResource(id = listData[it].second), contentDescription = null, modifier = Modifier
                        .width(90.dp)
                        .height(90.dp))
                    Text(text = listData[it].first, fontSize = 18.sp)
                }
            }
        }
    }
}

LazyHorizontalGrid横向宫格列表

效果图

代码

@Composable
fun APage() {
    val listData = remember {
        //mutableStateListOf 这个很关键,需要动态新增数据一定需要使用mutableStateListOf
        mutableStateListOf(
            "苹果" to R.mipmap.ic_fruit_apple,
            "香蕉" to R.mipmap.ic_fruit_banana,
            "牛油果" to R.mipmap.ic_fruit_avocado,
            "蓝莓" to R.mipmap.ic_fruit_blueberry,
            "椰子" to R.mipmap.ic_fruit_coconut,
            "葡萄" to R.mipmap.ic_fruit_grape,
            "哈密瓜" to R.mipmap.ic_fruit_hami_melon,
            "猕猴桃" to R.mipmap.ic_fruit_kiwifruit,
            "柠檬" to R.mipmap.ic_fruit_lemon,
            "荔枝" to R.mipmap.ic_fruit_litchi,
            "芒果" to R.mipmap.ic_fruit_mango,
            "橘子" to R.mipmap.ic_fruit_orange,
            "梨子" to R.mipmap.ic_fruit_pear,
        )
    }
    Column {
        Button(modifier = Modifier.fillMaxWidth(), onClick = { listData.add("山竹" to R.mipmap.ic_fruit_mangosteen) }) {
            Text(text = "新增数据")
        }
        //LazyHorizontalGrid
        //rows 设置显示几行
        LazyHorizontalGrid(rows = GridCells.Fixed(4)) {
            items(listData.size){
                Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.padding(20.dp)) {
                    Image(painter = painterResource(id = listData[it].second), contentDescription = null, modifier = Modifier
                        .width(90.dp)
                        .height(90.dp))
                    Text(text = listData[it].first, fontSize = 18.sp)
                }
            }
        }
    }
}

LazyVerticalStaggeredGrid 纵向瀑布列表

请注意!LazyVerticalStaggeredGrid为实验性API,并且Compose版本需要1.4.0以上。 这个其实跟我另一篇博客介绍的东西差不多 Android开发 Jetpack Compose FlowColumn与FlowRow瀑布流布局

效果图

代码

//因为LazyVerticalStaggeredGrid是实验性API,所以需要这个注解
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun APage() {
    val listData = remember {
        mutableStateListOf(
            "苹果" to R.mipmap.ic_fruit_apple,
            "香蕉" to R.mipmap.ic_fruit_banana,
            "牛油果" to R.mipmap.ic_fruit_avocado,
            "蓝莓" to R.mipmap.ic_fruit_blueberry,
            "椰子" to R.mipmap.ic_fruit_coconut,
            "葡萄" to R.mipmap.ic_fruit_grape,
            "哈密瓜" to R.mipmap.ic_fruit_hami_melon,
            "猕猴桃" to R.mipmap.ic_fruit_kiwifruit,
            "柠檬" to R.mipmap.ic_fruit_lemon,
            "荔枝" to R.mipmap.ic_fruit_litchi,
            "芒果" to R.mipmap.ic_fruit_mango,
        )
    }
    Column {
        //LazyVerticalStaggeredGrid
        //StaggeredGridCells设置显示几列
        LazyVerticalStaggeredGrid(columns = StaggeredGridCells.Fixed(4)) {
            items(listData.size){
                //这边设置一个item的随机高度,产生随机高度的瀑布效果
                val height = (80..200).random()
                Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.padding(10.dp).background(
                    Color.Gray)) {
                    Image(painter = painterResource(id = listData[it].second), contentDescription = null, modifier = Modifier
                        .width(90.dp)
                        .height(height.dp))
                    Text(text = listData[it].first, fontSize = 18.sp)
                }
            }
        }
    }
}

LazyHorizontalStaggeredGrid 横向瀑布列表

请注意!LazyHorizontalStaggeredGrid 为实验性API,并且Compose版本需要1.4.0以上。

效果图

代码

//因为LazyHorizontalStaggeredGrid是实验性API,所以需要这个注解
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun APage() {
    val listData = remember {
        mutableStateListOf(
            "苹果" to R.mipmap.ic_fruit_apple,
            "香蕉" to R.mipmap.ic_fruit_banana,
            "牛油果" to R.mipmap.ic_fruit_avocado,
            "蓝莓" to R.mipmap.ic_fruit_blueberry,
            "椰子" to R.mipmap.ic_fruit_coconut,
            "葡萄" to R.mipmap.ic_fruit_grape,
            "哈密瓜" to R.mipmap.ic_fruit_hami_melon,
            "猕猴桃" to R.mipmap.ic_fruit_kiwifruit,
            "柠檬" to R.mipmap.ic_fruit_lemon,
            "荔枝" to R.mipmap.ic_fruit_litchi,
            "芒果" to R.mipmap.ic_fruit_mango,
        )
    }
    Column {
        //LazyHorizontalStaggeredGrid
        //StaggeredGridCells设置显示几行
        LazyHorizontalStaggeredGrid(rows = StaggeredGridCells.Fixed(4)) {
            items(listData.size){
                //这边设置一个item的随机宽度,产生随机宽度的瀑布效果
                val width = (80..200).random()
                Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.padding(10.dp).background(
                    Color.Gray)) {
                    Image(painter = painterResource(id = listData[it].second), contentDescription = null, modifier = Modifier
                        .width(width.dp)
                        .height(90.dp))
                    Text(text = listData[it].first, fontSize = 18.sp)
                }
            }
        }
    }
}

列表的滚动监听与设置滚动位置

这里使用LazyColumn举例,因为其他形式的列表基本差不多。如果你不太了解下面代码中的rememberCoroutineScope,请参考博客 Android开发 Jetpack_Compose_6 附带效应  这属于附带效应中的知识。 如果你疑问为什么要使用rememberCoroutineScope?这是因为onClick不属于协程域和compose域,而rememberLazyListState需要协程域内才能调用。

效果图

代码

@Composable
fun APage() {
    val listData = remember {
        mutableStateListOf(
            "苹果" to R.mipmap.ic_fruit_apple,
            "香蕉" to R.mipmap.ic_fruit_banana,
            "牛油果" to R.mipmap.ic_fruit_avocado,
            "蓝莓" to R.mipmap.ic_fruit_blueberry,
            "椰子" to R.mipmap.ic_fruit_coconut,
            "葡萄" to R.mipmap.ic_fruit_grape,
            "哈密瓜" to R.mipmap.ic_fruit_hami_melon,
            "猕猴桃" to R.mipmap.ic_fruit_kiwifruit,
            "柠檬" to R.mipmap.ic_fruit_lemon,
            "荔枝" to R.mipmap.ic_fruit_litchi,
            "芒果" to R.mipmap.ic_fruit_mango,
        )
    }
    //协程范围
    val coroutineScope = rememberCoroutineScope()
    //关键代码!滚动状态监听
    val listState = rememberLazyListState()
    Column {
        DescribeText(text = "当前位置 = ${listState.firstVisibleItemIndex}")
        DescribeText(text = "滚动偏移量 = ${listState.firstVisibleItemScrollOffset}")
        DescribeText(text = "是否正在滚动 = ${listState.isScrollInProgress}")
        DescribeText(text = "可以向前滚动(是否可以向下滚) = ${listState.canScrollForward}")
        DescribeText(text = "可以向后滚动(是否可以向上滚) = ${listState.canScrollBackward}")
        Button(modifier = Modifier.fillMaxWidth(), onClick = {
            coroutineScope.launch {
                listState.scrollToItem(0)//关键代码!点击回到最上面
            }
        }) {
            Text(text = "回到最上面")
        }
        LazyColumn(
            state = listState,
            verticalArrangement = Arrangement.Center,
            modifier = Modifier.fillMaxSize(),
        ) {
            items(listData.size) {
                Row(
                    verticalAlignment = Alignment.CenterVertically,
                    modifier = Modifier.padding(20.dp)
                ) {
                    Image(
                        painter = painterResource(id = listData[it].second),
                        contentDescription = null,
                        modifier = Modifier
                            .width(100.dp)
                            .height(100.dp)
                    )
                    Text(
                        text = listData[it].first,
                        fontSize = 25.sp,
                        modifier = Modifier.padding(start = 80.dp)
                    )
                }
            }
        }
    }
}

@Composable
fun DescribeText(text:String) {
    Text(text = text, color = Color.Black, textAlign = TextAlign.Center, modifier = Modifier.fillMaxWidth())
}

使用pullRefresh简单实现下拉刷新

这种方式实现需要compose版本为1.4.0,使用pullRefresh与PullRefreshIndicator的组合

效果图

代码

@OptIn(ExperimentalMaterialApi::class)
@Composable
fun APage() {
    val listData = remember {
        mutableStateListOf(
            R.mipmap.ic_fruit_apple,
            R.mipmap.ic_fruit_banana,
            R.mipmap.ic_fruit_avocado,
            R.mipmap.ic_fruit_blueberry,
            R.mipmap.ic_fruit_coconut,
            R.mipmap.ic_fruit_grape,
            R.mipmap.ic_fruit_hami_melon,
            R.mipmap.ic_fruit_kiwifruit,
            R.mipmap.ic_fruit_lemon,
            R.mipmap.ic_fruit_litchi,
            R.mipmap.ic_fruit_mango,
        )
    }
    //是否正在刷新
    val isRefreshing = remember {
        mutableStateOf(false)
    }

    //refreshing为设置当前是否在刷新状态,如果设置为true则onRefresh不会触发
    val pullRefreshState =
        rememberPullRefreshState(refreshing = isRefreshing.value, onRefresh = {
            isRefreshing.value = true
            //这个回调里可以执行刷新数据的网络请求
        })

    Box(modifier = Modifier.pullRefresh(state = pullRefreshState, enabled = true)) {
        LazyColumn(
            Modifier.fillMaxSize(),
            verticalArrangement = Arrangement.Center
        ) {
            items(listData.size) {
                Image(
                    painter = painterResource(id = listData[it]),
                    contentDescription = null,
                    modifier = Modifier
                        .padding(top = 20.dp, bottom = 20.dp)
                        .fillMaxWidth()
                        .height(100.dp)
                )
            }
        }
        //请注意。这个PullRefreshIndicator并不是我自己实现的compose方法,这是最新1.4.0版本新增的下拉指示物
        //请注意,Modifier.align(Alignment.TopCenter)是上面Box的子属性
        PullRefreshIndicator(
            refreshing = isRefreshing.value,
            state = pullRefreshState,
            modifier = Modifier.align(Alignment.TopCenter)
        )
    }
}

使用pullRefresh实现下拉刷新与上拉加载

这种方式实现需要compose版本为1.4.0,这个是较为自定义的实现方式,相对上面的比较复杂。

效果图

代码

@OptIn(ExperimentalMaterialApi::class)
@Composable
fun APage() {
    val listData = remember {
        mutableStateListOf(
            "苹果" to R.mipmap.ic_fruit_apple,
            "香蕉" to R.mipmap.ic_fruit_banana,
            "牛油果" to R.mipmap.ic_fruit_avocado,
            "蓝莓" to R.mipmap.ic_fruit_blueberry,
            "椰子" to R.mipmap.ic_fruit_coconut,
            "葡萄" to R.mipmap.ic_fruit_grape,
            "哈密瓜" to R.mipmap.ic_fruit_hami_melon,
            "猕猴桃" to R.mipmap.ic_fruit_kiwifruit,
            "柠檬" to R.mipmap.ic_fruit_lemon,
            "荔枝" to R.mipmap.ic_fruit_litchi,
            "芒果" to R.mipmap.ic_fruit_mango,
        )
    }
    //是否显示下拉刷新
    val isShowDownPullRefresh = remember {
        mutableStateOf(false)
    }
    //是否显示上拉加载
    val isShowUpPullLoading = remember {
        mutableStateOf(false)
    }

    //滚动状态监听
    val listState = rememberLazyListState()

    Column(modifier = Modifier
            //请注意!这里的pullRefresh是关键
        .pullRefresh(onPull = { pullDelta ->
            //拉动中
            if (pullDelta > 0){
                //触发下拉刷新,在这里可以实现请求刷新数据的逻辑
                isShowDownPullRefresh.value = true
                return@pullRefresh pullDelta
            } else {
                if (!listState.canScrollForward){
                    //触发上拉加载,在这里可以实现请求更多数据的逻辑
                    isShowUpPullLoading.value = true
                }
                //这里一定需要返回0,否者会影响向下滑动
                return@pullRefresh 0F
            }
        }, onRelease = { flingVelocity ->
            //松开释放,停止拉动
            isShowDownPullRefresh.value = false
            isShowUpPullLoading.value = false
            return@pullRefresh flingVelocity
        }, true)
    ) {
        LazyColumn(
            Modifier.fillMaxSize(),
            state = listState,
            verticalArrangement = Arrangement.Center
        ) {
            //这里listData.size + 2, 加2是为了增加头部的”下拉刷新“与尾部的”上拉加载“
            items(listData.size + 2) {
                if (it == 0){
                    if (isShowDownPullRefresh.value){
                        Text(
                            text = "下拉刷新",
                            fontSize = 25.sp,
                            textAlign = TextAlign.Center,
                            modifier = Modifier.fillMaxWidth()
                        )
                    }
                    return@items
                }
                if (it == listData.size + 1){
                    if (isShowUpPullLoading.value){
                        Text(
                            text = "上拉加载",
                            fontSize = 25.sp,
                            textAlign = TextAlign.Center,
                            modifier = Modifier.fillMaxWidth()
                        )
                    }
                    return@items
                }
                Row(
                    verticalAlignment = Alignment.CenterVertically,
                    modifier = Modifier.padding(20.dp)
                ) {
                    Image(
                        painter = painterResource(id = listData[it - 1].second),
                        contentDescription = null,
                        modifier = Modifier
                            .width(100.dp)
                            .height(100.dp)
                    )
                    Text(
                        text = listData[it - 1].first,
                        fontSize = 25.sp,
                        modifier = Modifier.padding(start = 80.dp)
                    )
                }
            }
        }
    }
}

使用SwipeToDismiss实现侧滑删除

关键代码是SwipeToDismiss与rememberDismissState

效果图

代码


@OptIn(ExperimentalMaterialApi::class)
@Composable
fun APage() {
    val listData = remember {
        mutableStateListOf(
            R.mipmap.ic_fruit_apple,
            R.mipmap.ic_fruit_banana,
            R.mipmap.ic_fruit_avocado,
            R.mipmap.ic_fruit_blueberry,
            R.mipmap.ic_fruit_coconut,
            R.mipmap.ic_fruit_grape,
            R.mipmap.ic_fruit_hami_melon,
            R.mipmap.ic_fruit_kiwifruit,
            R.mipmap.ic_fruit_lemon,
            R.mipmap.ic_fruit_litchi,
            R.mipmap.ic_fruit_mango,
        )
    }

    val coroutineScope = rememberCoroutineScope()

    Box() {
        LazyColumn(
            Modifier.fillMaxSize(),
            verticalArrangement = Arrangement.Center
        ) {
            items(listData.size) { position ->
                //创建侧滑状态,请注意这里是在items里面创建的
                val dismissState = rememberDismissState()
                SwipeToDismiss(
                    state = dismissState,
                    //允许侧滑的方向
                    directions = setOf(DismissDirection.EndToStart),
                    dismissThresholds = {
                        //清除项目需要刷过的阈值,值越大需要拖动的距离就会越长
                        FixedThreshold(100.dp)
                    },
                    //background是侧滑后会显示出来的隐藏内容
                    background = {
                        //判断侧滑的方向
                        if (dismissState.isDismissed(DismissDirection.EndToStart)) {
                            Box(
                                modifier = Modifier
                                    .fillMaxSize()
                                    .background(Color.Gray)
                            ) {
                                //删除图标
                                Icon(
                                    imageVector = Icons.Default.Delete,
                                    contentDescription = null,
                                    modifier = Modifier
                                        .padding(end = 30.dp)
                                        .width(50.dp)
                                        .height(50.dp)
                                        .align(Alignment.CenterEnd)
                                        .clickable {
                                            coroutineScope.launch {
                                                dismissState.reset()
                                                //点击了删除,将这个数据删除掉
                                                listData.removeAt(position)
                                            }
                                        }
                                )
                                //恢复图标
                                Icon(
                                    imageVector = Icons.Default.SettingsBackupRestore,
                                    contentDescription = null,
                                    modifier = Modifier
                                        .padding(end = 100.dp)
                                        .width(50.dp)
                                        .height(50.dp)
                                        .align(Alignment.CenterEnd)
                                        .clickable {
                                            coroutineScope.launch {
                                                //将侧滑状态恢复
                                                dismissState.reset()
                                            }
                                        }
                                )
                            }
                        }
                    }) {
                    Image(
                        painter = painterResource(id = listData[position]),
                        contentScale = ContentScale.Inside,
                        contentDescription = null,
                        modifier = Modifier
                            .fillMaxWidth()
                            .background(Color.White)
                            .height(120.dp)
                            //使用drawBehind画一个上面和下面的边线
                            .drawBehind {
                                drawLine(
                                    Color.Blue,
                                    Offset(0f, 0f),
                                    Offset(size.width, 0f),
                                    strokeWidth = 3f
                                )
                                drawLine(
                                    Color.Blue,
                                    Offset(0f, size.height),
                                    Offset(size.width, size.height),
                                    strokeWidth = 3f
                                )
                            }
                    )
                }

            }
        }
    }
}

 

 

end

posted on 2023-08-08 14:47  观心静  阅读(1772)  评论(0编辑  收藏  举报