Android Jetpack Compose 实现一个电视剧选集界面

文章目录

需求概述

我们经常能看到爱奇艺或者腾讯视频这类的视频APP在看电视剧的时候都会有一个选集的功能。如下图所示

这个功能其实很简单,就是绘制一些方块,在上面绘制上数字,还有标签啥的。当用户点击对应的数字式时可以切换到对应的剧集。如果剧集太多,屏幕展示不完,就可以滑动屏幕查看更多的剧集,就这么一个很简单的UI小组件。我们使用Compose来实现下。

效果展示

如上图所示,在UI的最上面是标题(选集),下面是我们绘制出的小方块。当小方块选中的时候会绘制一个指示器,如果剧集太多就需要能上滑展示更多的剧集

如果剧集少的时候,居左展示。如下

如果是横屏,则如下展示:

实现思路

可能很多读者会很容易的想到使用网格布局的控件LazyVerticalGrid实现,如果说不需要透明背景的话,这种方法是可行的,而且性能也会很好,但是如果要求背景可以设置透明度的话,这种方式就不行了,因为LazyVerticalGrid无法将背景设置成透明的,如我们的效果展示图中,可以看到我们的选集UI出现后,还可以看到后面的背景,如果使用LazyVerticalGrid,则无法实现这个效果。所以我们采用的方式是直接通过for循环绘制。使用两个for循环,分别负责绘制行和列,然后再处理点击的回调和选中的指示器就行了,我们可以使用Column,Box,Row,Text组件搭配使用,这些组件都是可以设置透明度的,能达到需求的效果。

代码实现

代码的实现很简单,就是一个composable函数,在代码中都做了注释,所以就不多废话了,原理也很简单,就是通过两个for循环分别绘制行和列,根据行和列之间的对应关系去计算显示的高度,padding等。

kotlin 复制代码
/**
 * @param row 需要展示的行数
 * @param col 需要展示的列数
 * @param contentPadding 内容的padding,默认15dp
 * @param displayRowCount 展示的函数,比如这个值为7,传入的row 为10,那么只会展示7行,多余的三行需要滑动查看,默认展示5行
 * @param numberPadding 数字方块之间的padding,默认11dp
 * @param contentTopPadding 内容顶部的padding,默认0dp
 * @param currentNum 当前选中的数字,需要根据它绘制指示器
 * @param isPortrait 是否是竖屏,需要根据横屏和竖屏来调整布局,默认我是竖屏
 * @param
 */

@Composable
fun ShowDramaSelectUI(
    row: Int,
    col: Int,
    contentPadding: Dp = 15.dp,
    displayRowCount: Int = 5,
    numberPadding: Dp = 11.dp,
    contentTopPadding: Dp = 0.dp,
    currentNum: Int = 1,
    isPortrait: Boolean = true,
    onNumSelect: (Int) -> Unit
) {
    // 记录滚动的状态
    val scrollState = rememberScrollState()
    val displayHeight =
        // 根据显示的行数计算容器的高度,下面的表达式不能换行,否则根据kotlin的语法特性,换行后的表达式不会参与计算
        // 这里的60dp是数字的方块的大小,也可通过传参数指定
        (displayRowCount) * (60.dp).value + (displayRowCount - 1) * numberPadding.value
    // 记录选中的数字
    var selectedNum by remember { mutableIntStateOf(currentNum) }
    Log.d(TAG, "walt: selectedNum====>: $selectedNum")

    //背景蒙层
    Box(
        modifier = Modifier
            .fillMaxSize()
            .background(Color(0xCC000000))
    )

    Box(
        modifier = Modifier
            .fillMaxSize()
            .padding(
                top = contentTopPadding,
                start = contentPadding,
                end = contentPadding
            ),
        contentAlignment = Alignment.TopCenter
    ) {
        Column(
            modifier = Modifier
                .wrapContentHeight()
                .wrapContentWidth(),
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            Box(
                modifier = Modifier.fillMaxWidth().padding(start = 10.dp),
                contentAlignment = Alignment.CenterStart
            ) {
                Text(
                    text = "选集", style = TextStyle(
                        color = Color(0xFF797F85),
                        fontSize = 14.sp,
                        fontWeight = FontWeight.Normal
                    )
                )
            } // 选集Box

            Spacer(modifier = Modifier.height(10.dp).fillMaxWidth())

            Column(
                modifier = Modifier
                    .height(displayHeight.dp)
                    .fillMaxWidth()
                    // 让控件拥有滑动的能力
                    .verticalScroll(scrollState),
                verticalArrangement = Arrangement.Top,
                // 根据横竖屏设置对齐方式
                horizontalAlignment = if ((isPortrait && (col < 5))
                    || (!isPortrait && (col < 10))
                ) {
                    Alignment.Start
                } else {
                    Alignment.CenterHorizontally
                }
            ) {
                // 绘制行
                for (i in 1..row) {
                    Row(
                        modifier = Modifier.wrapContentWidth().wrapContentHeight()
                    ) {
                        // 绘制列
                        for (j in col * (i - 1) + 1..(i - 1) * col + col) {
                            Box(
                                modifier = Modifier
                                    .size(60.dp)
                                    .clip(RoundedCornerShape(6.dp))
                                    .background(Color(0x8031373D))
                                    // 回调选中的数字
                                    .clickable {
                                        selectedNum = j
                                        onNumSelect(selectedNum)
                                    },
                                contentAlignment = Alignment.Center
                            ) {
                                Text(
                                    text = "$j",
                                    style = TextStyle(
                                        color = Color.White,
                                        fontSize = 20.sp,
                                        textAlign = TextAlign.Center
                                    ),
                                )

                                // 只有选中的数字和当前的数字相同时,才会展示指示器
                                if (j == selectedNum) {
                                    Box(
                                        modifier = Modifier.fillMaxSize(),
                                        contentAlignment = Alignment.BottomCenter
                                    ) {
                                        Divider(
                                            modifier = Modifier
                                                .fillMaxWidth(),
                                            thickness = 6.dp,
                                            color = Color(0xFF037FF5)
                                        )
                                    }
                                }
                            }

//                        // 绘制两个列之间的间距,如果不是最后一个item,才加Spacer
                            if ((j != (i - 1) * col + col)) {
                                Spacer(modifier = Modifier.height(60.dp).width(numberPadding))
                            }
                        }
                    } // Row

                    // 绘制行之前的间距
                    Spacer(modifier = Modifier.height(numberPadding).fillMaxWidth())
                }
            }
        }
    }
}

测试代码

kotlin 复制代码
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyComposeTheme {
                Box(modifier =
                Modifier
                    .fillMaxSize()
                ){
                    Image(painter = painterResource(R.drawable.m10),
                        modifier = Modifier.fillMaxSize(),
                        contentScale = ContentScale.Crop,
                        contentDescription = null)
                    ShowDramaSelectUI(row = 10, col = 10, isPortrait = true, onNumSelect = {
                            num->
                        Log.d(TAG,"$num has selected1 !!!")
                    })
                }
            }
        }
    }
}

总结

本文主要介绍的是一个剧集选集的功能,这里只是介绍了实现的方式,比较粗糙,读者可以按照自己的需求修改,有更好的实现方案也可以在评论区交流。本文主要起抛砖引玉的作用,也是记录自己实现的一个小需求。给需要的小伙伴打个样,欢迎交流指正。

相关推荐
咖啡の猫1 小时前
Android开发-常用布局
android·gitee
程序员老刘2 小时前
Google突然“变脸“,2026年要给全球开发者上“紧箍咒“?
android·flutter·客户端
Tans52 小时前
Androidx Lifecycle 源码阅读笔记
android·android jetpack·源码阅读
雨白2 小时前
实现双向滑动的 ScalableImageView(下)
android
峥嵘life3 小时前
Android Studio新版本编译release版本apk实现
android·ide·android studio
studyForMokey5 小时前
【Android 消息机制】Handler
android
敲代码的鱼哇5 小时前
跳转原生系统设置插件 支持安卓/iOS/鸿蒙UTS组件
android·ios·harmonyos
翻滚丷大头鱼5 小时前
android View详解—动画
android
我是好小孩5 小时前
[Android]RecycleView的item用法
android
胖虎16 小时前
Android Studio 读取本地文件(以 ZIP 为例)
android·ide·android studio·本地文件·读取本地文件