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

相关推荐
laowangpython7 天前
Photoshop 2025 下载安装全攻略
其他·ui·photoshop
风华圆舞7 天前
Flutter + 鸿蒙 Intents Kit:页面直达能力的完整接入方案
flutter·ui·华为·harmonyos
鲲穹AI超级员工7 天前
多款实用配色工具汇总,适配设计、UI 创作等多元场景
ui·色彩设计
UXbot7 天前
帮助企业低门槛开展AI应用开发的平台推荐
前端·低代码·ui·交互·产品经理·原型模式·web app
烂白菜7 天前
智码美形:华为云码道 × UI-UX-Pro-Max 高品质界面智能生成实践
ui·华为云·ux
像风一样的男人@7 天前
python --实现代理服务器
git·ui
风华圆舞7 天前
鸿蒙 Flutter 页面怎么感知防窥状态并调整 UI 可见性
flutter·ui·harmonyos
UXbot8 天前
如何选择适合公司项目的UI设计工具?企业选型指南
前端·低代码·ui·团队开发·原型模式·设计规范·web app
UXbot8 天前
原型设计工具如何帮助新人快速进入产品行业?
前端·低代码·ui·交互·团队开发·原型模式·web app
烈焰晴天8 天前
Codex 桌面端如何链接Figma MCP 服务器拿到 Figma设计稿精准尺寸等结构化数据 来精准还原UI
服务器·ui·figma