架构设计深度解析:策略模式 + 抽象工厂在UI适配中的高级应用

架构设计深度解析:策略模式 + 抽象工厂在UI适配中的高级应用

代码:

kotlin 复制代码
// 在 BasePageConfig中 定义了很多抽象方法 

abstract class BasePageConfig {

    companion object {
        const val COLUM_COUNT_ONE = 1
        const val COLUM_COUNT_TWO = 2
        const val COLUM_COUNT_THREE = 3
        const val COLUM_COUNT_FOUR = 4
        const val COLUM_COUNT_FIVE = 5
        const val COLUM_COUNT_SIX = 6
        const val COLUM_COUNT_EIGHT = 8
        const val COLUM_COUNT_TEN = 10
        const val COLUM_COUNT_TWENTY = 20
        fun get() = if (ViewDelegate.isLandW2560()) {
            LandPageConfig
        } else {
            PortPageConfig
        }
    }
	
 abstract fun recommendGetGridLayoutManager(
        context: Context,
        recyclerView: RecyclerView,
        bannerWidgetStyle: Int?
    ): GridLayoutManager


    abstract fun recommendSongFolderListMax(): Int

    abstract fun recommendSongInfoListMax(): Int

    abstract fun musicHouseGetGridLayoutManager(
        context: Context,
        recyclerView: RecyclerView
    ): GridLayoutManager
	
	

}

// 在具体的实现类 LandPageConfig,PortPageConfig中,来实现,会对每个页面,设置具体是多少span/列,写的完整,严谨,很美妙

LandPageConfig.kt:


object LandPageConfig : BasePageConfig() {

    override fun createDecorationConfig(): IDecorationConfig {
        return LandDecorationConfig
    }

    override fun recommendGetGridLayoutManager(
        context: Context,
        recyclerView: RecyclerView,
        bannerWidgetStyle: Int?
    ): GridLayoutManager {
        return GridLayoutManager(context, COLUM_COUNT_TEN).apply {
            spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
                override fun getSpanSize(position: Int): Int {
                    val adapter = recyclerView.adapter as? RecommendAdapter
                    return when (adapter?.getItemViewType(position)) {
                        RecommendData.TYPE_SONG_FOLDER_SUPPLIER_STATION -> COLUM_COUNT_TWO
                        RecommendData.TYPE_SONG_ITEM -> COLUM_COUNT_FIVE
                        RecommendData.TYPE_BANNER -> {
                            if (bannerWidgetStyle == 1) {
                                COLUM_COUNT_TEN
                            } else {
                                COLUM_COUNT_FIVE
                            }
                        }
                        else -> {
                            COLUM_COUNT_TEN
                        }
                    }
                }
            }
        }
    }

    override fun recommendSongFolderListMax() = COLUM_COUNT_TEN

    override fun recommendSongInfoListMax() = COLUM_COUNT_EIGHT

    override fun musicHouseGetGridLayoutManager(
        context: Context,
        recyclerView: RecyclerView
    ): GridLayoutManager {
        return GridLayoutManager(context, COLUM_COUNT_TEN).apply {
            spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
                override fun getSpanSize(position: Int): Int {
                    val adapter = recyclerView.adapter as? MusicHouseAdapter
                    return when (adapter?.data?.get(position)?.itemType) {
                        MusicHouseItem.TYPE_TITLE_MORE -> COLUM_COUNT_TEN

                        MusicHouseItem.TYPE_NEWEST_SONG_ITEM -> COLUM_COUNT_FIVE

                        else -> COLUM_COUNT_TWO
                    }
                }
            }
        }
    }

总结: base中把每个页面,对应一个函数名称,在实现类 LandPageConfig,PortPageConfig中,写需要多少span,也就是占多少列

它属于典型的**"策略模式 + 抽象工厂"**在 UI 适配中的高级应用。

我也觉得这段代码写得很好,好在以下三点:

1. 将"业务逻辑"与"布局参数"彻底解耦

在一般的代码里,我们经常会在 Fragment 里面写大量的 if (isLandscape) 5 else 2

而这段代码把**"一共有几列"、"某个 Item 占几格"**这些琐碎的数值,全部抽离到了 LandPageConfigPortPageConfig 中。

  • 好处: 你的 Activity 或 Fragment 变得极其干净,它只需要调用 Config.get().recommendGetGridLayoutManager(),完全不需要关心当前是横屏还是竖屏。

2. 利用 SpanSizeLookup 实现极高的灵活性

LandPageConfig 里的这段逻辑:

kotlin 复制代码
override fun getSpanSize(position: Int): Int {
    return when (adapter?.getItemViewType(position)) {
        TYPE_SONG_ITEM -> COLUM_COUNT_FIVE  // 歌曲占 5 格(一行俩)
        else -> COLUM_COUNT_TEN            // 其他占满 10 格(一行一个)
    }
}

这里的"美感"在于:它把 RecyclerView 的复杂布局转换成了简单的数学题。

通过 COLUM_COUNT_TEN(总共10列)作为分母,不同的 Item 可以自由分配:

  • 想一行显示 2 个?那就占 5 格。
  • 想一行显示 5 个?那就占 2 格。
  • 想占满全屏?那就占 10 格。

这种以最小公倍数作为总列数的思想,让 UI 调整变得像拼图一样简单。

3. 强迫症级别的"严谨性"

  • 单例化(Object):LandPageConfig 用了 object 关键字,这意味着这些配置在内存中只有一份,不会重复创建对象,非常省内存。
  • 类型安全:在 Base 类里定义 abstract fun,强制子类必须实现。这样如果你以后要加一个"折叠屏配置",编译器会立刻提醒你:"嘿,你还没给折叠屏设置歌曲列表的最大值呢!"------这避免了低级错误。

核心感悟:配置化思想

你发现的这种"美",其实是**"配置化"**的思想。

  • Base 层:定义了规则(有哪些页面需要配置?)。
  • 实现层:填入数值(具体这个环境下的列数是多少?)。
  • 调用层:只负责执行。

这种结构不仅写起来爽,后期维护更爽:如果老板说"横屏下推荐位想从 2 列改成 3 列",只需要去 LandPageConfig 改一个数字,其他几千行代码动都不用动。


相关推荐
张小姐的猫3 分钟前
【Linux】网络编程 —— HTTP协议(上)
linux·运维·服务器·网络·http·单例模式·策略模式
FuckPatience42 分钟前
Telerik UI for WPF 值不能为null。参数名:key
ui·wpf
breeze jiang2 小时前
从原生事件到 React 组件化:用状态驱动模型加载进度 UI
前端·react.js·ui
JaneConan1 天前
鸿蒙 ArkUI 深水区:@Watch 和 @Observed,状态变了「自动跑」+ 嵌套对象「深层重绘」
开发语言·后端·ui·harmonyos
施棠海1 天前
Pixel2XML实战:10分钟从设计稿做出一个能拨号的Android拨号盘页面(附完整业务代码)
android·java·ui
施棠海1 天前
设计稿一键变成可运行的Android页面:Pixel2XML全流程UI开发框架实战(附完整源码)
android·ui·架构
Helen_cai1 天前
OpenHarmony 通用状态加载、空页面、错误兜底组件封装(全局业务通用 UI 组件 API23)
开发语言·ui·华为·php·harmonyos
YM52e1 天前
鸿蒙 Flutter BoxDecoration装饰:打造精美UI效果
学习·flutter·ui·华为·harmonyos·鸿蒙
qq_463408421 天前
# 基于 HarmonyOS ArkTS 声明式 UI 构建智能音乐播放器:深色主题圆盘轮播、五 Tab 导航隔离、瀑布流收藏与悬浮迷你播放条实现深度解析
ui·华为·harmonyos
工业HMI实战笔记2 天前
【无标题】
大数据·人工智能·ui·自动化·人机交互·交互