Flutter屏幕管理插件screen_manager的使用

Flutter屏幕管理插件screen_manager的使用

ScreenManager

Buy Me A Coffee

为什么?

每次提到依赖注入、UI规则分离时,我们通常会直接使用第三方插件来简化这些任务。然而,我一直对使用第三方插件来实现这些功能持保留态度,因为我认为使用Flutter本身的功能就可以完成这些任务。因此,我创建了一个插件,利用Flutter本身的资源来简化依赖注入和规则分离。

注意事项

在使用这个插件之前,请注意它仍处于初期阶段,这意味着它目前并没有很多功能。基本上,我在该插件中开发的一切只是一个项目结构的标准模板。它主要是根据我在初始项目中的需求而设计的。因此,如果您正在寻找某些特定功能,可能当前版本的ScreenManager还没有这些功能。但这并不妨碍该插件在未来继续发展,以满足日常开发中的各种情况。

需要理解的要点

ScreenManager拥有一个由我自己创建的结构,用于构建应用程序的UI。让我们理解这个结构,需要注意的是,您不必严格遵循这种结构,您可以根据自己的需求选择最适合的方式。现在,让我们开始。

 - lib
   - home
     - controller
     - injection
     - view
     - widgets || components
  • controller: 这里将包含您的视图控制器,业务逻辑,状态更新等。
  • injection: 这里将包含您希望屏幕能够访问的依赖项,如数据源、仓库、用例等。
  • view: 这里将包含您的视图,您在这里创建依赖项与视图之间的桥梁,也包含了您的小部件/组件的组合。
  • widgets || components: 如果您喜欢,当AppBar、FloatAction出现在您的视图中时,我通常会将这些小部件或组件分离到单独的文件中,以便使它们更解耦。这里将包含您的UI的所有这些部分。

控制器

现在让我们了解控制器。在您提问之前,是的,我在创建控制器时参考了GetX插件。创建控制器非常简单,只需遵循以下代码:

class HomeController extends ScreenController {}

这只是您需要创建控制器的内容。它还具有一些可以重载的方法,示例如下:

class HomeController extends ScreenController {
  @override
  void onInit() {
    super.onInit();
  }
  
  @override
  void onReady() {
    super.onReady();
  }
  
  @override
  void onClose() {
    super.onClose();
  }
  
  @override
  void onDependencies() {
    super.onDependencies();
  }
}
  • onInit: 此方法在StatefulWidget的initState中执行。
  • onReady: 此方法在StatefulWidget的build后执行。
  • onClose: 此方法在StatefulWidget的dispose时执行。
  • onDependencies: 此方法在StatefulWidget的didChangeDependencies中执行。
  • refresh: 此方法调用StatefulWidget的setState

注入

注入是您创建对象实例的部分,您可以在之后的UI中访问这些实例。创建注入的示例如下:

class HomeInjection extends ScreenInjection<HomeController> {
  HomeInjection({
    Key? key,
    required ScreenBridge child
  }) : super(
    key: key,
    child: child,
    controller: HomeController()
  );
}

正如您所见,在注入中我们也指定了哪个控制器负责,这样ScreenBridge(即我们的依赖注入和UI之间的桥梁)就可以完成它的职责,实现控制器在UI中的注入。 在注入中,还可以在构造函数的super中定义一个名为receiveArgs的参数,示例如下,这将在后面解释。

receiveArgs: const ScreenReceiveArgs(receive: true, identity: "homeview")

全局依赖

经过重新思考一些点,我想到了一个有趣的地方,开发者可以在应用程序中注入全局依赖项。 假设您有一个包含与sqlite连接的对象实例,在这种情况下,您需要在应用的多个地方创建依赖于该连接的数据源的新实例。在这种情况下,创建多个实例并不理想,因此在这些注入中现在有一个名为dependencies的方法,您可以通过覆盖此方法并实例化变量来创建依赖项。 现在来看例子。 首先,创建一个包含全局依赖项的InheritedWidget。在这个例子中,我将创建一个虚构的类,代码如下:

class GlobalDependencies extends InheritedWidget {
  final ConnectionSqlite connection = ConnectionSqlite();

  const GlobalDependencies({
    super.key,
    required super.child
  });

  static GlobalDependencies of(BuildContext context) {
    final result = context.dependOnInheritedWidgetOfExactType<GlobalDependencies>();
    assert(result != null, "No injection found on context");
    return result!;
  }

  @override
  bool updateShouldNotify(covariant InheritedWidget oldWidget) => false;
}

现在在您的main.dart中,将这个InheritedWidget作为所有内容的父级:

runApp(
  GlobalDependencies(
    child: MaterialApp(
      initialRoute: "/",
      routes: {
        FirstPage.firstPageRoute: (context) => const FirstPage(),
        SecondPage.secondPageRoute:(context) => const SecondPage()
      },
    )
  )
);

现在以HomeInjection为基础,在这个类中覆盖dependencies方法,您会注意到这个方法需要一个上下文。该方法将在Injection被实例化时调用。 通过这个上下文,您可以调用GlobalDependencies并查找需要实例化新对象的依赖项。假设在这个我的注入中,我将实例化一个依赖于连接的新DataSource。

@override
void dependencies(BuildContext? context) {
    homeDataSource = HomeDataSource(
      connection: GlobalDependencies.of(context!).connection
    );
}

这样,您就可以在任何依赖于例如连接的注入中创建新的实例。

最后,请记住,如果您打算使用这种方法,应该在变量前加上late关键字,并且在创建Injection实例时应传递上下文。示例如下:

@override
FirstPageInjection build(BuildContext context) {
  return FirstPageInjection(
    context: context,
    child: const ScreenBridge<FirstPageController, FirstPageInjection>(
      child: FirstPageView(),
    )
  );
}

如何获取依赖项

获取Injection中的依赖项非常简单。以之前的例子为基础,我们将尝试在Controller中获取依赖项。请注意,这个依赖项也可以在Widget或View中获取。

class HomeInjection extends ScreenInjection<HomeController> {
  final user = Usuario();

  HomeInjection({
   Key? key,
   required ScreenBridge child
  }) : super(
    key: key,
    child: child,
    controller: HomeController()
  );
 }

正如您可以看到的,我创建了一个新的Usuario实例。假设您希望在Controller中获取这个实例,您可以通过ScreenInjection类来实现。请注意,在Controller中,这些依赖项应在onInitonReadyonDependencies方法中获取,此时上下文实际上已经被创建。

class HomeController extends ScreenController {
 late final Usuario user;
 
 @override
 void onInit() {
  super.onInit();
  
  user = ScreenInjection.of<HomeInjection>(context).user;
 }
}

基本上,您需要在of方法中指定应该从哪个Injection中获取信息,这样您就可以在Controller中获得Usuario的实例了。

您还可能注意到,在Injection中包含以下方法:

@override
bool updateShouldNotify(covariant InheritedWidget oldWidget) => false;

这个方法来自InheritedWidget。如果您希望由于Injection属性的变化而更新您的View,您可以实现这个方法以达到目的。为了更好地理解,请查阅有关InheritedWidget的文档,看看如何实现这一点。

视图

现在是时候创建您的视图了。在此部分将包含ScreenBridge、Injection和View,示例如下:

class HomeBridge extends Screen {
  const HomeBridge({Key? key}) : super(key: key);
  
  @override
  HomeInjection build(BuildContext context) {
    return HomeInjection(
      child: const ScreenBridge<HomeController, HomeInjection>(
        child: HomeView(),
      )
    );
  }
}

class HomeView extends ScreenView<HomeController> {
  const HomeView({Key? key}) : super(key: key);
  
  @override
  Scaffold build(BuildContext context) {
    return const Scaffold();
  }
}

正如您所看到的,在Bridge部分,您首先返回您的Injection,然后返回ScreenBridge,指示它应该使用哪个Controller和Injection来执行必要的注入和创建。然后,Bridge的子元素实际上是您的View。这个桥接是必不可少的,必须正确配置,否则无法创建View,可能会出现错误。

在View部分,只需要重载build方法并返回一个Scaffold。每个View都需要有一个Scaffold才能执行操作,如BottomSheet等需要Scaffold的其他小部件。

一个重要点,所有的View在这种情况下都是StatefulWidget。

小部件

如果您想为您的屏幕创建小部件,并且这些小部件也需要访问UI的控制器,请注意,这些小部件是专门为UI创建的,因为它们共享同一个控制器。

class FabWidget extends ScreenWidget<HomeController> {
  @override
  void onInit() {
    super.onInit();
    
    print("INIT");
  }
  
  @override
  void onReady() {
    print("READY");
  }

  @override
  Widget build(BuildContext context) {
    super.build(context);
    
    return FloatingActionButton(
      onPressed: controller.add,
      child: const Icon(Icons.add)
    );
  }
}

还有一些可重载的方法:

  • onInit: 在返回小部件本身之前执行此方法。
  • onReady: 在小部件的build之后调用此方法。

重要的一点是,所有继承自ScreenWidget的小部件都是无状态小部件。

在视图之间触发消息(ScreenReceiveArgs)

还记得我提到的那个您可以添加到Injection构造函数中的参数吗?就是这里,那个参数用于识别您的视图是否可见以接收调用。再次展示一个示例。

首先,在您的Injection中:

class HomeInjection extends ScreenInjection<HomeController> {
  HomeInjection({
    Key? key,
    required ScreenBridge child
  }) : super(
    key: key,
    child: child,
    controller: HomeController(),
    receiveArgs: const ScreenReceiveArgs(receive: true, identity: "homeview")
  );

  @override
  bool updateShouldNotify(covariant InheritedWidget oldWidget) => false;
}

正如您所见,在Home的Injection中,我们定义了它将接收调用,通过在参数receive中传递true,并使用一个标识符,该标识符将用于调用此视图。 之后,在我们的Controller中,我们将实现一个名为receive的方法,或者您可以选择一个更好的名称,示例如下:

void receive(String message, dynamic value, {ScreenReceive? screen}) {
  switch (message) {
    case "new_people":
      peoples.add(value);
      break;
    case "update_people":
      int position = peoples.indexWhere((people) => people.id == value.id);
      peoples[position] = value;
  }

  refresh();
}

并且在您的View中,将覆盖receive方法:

@override
void receive(String message, value, {ScreenReceive? screen}) {
  controller.receive(message, value);
}

receive方法有三个参数:

  • message: 调用的消息,例如,在我们的例子中,消息为new_peopleupdate_people,基于这条消息,您将触发不同的功能。
  • value: 如果您希望向视图发送值,您将通过这个参数传递。在我们的例子中,我传递了一个类型为People的实体。
  • screen: 如果您希望传递执行调用的View的实例,并创建一种逻辑,例如,如果该屏幕正在更新,则不会接受来自视图x的任何调用,您也可以以这种方式使用它。

要执行调用,非常简单,您将使用ScreenMediator类,示例如下:

ScreenMediator.callScreen("homeview", "new_people", people);

更多关于Flutter屏幕管理插件screen_manager的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter屏幕管理插件screen_manager的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


screen_manager 是一个用于在 Flutter 应用中管理屏幕(如锁定屏幕、解锁屏幕、更改屏幕亮度等)的插件。它提供了与设备屏幕相关的各种功能,允许开发者以编程方式控制屏幕的行为。

安装

首先,你需要在 pubspec.yaml 文件中添加 screen_manager 插件的依赖:

dependencies:
  flutter:
    sdk: flutter
  screen_manager: ^0.0.1  # 请使用最新的版本号

然后运行 flutter pub get 来安装依赖。

使用

1. 导入包

在你的 Dart 文件中导入 screen_manager 包:

import 'package:screen_manager/screen_manager.dart';

2. 初始化

在使用 screen_manager 之前,你需要初始化它:

ScreenManager screenManager = ScreenManager();

3. 屏幕锁定与解锁

你可以使用 screenManager.lock() 方法来锁定屏幕,使用 screenManager.unlock() 方法来解锁屏幕:

// 锁定屏幕
screenManager.lock();

// 解锁屏幕
screenManager.unlock();

4. 设置屏幕亮度

你可以使用 screenManager.setBrightness() 方法来设置屏幕亮度。亮度值范围是 0.01.0,其中 0.0 表示最暗,1.0 表示最亮:

// 设置屏幕亮度为 50%
screenManager.setBrightness(0.5);

5. 获取当前屏幕亮度

你可以使用 screenManager.getBrightness() 方法来获取当前屏幕亮度:

double brightness = await screenManager.getBrightness();
print('当前屏幕亮度: $brightness');

6. 保持屏幕常亮

你可以使用 screenManager.keepOn() 方法来保持屏幕常亮:

// 保持屏幕常亮
screenManager.keepOn(true);

// 取消保持屏幕常亮
screenManager.keepOn(false);

7. 监听屏幕状态

你可以监听屏幕状态的变化,例如屏幕是否被锁定或解锁:

screenManager.onScreenStateChanged.listen((ScreenState state) {
  if (state == ScreenState.locked) {
    print('屏幕已锁定');
  } else if (state == ScreenState.unlocked) {
    print('屏幕已解锁');
  }
});

示例代码

以下是一个完整的示例代码,展示了如何使用 screen_manager 插件来管理屏幕:

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

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

class MyApp extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      home: ScreenManagerExample(),
    );
  }
}

class ScreenManagerExample extends StatefulWidget {
  [@override](/user/override)
  _ScreenManagerExampleState createState() => _ScreenManagerExampleState();
}

class _ScreenManagerExampleState extends State<ScreenManagerExample> {
  ScreenManager screenManager = ScreenManager();

  [@override](/user/override)
  void initState() {
    super.initState();
    screenManager.onScreenStateChanged.listen((ScreenState state) {
      if (state == ScreenState.locked) {
        print('屏幕已锁定');
      } else if (state == ScreenState.unlocked) {
        print('屏幕已解锁');
      }
    });
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Screen Manager Example'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            ElevatedButton(
              onPressed: () {
                screenManager.lock();
              },
              child: Text('锁定屏幕'),
            ),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                screenManager.unlock();
              },
              child: Text('解锁屏幕'),
            ),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                screenManager.setBrightness(0.5);
              },
              child: Text('设置屏幕亮度为 50%'),
            ),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: () async {
                double brightness = await screenManager.getBrightness();
                print('当前屏幕亮度: $brightness');
              },
              child: Text('获取当前屏幕亮度'),
            ),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                screenManager.keepOn(true);
              },
              child: Text('保持屏幕常亮'),
            ),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                screenManager.keepOn(false);
              },
              child: Text('取消保持屏幕常亮'),
            ),
          ],
        ),
      ),
    );
  }
}
回到顶部