架构设计深度解析:策略模式 + 抽象工厂在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 占几格"**这些琐碎的数值,全部抽离到了 LandPageConfig 和 PortPageConfig 中。
- 好处: 你的 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 改一个数字,其他几千行代码动都不用动。