Flutter 插件lib_x的使用_lib_x 提供了一些预先配置的解决方案和简化方法,以更好地进行架构设计

发布于 1周前 作者 wuwangju 最后一次编辑是 5天前 来自 Flutter

Flutter 插件lib_x的使用_lib_x是一个包含一些经过良好测试且常用包的简单库。在此基础上,它提供了一些预先配置的解决方案和简化方法,以更好地进行架构设计

lib_x是一个包含一些经过良好测试且常用包的简单库。在此基础上,它提供了一些预先配置的解决方案和简化方法,以更好地进行架构设计。

lib_x旨在补充两件事:

  • 面向对象的Dart特性,通过在Flutter应用中分离视图和数据关注点。
  • 简化Flutter框架的方法,使我们能够更符合Flutter的方式进行开发。

此库的目的:

  • 将一些基本包组合在一起。
  • 跳过一些常用的样板代码。
  • 提供简单的解决方案来处理路由、状态管理和数据提供者。
  • 帮助设计模式和关注点分离。
  • 语义化和自解释命名。

库中包含的包:

安装lib_x后,你可以直接导入这些包,并使用以下解决方案之一。

解决方案

MaterialX & X 控制器

我们经常需要的功能不仅仅是push()pop(),例如,我们需要通过URL导航、深度链接、不依赖于context的路由等。为此,我们需要通过MaterialApp.router()MaterialX()配置Navigator 2.0,并使用一个控制器类X来分离和封装所有路由和主题管理的关注点。

MaterialX

它接受两个命名参数并构建一个MaterialApp.router(),用于在runApp()函数中使用:

MaterialApp materialApp: 包含主题和本地化关注点的MaterialApp。
RouteMap routeMap: 一个路由模式及其对应`Scaffold`的映射。

例如:

// lib/main.dart
void main() {
  runApp(const MyApp());
}

// lib/src/views/const/route_map.dart
const String LoginPath = '/login';
const String RootPath = '/';
const String UserPath = '/user/';
const String ContactMePath = '/contactMe/';
const String PostPath = '/post/';

// if authentication, guarded routes
bool isLoggedIn = true;

final RouteMap routeMap = RouteMap(
  routes: {
    LoginPath: (info) => const MaterialPage(child: LoginPage()), // 无保护
    RootPath: (info) => isLoggedIn // 有保护
      ? const MaterialPage(child: HomePage())
      : const Redirect(LoginPath),
    UserPath + ':username': (RouteData info) => isLoggedIn
      ? MaterialPage(child: UserPage(username: info.pathParameters['username']!))
      : const Redirect(LoginPath),
    UserPath + ':username' + ContactMePath : (RouteData info) => isLoggedIn
      ? MaterialPage(child: ContactMePage(username: info.pathParameters['username']!))
      : const Redirect(LoginPath),
    PostPath + ':postId': (RouteData info) => isLoggedIn
      ? MaterialPage(child: PostPage(postId: info.pathParameters['postId']!))
      : const Redirect(LoginPath),
    PostPath + ':postId/:commentId': (RouteData info) => isLoggedIn
      ? MaterialPage(
          child: CommentPage(
          postId: info.pathParameters['postId']!,
          commentId: info.pathParameters['commentId']!,
        ))
      : const Redirect(LoginPath),
    // ... 其他路径
  },
  onUnknownRoute: (_) => const MaterialPage(child: NotFoundPage()), // 默认路由
);

// lib/src/views/const/material_app.dart
final MaterialApp materialApp = MaterialApp(
  title: 'Example App',
  debugShowCheckedModeBanner: false,
  theme: myLightTheme,
  darkTheme: myDarkTheme,
  themeMode: ThemeMode.system,
);

// lib/src/views/my_app.dart
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialX(
        materialApp: materialApp,
        routeMap: routeMap,
      );
  }
}

X 控制器

它是MaterialX小部件的抽象控制器类,具有以下接口:

ThemeData X.theme: 返回当前上下文的ThemeData对象。
MediaQueryData X.mediaQuery: 返回当前上下文的MediaQueryData对象。
ValueController<ThemeMode> X.themeMode: 是MaterialX的主题模式控制器。如果提供了亮色和暗色主题,则MaterialX将自动更改系统主题模式的变化,除非你指定了一个与`ThemeMode.system`不同的值。
void X.switchTheme({ThemeMode? to}): 它接受这些值`[ThemeMode.system, ThemeMode.dark, ThemeMode.light]`,并更新`X.themeMode`的值。它还会相应地更新状态栏的颜色和亮度。如果你没有传递主题模式的值,它将只是切换当前主题模式到相反的状态,并停止监听系统主题模式的变化。要再次更改主题与系统一致,使用`X.switchTheme(to: ThemeMode.system);`
void X.setStatusBar({Color? color, Brightness? brightness}): 设置状态栏的颜色和亮度。
void X.forcePortrait(): 强制纵向方向。
void X.forceLandscape(): 强制横向方向。
void X.allowAutoOrientation(): 允许纵向和横向方向。

ScaffoldX

在完成MyApp之后,我们需要创建包含Scaffold小部件并对应于routeMap中定义路径的页面。ScaffoldX是一种快速组合带有默认配置的Scaffold的方式。它有以下命名参数:

Widget ScaffoldX({
  required Widget body,
  Color? bgColor,
  DecorationImage? bgDecorationImage,
  Widget? appBar,
  double appBarHeight = 60,
  Widget? drawer,
  Widget? bottomNavigationBar,
  Widget? bottomSheet,
  Widget? fab, // FloatingActionButton
  FloatingActionButtonLocation? fabLocation,
  BoxConstraints? constraints,
  TextStyle textStyle = const TextStyle(color: black, fontSize: 16),
  VoidCallback? onInit,
  bool safeArea = true,
  bool scrollView = false,
})

例如:

class UserPage extends StatelessWidget {
  final String username;
  const UserPage({Key? key, required this.username}) : super(key: key);

  @override
  Widget build(BuildContext context) {

    return ScaffoldX(
      onInit: () => debugPrint(username),
      appBar: const MyAppBar(),
      bottomNavigationBar: const MyNavBar(),
      bgDecorationImage: const DecorationImage(
        image: AssetImage('assets/images/bg.png'),
        fit: BoxFit.fill,
        opacity: .3,
      ),
      body: Center(
        child: Text('Hello $username'),
      ),
    );
  }
}

注意:如果不使用ScaffoldX,确保实现BackButtonInterceptor来处理返回手势。

数据提供者

我们不再需要通过参数从父组件传递数据到子组件,这很快会变得混乱。我们可以使用DataProvider来分配所需的数据给一个组件,只有其后代组件可以访问该数据。

数据提供者小部件

class UserProvider extends DataProvider<UserModel> {
  final UserModel userModel;

  const UserProvider({
    super.key,
    required this.userModel, // 第一个参数:需要声明的数据对象
    required super.child, // 第二个参数:需要并传递子组件给超类
  }) : super(data: userModel); // 将数据传递给超类

  static UserProvider of(BuildContext context) =>
      context.dependOnInheritedWidgetOfExactType<UserProvider>()!;
}

StatefulData & ReBuilder

StatefulWidget在许多情况下非常有用,但在数据管理方面可能不太适用。在实际应用中,我们需要将数据层与渲染层解耦,将每一层分开。这就是为什么这种解决方案分为两个单独的类:视图类WidgetStatefulData控制器类。

StatefulData

它是一个扩展了ChangeNotifier的更好命名的类。它是ReBuilder小部件的控制器类。这个类应该将所有的数据逻辑与视图逻辑分离。当数据发生变化时,并且调用了@protected update()方法时,ReBuilder小部件将重建以反映数据变化。

ReBuilder

它是一个从上下文中抽象出来的AnimatedBuilder。当StatefulData的状态改变时,它会重新构建。它有两个命名参数:

StatefulData controller: StatefulData对象的一个实例。
Function builder: 返回一个Widget的函数,当控制器需要时,它将重新构建。

例如:

class UserModel extends StatefulData {
  final String id;
  String username;

  UserModel({required this.id, required this.username});

  void changeUsername(String newName) {
    username = newName;
    update(); // 触发Rebuilder渲染更改
  }
}

class ProfileWidget extends StatelessWidget {
  const ProfileWidget({super.key});

  @override
  Widget build(BuildContext context) {
    final UserModel userModel = UserProvider.of(context).userModel;
    return ReBuilder(
      controller: userModel,
      builder: () {
        return Text(userModel.username);
      }
    );
  }
}

ValueController & ReactiveBuilder

有时我们只需要监听一个独立值的状态。我们将创建两个单独的类:视图类和值控制器类。

ValueController<T>

它基于ValueNotifier构建的值控制器对象,具有以下公共接口:

value: 返回当前值。
ValueListenable listenable: 可用于`ValueListenableBuilder`的可监听对象。
void update(T v): 更新类型为T的值属性。
void set onChange(VoidCallback callback): 如果你想附加一个回调函数,每当值改变时运行。
void dispose(): 调用此方法后,valueController不能被使用。

ReactiveBuilder

它是从上下文中抽象出来的ValueListenableBuilder。当controller.update(value)被调用时,它会重新构建。它有两个命名参数:

ValueController<T>
Function builder(T value): 带值参数的函数,返回一个将在控制器值改变时重新构建的小部件。

例如:

final ValueController<ThemeMode> themeModeController = ValueController<ThemeMode>(ThemeMode.dark);

class SwitchThemeButton extends StatelessWidget {
  const SwitchThemeButton({super.key});

  @override
  Widget build(BuildContext context) {
    ThemeMode value() => themeModeController.value == ThemeMode.dark
        ? ThemeMode.light
        : ThemeMode.dark;

    return TextButton(
      onPressed: () => themeModeController.update(value()),
      child: const Text('Change Theme'),
    );
  }
}

class AdaptiveText extends StatelessWidget {
  final String text;
  final Color? lightModeC;
  final Color? darkModeC;
  final double? fontSize;

  const AdaptiveText(
    this.text, {
    super.key,
    this.lightModeC,
    this.darkModeC,
    this.fontSize,
  });

  @override
  Widget build(BuildContext context) {
    return ReactiveBuilder(
      controller: themeModeController,
      builder: (ThemeMode mode) => Text(
        text,
        style: TextStyle(
          color: mode == ThemeMode.dark 
            ? darkModeC ?? white 
            : lightModeC ?? black,
          fontSize: fontSize,
        ),
        textAlign: TextAlign.center,
      ),
    );
  }
}

XUtils

这是一个提供一些便捷快速解决方案的抽象类。它具有以下静态接口:

bool XUtils.isUrl(String string): 检查字符串是否为URL。
bool XUtils.isAsset(String path): 检查路径是否以"assets/"开头。
bool XUtils.isSVG(String path): 检查路径是否包含".svg"。
bool getter XUtils.isSysDarkMode: 如果系统处于暗模式则返回true,否则返回false。
ThemeMode getter XUtils.sysThemeMode: 返回系统的当前ThemeMode值。
int getter XUtils.now: 返回现在时间戳的整数值。
String XUtils.formatTimestamp(int timestamp, {bool shortMonthFormat = true}): 将时间戳转换为可读格式。
bool XUtils.isNumeric(String str): 检查字符串是否为数字。
bool XUtils.isEmail(String email): 根据HTML5电子邮件验证规范检查字符串是否为有效电子邮件格式。
String XUtils.genString({int length = 16}): 生成默认长度为16的随机字符串。
String XUtils.genNum({int length = 16}): 生成默认长度为16的随机数字字符串。
String XUtils.genId({int length = 16}): 生成基于时间戳的ID字符串。

Widgets

PersistStateWidget

如果你有一个滚动视图(如ScrollView),并且希望在导航到其他标签页时保持其滚动位置,可以使用PersistStateWidget包装滚动视图。

例如:

class MyListView extends StatelessWidget {
  const MyListView({super.key});

  @override
  Widget build(BuildContext context) {
    final List<Widget> myList = List.generate(100, (index) => Text(index.toString())).toList();
    // 包装ListView以维护其滚动状态
    return PersistStateWidget(
      child: ListView.builder(
        itemBuilder: (context, index) => myList[index],
      ),
    );
  }
}

DismissModalWidget

如果你要推送一个模态对话框,并希望点击对话框外部时弹出,可以使用DismissModalWidget包装对话框。这比showDialog()中的barrierDismissible更可靠。

例如:

class MyDialog extends StatelessWidget {
  const MyDialog({super.key});

  @override
  Widget build(BuildContext context) {
    return const Center(child: Text('Dialog'));
  }
}

class ShowMyDialogButton extends StatelessWidget {
  const ShowMyDialogButton({super.key});

  @override
  Widget build(BuildContext context) {
    return TextButton(
      onPressed: () => showDialog(
        context: context,
        barrierDismissible: true, // 不可靠
        builder: (context) {
          return const DismissableModal(child: MyDialog());
        },
      ),
      child: const Text('Show Dismissable Dialog'),
    );
  }
}

注意:如果你使用X.showModal(child: MyDialog())而不是showDialog(),则默认具有可取消行为。

结束语

这些是我个人的实现,不知道是否对其他人有用,但希望有所帮助。如果您有任何建议或优化,请随时提出GitHub上的pull请求。您也可以自由地扩展或修改它以适应您的需求。

感谢实现这些包的作者,当然还有Dart和Flutter的开发者。

祝编码愉快!


更多关于Flutter 插件lib_x的使用_lib_x 提供了一些预先配置的解决方案和简化方法,以更好地进行架构设计的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter 插件lib_x的使用_lib_x 提供了一些预先配置的解决方案和简化方法,以更好地进行架构设计的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,作为一个IT专家,我可以为你提供一个关于如何在Flutter项目中集成并使用一个假想的未知功能插件lib_x的代码案例。请注意,由于lib_x是一个假想的插件,其具体功能和API可能会有所不同,但以下代码案例展示了如何集成和使用一个Flutter插件的基本步骤。

首先,假设lib_x插件已经发布在pub.dev上,你可以通过修改pubspec.yaml文件来添加这个依赖:

dependencies:
  flutter:
    sdk: flutter
  lib_x: ^1.0.0  # 假设这是lib_x的最新版本号

然后,运行flutter pub get来下载并安装这个插件。

接下来,在你的Flutter应用中,你可以按照以下方式使用lib_x插件。以下是一个简单的例子,展示了如何初始化插件并调用其某个假设的功能(由于我们不知道lib_x的具体功能,这里假设它有一个名为performAction的方法):

import 'package:flutter/material.dart';
import 'package:lib_x/lib_x.dart';  // 导入lib_x插件

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  LibX? _libX;

  @override
  void initState() {
    super.initState();
    // 初始化lib_x插件
    _libX = LibX();
    
    // 调用lib_x的某个假设功能
    _performAction();
  }

  Future<void> _performAction() async {
    try {
      // 假设performAction是一个返回Future的方法
      var result = await _libX!.performAction();
      // 处理结果
      print('Action performed with result: $result');
    } catch (e) {
      // 处理错误
      print('Error performing action: $e');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Flutter Demo Home Page'),
      ),
      body: Center(
        child: Text('Waiting for lib_x action to complete...'),
      ),
    );
  }
}

在这个例子中,我们做了以下几件事:

  1. pubspec.yaml文件中添加了lib_x作为依赖。
  2. MyApp类中创建了Flutter应用的基本结构。
  3. MyHomePage类中,通过initState方法初始化了lib_x插件,并调用了其假设的performAction方法。
  4. 使用Futureasync/await来处理异步操作。

请注意,由于lib_x是一个假想的插件,上述代码中的LibX类和performAction方法都是假设的。在实际使用中,你需要参考lib_x插件的官方文档来了解其具体的API和使用方法。

希望这个代码案例能帮助你理解如何在Flutter项目中集成和使用一个未知功能的插件。如果你有更多关于lib_x插件的具体信息或问题,欢迎继续提问!

回到顶部