hadss_adaptive_layout 1.0.0-rc.3
hadss_adaptive_layout: ^1.0.0-rc.3 copied to clipboard
A responsive component plug-in library for multi-device adaptation.
hadss_adaptive_layout #
介绍 #
这是一个旨在解决Flutter多设备适配问题的三方库,为不同设备类型(包括折叠屏、平板、手机等)提供了便捷的支持。该库包含的接口和开箱即用的组件,使开发者能够轻松应对各种设备的布局适配需求。
目前该库提供的组件如下:
- 断点相关组件:使用ArkUI断点能力开发的断点管理组件,能够让开发者直接使用ArkUI断点能力。
- 自适应显隐容器:对标ArkUI的自适应显隐布局开发的容器,使开发者能够轻松的完成自适应显隐布局能力的开发。
- 侧边栏组件:对标ArkUI的SideBarContainer组件开发的Flutter侧边栏组件,使开发者能够轻松的完成侧边栏相关布局能力的开发。
- 自适应分栏组件:参考ArkUI的Navigation组件开发的Flutter分栏组件,使开发者能够轻松的完成分栏及路由跳转能力的开发。
- 栅格组件:对标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');
});
}