hadss_adaptive_layout

介绍

这是一个旨在解决Flutter多设备适配问题的三方库,为不同设备类型(包括折叠屏、平板、手机等)提供了便捷的支持。该库包含的接口和开箱即用的组件,使开发者能够轻松应对各种设备的布局适配需求。

目前该库提供的组件如下:

  1. 断点相关组件:使用ArkUI断点能力开发的断点管理组件,能够让开发者直接使用ArkUI断点能力。
  2. 自适应显隐容器:对标ArkUI的自适应显隐布局开发的容器,使开发者能够轻松的完成自适应显隐布局能力的开发。
  3. 侧边栏组件:对标ArkUI的SideBarContainer组件开发的Flutter侧边栏组件,使开发者能够轻松的完成侧边栏相关布局能力的开发。
  4. 自适应分栏组件:参考ArkUI的Navigation组件开发的Flutter分栏组件,使开发者能够轻松的完成分栏及路由跳转能力的开发。
  5. 栅格组件:对标ArkUI的GridRow/GridCol开发的Flutter栅格组件,提供基础的栅格布局能力,使开发能够直接使用ArkUI栅格布局能力。

工程目录

.
├─example                             // example工程
│  └─lib
│     ├─pages
│     │   ├─breakpoint_page.dart        // 横纵向断点效果演示页
│     │   ├─grid_page.dart              // 栅格组件效果演示页
│     │   ├─display_priority_col_page.dart        // 自适应显隐组件纵向排列效果演示页
│     │   ├─display_priority_row_page.dart        // 自适应显隐组件横向排列效果演示页
│     │   ├─display_priority_page.dart            // 自适应显隐组件效果演示入口页
│     │   ├─navigation_split_page.dart            // 自适应导航分栏组件效果演示入口页
│     │   ├─side_bar_container_page.dart        // 自适应侧边栏组件效果演示入口页
│     │   └─home_page.dart              // 首页
│     ├─routes         
│     │   └─routers.dart                   // 路由配置   
│     ├─utils       
│     │   └─measure_size.dart              // 测量组件尺寸工具类    
│     └─main.dart                          // 工程入口              
│                   
├─lib
│  ├─src
│  │  ├─breakpoint           // 断点类
│  │  ├─grid                 // 栅格组件
│  │  ├─display_priority     // 自适应显隐组件
│  │  ├─navigation_split_container     // 自适应分栏组件
│  │  └─slider_bar_container           // 自适应侧边栏组件
│  └─multidevice_layout.dart                  // 多设备布局库

安装与使用

请拉取flutter_multidevice_layout_scenepkg 代码仓并执行flutter pub get。

进入到工程目录并输入以下命令:

运行前准备

flutter pub get
cd example

移动设备运行

flutter run

Web端运行

flutter run -d chrome --web-hostname=127.0.0.1

API

说明: OpenHarmony: API >= 12 flutter版本: >= 3.7.12

断点

BreakpointManager断点管理单例

方法名 参数 返回值 说明
init void Future 初始化断点,注册窗口尺寸变化监听,并返回当前断点值
destroy void void 销毁窗口尺寸变化监听,清空所有添加的回调函数
addBreakpointCallback Function(BreakpointData) callback bool 添加断点状态变化回调函数,添加成功返回true
removeBreakpointCallback {Function(BreakpointData)? callback} bool 删除断点状态变化回调函数,不传值时删除所有,删除成功返回true
setWidthBreakpointRange WidthBreakpointRange range void 设置横向断点区间之后,OpenHarmony不再采用动态断点
setHeightBreakpointRange HeightBreakpointRange range void 设置纵向断点区间,OpenHarmony不再采用动态断点
getWindowWidthBreakpoint void WidthBreakpoint 获取窗口横向断点状态
getWindowHeightBreakpoint void HeightBreakpoint 获取窗口纵向断点状态

说明: 在ohos设备上,且api >= 13采用ohos动态断点能力,因此设置横向和纵向的断点区间将无效。

WidthBreakpoint 横向断点枚举

参数 描述 区间
xs 超小屏幕 (0, xs)
sm 小屏幕 [xs, sm)
md 中屏 [sm, md)
lg 大屏幕 [md, lg)
xl 超大屏幕 [lg, xl)
unknown 未知的屏幕大小 未知区间

HeightBreakpoint 横向断点枚举

参数 描述 区间
sm 小屏幕 [0, sm)
md 中屏 [sm, md)
lg 大屏幕 [md, lg)
unknown 未知的屏幕大小 未知区间

WidthBreakpointRange横向断点区间,设置值为右边界

参数 属性类型 默认值
xs double 0
sm double 0
md double 0
lg double 0
xl double 0

HeightBreakpointRange纵向断点区间,设置值为右边界

参数 属性类型 默认值
sm double 0
md double 0
lg double 0

BreakpointData断点数据对象

属性 类型 描述
widthBreakpoint WidthBreakpoint 当前宽度断点
heightBreakpoint HeightBreakpoint 当前高度断点

下面的代码展示了断点库的使用方式:

/// 断点状态变量管理
class BreakpointProvider extends ChangeNotifier {
  WidthBreakpoint _widthBreakpoint = WidthBreakpoint.unknown;
  HeightBreakpoint _heightBreakpoint = HeightBreakpoint.unknown;

  BreakpointProvider() {
    _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;
}

/// main文件初始化断点
void main() {
  WidgetsFlutterBinding.ensureInitialized();
  final breakpointProvider = BreakpointProvider();
  runApp(MultiProvider(
    providers: [
      ChangeNotifierProvider(create: (context) => RouteManager()),
      ChangeNotifierProvider(create: (context) => breakpointProvider)
    ],
    child: const MyApp(),
  ));
}

/// 断点页面使用刷新参数
@override
Widget build(BuildContext context) {
  return Consumer<BreakpointProvider>(
    builder: (context, provider, child) {
      _widthBp = provider.widthBreakpoint;
      _heightBp = provider.heightBreakpoint;
      blueCol = _widthMap[_widthBp]!;
      redCol = _heightMap[_heightBp]!;
      return _mainView(context);
    },
  );
}

栅格组件

参数 属性 说明
gridCols List 单元格GridCol列表容器
columns int 栅格中的列数,其数值决定了内容的布局复杂度(默认值:12)
gutter Gutter 相邻的两个Column之间的距离,决定内容间的紧密程度(包括水平、垂直方向的距离)
margin EdgeInsetsGeometry 相对应用窗口、父容器的左右边缘的距离
breakpoints Breakpoints 设置断点值的断点数列和参考对象,默认情况下:非OpenHarmony,区间值320, 600, 840启用xs、sm、md、lg,OpenHarmony采用动态断点,区间启用到xl

下面的代码展示了Flutter栅格组件的使用方式:

import 'package:flutter/material.dart';
import 'package:hadss_adaptive_layout/hadss_adaptive_layout.dart';

class MyGrid extends StatefulWidget {
  const MyGrid({super.key});

  @override
  State<MyGrid> createState() => _MyGridState();
}

class _MyGridState extends State<MyGrid> with WidgetsBindingObserver {
  double _currentWidth = 0;
  List<GridCol> childList = [];

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
    childList.add(
      GridCol(
        span: SpanOption(xs: 1,
            sm: 1,
            md: 3,
            lg: 4,
            xl: 5,
            xxl: 6),
        offset: 0,
        order: 0,
        child: Container(
          color: Colors.blue,
          width: double.infinity,
          alignment: Alignment.center,
          child: const Text('1'),
        ),
      ),
    );
    for (int i = 0; i < 20; i++) {
      childList.add(
        GridCol(
          span: 1,
          offset: 0,
          order: (i + 1) % 3,
          child: Container(
            color: Colors.blue,
            width: double.infinity,
            alignment: Alignment.center,
            child: Text((i + 2).toString()),
          ),
        ),
      );
    }
    updateScreenSize();
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  @override
  void didChangeMetrics() {
    updateScreenSize();
  }

  void updateScreenSize() {
    final Size size = WidgetsBinding.instance.window.physicalSize;
    final double ratio = WidgetsBinding.instance.window.devicePixelRatio;
    setState(() {
      _currentWidth = size.width / ratio;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('栅格组件示例'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.start,
          children: [
            Text('当前屏幕宽度:$_currentWidth'),
            GridRow(
              gridCols: childList,
              columns: 12,
              gutter: const Gutter(x: 10, y: 8),
              margin: const EdgeInsets.all(0),
              range: BreakpointRange(
                list: [0, 320, 600, 840, 1440, 2000],
              ),
            )
          ],
        ),
      ),
    );
  }
}

自适应显隐组件

自适应显隐容器

属性名 类型 必填 默认值 说明
direction Axis 定义子组件排列的方向。可以是水平排列(Axis.horizontal)或垂直排列(Axis.vertical)。
mainAxisAlignment MainAxisAlignment MainAxisAlignment.start 定义主轴方向上子组件的对齐方式。
crossAxisAlignment CrossAxisAlignment CrossAxisAlignment.center 定义交叉轴方向上子组件的对齐方式。
displayPriorityList List 显隐优先级对象的列表。
textDirection TextDirection TextDirection.ltr 确定文本和子组件的布局方向。
verticalDirection VerticalDirection VerticalDirection.down 定义垂直方向上子组件的排列顺序。

显隐容器属性配置类

属性名 类型 必填 默认值 说明
displayPriority int 1 设置当前组件在布局容器中显示的优先级。默认值:1。说明:仅在DisplayPriorityBox组件中生效。
child Widget 子组件

下面的代码展示了自适应显隐组件的使用方式:

import 'package:flutter/material.dart';
import 'package:hadss_adaptive_layout/hadss_adaptive_layout.dart';

class DisplayPriorityRowPage extends StatefulWidget {
  const DisplayPriorityRowPage({super.key});

  @override
  State<DisplayPriorityRowPage> createState() => _DisplayPriorityRowPageState();
}

class _DisplayPriorityRowPageState extends State<DisplayPriorityRowPage> {
  List<DisplayPriorityObject> childList = [];

  @override
  void initState() {
    super.initState();
    for (int i = 0; i < 60; i++) {
      childList.add(
        DisplayPriorityObject(
          displayPriority: i % 3 + 1,
          child: Text(
            (i % 3 + 1).toString(),
            style: const TextStyle(color: Colors.red, fontSize: 50),
          ),
        ),
      );
    }
  }

  @override
  void dispose() {
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('显隐组件:水平效果'),
      ),
      body: SafeArea(
        child: DisplayPriorityBox(
          direction: Axis.horizontal,
          displayPriorityList: childList,
        ),
      ),
    );
  }
}

侧边栏组件

SideBarContainer

属性名 类型 必填 默认值 说明
sideBar Widget SizedBox.shrink() 侧边栏区域。
content Widget SizedBox.shrink() 内容区域。
type SideBarContainerType SideBarContainerType.embed 侧边栏显示模式。
showSideBar bool true 设置是否显示侧边栏。
showControlButton bool true 是否显示控制按钮。
sideBarWidth double 240.0 设置侧边栏的宽度。受最小宽度和最大宽度限制,不在限制区域内取最近的点。约束:值不能小于0。
minSideBarWidth double 240.0 设置侧边栏最小宽度。值不能超过侧边栏容器本身宽度,超过使用侧边栏容器本身宽度。约束:值不能小于0。
maxSideBarWidth double 280.0 设置侧边栏最大宽度。值不能超过侧边栏容器本身宽度,超过使用侧边栏容器本身宽度。约束:值不能小于0。
minContentWidth double 360.0 设置SideBarContainer组件内容区可显示的最小宽度。约束:值不能小于0。
autoHide bool true 设置当侧边栏拖拽到小于最小宽度后,是否自动隐藏。受minSideBarWidth属性方法影响,minSideBarWidth属性方法未设置值使用默认值。
sideBarPosition SideBarPosition SideBarPosition.start 设置侧边栏显示位置。
controlButton ButtonStyle ButtonStyle() 设置侧边栏控制按钮的属性。
divider DividerStyle DividerStyle(strokeWidth: 1) 设置分割线的样式。
onChange Function(bool) Null 当侧边栏的状态在显示和隐藏之间切换时触发回调。

SideBarContainerType

名称 说明
embed 侧边栏嵌入到组件内,和内容区并列显示。组件尺寸小于minContentWidth + minSideBarWidth,并且未设置showSideBar时,侧边栏自动隐藏。未设置minSideBarWidth或者minContentWidth采用未设置接口的默认值进行计算。组件在自动隐藏后,如果通过点击控制按钮唤出侧边栏,则侧边栏悬浮在内容区上显示。
overlay 侧边栏浮在内容区上面。
auto 组件尺寸大于等于minSideBarWidth+minContentWidth时,采用embed模式显示。组件尺寸小于minSideBarWidth+minContentWidth时,采用overlay模式显示。未设置minSideBarWidth或minContentWidth时,会使用未设置接口的默认值进行计算,若计算的值小于600,则使用600做为模式切换的断点值。

SideBarContainerType

名称 说明
start 侧边栏位于容器左侧。
end 侧边栏位于容器右侧。

DividerStyle

名称 类型 说明
strokeWidth double 分割线的线宽。默认值:1
color Color 分割线的颜色。默认值:#000000,3%
startMargin double 分割线与侧边栏顶端的距离。默认值:0
endMargin double 分割线与侧边栏底端的距离。默认值:0

ButtonStyle

名称 类型 必填 说明
left double 设置侧边栏控制按钮距离容器左界限的间距。默认值:16
top double 设置侧边栏控制按钮距离容器上界限的间距。默认值:48
width double 设置侧边栏控制按钮的宽度。默认值:24
height double 设置侧边栏控制按钮的高度。默认值:24
icons {shown: Image;hidden:Image ;switching:Image? } 设置侧边栏控制按钮的图标:- shown: 设置侧边栏显示时控制按钮的图标。说明:资源获取错误时,使用默认图标。- hidden: 设置侧边栏隐藏时控制按钮的图标。- switching:设置侧边栏显示和隐藏状态切换时控制按钮的图标。

下面的代码展示了侧边栏组件的使用方式:

import 'package:flutter/material.dart' hide ButtonStyle;
import 'package:hadss_adaptive_layout/hadss_adaptive_layout.dart';

class SliderBarContainerPage extends StatefulWidget {
  const SliderBarContainerPage({Key? key}) : super(key: key);

  @override
  State<SliderBarContainerPage> createState() => _SliderBarContainerPageState();
}

class _SliderBarContainerPageState extends State<SliderBarContainerPage> {
  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        return Scaffold(
          appBar: AppBar(
            title: const Text('SideBarContainer 示例'),
          ),
          body: SideBarContainer(
            sideBar: Container(
              color: Colors.blue,
              child: const Center(
                child: Text(
                  '侧边栏',
                  style: TextStyle(
                    color: Colors.white,
                    fontSize: 18.0,
                    fontWeight: FontWeight.bold,
                  ),
                ),
              ),
            ),
            content: Container(
              color: Colors.grey,
              child: const Center(
                child: Text(
                  '内容区',
                  style: TextStyle(
                    color: Colors.black,
                    fontSize: 18.0,
                    fontWeight: FontWeight.bold,
                  ),
                ),
              ),
            ),
            type: SideBarContainerType.auto,
            showControlButton: true,
            showSideBar: true,
            sideBarWidth: 50.0,
            minSideBarWidth: 75.0,
            maxSideBarWidth: 100.0,
            minContentWidth: 200.0,
            autoHide: true,
            sideBarPosition: SideBarPosition.start,
            controlButton: const ButtonStyle(
                left: 16.0,
                top: 16.0,
                icons: ButtonIcons(
                    shown: Image(image: AssetImage('assets/show_all.png')),
                    hidden: Image(image: AssetImage('assets/show_all.png')))),
            divider: const DividerStyle(
              strokeWidth: 1.0,
              color: Colors.black,
              startMargin: 10.0,
              endMargin: 10.0,
            ),
            onChange: (isVisible) {
              print('isVisible: $isVisible');
            },
          ),
        );
      },
    );
  }
}

自适应分栏组件

NavigationSplitContainer

属性名 类型 必填 默认值 说明
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:当前显示为单栏

NavigationSplitMode

名称 说明
stack 导航栏与内容区独立显示,相当于两个页面。
split 导航栏与内容区分栏显示。
以下navBarWidthRange的值用minNavBarWidth, maxNavBarWidth表示
1.当navBarWidth值在navBarWidthRange范围以外时,navBarWidth按如下规则显示:
navBarWidth < minNavBarWidth时,navBarWidth修正为minNavBarWidth;
navBarWidth > maxNavBarWidth,且组件宽度 - minContentWidth - 分割线宽度(1) > maxNavBarWidth时,navBarWidth修正为maxNavBarWidth;
navBarWidth > maxNavBarWidth,且组件宽度 - minContentWidth - 分割线宽度(1) < minNavBarWidth时,navBarWidth修正为minNavBarWidth;
navBarWidth > maxNavBarWidth,且组件宽度 - minContentWidth - 分割线宽度(1);
在navBarWidthRange范围内,navBarWidth修正为组件宽度 - 分割线宽度(1) - minContentWidth。
2.当navBarWidth属性的值,在navBarWidthRange属性的值范围以内时,navBarWidth按如下规则显示:minNavBarWidth + minContentWidth + 分割线宽度(1) >= 组件宽度时,navBarWidth修正为minNavBarWidth;
minNavBarWidth + minContentWidth + 分割线宽度(1) < 组件宽度,且navBarWidth + minContentWidth + 分割线宽度(1) >= 组件宽度时,navBarWidth修正为组件宽度 - 分割线宽度(1) - minContentWidth;
minNavBarWidth + minContentWidth + 分割线宽度(1) < 组件宽度,且navBarWidth + minContentWidth + 分割线宽度(1) < 组件宽度时,navBarWidth为设置的值。
3.缩小组件尺寸时,先缩小内容区的尺寸至minContentWidth,然后再缩小导航栏的尺寸至minNavBarWidth。若继续缩小,先缩小内容区,内容区消失后再缩小导航栏。
4.设置导航栏为固定尺寸时,若持续缩小组件尺寸,导航栏最后压缩显示。
5.若只设置了navBarWidth属性,则导航栏宽度为navBarWidth,且分割线不可拖动。
auto 组件宽度 >= 600时,采用split模式显示;组件宽度 < 600时,采用stack模式显示, 600等于minNavBarWidth(240) + minContentWidth(360)

NavBarPosition

名称 说明
start 双栏显示时,导航栏显示在容器左侧
end 双栏显示时,导航栏显示在容器右侧

使用方式:

@override
Widget build(BuildContext context) {
  return NavigationSplitContainer(
      navBarWidth: 240,
      minContentWidth: 200,
      navBarWidthRange: const [200, 300],
      mode: NavigationSplitMode.auto,
      navBarPosition: NavBarPosition.start,
      navigationPane: const NavigationPage(),
      mainContent: const DetailsPage(),
      onNavBarStateChange: (bool isVisible) {
        print('onNavBarStateChange: $isVisible');
      },
      onNavigationModeChange: (NavigationSplitMode mode) {
        print('onNavigationModeChange: $mode');
      });
}