美文网首页FlutterFlutter第三方项目博客资源
Flutter+Mobx实战,写一个App应用

Flutter+Mobx实战,写一个App应用

作者: 阿明_1719 | 来源:发表于2019-05-15 16:17 被阅读139次

    说明

    目前增加了路由跳转,可以带参数跳转页面。下拉可以自定义刷新样式,IOS点击Status Bar回到顶部,目前已经测试过。状态管理器使用Mobx,我自己觉得对于Redux使用起来会复杂一点,下面是提供的预览GIF图,卡顿现象是因为屏幕录制的帧率有点低。

    项目地址:https://github.com/Tecode/flutter_book,不定时的更新,欢迎start。

    安卓预览

    image

    IOS预览

    image

    依赖库

    environment:
      sdk: ">=2.1.0 <3.0.0"
    dependencies:
      flutter:
        sdk: flutter
      mobx:
      flutter_mobx: // Mobx
      cupertino_icons: ^0.1.2
      flutter_svg: ">=0.12.4" // 处理SVG图片
      carousel_slider: ^1.3.0 // 轮播图
      fluro: "^1.4.0" // 路由
      provider: ^2.0.1 // 用于包裹mobx
    
    dev_dependencies:
      flutter_test:
        sdk: flutter
      build_runner: ^1.3.1 //Mobx依赖
      mobx_codegen: // Mobx依赖
    

    Flutter版本

    Flutter 1.5.9-pre.223 • channel master • https://github.com/flutter/flutter.git
    Framework • revision b76a1e8312 (25 hours ago) • 2019-05-13 09:06:30 +0100
    Engine • revision 816d3fc586
    Tools • Dart 2.3.1 (build 2.3.1-dev.0.0 a0290f823c)
    

    修改系统状态栏颜色

    image
    import 'package:flutter/material.dart';
    import 'package:flutter/cupertino.dart';
    import 'package:flutter_book/containers/Entrance.dart';
    import 'package:flutter_book/helpers/constants.dart' show AppColors;
    import 'package:flutter/services.dart';
    
    void main() {
      // 修改系统状态栏颜色
      SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
        systemNavigationBarColor: Color(AppColors.themeColor), // navigation bar color
        statusBarColor: Color(AppColors.themeColor), // status bar color
      ));
      runApp(MyApp());wenti
    }
    

    自定义appBar左侧导航显示的内容

    image
    appBar: AppBar(
    ...
            leading: IconButton(
              alignment: Alignment.centerRight,
              icon: SvgPicture.asset(
                'assets/icon/icon_trophy.svg',
                width: Constants.appBarIconSize + 5.0,
                height: Constants.appBarIconSize + 5.0,
              ),
              onPressed: () {
                print("ok");
              },
            )
    ...
    )
    

    媒体查询

    MediaQuery.of(context)
    

    资源配置

      assets:
       - assets/icon/
       - lib/containers/
       - lib/model/
       - lib/helpers/
       - lib/routers/
       - assets/images/
    

    路由配置

    这里我使用的是fluro配置路由,这里我偷一下懒了,就没有使用原生的方法,不过他帮我们封装了好多的方法我们可以很方便的去使用它,下面说一下路由的配置。

    lib\routers\routers.dart

    配置路由对应的模块,可以理解成Vue-routerReact-router一样,先要将对应的路由配置到你要跳转的模块去。

    import 'package:fluro/fluro.dart';
    import 'package:flutter/material.dart';
    import 'package:flutter_book/routers/route_handlers.dart';
    
    class Routes {
      static String root = "/";
      static String setting = "/setting";
      static String detail = "/detail";
      static String demoSimpleFixedTrans = "/demo/fixedtrans";
      static String demoFunc = "/demo/func";
      static String deepLink = "/message";
    
      static void configureRoutes(Router router) {
        router.notFoundHandler = Handler(
            handlerFunc: (BuildContext context, Map<String, List<String>> params) {
          print("ROUTE WAS NOT FOUND !!!");
        });
        router.define(root, handler: rootHandler);
        router.define(setting, handler: settingRouteHandler);
        router.define(detail, handler: detailRouterHandler);
      }
    }
    

    lib\routers\route_handlers.dart

    在这里可以处理一些传过来的参数,然后我们将参数放入类中实例化。

    import 'package:flutter_book/containers/Setting.dart';
    import 'package:flutter_book/containers/FirstScreen.dart';
    import 'package:flutter_book/containers/Detail.dart';
    import 'package:fluro/fluro.dart';
    import 'package:flutter/material.dart';
    import 'package:flutter_book/helpers/fluro_convert_util.dart';
    
    Handler rootHandler = Handler(
        handlerFunc: (BuildContext context, Map<String, List<String>> params) {
      return FirstScreen();
    });
    
    Handler settingRouteHandler = Handler(
        handlerFunc: (BuildContext context, Map<String, List<String>> params) {
        return Setting();
    });
    
    Handler detailRouterHandler = Handler(
        handlerFunc: (BuildContext context, Map<String, List<String>> params) {
      return Detail(
          title: FluroConvertUtils.fluroCnParamsDecode(params["title"]?.first));
    });
    
    

    lib\main.dart

    将路由与Flutter绑定,这样你的路由就可以生效了

    class MyApp extends StatelessWidget {
      MyApp() {
        final router = new Router();
        Routes.configureRoutes(router);
        Application.router = router;
      }
      // This widget is the root of your application.
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Book',
          theme: ThemeData(
              primaryColor: Color(AppColors.themeColor),
              accentColor: Color(AppColors.themeColor),
              scaffoldBackgroundColor: Color(AppColors.themeColor)),
          home: Entrance(),
          onGenerateRoute: Application.router.generator,
        );
      }
    }
    

    使用

    import 'package:fluro/fluro.dart';
    import 'package:flutter_book/routers/application.dart';
    import 'package:flutter_book/helpers/fluro_convert_util.dart';
    
    ...代码省略了
    
     Application.router.navigateTo(
        context,
        "/detail?title=${FluroConvertUtils.fluroCnParamsEncode('热门图书')}",
        transition: TransitionType.native
    );
    

    路由传参

    路由不支持中文字符需要编码再解码

    import 'dart:convert';
    
    /// fluro 参数编码解码工具类
    class FluroConvertUtils {
      /// fluro 传递中文参数前,先转换,fluro 不支持中文传递
      static String fluroCnParamsEncode(String originalCn) {
        StringBuffer sb = StringBuffer();
        var encoded = Utf8Encoder().convert(originalCn);
        encoded.forEach((val) => sb.write('$val,'));
        return sb.toString().substring(0, sb.length - 1).toString();
      }
    
      /// fluro 传递后取出参数,解析
      static String fluroCnParamsDecode(String encodedCn) {
        var decoded = encodedCn.split('[').last.split(']').first.split(',');
        var list = <int>[];
        decoded.forEach((s) => list.add(int.parse(s.trim())));
        return Utf8Decoder().convert(list);
      }
    }
    

    编码

    import 'package:flutter_book/helpers/fluro_convert_util.dart';
    
    Application.router.navigateTo(
        context,
        "/detail?title=${FluroConvertUtils.fluroCnParamsEncode('热门图书')}",
        transition: TransitionType.native,
        // transitionDuration: const Duration(milliseconds: 300),
    );
    

    解码

    import 'package:flutter_book/helpers/fluro_convert_util.dart';
    
    Handler detailRouterHandler = Handler(
        handlerFunc: (BuildContext context, Map<String, List<String>> params) {
      return Detail(
          title: FluroConvertUtils.fluroCnParamsDecode(params["title"]?.first));
    });
    

    使用Mobx状态管理器

    pubspec.yaml配置

    environment:
      sdk: ">=2.1.0 <3.0.0"
    
    dependencies:
      flutter:
        sdk: flutter
      mobx:
      flutter_mobx:
    
    
      # The following adds the Cupertino Icons font to your application.
      # Use with the CupertinoIcons class for iOS style icons.
      cupertino_icons: ^0.1.2
      flutter_svg: ">=0.12.4"
      carousel_slider: ^1.3.0
      fluro: "^1.4.0"
      provider: ^2.0.1
    
    dev_dependencies:
      flutter_test:
        sdk: flutter
      build_runner: ^1.3.1
      mobx_codegen:
    

    多个页面使用一个store

    这里要使用到provider: ^2.0.1,类似ReactProvider。使用Provider来包裹我们的组件,使Mobx和我们的React联系起来。

    React Provider

    <Provider {...store}>
        <Router history={browserHistory}
            <App />
        </Router>
    </Provider>
    

    Dart Provider

    Dart Provider也是一样的道理,将MobxFlutter联系起来,lib/main.dart完整代码,这样使用可以保证你实例化的的store是同一个类。

      runApp(MultiProvider(
        providers: [
          Provider<FindStore>(
            builder: (_) => FindStore(),
          )
        ],
        child: MyApp(),
      ));
    

    如何使用

    我的导航发现那一栏和下面的内容是分开的,当我点击导航的切换按钮就会改变显示的页面,这样我们可以复用显示层的UI组件,数据放专门的文件去管理。

    image image

    来看看如何实现的

    通过点击然后改变数据findStore.setTile('tile', true);

    导航lib/widgets/NavBar/FindNavBar.dart

    import 'package:flutter/material.dart';
    import 'package:flutter_svg/svg.dart';
    import 'package:flutter_mobx/flutter_mobx.dart';
    import 'package:flutter_book/helpers/constants.dart';
    import 'package:flutter_book/stores/findStore.dart';
    import 'package:provider/provider.dart';
    
    class FindNavBar extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
      // 我们的store
        final findStore = Provider.of<FindStore>(context);
    
        return Observer(
          builder: (_) => AppBar(
                title: Text("发现"),
                actions: <Widget>[
                  IconButton(
                    alignment: Alignment.centerRight,
                    onPressed: () {
                      findStore.setTile('tile', true);
                      findStore.counter();
                    },
                    icon: SvgPicture.asset(
                      'assets/icon/icon_more.svg',
                      width: Constants.appBarIconSize + 2.0,
                      height: Constants.appBarIconSize + 2.0,
                      color: Color(findStore.tile
                          ? AppColors.fontColor
                          : AppColors.fontColorGray),
                    ),
                  ),
                  IconButton(
                    alignment: Alignment.centerLeft,
                    onPressed: () {
                      findStore.setTile('tile', false);
                    },
                    icon: SvgPicture.asset(
                      'assets/icon/icon_cube.svg',
                      width: Constants.appBarIconSize + 2.0,
                      height: Constants.appBarIconSize + 2.0,
                      color: Color(findStore.tile
                          ? AppColors.fontColorGray
                          : AppColors.fontColor),
                    ),
                  ),
                ],
                centerTitle: true,
                elevation: 0,
              ),
        );
      }
    }
    

    内容lib/containers/Find.dart

    检测到数据发生变化,页面重新渲染得到新的页面

    import 'package:flutter/material.dart';
    import 'package:flutter_book/widgets/Find/BookTile.dart';
    import 'package:flutter_book/widgets/Find/BookCover.dart';
    
    import 'package:flutter_book/stores/findStore.dart';
    import 'package:provider/provider.dart';
    import 'package:flutter_mobx/flutter_mobx.dart';
    
    class Find extends StatefulWidget {
      @override
      _FindState createState() => _FindState();
    }
    
    class _FindState extends State<Find> {
      @override
      Widget build(BuildContext context) {
        final findStore = Provider.of<FindStore>(context);
        return Observer(builder: (_) => findStore.tile ? BookTile() : BookCover());
      }
    }
    

    FindStore lib/stores/findStore.dart

    import 'package:mobx/mobx.dart';
    
    // Include generated file
    part 'findStore.g.dart';
    
    // This is the class used by rest of your codebase
    class FindStore = _FindStore with _$FindStore;
    
    // The store-class
    abstract class _FindStore implements Store {
      @observable
      bool tile = false;
    
      @observable
      num count = 0;
    
      @action
      void setTile(String key, dynamic value) => tile = value;
    
      @action
      num counter() => this.count++;
    }
    

    注意

    如果你是很多个页面共享一个Store不要直接导入然后实例化,例如:

    第一个页面 demo1.dart

    这个页面我们导入了counter.dart这个store而且我们将它实例化,当我们点击的时候数据发生变化页面会重新渲染

    import 'package:flutter/material.dart';
    import 'package:flutter_mobx/flutter_mobx.dart';
    
    import 'counter.dart'; // Import the Counter
    
    final counter = Counter(); // Instantiate the store
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      // This widget is the root of your application.
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'MobX',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: const MyHomePage(),
        );
      }
    }
    
    class MyHomePage extends StatelessWidget {
      const MyHomePage();
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('MobX Counter'),
          ),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Text(
                  '数值是:',
                ),
                // Wrapping in the Observer will automatically re-render on changes to counter.value
                Observer(
                  builder: (_) => Text(
                        '${counter.value}',
                        style: Theme.of(context).textTheme.display1,
                      ),
                ),
              ],
            ),
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: counter.increment,
            tooltip: 'Increment',
            child: Icon(Icons.add),
          ),
        );
      }
    }
    

    第二个页面 demo2.dart

    这个页面我们也导入了counter.dart,我们要的结果是第一个页面的数据变化了也影响这个页面,但是显然是不能的。因为store虽然是一个,但是实例化的时候是两个不同的,所以第一个页面的数据变化了也不会影响到这里。

    怎么解决呢?我们可以使用之前提到的Provider去将MobxFlutter联系起来然后通过上下关系去的到我们想要的Store,例如final findStore = Provider.of<FindStore>(context);

    import 'package:flutter/material.dart';
    import 'package:flutter_mobx/flutter_mobx.dart';
    
    import 'counter.dart'; // Import the Counter
    
    final counter = Counter(); // Instantiate the store
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      // This widget is the root of your application.
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'MobX',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: const MyHomePage(),
        );
      }
    }
    
    class MyHomePage extends StatelessWidget {
      const MyHomePage();
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('MobX Counter'),
          ),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Text(
                  '第二个页面显示第一个页面的数是:',
                ),
                Observer(
                  builder: (_) => Text(
                        '${counter.value}',
                        style: Theme.of(context).textTheme.display1,
                      ),
                ),
              ],
            ),
          ),
        );
      }
    }
    

    公共的Store counter.dart

    import 'package:mobx/mobx.dart';
    
    // Include generated file
    part 'counter.g.dart';
    
    // This is the class used by rest of your codebase
    class Counter = _Counter with _$Counter;
    
    // The store-class
    abstract class _Counter implements Store {
      @observable
      int value = 0;
    
      @action
      void increment() {
        value++;
      }
    }
    

    正确的使用方法

    页面1-导航栏

    页面2-内容

    公共Store

    将Mobx和Flutter联系起来

    结束语

    感谢你的围观,目前是我写Flutter遇到的一些坑,欢迎大家一踩坑,大家有什么意见和建议都可以提出来,谢谢。

    相关文章

      网友评论

        本文标题:Flutter+Mobx实战,写一个App应用

        本文链接:https://www.haomeiwen.com/subject/batmaqtx.html