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

 

 

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐