Flutter多设备适配开发指导
1 概述
在使用 Flutter 进行移动端开发时,开发者往往需要面对多设备适配的挑战。如果所有页面都完全使用 Flutter 组件重构,不仅开发周期长、成本高,也难以落地。为了实现一次开发,多端部署的体验,本文将介绍一套面向多设备适配的 Flutter 响应式布局方案,涵盖动态断点、栅格系统、导航分栏、自适应隐藏等关键能力,帮助开发者在保证体验一致性的同时提升开发效率。
使用场景
为更直观地展示 Flutter 在多设备适配中的响应式布局能力,本文结合实际业务页面,选取了三个典型场景进行分析:视频首页聚焦于栅格系统与断点联动;IM 聊天首页体现分栏导航与断点适配;音乐首页则演示了自适应显隐与断点控制的配合。通过这些场景,展示如何灵活运用响应式布局能力,在不同终端下实现一致、可扩展的用户体验。
页面 |
sm |
md |
lg |
---|---|---|---|
视频首页 |
|
|
|
IM聊天首页 |
|
|
|
音乐首页 |
|
|
|
2 多设备适配指导
2.1 视频首页开发说明
视频首页划分为4个区域,效果图如下:
- 整个页面响应式适配,借助栅格组件能力监听不同断点变化实现不同的布局效果。
- 区域2在sm设备上呈两行显示,在md设备和lg设备上单行显示,断点变化时切换显示效果。
- 区域3使用自适应布局延伸能力随不同设备尺寸延伸或隐藏。
- 区域4使用响应式布局的栅格断点系统,根据断点变化切换组件属性实现布局效果。
视频首页包含4个基础区域,具体介绍及实现方案如下表所示:
区域编号 |
简介 |
实现方案 |
---|---|---|
1 |
底部/侧边页签 |
监听断点变化改变位置。 |
2 |
顶部页签及搜索框 |
监听断点变化实现折行显示。 |
3 |
Banner图和图标列表区域 |
监听断点变化展示不同状态的banner。 |
4 |
推荐影片 |
借助栅格组件能力监听断点变化改变列数。 |
2.1.1 断点区间
Flutter 断点机制是结合HarmonyOS平台动态断点能力封装的一套跨平台响应式适配方案。在HarmonyOS平台下,断点值通过调用 ArkTS 接口直接获取;在其他平台,则根据当前屏幕宽度计算所在断点区间。该机制帮助开发者基于应用窗口宽度实现差异化的页面布局,从而提升多设备下的 UI 适配效率与一致性体验。
断点系统以应用窗口宽度为基础,将其划分为多个区间(即断点),默认提供的断点区间如下所示:
断点名称 |
取值范围(px) |
xs |
[0, 320) |
sm |
[320, 600) |
md |
[600, 840) |
lg |
[840, 1440) |
xl |
[1440, +∞) |
断点设置与获取方法如下表所示:
方法名 |
参数 |
返回值 |
说明 |
---|---|---|---|
init |
void |
Future |
初始化断点,注册窗口尺寸变化监听,并返回当前断点值。 |
destroy |
void |
void |
销毁窗口尺寸变化监听,清空所有添加的回调函数。 |
addBreakpointCallback |
Function(BreakpointData) callback |
bool |
添加断点状态变化回调函数,添加成功返回true。 |
removeBreakpointCallback |
{Function(BreakpointData)? callback} |
bool |
删除断点状态变化回调函数,不传值时删除所有,删除成功返回true。 |
setWidthBreakpointRange |
WidthBreakpointRange range |
void |
设置横向断点区间。 |
setHeightBreakpointRange |
HeightBreakpointRange range |
void |
设置纵向断点区间。 |
关键代码片段:
void main() {
WidgetsFlutterBinding.ensureInitialized();
final routeManager = RouteManager();
runApp(ChangeNotifierProvider(
create: (context) => routeManager, child: const MyApp())); //用provider暴露断点信息
}
class MyApp extends StatelessWidget {...}
class RouteManager extends ChangeNotifier {
final ValueNotifier<List<Page>> pages = ValueNotifier([]);
WidthBreakpoint _widthBreakpoint = WidthBreakpoint.unKnow;
HeightBreakpoint _heightBreakpoint = HeightBreakpoint.unKnow;
RouteManager() {
_initBreakpoint(); //断点状态需要初始化一次
}
Future<void> _initBreakpoint() async {
final BreakpointData data = await BreakpointManager().init(); //初始化断点,注册窗口尺寸变化监听,并返回当前断点值。
_widthBreakpoint = data.widthBreakpoint;
_heightBreakpoint = data.heightBreakpoint;
BreakpointManager().addBreakpointCallback((breakpointData) { //添加断点状态变化回调函数
_widthBreakpoint = breakpointData.widthBreakpoint;
_heightBreakpoint = breakpointData.heightBreakpoint;
notifyListeners();
});
}
WidthBreakpoint get widthBreakpoint => _widthBreakpoint;
HeightBreakpoint get heightBreakpoint => _heightBreakpoint;
pushPage(Page page) {...}
popPage() {...}
void replaceAll(List<Page> newPages) {...}
}
2.1.2 底部/侧边页签
底部/侧边页签区域,不同断点下显示在首页的不同位置。在sm和md断点下,页签显示在底部;在lg断点下页签显示在左侧,且页签居中显示。
关键代码片段:
Scaffold(
appBar:...
body: Container(
height: _currentHeight,
width: _currentWidth,
decoration: const BoxDecoration(color: Colors.white),
child: Row(
children: [
if (screenType == WidthBreakpoint.lg ||
screenType == WidthBreakpoint.xl) //在lg和xl屏幕下展示侧边页签
Container(
width: 80, //调整宽度
color: const Color(0xFFF1F3F5),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: sideBarList.map((item) {
return _buildVerticalNavButton(...)
}).toList()),
),
Expanded(....), //占居剩下空间
],
),
),
bottomNavigationBar: screenType == WidthBreakpoint.lg ||
screenType == WidthBreakpoint.xl //在不是lg和不是xl屏幕展示底部页签
? null
: BottomNavigationBar(...)
2.1.3 顶部页签及搜索框
不同断点下,顶部页签和搜索框占用不同栅格列数,使用栅格布局实现在sm断点下分两行显示。
关键代码片段:
if (widget.screenType == WidthBreakpoint.sm ||
widget.screenType == WidthBreakpoint.xs) { //在sm、xs下展示小屏幕的顶部
return getSmTop(...);
} else if (widget.screenType == WidthBreakpoint.md) { //在md下展示中屏幕的顶部
return getMdTop(...);
} else { //在lg、xl下展示大屏幕的顶部
return getLgTop(...);
}
2.1.4 Banner图和图标列表区域
该区在不同断点下,Banner图展示不同;
在“一多”的应用中,经常会出现窗口大小。改变如果组件随着窗口宽度变化只改变宽度、不改变高度,会导致图片变形,视觉上会给用户带来较差体验。实现这部分时,主要利用断点进行判断,展示不同宽度的图片。
关键代码片段:
if (screenType == WidthBreakpoint.sm || screenType == WidthBreakpoint.xs) { //在sm、xs下展示小屏幕的Banner图
return const BannerForSm();
} else if (screenType == WidthBreakpoint.md) { //在md下展示中屏幕的Banner图
return ScrollConfiguration(...);
}
return ScrollConfiguration(...); //在lg、xl下展示大屏幕的Banner图
}
2.1.5 推荐影片区域
该区域采用栅格组件实现响应式布局,通过在不同断点下动态划分父组件的列数,实现灵活的自适应布局能力。
该栅格组件参考 ArkUI 的 GridRow/GridCol 设计,基于 Flutter 实现,提供与 ArkUI 一致的基础栅格布局能力,方便开发者快速上手并复用 ArkUI 的栅格设计经验。
栅格组件支持的配置属性如下表所示:
属性名 |
描述 |
类型 |
平台 |
---|---|---|---|
gridCols |
单元格GridCol列表容器。 |
List<GridCol> |
All |
columns |
栅格中的列数,其数值决定了内容的布局复杂度(默认值:12)。 |
int |
All |
gutter |
相邻的两个Column之间的距离,决定内容间的紧密程度(包括水平、垂直方向的距离)。 |
Gutter |
All |
margin |
相对应用窗口、父容器的左右边缘的距离。 |
EdgeInsetsGeometry |
All |
range |
设置断点值的断点数列。 |
BreakpointRange |
All |
关键代码片段:
GridRow(
gridCols: gridList.map((item) {
return GridCol(
span: SpanOption(xs: 6, sm: 6, md: 4, lg: 3, xl: 3, xxl: 3), //不同断点下,col占据的空间
offset: 0,
order: 1,
child: getColItem(...),
);
}).toList(),
columns: 12, //设置一行被分成多少份
gutter: const Gutter(x: 10, y: 8), //设置每个col的X,Y方向的距离
margin: const EdgeInsets.symmetric(horizontal: 0),
range: BreakpointRange(list: [320, 600, 840, 1440, 2000]), //设置断点区间
)
2.2 IM聊天首页开发说明
将IM聊天首页划分为2个区域,效果图如下:
- 整个页面响应式适配,监听不同断点变化实现不同的布局效果。
- 区域2在sm设备上呈现单列显示,在md设备和lg设备上双列显示,断点变化时切换显示效果。
IM聊天首页包含2个基础区域,具体介绍及实现方案如下表所示:
区域编号 |
简介 |
实现方案 |
---|---|---|
1 |
底部/侧边页签 |
监听断点变化改变位置。(同视频首页) |
2 |
聊天主体部分 |
监听断点变化利用分栏组件实现单栏/分栏效果。 |
2.2.1 聊天主体部分开发说明
自适应分栏组件参考 ArkUI 的 Navigation 组件实现,基于 Flutter 开发,支持分栏显示与路由跳转能力,帮助开发者便捷构建多设备下的一致性分栏/导航体验。
组件支持的配置属性如下表所示:
属性名 |
类型 |
必填 |
默认值 |
说明 |
---|---|---|---|---|
navBarWidth |
double |
否 |
240 |
导航栏宽度。 |
minContentWidth |
double |
否 |
360 |
最小主内容区域宽度。 |
navBarWidthRange |
List<double> |
否 |
[240,432] |
导航栏最小和最大宽度。 最大默认值为组件宽度的40%,且不大于432,如果只设置一个值,该值为最大宽度,未设置的值按照默认值计算。 |
mode |
NavigationSplitMode |
否 |
NavigationSplitMode.auto |
导航栏的显示模式。 自适应:基于组件宽度自适应单栏和双栏。 |
navBarPosition |
NavBarPosition |
否 |
NavBarPosition.start |
导航栏位置。 |
navigationPane |
Widget |
否 |
SizeBox.shrink |
导航栏内容。 |
mainContent |
Widget |
否 |
SizeBox.shrink |
主内容。 |
onNavBarStateChange |
Function(bool isVisible) |
否 |
null |
导航栏显示状态切换时触发该回调。isVisible为true时表示显示,为false时表示隐藏。 |
onNavigationModeChange |
Function(NavigationSplitMode mode) |
否 |
null |
当navigationPane首次显示或者单双栏状态发生变化时触发该回调。 NavigationSplitMode.split:当前显示为双栏。 NavigationSplitMode.stack:当前显示为单栏。 |
关键代码片段:
NavigationSplitContainer(
navBarWidth: 320, //给导航栏一个固定宽度
mode: screenType == WidthBreakpoint.sm ||
screenType == WidthBreakpoint.xs
? NavigationSplitMode.stack
: NavigationSplitMode.split, //不同断点下展示不同的导航栏模式
navigationPane: navBarIsShow //根据分栏状态展示不同的导航内容
? renderNavigationPane(
_currentWidth, _currentHeight, screenType, il8n)
: Container(),
mainContent: navBarIsShow //根据分栏状态展示不同的主体内容
? renderMainContent(
_currentWidth, _currentHeight, screenType, il8n)
: renderNavigationPane(
_currentWidth, _currentHeight, screenType, il8n),
onNavBarStateChange: (isVisible) {
if (isVisible != navBarIsShow) {
Future.delayed(Duration.zero, () {
setState(() {
navBarIsShow = isVisible; //当分栏变化时触发
});
});
}
},
)
2.3 音乐首页开发说明
将音乐首页划分为3个区域,效果图如下:
- 整个页面响应式适配,借助断点变化实现不同的布局效果。
- 区域3在设备上呈现3个区域,头像、音乐控制按钮、收藏,音乐控制按钮部分使用自动隐藏组件。
音乐首页包含3个基础区域,具体介绍及实现方案如下表所示:
区域编号 |
简介 |
实现方案 |
---|---|---|
1 |
顶部页签 |
监听断点变化改变展示大小和位置。 |
2 |
音乐列表 |
借助栅格组件能力监听断点变化改变列数。 |
3 |
音乐播放控制和收藏 |
监听断点变化展示不同状态,利用自适应显隐组件控制音乐控制按钮的显示与隐藏。 |
2.3.1 音乐播放控制和收藏开发说明
本章节重点介绍音乐控制按钮的自适应显隐功能。
自适应显隐容器:容器组件内的子组件,按照其预设的显示优先级,随容器组件尺寸变化显示或隐藏,其中相同显示优先级的子组件同时显示或隐藏。它是一种比较高级的布局方式,常用于分辨率变化较大,且不同分辨率下显示内容有所差异的场景。主要思想是通过增加或减少显示内容,来保持最佳的显示效果。
自适应显隐组件支持的配置属性如下表所示:
属性名 |
类型 |
必填 |
默认值 |
说明 |
---|---|---|---|---|
direction |
Axis |
是 |
定义子组件排列的方向。可以是水平排列(Axis.horizontal)或垂直排列(Axis.vertical)。 |
|
mainAxisAlignment |
MainAxisAlignment |
否 |
MainAxisAlignment.start |
定义主轴方向上子组件的对齐方式。 |
crossAxisAlignment |
CrossAxisAlignment |
否 |
CrossAxisAlignment.center |
定义交叉轴方向上子组件的对齐方式。 |
displayPriorityList |
List<DisplayPriorityObject> |
是 |
显隐优先级对象的列表。 |
|
textDirection |
TextDirection |
否 |
TextDirection.ltr |
确定文本和子组件的布局方向。 |
verticalDirection |
VerticalDirection |
否 |
VerticalDirection.down |
定义垂直方向上子组件的排列顺序。 |
显隐容器属性配置类
属性名 |
类型 |
必填 |
默认值 |
说明 |
---|---|---|---|---|
displayPriority |
int |
否 |
1 |
设置当前组件在布局容器中显示的优先级。默认值:1。说明:仅在DisplayPriorityBox组件中生效。 |
child |
Widget |
是 |
子组件。 |
关键代码片段:
DisplayPriorityBox(
direction: Axis.horizontal, //子元素的排列方式
displayPriorityList: [
DisplayPriorityObject(
displayPriority: 2, //宽度不够的时候展示权重
child: Container(...)),
DisplayPriorityObject(
displayPriority: 2, //宽度不够的时候展示权重
child: Container(...))),
DisplayPriorityObject(
displayPriority: 3, //宽度不够的时候展示权重
child: Container(...)),
),
DisplayPriorityObject(
displayPriority: 2, //宽度不够的时候展示权重
child: Container(...)),
DisplayPriorityObject(
displayPriority: 99, //宽度不够的时候展示权重
child: Container(...)),
],
)
3 示例代码
基于Flutter框架的多设备开发sample示例代码地址:adaptive_layout_sample。
更多推荐
所有评论(0)