推荐页核心 UI 实现逻辑说明

推荐页核心UI实现逻辑说明

kotlin 复制代码
xxxScreen.kt:


@Composable
fun xxxxScreen(){
//  padding 调用封装的方法 getItemPadding
 xxxxData.TYPE_TITLE_MORE -> {
                    NavigationHeader(
                        item.title ?: "",
                        modifier = Modifier.padding(item.getItemPadding())
                    ) {
                       
                    }
                }

                xxxxData.TYPE_SONG_FOLDER_SUPPLIER_STATION -> {
                    CovertArt(
                        title = item.card?.title ?: "",
                        model = item.card?.cover ?: "",
                        modifier = Modifier.padding(item.getItemPadding())
                    ) {
                      
                    }
                }

}

@Composable
fun xxxxCoent(){

// 跨度调用的是 getxxxxGridItemSpan方法
 SimpleDataScreen(
        loadState = loadState,
        dataIsEmpty = list.isEmpty(),
        onRetry = { currentFetchData() }
    ) {
        LazyVerticalGrid(
            columns = GridCells.Fixed(COLUM_COUNT_TEN),
            horizontalArrangement = Arrangement.spacedBy(appDimens.spacing.spacingXSmall),
            verticalArrangement = Arrangement.spacedBy(0.dp),
            contentPadding = PaddingValues(
                start = DimensionKeyTokens.ContentLayoutSideLayoutLeftPadding.value,
                top = appDimens.spacing.spacing7xLarge,
                end = DimensionKeyTokens.ContentLayoutSideLayoutRightPadding.value,
                bottom = appDimens.spacing.spacing5xLarge
            )
        ) {
            itemsIndexed(
                items = list,
                span = { index, item ->
                    getxxxxxGridItemSpan(bannerWidgetStyle, item)
                }
            ) { index, item ->
                content(index, item)
            }
        }
    }
	
	
}



 xxxxxData.TYPE_TITLE_MORE -> {
                    NavigationHeader(
                        item.title ?: "",
                        modifier = Modifier.padding(item.getItemPadding())
                    ) {
                        EventTrack.General.trackMore()
                        routerToDestination(
                            uriStr = item.routerUrl ?: "",
                            parcelable = item.localAlbumParcelizeData
                        )
                    }
                }

                xxxxData.TYPE_SONG_FOLDER_SUPPLIER_STATION -> {
                    CovertArt(
                        title = item.card?.title ?: "",
                        model = item.card?.cover ?: "",
                        modifier = Modifier.padding(item.getItemPadding())
                    ) {
                        "CovertArt".iLog(TAG)
                        EventTrack.SongFoldClick.track(
                            name = item.card?.title,
                            id = item.card?.id
                        )
                        routerToDestination(
                            uriStr = item.routerUrl ?: "",
                            parcelable = item.localAlbumParcelizeData
                        )
                    }
                }
				
				
	


@Composable
fun xxxxxData.getItemPadding(): PaddingValues {
    return when (this.type) {
        xxxxData.TYPE_CARD -> PaddingValues(bottom = appDimens.spacing.spacing5xLarge)
        xxxxxData.TYPE_TITLE_MORE -> PaddingValues(bottom = appDimens.spacing.spacingMedium)
        xxxxData.TYPE_SONG_FOLDER_SUPPLIER_STATION -> PaddingValues(bottom = appDimens.spacing.spacing5xLarge)
        xxxxData.TYPE_SONG_LIST_TITLE -> PaddingValues(bottom = appDimens.spacing.spacingMedium)
        xxxxData.TYPE_SONG_ITEM -> PaddingValues(bottom = 0.dp)
        else -> PaddingValues(0.dp)
    }
}

private fun getxxxxxGridItemSpan(bannerWidgetStyle: Int?, item: xxxxxData): GridItemSpan {
    return when (item.type) {
        xxxxData.TYPE_SONG_FOLDER_SUPPLIER_STATION -> {
            GridItemSpan(COLUM_COUNT_TWO)
        }

        xxxxData.TYPE_SONG_ITEM -> {
            GridItemSpan(COLUM_COUNT_FIVE)
        }

        xxxxData.TYPE_BANNER -> {
            if (bannerWidgetStyle == 2) {
                GridItemSpan(COLUM_COUNT_FIVE)
            } else {
                GridItemSpan(COLUM_COUNT_TEN)
            }
        }

        else -> {
            GridItemSpan(COLUM_COUNT_TEN)
        }
    }
}

1. 核心布局架构:分层治理

代码划分为三个互不干扰的逻辑层:

  • 容器层 (xxxxContent):负责整体的加载流(Loading/Success/Error/Empty)和网格的基础框架
  • 规则层 (getxxxxxGridItemSpan):负责空间分配,决定Item占列数,不关心具体UI样式
  • 表现层 (xxxxScreen):负责具体样式,通过when判断类型并渲染对应的Composable组件

2. 关键函数深度解析

A. getxxxxxGridItemSpan:动态网格跨度控制

  • 逻辑:网格总列数为COLUM_COUNT_TEN (10列)
  • 计算公式:
    • TYPE_SONG_ITEM 占5份 → 一行显示2个
    • TYPE_SONG_FOLDER_SUPPLIER_STATION 占2份 → 一行显示5个
    • TYPE_BANNER 根据bannerWidgetStyle切换:Style为2占一半(一行两个),否则占满全屏
  • 好处:布局比例在函数内清晰可见,无需在UI代码中查找配置

B.xxxxxData.getItemPadding():业务感知间距

扩展函数,赋予数据对象"自我描述间距"的能力

  • 原理:根据xxxxxxData的类型,自动返回对应的PaddingValues
  • 规范化:避免Modifier.padding()写死魔法数值,保证同类组件间距完全统一

C. SimpleDataScreen:一站式状态包装

  • 功能:内置loadState监听,数据加载中显示Loading,加载失败显示重试按钮并触发onRetry
  • 优势:开发者只需关注LazyVerticalGrid内的内容,无需重复编写状态页逻辑

3. 开发规范与建议

新增业务类型(如TYPE_VIDEO)执行步骤:

  1. 定义间距:在getItemPadding()扩展函数中添加该类型的Padding规范
  2. 定义跨度:在getxxxxGridItemSpan中定义占列数(占满全屏赋值COLUM_COUNT_TEN)
  3. 编写UI:在xxxxScreen的when分支里,调用对应组件,并传入item.getItemPadding()

总结

配置化与扩展函数实现网格布局规则与UI渲染彻底解耦,遵循"改一处,动全身"原则,修改统一规则即可同步全页面样式,降低维护成本与出错率,完成从"写死代码"到"编写协议"的思维转变。

相关推荐
AI_零食2 小时前
Flutter 框架跨平台鸿蒙开发 - 自定义式按钮设计应用
学习·flutter·ui·华为·harmonyos·鸿蒙
千百元3 小时前
codex 中使用 ui-ux-pro-max-skill
ui·ux
samroom7 小时前
【鸿蒙应用开发 Dev ECO Studio 5.0版本】从0到1!从无到有!最全!计算器------按钮动画、滑动退格、中缀表达式转后缀表达式、UI设计
数据结构·ui·华为·typescript·harmonyos·鸿蒙
之歆10 小时前
Element Plus 深度解析 - 企业级 UI 组件库的设计与实践
ui·element plus
AI_零食11 小时前
开源鸿蒙跨平台Flutter开发:研究生科研贡献雷达矩阵架构
学习·flutter·ui·华为·矩阵·开源·harmonyos
Dontla11 小时前
Playwright有头模式Headed Mode(正常显示UI界面)与无头模式Headless Mode(浏览器在后台运行)介绍
ui
希望上岸的大菠萝13 小时前
HarmonyOS 6.0 极简 UI 设计系统实战 - 基于「今天空白」当前 UiTokens 拆颜色、间距与样式约束
ui·华为·harmonyos
stevenzqzq13 小时前
架构设计深度解析:策略模式 + 抽象工厂在UI适配中的高级应用
ui·策略模式
sycmancia13 小时前
Qt——计算器示例(用户界面与业务逻辑的分离)
开发语言·qt·ui