简介:面向Android开发者的完整项目UI框架资源包,聚焦底部任务栏自定义、Fragment高效切换与整体架构设计三大核心问题,适合需要搭建或重构应用界面的初中级开发者。包内包含可直接参考的Android工程源码与示例,通过自定义TabBar、FragmentManager事务管理、MVP/MVVM分层等实现方式,演示动态改样式、平滑过渡动画及界面状态控制等关键技巧,同时兼顾不同屏幕尺寸的适配方案;结合对Fragment管理、Activity生命周期的源码级剖析,并融入单例、工厂、代理等设计模式,帮助开发者掌握高复用、易维护的界面组织思路。资源共214个文件,压缩包仅5.25MB,其中png图片与xml布局构成主要界面资源,java/class文件对应核心逻辑与编译产物,并附带apk安装包便于直接体验效果,整体目录结构清晰,便于按模块对照学习。目前已有499人学习,适合希望提升Android UI架构能力并快速落地的开发读者。 很多Android开发者在项目做到中期都会遇到一个尴尬局面:单个页面的控件越来越多,代码越来越散,新来的同事改个按钮颜色要找半天布局文件。这时候大家会本能地想到“要不要引入一个UI框架”,但说实话,我见过太多项目折腾了一圈开源框架之后,反而把原本还能维护的代码搞得更乱。问题不在框架本身,而在于把“UI框架”理解成了一堆第三方库的堆砌。这篇文章我想结合自己做过的几个中大型项目的经验,聊聊Android项目整体UI框架到底应该怎么搭,它本质上解决的是什么问题,以及每一层具体应该怎么设计。文章会覆盖分层架构、View与Compose的混编选型、主题系统、适配方案、Base封装、组件沉淀,还有那些只在真实项目中才会踩到的坑,适合正在搭建或重构项目UI层的技术负责人和一线开发参考。
1. “UI框架”不是引入一个库那么简单——动手前的三连问
先纠正一个认知偏差:所谓整体UI框架,并不是你接一个Bootstrap、Ant Design或者某个Android开源UI库的壳子就叫搭建完成。Android项目里的UI框架,本质上是一套围绕界面层搭建的组织结构和协作契约。它决定了几十个页面怎么共用同一套视觉语言、一个人改主题色如何全局生效、新增一个页面时开发要写多少样板代码、设计师给的标注稿怎么低成本翻译成代码。
所以在写任何代码之前,我建议你先回答三个问题。
第一个问题是现状评估:项目是新建的还是已经运行了一两年的老项目?老项目里View体系和Jetpack Compose的占比是多少?有没有历史遗留的全局主题、自定义View、第三方控件?新项目可以从零规划,但老项目必须考虑渐进式改造的路径,不可能推倒重来。
第二个问题是团队规模和技术栈:团队是5个人以下的小团队,还是20人以上的多业务线协作?大家Compose熟练度如何,还是更熟悉传统的XML布局?如果团队对Compose的掌握不足,强行全面迁移会带来一段很长的低效期。
第三个问题是产品形态和设计约束:是只有手机端,还是需要同时适配平板、折叠屏甚至车机?有没有深色模式、字体大小切换、国际化这类动态需求?设计师交付的是固定像素标注,还是基于间距网格的token体系?
这三个问题决定了你的UI框架是偏轻还是偏重。我见过一些中小团队把企业级项目的UI框架整套搬过来,结果是十几个模块几百个类,绝大多数根本用不上,光维护成本就把团队拖垮了。合理的做法是按需设计,能简单的绝不过度设计。
2. 分层架构设计:横向划模块,纵向分层级
项目整体UI框架的核心是分层。我常用的划分方法分两个维度:横向把所有UI能力拆成几个相对独立的模块,纵向则在每个模块里再区分不同层级,避免互相依赖成一锅粥。
2.1 横向:基础组件层、业务组件层、页面层
横向模块划分上,我习惯把UI相关的东西切成三层。
基础组件层是最底层的通用控件类组件,比如按钮、输入框、列表项、Tab、对话框、Toast、空态页、错误页、骨架屏。这一层的特征是抽象、稳定、不依赖任何业务数据。它的输入只是数据和回调,输出是符合设计规范的界面元素。基础组件层一般沉淀成一个独立的UI组件库,比如公司内部的common-ui模块。
业务组件层则是由基础组件拼装出来的、带有一定业务语义的复合模块。举个典型例子:一个“商品卡片”组件,它不是单纯的一个图片加两行文字,而是组合了图片加载、价格标签、促销角标、加购按钮,甚至埋点逻辑。类似还有“用户头像+昵称+等级标识”这种个人信息的复合组件。这一层可以跨业务模块复用,但已经有明确的业务倾向。
页面层就是各个业务模块里具体的Activity、Fragment或者Compose页面,它负责把组件组合成完整功能页面。页面层只允许依赖基础组件层和业务组件层,不能反过来。同时页面与页面之间不要直接互相调用彼此的UI细节,跨模块复用UI能力必须下沉到对应的组件层。
2.2 纵向:资源与主题、工具与规范、状态与导航
横向模块之外,还需要纵向切几刀。这些纵向能力是横切所有层次的,必须单独规划。
第一刀是资源与主题。所有颜色、字体、间距、圆角、阴影、动效时长等设计变量,必须收敛到一套统一的资源体系里。在Android里就是colors.xml、dimens.xml、themes.xml这些文件,以及在Compose里的Color、Typography、Shape等Token定义。这一层几乎不允许业务代码里出现硬编码的颜色值和尺寸值。
第二刀是工具与规范。比如屏幕适配方案、状态栏处理、安全区适配、基类封装、通用工具类。这些并不是直接的UI控件,但是UI开发的“基础设施”。没有这一层,每个页面都要自己处理状态栏高度、底部导航避让、多窗口尺寸变化,写出来的代码必然是重复和混乱的。
第三刀是状态与导航。比如全局的Loading状态管理、页面通信、路由框架。路由不只是为了解耦页面跳转,更是为了让UI框架在模块化工程里能够独立存在。如果你有独立的UI组件库,组件内部的页面(比如组件的Demo页面)跳转也必须依赖路由来解耦。
这三刀加三个横向模块,才能算一个完整的UI框架结构。很多所谓的UI框架不好用,就是因为只做了横向的组件库,完全忽略了纵向的资源、主题、规范和状态管理。
3. 双轨制选型:View体系、Compose与主题规范的共存策略
选型是聊UI框架时最绕不开的话题。2024年聊Android UI框架,几乎都会面对一个现实:老项目以View体系为主,新写页面又非常想用Compose。这就形成了“双轨制”局面。双轨制不是过渡期的临时状态,它可能是未来两年大多数项目的常态,所以UI框架设计必须考虑两者如何共存、如何共用一套设计语言。
3.1 View与Compose:不选边,找交集
如果项目是2019年以前启动的老项目,全部迁移到Compose的工作量是巨大的。我在一个日活百万级的App里见过单套View项目重构为Compose的成本评估报告,结论是纯UI层迁移的工时就要6人月起步,而且业务复杂页面容易出现行为差异。所以更务实的策略是:新写的页面优先Compose,老页面继续维护View体系,两者通过ComposeView和AndroidView互相嵌套。
这个策略要求UI框架在底层统一定义设计Token。也就是说,Compose里的MaterialTheme.colorScheme.primary和XML里的?attr/colorPrimary必须指向同一个设计变量来源。推荐的做法是基于Compose的MaterialTheme做一层包装,同时维护一套View体系下的ThemeOverlay资源,用一套语义化命名(colorPrimary、colorOnPrimary这类)把两套体系绑定在一起。主题在运行时的切换(比如深色模式)也通过同一套配置驱动,这样无论底层是Compose还是View,最终呈现的视觉都是一致的。
3.2 主题系统:语义化命名才是灵魂
很多项目的主题规划停留在“定义了10个品牌色”的层面,这远不够。完整的主题系统至少应该包含三层:
基础层是原始的设计变量,比如品牌色#FF4D4F、主文字色#1F1F1F、间距基数4dp。这一层直接映射设计稿上的具体值。
语义层是把原始变量关联到“用途”上去:colorPrimary、colorBackground、textColorPrimary、textColorHint、dividerColor等等。这一层才是开发者写代码时真正使用的变量。它的价值在于,当你需要换肤或者适配深色模式时,你只需要改语义层与基础层的映射关系,而不需要去改每一个页面。
组件的内部状态层是更细粒度的,比如按钮的enable/disable状态、按压效果、加载状态的各自配色,Tab的选中/未选中效果。这一层通常用StateListDrawable(XML)或者Compose的ButtonDefaults配置。很多自定义组件的观感差,就是因为只定义了静态颜色,没有定义状态切换的完整视觉反馈。
顺带一提,字号规范也最好做成语义化的TextAppearance体系,比如textAppearanceTitleLarge、textAppearanceBodyMedium。Android的字体缩放(用户调系统字号)和动态字体(Font scale)在自定义组件里很容易被忽略,统一走TextAppearance可以规避很多显示问题。
3.3 适配方案:从暴力dp到容器查询
屏幕适配是所有Android UI框架都躲不过的坎。早年流行在XML里写死dp配合ScreenAdapter强行把屏幕宽度换算成设计稿宽度的做法,在天马行空、安卓Pad和折叠屏上早就翻车了。我现在的建议很简单:常规手机以最小宽度(sw400dp)为基准保证基础体验,复杂布局靠自适应容器而不是固定尺寸。
这里的核心思想是用ConstraintLayout的比例约束、Weight权重分配、Flow或者Compose的BoxWithConstraints来做“响应式”,而不是去做像素级换算。比如一个商品列表,在窄屏上显示两列,在平板宽度下可以自动变为四列,这就是容器查询的思路。对折叠屏这种设备状态会动态变化的场景,还得处理好resizeableActivity以及onConfigurationChanged带来的重建问题。
4. 落地实操:目录结构、Base封装、资源规范与组件沉淀
理论讲了不少,接下来给你一套可以直接套用的落地清单。这部分内容是我多个项目的实际整理,拿来改改就能用。
4.1 一套能落地的目录结构
在普通的分层项目里,UI相关代码我会按下面的方式组织:
com.xxx.project ├── ui │ ├── base # Base基类 │ │ ├── BaseActivity.kt │ │ ├── BaseFragment.kt │ │ └── BaseViewModel.kt │ ├── components # 通用组件层(跨模块复用) │ │ ├── commonui # 基础组件:按钮、输入框、Dialog等 │ │ └── widget # 业务组件:商品卡、用户卡片等 │ ├── theme # 主题体系(颜色、字体、间距、TextAppearance) │ │ ├── Color.kt │ │ ├── Type.kt │ │ └── Theme.kt │ ├── navigation # 路由与导航 │ ├── pages # 页面层(按业务模块分包) │ │ ├── home │ │ ├── order │ │ └── mine │ └── adapter # 通用Adapter与ViewHolder封装 └── res ├── values │ ├── colors.xml │ ├── dimens.xml │ └── themes.xml └── drawable这里有一个容易被忽略的点:基础组件层必须以独立的模块或包存在,并配有自己的版本号。哪怕只是在同一个App工程里,也建议把commonui做成Gradle模块或者至少是物理隔离的包,这样才能从工程层面强制页面代码不能直接跳级引用其他业务页面的UI实现。
4.2 Base类的封装应该做什么,不做什么
BaseActivity和BaseFragment是UI框架的骨架。好的封装应当解决:统一的布局加载流程、ViewBinding或DataBinding的自动绑定、状态栏配置、权限回调分发、通用Loading和空态视图、进入退出的转场动画。但是一定要注意避免过度封装。
我见过一个团队的BaseActivity里塞了300行代码,包含了埋点初始化、登录态检查、网络状态监听、甚至热修复检测。这种大而全的基类带来的问题是:新开发者接手时根本搞不清Activity生命周期里哪些逻辑是什么时候执行的,排查问题变得极其困难。
我的建议是:Base类只处理与UI框架相关的通用职责,业务逻辑一律下沉到ViewModel或者独立的业务管理器。比如做LoginCheck,不要在BaseActivity里塞判断逻辑,而是通过注解或者在需要登录的页面的ViewModel里做前置检查。基类越小,越稳定,越好维护。
4.3 资源规范与组件沉淀的可持续机制
组件沉淀这件事,大多数团队挂在口头上却没有持续运行的机制。这里分享一个我亲测有效的策略:组件库与Demo页面同库共存。每个组件在提交代码时必须配套一个Demo页面,页面里展示组件的所有状态变体、不同宽度下的表现,并且这些Demo页面要通过路由可以被独立打开。
这样做的价值非常大:新同学接手组件时打开Demo页面就能看运行效果,而不是读源码猜行为;设计师验收组件时也能直接对比设计稿;发版前跑一次回归,点开所有组件页面看是否异常,UI回归测试的成本大大降低。
资源规范上也有一点值得强调:资源文件的命名要有统一的前缀和语义。比如项目前缀+组件名+状态:selector_btn_main_enabled.xml、bg_dialog_bottom_corner.xml、ic_arrow_left.svg。字符串资源统一用页面_模块_描述的结构。这个表面看起来是个小规范,实际对于多人协作时查找、修改、以及干掉无用资源都特别有帮助。
5. 排查链路:UI框架上线后,我被线上问题反向教育
框架搭建好之后,真正开始在各个业务线跑,各种问题才冒出来。有些是我在设计框架时完全没预料到的,这里挑几个典型的排查案例,每一次的排查链路其实都能反过来完善框架设计。
5.1 深色模式下自定义View的颜色诡异
现象:开启深色模式后,某个业务方自定义的富文本View里的文字颜色变得非常浅,几乎看不清。
排查过程:第一反应是自定义View里硬编码了白色文字。打开代码发现,富文本View的文字颜色来自一个预设的SpannableStringBuilder的色值常量,写死成了#666666。浅色模式下这个颜色显示还算正常,到了深色模式,背景变成灰黑色,这个中灰色文字就几乎“消失”了。
根因:自定义View没有遵循语义化颜色,而是直接用业务色值硬编码了。更深一层的原因是,UI框架规定了?attr/textColorPrimary的使用规范,但SpannableStringBuilder等代码中设置的色值没有纳入规范检查。
修复与规范升级:将硬编码色值改为根据当前主题动态解析?attr/textColorPrimary对应色值,同时把SpannableString构建逻辑收敛到统一工具类,由工具类负责主题色解析。更重要的是,在组件代码审查规范里增加了“所有色值必须取自Theme”这一条,并在自定义View的Demo页面里增加深色模式截图对比。
5.2 Compose与XML混编导致的View层级性能下滑
现象:某些页面从XML迁移到Compose后,列表滚动明显卡顿,帧率掉到40帧左右。
排查过程:用Profile分析发现是混编布局触发了频繁的Layout和Measure。原因是XML页面View体系内嵌了多个ComposeView,而ComposeView在XML里默认尺寸是wrap_content,它必须每次测量时动态计算内容尺寸,导致嵌套层级一多就出现性能瓶颈。
根因:混编方案里没有规定ComposeView在父布局中的尺寸测量策略。尺寸固定的场景(比如一个固定高度的header banner)不需要让ComposeView去动态测量两次。
修复与规范升级:修复方式是把这类ComposeView的宽高设置成固定的尺寸或者用match_parent,减少动态测量。同时在框架的混编规范中补充了明确的尺寸约束规则:凡是已知固定尺寸的ComposeView,禁止使用wrap_content外加复杂的动态内容,并且要求使用ComposeView的setViewCompositionStrategy来控制组合回收,避免页面销毁后泄露。
5.3 折叠屏展开瞬间的布局闪变
现象:折叠屏手机从小屏展开到大屏过程中,部分页面出现短暂的布局跳变和元素位移。
排查过程:起初怀疑是布局本身的问题,反复调整ConstraintLayout的约束条件都没有解决。最后看到onConfigurationChanged的调用时机才明白——设备在展开过程中会触发Configuration变更,如果不处理resizeableActivity,Activity会被系统重建,重建过程中的布局是拿旧状态渲染的,自然会对不上新尺寸。
根因:UI框架的适配规范没有明确规定resizeableActivity、screenOrientation等配置,也没有针对展开收起过程中的尺寸变化做容器级响应。
修复与规范升级:给框架的BaseActivity加上统一的android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation",这样尺寸变化时不会重建Activity,而是走onConfigurationChanged回调。在这个回调里处理布局更新、缓存清理,并确保Compose重组发生在尺寸确定之后。从此折叠屏展开不再闪变。
5.4 组件的按钮按压态失效引发“触感困惑”
现象:上线后收到反馈,部分页面的按钮点击没有按压变化,用户误以为按钮没反应。
排查过程:这类问题很隐蔽。检查代码发现按钮的背景是直接setBackgroundColor()设置的实色,而按压操作的视觉反馈依赖的是StateListDrawable里的state_pressed状态。当你在代码里直接setBackgroundColor时,相当于把背景替换成了一个无状态变化的ColorDrawable,系统的按压反馈机制就失效了。
根因:业务开发图省事,绕过框架提供的Button组件,直接给View设置了背景色,导致按压反馈被冲掉。框架层没有阻止这种写法,按钮按压效果规范也没有在CodeReview阶段被发现。
修复与规范升级:一方面补上全局的按钮按压效果检查工具,通过Lint扫描代码里直接调用setBackgroundColor的地方。另一方面在框架规范中强制要求:业务中所有可点击的反馈必须使用框架提供的Button或者对应的状态列表背景。与此同时,全部Button组件自定义了压缩和放大动画,让点击反馈不止颜色变化,触感上也更明确。
5.5 Git协作冲突:多人同时改主题文件
现象:三个业务线并行开发时,各自在themes.xml和Color.kt里添加自己的颜色定义,提交代码时频繁冲突,而且一不小心就会覆盖掉别人的定义。
排查过程:这个问题的排查很简单,就是版本冲突日志一查,几乎都是同一个资源文件里多人同时修改。
根因:UI框架把主题资源做成了单一文件,缺少承载多人并行的细分维度,参与协作的人一多,瓶颈就在文件本身上。
修复与规范升级:采用“分类小文件”替代单文件管理,把themes.xml拆分为themes_core.xml、themes_components.xml、themes_extension.xml等,公用的语义Token放在core里,第三方组件的自定义主题放components,业务扩展主题放extension,各改各的文件。同时配合Bitrise/GitLab CI新增了一个资源冲突检测的Lint规则,能在提交时自动提示哪些资源命名重复或覆盖。
这五个案例让我深刻体会到,UI框架的稳定性不是靠一次设计就能保证的,它需要一套持续运转的规范、工具和审查机制来共同维护。框架的升级应该像一个活的生态,跟随业务真实反馈不断进化,而不是死守着一开始的架构图。
如果我现在要从零开始搭一套项目整体UI框架,我会把60%的精力放在主题系统、规范定义和基础组件这三件事上,候选组件和页面层代码反而是最简单的部分。因为有了明确的规范和好用的底层设施,页面代码是水到渠成的事。反过来,如果底层规范混乱,上层再努力也会到处打架。动手之前先想清楚这些,你的框架才真正撑得起整个项目的UI体系。
本文还有配套的精品资源,点击获取