Flutter自适应扩展插件adaptive_extensions的使用
Flutter自适应扩展插件adaptive_extensions
的使用
添加adaptive()
到Material
小部件后
受内置自适应小部件如CircularProgressIndicator.adaptive()
的启发。要获取Cupertino小部件,请在Material
小部件后添加adaptive()
。
TextButton(
onPressed: showAdaptiveSnackbar,
child: const Text('Adaptive TextButton'),
).adaptive(),
录屏
访问 DartPad 示例
使用
找到Material
小部件,并在其后添加adaptive()
。
这些扩展仅在平台为iOS或macOS时自动转换为Cupertino小部件。
final TextButton textbutton = TextButton(your code).adaptive();
final FilledButton filledButton = FilledButton(your code).adaptive();
// 如果你想使用带有图标的TextButton,请在adaptive中包含图标
final textbutton = TextButton(your code).adaptive(icon: Icon(Icons.abc));
设计概念
该包由MaterialWidget
的扩展组成。因此,这些扩展返回的是Material
小部件的this
。
extension Adaptive on Material {
dynamic adaptive({}) {
if (forceCupertino || Platform.isIOS || Platform.isMacOS) {
return Cupertino;
} else {
return this; // Material;
}
}
}
可用的小部件
小部件 | Material | Cupertino | adaptive_extensions |
---|---|---|---|
TextButton | TextButton | CupertinoButton | TextButton.adaptive |
带图标的TextButton | TextButton.icon | CupertinoButton | TextButton.adaptive(icon: Icon) |
FilledButton | FilledButton | CupertinoButton.filled | FilledButton.adaptive |
带图标的FilledButton | FilledButton.icon | CupertinoButton.filled | FilledButton.adaptive(icon: Icon) |
ListTile | ListTile | CupertinoListTile | ListTile.adaptive |
Snackbar 或 Toast | SnackBar | Toast-like SnackBar | SnackBar.adaptive |
(段落按钮正在开发中)
可用的主题数据
主题 | Material | Cupertino | adaptive_extensions |
---|---|---|---|
Snackbar 或 Toast | SnackBarThemeData | Toast-like SnackBarThemeData | SnackBarThemeData.adaptive |
AppBarTheme | AppBarTheme | Cupertino-like AppBarTheme | AppBarTheme.adaptive |
相关链接
如果你正在寻找Switch、Slider、CircularProgressIndicator、CheckBox或Radio,请参阅官方文档 自动平台适配。
如果你正在寻找一些自适应对话框,我建议你使用由mono0926.com编写的 adaptive_dialog 包。
如果你需要更多平台适配的小部件,也请查看由stryder.dev编写的 flutter_platform_widgets 包。
示例代码
以下是完整的示例代码,展示了如何使用adaptive_extensions
插件:
import 'dart:io';
import 'package:adaptive_extensions/adaptive_extensions.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'theme.dart';
void main() {
runApp(const MyApp());
}
enum Calendar { day, week, month, year }
enum Pages { favorites, recents, contacts, keypad }
class MyApp extends StatelessWidget {
const MyApp({super.key});
[@override](/user/override)
Widget build(BuildContext context) => MaterialApp(
theme: MyTheme.light,
title: 'Adaptive Extensions',
home: const MyHomePage(title: 'Adaptive Extensions'),
);
}
class MyHomePage extends StatefulWidget {
const MyHomePage({required this.title, super.key});
final String title;
[@override](/user/override)
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool forceCupertino = Platform.isIOS;
Calendar calendarView = Calendar.day;
GlobalKey bottomNaviKey = GlobalKey(debugLabel: 'bottomNavi');
String? title;
// Sky _selectedSegment = Sky.midnight;
int bottomIndex = 0;
void showAdaptiveSnackbar() {
ScaffoldMessenger.of(context).clearSnackBars();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Hello adaptive snackbar, toast'),
).adaptive(
forceCupertino: forceCupertino,
forceMaterial: !forceCupertino,
),
);
}
[@override](/user/override)
Widget build(BuildContext context) {
return Theme(
data: ThemeData(
appBarTheme: const AppBarTheme().adaptive(
forceCupertino: forceCupertino,
forceMaterial: !forceCupertino,
),
),
child: Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(title ?? widget.title),
actions: [
Icon(forceCupertino ? Icons.apple : Icons.android),
Switch.adaptive(
value: forceCupertino,
onChanged: (_) {
setState(() {
forceCupertino = !forceCupertino;
});
},
),
],
),
bottomNavigationBar: BottomNavigationBar(
key: bottomNaviKey,
currentIndex: bottomIndex,
type: BottomNavigationBarType.fixed,
onTap: (idx) => setState(() {
title = Pages.values[idx].toString();
bottomIndex = idx;
}),
items: const [
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.star_fill),
label: 'Favorites',
),
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.clock_solid),
label: 'Recents',
),
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.person_alt_circle_fill),
label: 'Contacts',
),
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.circle_grid_3x3_fill),
label: 'Keypad',
),
],
).adaptive(
forceCupertino: forceCupertino,
forceMaterial: !forceCupertino,
),
body: ListView(
padding: const EdgeInsets.all(8),
children: [
TextButton(
onPressed: showAdaptiveSnackbar,
child: const Text('Adaptive TextButton'),
).adaptive(
forceCupertino: forceCupertino,
forceMaterial: !forceCupertino,
),
const SizedBox(height: 8),
TextButton(
onPressed: showAdaptiveSnackbar,
child: const Text('Adaptive TextButton'),
).adaptive(
icon: Icon(forceCupertino ? Icons.apple : Icons.android),
forceCupertino: forceCupertino,
forceMaterial: !forceCupertino,
),
const SizedBox(height: 8),
FilledButton(
onPressed: showAdaptiveSnackbar,
child: const Text('Adaptive FilledButton'),
).adaptive(
forceCupertino: forceCupertino,
forceMaterial: !forceCupertino,
),
const SizedBox(height: 8),
FilledButton(
onPressed: showAdaptiveSnackbar,
child: const Text('Adaptive FilledButton'),
).adaptive(
icon: Icon(forceCupertino ? Icons.apple : Icons.android),
forceCupertino: forceCupertino,
forceMaterial: !forceCupertino,
),
const SizedBox(height: 8),
FilledButton(
onPressed: showAdaptiveSnackbar,
child: const Text('Adaptive Tonal Filled Button'),
).adaptive(
isTonal: true,
color: CupertinoColors.systemBlue.darkHighContrastColor,
icon: Icon(forceCupertino ? Icons.apple : Icons.android),
forceCupertino: forceCupertino,
forceMaterial: !forceCupertino,
),
const SizedBox(height: 8),
const FilledButton(
onPressed: null,
child: Text('Adaptive Disabled Button'),
).adaptive(
isGray: true,
icon: Icon(forceCupertino ? Icons.apple : Icons.android),
forceCupertino: forceCupertino,
forceMaterial: !forceCupertino,
),
const SizedBox(height: 8),
ListTile(
onTap: showAdaptiveSnackbar,
leading: Icon(forceCupertino ? Icons.apple : Icons.android),
title: const Text('Adaptive ListTile'),
subtitle: const Text('Tab here to see snackbar'),
trailing: const CupertinoListTileChevron(),
tileColor: Colors.yellow[200],
).adaptive(
cupertinoThemeData: const CupertinoThemeData(
primaryColor: Colors.blue,
),
forceCupertino: forceCupertino,
forceMaterial: !forceCupertino,
),
const SizedBox(height: 8),
ListTile(
onTap: showAdaptiveSnackbar,
leading: Icon(forceCupertino ? Icons.apple : Icons.android),
title: const Text('Adaptive ListTile notched'),
subtitle: const Text('Tab here to see snackbar'),
trailing: const CupertinoListTileChevron(),
tileColor: Colors.yellow[200],
).adaptive(
cupertinoThemeData: const CupertinoThemeData(
primaryColor: Colors.blue,
),
isNotched: true,
forceCupertino: forceCupertino,
forceMaterial: !forceCupertino,
),
const SizedBox(height: 8),
// SegmentedButton(
// segments: const [
// ButtonSegment<Calendar>(
// value: Calendar.day,
// label: Text('Day'),
// icon: Icon(Icons.calendar_view_day),
// ),
// ButtonSegment<Calendar>(
// value: Calendar.week,
// label: Text('Week'),
// icon: Icon(Icons.calendar_view_week),
// ),
// ButtonSegment<Calendar>(
// value: Calendar.month,
// label: Text('Month'),
// icon: Icon(Icons.calendar_view_month),
// ),
// ButtonSegment<Calendar>(
// value: Calendar.year,
// label: Text('Year'),
// icon: Icon(Icons.calendar_today),
// ),
// ],
// selected: <Calendar>{calendarView},
// onSelectionChanged: (newSelection) {
// setState(() {
// calendarView = newSelection.first;
// });
// },
// ).adaptive(
// forceCupertino: forceCupertino,
// forceMaterial: !forceCupertino,
// ),
// const SizedBox(height: 8),
// CupertinoSegmentedControl<Sky>(
// selectedColor: skyColors[_selectedSegment],
// // Provide horizontal padding around the children.
// padding: const EdgeInsets.symmetric(horizontal: 12),
// // This represents a currently selected segmented control.
// groupValue: _selectedSegment,
// // Callback that sets the selected segmented control.
// onValueChanged: (Sky value) {
// setState(() {
// _selectedSegment = value;
// });
// },
// children: const <Sky, Widget>{
// Sky.midnight: Padding(
// padding: EdgeInsets.symmetric(horizontal: 20),
// child: Text('Midnight'),
// ),
// Sky.viridian: Padding(
// padding: EdgeInsets.symmetric(horizontal: 20),
// child: Text('Viridian'),
// ),
// Sky.cerulean: Padding(
// padding: EdgeInsets.symmetric(horizontal: 20),
// child: Text('Cerulean'),
// ),
// },
// ),
],
),
).adaptive(),
);
}
}
// enum Sky { midnight, viridian, cerulean }
// Map<Sky, Color> skyColors = <Sky, Color>{
// Sky.midnight: const Color(0xff191970),
// Sky.viridian: const Color(0xff40826d),
// Sky.cerulean: const Color(0xff007ba7),
// };
更多关于Flutter自适应扩展插件adaptive_extensions的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter自适应扩展插件adaptive_extensions的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,以下是一个关于如何使用Flutter自适应扩展插件adaptive_extensions
的代码案例。adaptive_extensions
插件提供了一些实用的工具,帮助开发者在不同屏幕尺寸和方向上实现自适应布局。
首先,确保你已经在pubspec.yaml
文件中添加了依赖:
dependencies:
flutter:
sdk: flutter
adaptive_extensions: ^latest_version # 请替换为最新版本号
然后运行flutter pub get
来安装依赖。
接下来,我们来看一个具体的代码案例,展示如何使用adaptive_extensions
插件来实现自适应布局。
import 'package:flutter/material.dart';
import 'package:adaptive_extensions/adaptive_extensions.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Adaptive Extensions Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: AdaptiveScaffold(
appBar: AppBar(
title: Text('Adaptive Extensions Demo'),
),
body: AdaptiveLayoutBuilder(
tablet: (context, screenSize) => _buildTabletLayout(context),
phone: (context, screenSize) => _buildPhoneLayout(context),
desktop: (context, screenSize) => _buildDesktopLayout(context),
),
),
);
}
Widget _buildPhoneLayout(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('This is a phone layout', style: TextStyle(fontSize: 24)),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {},
child: Text('Click Me'),
),
],
),
);
}
Widget _buildTabletLayout(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Left Column on Tablet', style: TextStyle(fontSize: 24)),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {},
child: Text('Left Button'),
),
],
),
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Right Column on Tablet', style: TextStyle(fontSize: 24)),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {},
child: Text('Right Button'),
),
],
),
],
),
);
}
Widget _buildDesktopLayout(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(32.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Left Column on Desktop', style: TextStyle(fontSize: 24)),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {},
child: Text('Left Button'),
),
],
),
),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Center Column on Desktop', style: TextStyle(fontSize: 24)),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {},
child: Text('Center Button'),
),
],
),
),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Right Column on Desktop', style: TextStyle(fontSize: 24)),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {},
child: Text('Right Button'),
),
],
),
),
],
),
);
}
}
在这个示例中,我们使用了AdaptiveLayoutBuilder
来根据设备类型(手机、平板、桌面)构建不同的布局。每个布局都有自己特定的UI组件和排列方式。
_buildPhoneLayout
函数用于手机布局,显示一个简单的列布局。_buildTabletLayout
函数用于平板布局,显示一个包含两列的行布局。_buildDesktopLayout
函数用于桌面布局,显示一个包含三列的行布局。
这个示例展示了如何利用adaptive_extensions
插件中的AdaptiveLayoutBuilder
和AdaptiveScaffold
来创建响应式和自适应的Flutter应用。