Flutter自适应扩展插件adaptive_extensions的使用

发布于 1周前 作者 eggper 来自 Flutter

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

1 回复

更多关于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插件中的AdaptiveLayoutBuilderAdaptiveScaffold来创建响应式和自适应的Flutter应用。

回到顶部