推荐页核心 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渲染彻底解耦,遵循"改一处,动全身"原则,修改统一规则即可同步全页面样式,降低维护成本与出错率,完成从"写死代码"到"编写协议"的思维转变。

相关推荐
monsion3 小时前
AG-UI 协议详解:Agent 与用户之间缺失的那一层
人工智能·ui·个人开发
Full Stack Developme4 小时前
flowable-spring-boot-starter 与 flowable-ui
ui
天天进步20155 小时前
UI-TARS 源码解析 #5:prompt.py 解析:COMPUTER_USE、MOBILE_USE、GROUNDING 三种提示词模板
ui·prompt
一键生成网站5 小时前
AI生成UI原型支持复杂逻辑交互工具对比与场景适配分析
人工智能·ui·交互·
糯米导航14 小时前
AI 视觉回归实战:截图对比不是“找不同”,如何让智能差异分析真正服务 UI 质量
人工智能·ui·回归
CedarQR1 天前
VS Code Copilot Chat 配置 MCP Server 全攻略(以 Next AI Draw.io 为例)
人工智能·ui·json
console.log('npc')1 天前
网页移动端UI/UX Pro Max - 设计智能skill
前端·人工智能·ui·ux
工业HMI实战笔记1 天前
HMI性能优化技巧:告别卡顿,打造流畅体验
前端·ui·性能优化·自动化·交互
解局易否结局1 天前
鸿蒙多设备形态适配实战:从手表到车机的差异化 UI 设计
ui·华为·harmonyos
ltqvibe1 天前
TokUI怎么让AI输出从文字墙变成可交互UI
人工智能·ui·交互