12 自定义 Compose Banner 组件(一) 基础功能

600 阅读2分钟

Accompanist

Google 为 Compose 提供的额外支持库,添补了 Jetpack Compose 缺少的一些常用功能,官方说这些库最终会被整合到 Compose 中(Insets 已经被整合到 1.2.0 版本中了)。 Git 地址文档地址

  • Pager 类似 ViewPager ,作为 Banner 组件的容器
  • pager-indicators 和 Pager 联用的指示器
  • Placeholder Banner 图片加载时的占位,项目中其他位置也可以使用,数据初始化时可以实现骨架屏的效果。

Placeholder 有 Placeholder Foundation 和 Placeholder Material 两个库,后者依赖前者默认使用了 Material 主题中的颜色。这两个库选择一个引入依赖即可。

Coil

官方推荐的图片加载库,Git 地址

自定义 Banner 容器

容器是个水平方向的 Pager 。

  • items : Banner 要显示的数据
  • onItemClick :item 点击事件
  • activeIndicatorColor :当前页所在页指示器的颜色
  • inactiveIndicatorColor :非当前页所在页指示器的颜色
  • itemContent :每个 item 对应在 Pager 中的具体页面
@OptIn(ExperimentalPagerApi::class)
@Composable
fun <T> Banner(
    modifier: Modifier = Modifier,
    items: List<T>,
    onItemClick: ((T) -> Unit)? = null,
    activeIndicator: Color = Color.Magenta,
    inactiveIndicator: Color = Color.Gray,
    itemContent: @Composable (T) -> Unit
) {
    val pagerState = rememberPagerState()
    HorizontalPager(
        state = pagerState,
        count = items.size,
        modifier = Modifier
            //点击事件
            .clickable {
                onItemClick?.invoke((items[pagerState.currentPage]))
            }
            .then(modifier)
    ) { pageIndex ->
        val itemData = items[pageIndex]
        Box(modifier = Modifier
            .height(IntrinsicSize.Min)
            .fillMaxWidth()) {
            //内容
            itemContent(itemData)
            //指示器
            HorizontalPagerIndicator(
                modifier = Modifier
                    .align(Alignment.BottomCenter)
                    .padding(8.dp),
                pagerState = pagerState,
                activeColor = activeIndicatorColor,
                inactiveColor = inactiveIndicatorColor
            )
        }
    }
}

自定义 BannerItem

显示图片的 BannerItem , 图片加载时显示 placeHolder。

@Composable
fun ImageBannerItem(imageUrl:String,modifier: Modifier = Modifier){
    var placeHolderVisible by remember { mutableStateOf(true) }
    AsyncImage(
        model = imageUrl,
        contentDescription = "",
        contentScale = ContentScale.Crop,
        modifier = Modifier
            .fillMaxWidth()
            .placeholder(visible = placeHolderVisible, highlight = PlaceholderHighlight.fade())
            .then(modifier),
        onState = {
            when (it) {
                is AsyncImagePainter.State.Success -> {
                    placeHolderVisible = false
                }
                is AsyncImagePainter.State.Error -> {
                    //todo 异常处理
                    Log.e("AsyncImage", "ImageBannerItem:State.Error ${it.result.throwable} ")
                }
            }
        }
    )
}

WanAndroid Banner 实现

修改 UiHome 。

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun UiHome(navController: NavController,viewModel: HomeViewModel = hiltViewModel()){
    val homeState = rememberHomeState(viewModel = viewModel, navController = navController)

    Scaffold { paddingValues ->
        Column (modifier = Modifier.padding(paddingValues)){
            Banner(items = homeState.banners) { 
                ImageBannerItem(modifier = Modifier.height(200.dp),imageUrl = it.imagePath)
            }
        }
    }
}

image.png

(总算能贴上张图了 -。-)

So easy! , 组合形式的自定义 Compose 组件就这么简单,类似 Compose Material 组件的实现方式,定义好槽位后将内容声明成参数即可。

Banner 还欠缺了自动滚动功能,添加自动滚动功能涉及到 Compose side-effect APIs 和手势处理知识,放到下一章完成。