小白篇 -- 前端人的 Flutter 试探

859 阅读7分钟

前言

  • 中文文档网站 (flutter.cn/)
  • 包管理 (pub.flutter-io.cn/)
  • 项目使用到的插件
    • provider -- 状态管理
    • dio -- 网络请求
    • flustars -- 常用工具类库(目前包含SharedPreferences Util, Screen Util, Directory Util, Widget Util, Image Util)

一、新建项目

1. 新建

参考文档中对 Android Studio and Intellij、Visual Studio Code 对应的新建方式。

2. 目录

Project
├─ .dart_tool	 记录了一些dart工具库所在的位置和信息
├─ .idea	 android studio 是基于idea开发的,.idea 记录了项目的一些文件的变更记录
├─ android	 Android项目文件夹
├─ build  编译或运行后产物
├─ ios	 iOS项目文件夹
├─ lib  包含.dart结尾的工程相关文件
│	 ├─ http
│	 ├─ layout  布局
│	 │	└─ basicLayout.dart
│	 ├─ models
│	 ├─ pages  页面
│	 ├─ themes  主题
│	 ├─ utils  工具
│	 ├─ widgets  公共组件
│	 ├─ main.dart  入口文件
│	 └─ routes.dart  路由 
├─ test	 用于存放我们的测试代码
├─ .gitignore	 git忽略配置文件
├─ .metadata	 IDE 用来记录某个 Flutter 项目属性的的隐藏文件
├─ .packages	 pub 工具需要使用的,包含 package 依赖的 yaml 格式的文件
├─ flutter_app.iml  工程文件的本地路径配置
├─ pubspec.lock	 当前项目依赖所生成的文件
├─ pubspec.yaml	 当前项目的一些配置文件,包括依赖的第三方库、图片资源文件等
└─ README.md  READEME文件

二、基础布局

1. 创建相关页面

pages/home/index.dart

import 'package:flutter/material.dart';

class Home extends StatefulWidget{
  @override
  _Home createState()=> new _Home();
}

class _Home extends State<Home>{
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('Home'),
      ),
      body: new Container(
        child: new Column(
          children: <Widget>[
            new Text('Home'),
            new Text('Home'),
          ],
        ),
      ),
    );
  }
}

pages/my/index.dart

import 'package:flutter/material.dart';

class My extends StatefulWidget{
  @override
  _My createState()=> new _My();
}

class _My extends State<My>{
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('My'),
      ),
      body: new Container(
        child: new Column(
          children: <Widget>[
            new Text('My'),
            new Text('My'),
          ],
        ),
      ),
    );
  }
}

2. 创建基础布局页面(bottomNavigationBar 底部导航栏)

layout/basicLayout.dart

import 'package:flutter/material.dart';

import 'package:flutter_template/pages/home/index.dart';
import 'package:flutter_template/pages/my/index.dart';

// 这个页面是作为整个APP的最外层的容器,以Tab为基础控制每个item的显示与隐藏
class BasicLayout extends StatefulWidget {
  @override
  _BasicLayout createState() => new _BasicLayout();
}

class _BasicLayout extends State<BasicLayout> {
  // bottomNavigationBar 当前的 val 值
  int _selectedIndex = 0;

  // pages
  List<Widget> pages = [Home(),My()];

  // bottomNavigationBar 切换
  void _onTabChanged(value) {
    setState(() {
//      print('Tab -- $value');
      _selectedIndex = value;
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      bottomNavigationBar:  new BottomNavigationBar(
        type: BottomNavigationBarType.fixed,
        // backgroundColor: Color.fromRGBO(214,211,205, 1),
        // selectedItemColor:Colors.red,
        // unselectedItemColor:  Colors.red,
        selectedFontSize: 14,
        unselectedFontSize: 14,
        currentIndex: _selectedIndex,
        onTap: (value) => {
          _onTabChanged(value)
          // Respond to item press.
        },
        items: [
          BottomNavigationBarItem(title: Text('Home'), icon: Icon(Icons.radio_button_unchecked)),
          BottomNavigationBarItem(
              title: Text('My'), icon: Icon(Icons.details)),
        ],
      ),
      body: pages[_selectedIndex],
    );
  }
}

3. 将基础布局页面放置 MaterialApp 里面

main.dart

import 'package:flutter/material.dart';

import './routes.dart';

import 'package:flutter_template/layout/basicLayout.dart';


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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter template',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home:BasicLayout(), // <= 这里
    );
  }
}

三、路由

1. 创建相关页面

pages/demo/index.dart

import 'package:flutter/material.dart';

class Demo extends StatefulWidget{
  @override
  _Demo createState()=> new _Demo();
}

class _Demo extends State<Demo>{
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('Demo'),
      ),
      body: new Container(
        child: new Column(
          children: <Widget>[
            new Text('Demo'),
            new Text('Demo'),
          ],
        ),
      ),
    );
  }
}

2. 配置路由

routes.dart

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

import 'package:flutter_template/pages/home/index.dart';
import 'package:flutter_template/pages/my/index.dart';
import 'package:flutter_template/pages/demo/index.dart';

class RouteConfig {
  static final initRouteName = '/my'; // 默认路由

  static final Map<String, WidgetBuilder> router = {
//    '/basicLayout': (BuildContext context) => BasicLayout(), // 不带参路由
//    '/basicLayout':(BuildContext context,{arguments})=> BasicLayout(arguments:arguments), // 带参路由
    '/home': (BuildContext context) => Home(),
    '/my': (BuildContext context) => My(),
    '/demo':(BuildContext context) => Demo(),
  };


  static Route<dynamic> onGenerateRoute(RouteSettings settings) {
    // 统一处理路由
    final String name = settings.name;
    final Function pageContentBuilder = router[name];
    print('routeNameSettings ------ $settings');

    // 定义当前需要返回的 route 对象
    Route route;
    if (pageContentBuilder != null) {
      if (settings.arguments != null) {
        // 带参数的处理方式
        switch (name) {
          default:
            route = CupertinoPageRoute(
                builder: (context) =>
                    pageContentBuilder(context, arguments: settings.arguments));
            break;
        }
      } else {
        // 不带参数的处理方式
        switch (name) {
          case '/signUp':
            route = CupertinoPageRoute(
                builder: (context) => pageContentBuilder(context),
                fullscreenDialog:
                true // fullscreenDialog表示新的路由页面是否是一个全屏的模态对话框,在iOS中,如果fullscreenDialog为true,新页面将会从屏幕底部滑入(而不是水平方向)。
            );
            break;
          default:
          //这里调用自定义页面切换为淡入淡出
          //   route = FadeAnimation(pageContentBuilder);
            route = CupertinoPageRoute(
                builder: (context) => pageContentBuilder(context));
            break;
            break;
        }
      }
    }
    return route;
  }
}

3. 将路由文件配置到 main.dart

main.dart

import 'package:flutter/material.dart';

import './routes.dart';

import 'package:flutter_template/layout/basicLayout.dart';


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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter template',
      // initialRoute: RouteConfig.initRouteName, // 初始路由
      onGenerateRoute: RouteConfig.onGenerateRoute, // 路由配置
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home:BasicLayout(),
    );
  }
}

跳转路由

new FlatButton(
  color: Colors.blue,
  highlightColor: Colors.redAccent[700],
  splashColor: Colors.grey,
  shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20.0)),
  child: Text('跳转到Demo页面',style: TextStyle(color: Colors.white),),
  onPressed: () {
  	Navigator.pushNamed(context, '/demo'); // <= 路由跳转
  },
)

路由小知识 juejin.cn/post/684490…

四. 暗黑模式

1. 小前言

我们可以直接在 MaterialAppdarkTheme 选项中使用:

MaterialApp( 
    theme: ThemeData( 
        brightness: Brightness.light, 
        primaryColor: Colors.blue, 
    ), 
    darkTheme: ThemeData( 
        brightness: Brightness.dark, 
    ),
);

也可以写成:

darkTheme: ThemeData.dark()

这样写的好处是,用户无需单独设置深/浅色模式,完全根据系统设置来切换。

但是,

我们更多的是需要有控制权(手动)来设置。

2. 准备

这里需要使用 shared_preferences 保存用户设置,通过 Provider 实现状态管理。

flustars 号称 ”Flutter 全网最全常用工具类”,这里使用 SpUtil 来储存用户所选择的信息;

pub.dev/packages/fl…

Provider 官方推荐的状态管理库,相较其他库来说更具优势;

provider: ^4.3.2+2
flustars: ^0.3.3

3. 配置全局的主题

themes/themes.dart

// 由于直接在 main.dart 配置 明亮/暗黑 样式会显得很乱,故提出来单独处理
import 'package:flutter/material.dart';

class Themes {
  //  明亮
  static ThemeData lightTheme() {
    return ThemeData(
      brightness: Brightness.light,
      // appBarTheme: AppBarTheme(
      //   color: Color.fromRGBO(229, 229, 229, 1),
      //   elevation: 0, //去掉Appbar底部阴影
      //   iconTheme: IconThemeData(
      //     color: Color(0xFF323232), //change your color here
      //   ),
      // ),
      // scaffoldBackgroundColor: Color.fromRGBO(229, 229, 229, 1),
    );
  }

  //  暗黑
  static ThemeData dartTheme() {
    return ThemeData(
      brightness: Brightness.dark,
      // appBarTheme: AppBarTheme(
      //   color: Colors.black,
      //   elevation: 0, //去掉Appbar底部阴影
      //   iconTheme: IconThemeData(
      //     color: Color(0xFFFFFFFF), //change your color here
      //   ),
      // ),
      // scaffoldBackgroundColor: Colors.black,
    );
  }
}

其他 ThemeData 的参数:

4. 主题模式状态管理类

models/globalModeProvider.dart

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

import 'package:flutter_template/themes/themes.dart';

class GlobalModeProvider with ChangeNotifier {
  // 主题 0:浅色  1: 深色 2: 随系统
  int _darkMode = SpUtil.getInt("darkMode", defValue: 2); // defValue 默认值

  int get darkMode => _darkMode;

  ThemeData get themeValue => darkMode == 2
      ? Themes.lightTheme()
      : darkMode == 1 ? Themes.dartTheme() : Themes.lightTheme(); // 是否是随系统?是的就是明亮,不是,则按照手动选择主题展示

  ThemeData get dartThemeValue => darkMode == 2 ? Themes.dartTheme() : null; // 是否是随系统?是的就是暗黑,不是,为空

  // 更改主题
  void changeThemeMode(int darkMode) async {
    _darkMode = darkMode;
    // print('当前主题$_darkMode');

    notifyListeners(); // `notifyListeners();` 用于通知顶层容器状态的变化
    SpUtil.putInt('darkMode', darkMode); // 用于保存用户设置(持久化选择)。
  }
}

5. 配置 main.dart

import 'package:flutter/material.dart';
import 'package:flustars/flustars.dart';
import 'package:flutter/rendering.dart';
import 'package:provider/provider.dart';
import './routes.dart';

import 'package:flutter_template/models/globalModeProvider.dart';
import 'package:flutter_template/layout/basicLayout.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized(); // 在 runApp 之前若需要初始化插件,则需要此行代码
  await SpUtil.getInstance(); // 初始化 SpUtil (由于 SharedPreferences 需要异步生成才能使用。)
  debugPaintSizeEnabled = !true;
  runApp(
    // 全局注册 model
    MultiProvider(providers: [
      ChangeNotifierProvider.value(value: GlobalModeProvider()),
    ], child: MyApp()),
  );
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return Consumer<GlobalModeProvider>(
        builder: (context, globalModeProvider, _) {
      return MaterialApp(
        title: 'Flutter template',
        initialRoute: RouteConfig.initRouteName, // 初始路由
        onGenerateRoute: RouteConfig.onGenerateRoute,  // 路由配置
        theme: globalModeProvider.themeValue,
        darkTheme: globalModeProvider.dartThemeValue,
        home: BasicLayout(),
      );
    });
  }
}

6. 创建进入设置页面入口

pages/my/index.dart

body: new Container(
  child: new Column(
    children: <Widget>[
      new Container(
        width: MediaQuery.of(context).size.width, // 获取屏幕的宽度
        height: 55,
        child: new FlatButton(
          onPressed: () {
            Navigator.pushNamed(context, '/setting');
          },
          child: new Flex(
            direction: Axis.horizontal,
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: <Widget>[
              new Row(
                children: <Widget>[
                  new Icon(Icons.settings,size: 20),
                  new Padding(padding: EdgeInsets.only(right: 10)),
                  new Text('设置',),
                ],
              ),
              new Icon(Icons.chevron_right),
            ],
          ),
        ))
    ],
  ),
),

7. 设置页面

pages/setting/index.dart

import 'package:flutter/material.dart';
import 'package:flutter_template/models/globalModeProvider.dart';
import 'package:provider/provider.dart';


class Setting extends StatefulWidget {
  @override
  _Setting createState() => new _Setting();
}

class _Setting extends State<Setting> {
  // 更改 主题
  void _changeTheme(context, value) {
    Provider.of<GlobalModeProvider>(context, listen: false)
        .changeThemeMode(value);
  }

// 渲染主题列表
  List<Widget> renderthemeLists(context, themeLists, _currentTheme) {
    List<Widget> widgetTheme = [];
    for (int i = 0; i < themeLists.length; i++) {
      widgetTheme.add(
        new Container(
            width: MediaQuery.of(context).size.width,
            height: 55,
            decoration: BoxDecoration(
                border: Border(
                    top: BorderSide(
                        color: Colors.black12,
                        width: 1,
                        style: BorderStyle.solid))),
            child: new FlatButton(
              onPressed: () => _changeTheme(context, themeLists[i]['value']),
              child: new Container(
                padding: EdgeInsets.only(left: 0, right: 0),
                child: new Flex(
                  direction: Axis.horizontal,
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: <Widget>[
                    new Text(themeLists[i]['label']),
                    _currentTheme == themeLists[i]['value']
                        ? new Icon(Icons.done)
                        : new Text('')
                  ],
                ),
              ),
            )),
      );
    }
    return widgetTheme;
  }

  @override
  Widget build(BuildContext context) {
    List themeLists = [
      {'label': '明亮模式', 'value': 0},
      {'label': '暗黑模式', 'value': 1},
      {'label': '跟随系统', 'value': 2}
    ];

    // 仓库
    final _globalModeStore = Provider.of<GlobalModeProvider>(context);

    // 当前主题
    var _currentTheme = _globalModeStore.themeMode;

    return new Scaffold(
      appBar: new AppBar(
        title: new Text('设置'),
      ),
      body: new Container(
        child: new Column(
          children: <Widget>[
            new ExpansionTile(
                title: new Text('主题'),
                children: renderthemeLists(context, themeLists, _currentTheme))
          ],
        ),
      ),
    );
  }
}

五、暗黑模式扩展

如果需要自定义组件的颜色样式等等,这有一个参考的解决方案。

问题?

如图,无论在明亮还是暗黑模式下,块A是蓝色,块B是红色。但我们需求是明亮模式下块A是蓝色,块B是红色,暗黑模式下块A是黄色色,块B是绿色。

pages/demo/index.dart

import 'package:flutter/material.dart';

class Demo extends StatefulWidget {
  @override
  _Demo createState() => new _Demo();
}

class _Demo extends State<Demo> {
  @override
  Widget build(BuildContext context) {
    final gW = MediaQuery.of(context).size.width;

    return new Scaffold(
      appBar: new AppBar(
        title: new Text('Demo'),
      ),
      body: new Container(
        padding: EdgeInsets.all(10),
        child: new Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          children: <Widget>[
            new Container(
              width: gW,
              height: 150,
              decoration: BoxDecoration(
                color: Colors.blue,
              ),
            ),
            new Container(
              width: gW,
              height: 150,
              margin: EdgeInsets.only(top: 10),
              decoration: BoxDecoration(
                color: Colors.red,
              ),
            )
          ],
        ),
      ),
    );
  }
}

解决方案!

themes/colorsThemes.dart (colors 配置表)

import 'package:flutter/material.dart';

class ColorsThemes{
  static dynamic theme(context) {
    final themeType = Theme.of(context).brightness.toString().toLowerCase(); // 获取当前主题

    var lightTheme = {
      'demoBY': Colors.blue,
      'demoRG':Colors.red
    };

    var dartTheme = {
      'demoBY': Colors.yellow,
      'demoRG':Colors.green
    };

    if (themeType == 'brightness.dark') {
      // brightness.dark 暗黑主题
      return dartTheme;
    } else {
      // brightness.light 亮色主题
      return lightTheme;
    }
  }
}

pages/demo/index.dart

import 'package:flutter/material.dart';

import 'package:flutter_template/themes/colorsThemes.dart'; // 引入颜色配置表

class Demo extends StatefulWidget {
  @override
  _Demo createState() => new _Demo();
}

class _Demo extends State<Demo> {
  @override
  Widget build(BuildContext context) {
    final gW = MediaQuery.of(context).size.width;
    final colors = ColorsThemes.theme(context); // 定义

    return new Scaffold(
      appBar: new AppBar(
        title: new Text('Demo'),
      ),
      body: new Container(
        padding: EdgeInsets.all(10),
        child: new Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          children: <Widget>[
            new Container(
              width: gW,
              height: 150,
              decoration: BoxDecoration(
                color: colors['demoBY'], // 使用
              ),
            ),
            new Container(
              width: gW,
              height: 150,
              margin: EdgeInsets.only(top: 10),
              decoration: BoxDecoration(
                color: colors['demoRG'], // 使用
              ),
            )
          ],
        ),
      ),
    );
  }
}

六、国际化

1. 小前言

网上解决国际化的方案有很多,找到的这种 Flutter intl 插件形式相对比较简单;

2. 准备

Flutter intl 插件介绍:

3. 配置多语言

l18n/intl_en.arb

{
  "Home": "Home",
  "jumpDemoPage": "Skip to the Demo page",
  "Demo": "Demo",
  "My": "My",
  "Setting": "Setting",
  "multiLanguage": "multi-language",
  "FollowSystem": "Follow the system",
  "Theme": "Theme",
  "LightMode": "Light mode",
  "DarkMode": "Dark mode"
}

intl_zh_CN.arb

{
  "Home": "首页",
  "jumpDemoPage": "跳转到{page}页面",
  "Demo": "示例",
  "My": "我的",
  "Setting": "设置",
  "multiLanguage": "多语言",
  "FollowSystem": "跟随系统",
  "Theme": "主题",
  "LightMode": "明亮模式",
  "DarkMode": "暗黑模式"
}

intl_zh_HK.arb

{
  "Home": "首頁",
  "jumpDemoPage": "跳轉到{page}頁面",
  "Demo": "示例",
  "My": "我的",
  "Setting": "設置",
  "multiLanguage": "多語言",
  "FollowSystem": "跟隨系統",
  "Theme": "主題",
  "LightMode": "明亮模式",
  "DarkMode": "暗黑模式"
}

……(更多的其他语言)

4. 配置 main.dart

import 'package:flutter/material.dart';
import 'package:flustars/flustars.dart';
import 'package:flutter/rendering.dart';
import 'package:provider/provider.dart';
import 'package:flutter_template/generated/l10n.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import './routes.dart';

import 'package:flutter_template/models/globalModeProvider.dart';
import 'package:flutter_template/layout/basicLayout.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized(); // 在 runApp 之前若需要初始化插件,则需要此行代码
  await SpUtil.getInstance(); // 初始化 SpUtil (由于 SharedPreferences 需要异步生成才能使用。)
  debugPaintSizeEnabled = !true;
  runApp(
    // 全局注册 model
    MultiProvider(providers: [
      ChangeNotifierProvider.value(value: GlobalModeProvider()),
    ], child: MyApp()),
  );
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return Consumer<GlobalModeProvider>(
        builder: (context, globalModeProvider, _) {
      return MaterialApp(
        title: 'Flutter template',
        // initialRoute: RouteConfig.initRouteName, // 初始路由
        onGenerateRoute: RouteConfig.onGenerateRoute,  // 路由配置
        theme: globalModeProvider.themeValue,
        darkTheme: globalModeProvider.dartThemeValue,
        // 默认
        locale:  Locale('zh','CN'), // <= 可以更改这里看到效果
        // 设置语言
        localizationsDelegates: const [
          S.delegate,
          GlobalMaterialLocalizations.delegate, // 指定本地化的字符串和一些其他的值
          GlobalCupertinoLocalizations.delegate, // 对应的Cupertino风格
          GlobalWidgetsLocalizations.delegate // 指定默认的文本排列方向, 由左到右或由右到左
        ],
        // 讲en设置为第一项,没有适配语言时,英语为首选项
        supportedLocales: [
          const Locale('en', ''),
          const Locale('zh', 'CN'),
          ...S.delegate.supportedLocales
        ],
        // // 插件目前不完善手动处理简繁体
        localeResolutionCallback: (locale, supportLocales) {
          print('当前系统语言环境$locale');
          // 中文 简繁体处理
          // if (locale?.languageCode == 'zh') {
          //   if (locale?.scriptCode == 'Hant') {
          //     return const Locale('zh', 'HK'); //繁体
          //   } else {
          //     return const Locale('zh', 'CN'); //简体
          //   }
          // }
          return null;
        },
        home: BasicLayout(),
      );
    });
  }
}

在需要使用的文件中:

import 'package:flutter_template/generated/l10n.dart';

// 两种方式使用
new Text(S.current.Home),
new Text(S.of(context).Home),

5. 手动修改语言

这里跟修改主题是一样的,也需要使用 shared_preferences 保存用户设置,通过 Provider 实现状态管理。

5.1 配置全局的语言

models/globalModeProvider.dart

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

import 'package:flutter_template/themes/themes.dart';

class GlobalModeProvider with ChangeNotifier {
  // 主题 0:浅色  1: 深色 2: 随系统
  int _themeMode = SpUtil.getInt("themeMode", defValue: 2); // defValue 默认值

  int get themeMode => _themeMode;

  ThemeData get themeValue => themeMode == 2
      ? Themes.lightTheme()
      : themeMode == 1 ? Themes.dartTheme() : Themes.lightTheme(); // 是否是随系统?是的就是明亮,不是,则按照手动选择主题展示
  ThemeData get dartThemeValue => themeMode == 2 ? Themes.dartTheme() : null; // 是否是随系统?是的就是暗黑,不是,为空

  // 语言
  dynamic _languageMode = SpUtil.getString("languageMode", defValue: null); // null 为跟随系统
  String get languageMode => _languageMode;
  Locale get localeValue => _languageMode == null ? null: Locale(_languageMode.split('_')[0], _languageMode.split('_')[1]);

  // 更改主题
  void changeThemeMode(int themeMode) async {
    _themeMode = themeMode;
    // print('当前主题$_darkMode');

    notifyListeners(); // `notifyListeners();` 用于通知顶层容器状态的变化
    SpUtil.putInt('themeMode', themeMode); // 用于保存用户设置。
  }

  // 更改语言
  void changeLanguageMode(String languageMode) async {
    _languageMode = languageMode;
    // print('当前语言 $languageMode');

    notifyListeners();
    SpUtil.putString('languageMode', languageMode);
  }

}

5.2 配置 main.dart

main.dart

// 更改默认
// locale:  Locale('zh','CN'), // <= 可以更改这里看到效果
locale: globalModeProvider.localeValue,

5.3 设置页面

pages/setting/index.dart

import 'package:flutter/material.dart';
import 'package:flutter_template/models/globalModeProvider.dart';
import 'package:provider/provider.dart';
import 'package:flutter_template/generated/l10n.dart';


class Setting extends StatefulWidget {
  @override
  _Setting createState() => new _Setting();
}

class _Setting extends State<Setting> {
  // 更改 语言
  void _changeLanguage(context, value) async {
    Provider.of<GlobalModeProvider>(context, listen: false)
        .changeLanguageMode(value);
  }

  // 更改 主题
  void _changeTheme(context, value) {
    Provider.of<GlobalModeProvider>(context, listen: false)
        .changeThemeMode(value);
  }

  // 渲染语言列表
  List<Widget> renderLangugeLists(context, langugeLists, _currentLanguge) {
    List<Widget> widgetLanguge = [];
    langugeLists.forEach((item) => {
          widgetLanguge.add(
            new Container(
                width: MediaQuery.of(context).size.width,
                height: 55,
                decoration: BoxDecoration(
                    border: Border(
                        top: BorderSide(
                            color: Colors.black12,
                            width: 1,
                            style: BorderStyle.solid))),
                child: new FlatButton(
                  onPressed: () => _changeLanguage(context, item['value']),
                  child: new Container(
                    padding: EdgeInsets.only(left: 0, right: 0),
                    child: new Flex(
                      direction: Axis.horizontal,
                      mainAxisAlignment: MainAxisAlignment.spaceBetween,
                      children: <Widget>[
                        new Text(item['label']),
                        _currentLanguge == item['value']
                            ? new Icon(Icons.done)
                            : new Text('')
                      ],
                    ),
                  ),
                )),
          )
        });
    return widgetLanguge;
  }

// 渲染主题列表
  List<Widget> renderthemeLists(context, themeLists, _currentTheme) {
    List<Widget> widgetTheme = [];
    for (int i = 0; i < themeLists.length; i++) {
      widgetTheme.add(
        new Container(
            width: MediaQuery.of(context).size.width,
            height: 55,
            decoration: BoxDecoration(
                border: Border(
                    top: BorderSide(
                        color: Colors.black12,
                        width: 1,
                        style: BorderStyle.solid))),
            child: new FlatButton(
              onPressed: () => _changeTheme(context, themeLists[i]['value']),
              child: new Container(
                padding: EdgeInsets.only(left: 0, right: 0),
                child: new Flex(
                  direction: Axis.horizontal,
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: <Widget>[
                    new Text(themeLists[i]['label']),
                    _currentTheme == themeLists[i]['value']
                        ? new Icon(Icons.done)
                        : new Text('')
                  ],
                ),
              ),
            )),
      );
    }
    return widgetTheme;
  }

  @override
  Widget build(BuildContext context) {
    List langugeLists = [
      {'label': S.current.FollowSystem, 'value': null},
      {'label': 'English', 'value': 'en_US'},
      {'label': '简体中文', 'value': 'zh_CN'},
      {'label': '繁體中文', 'value': 'zh_HK'},
    ];
    List themeLists = [
      {'label': S.current.FollowSystem, 'value': 2},
      {'label': S.current.LightMode, 'value': 0},
      {'label': S.current.DarkMode, 'value': 1},
    ];

    // 仓库
    final _globalModeStore = Provider.of<GlobalModeProvider>(context);

    // 当前主题
    var _currentTheme = _globalModeStore.themeMode;

    // 当前语言
    var _currentLanguge = _globalModeStore.languageMode;

    final intls = S.of(context);

    return new Scaffold(
      appBar: new AppBar(
        title: new Text(intls.Setting),
      ),
      body: new Container(
        child: new Column(
          children: <Widget>[
            new ExpansionTile(
              title: new Text(intls.multiLanguage),
              children:
                  renderLangugeLists(context, langugeLists, _currentLanguge),
            ),
            new ExpansionTile(
                title: new Text(intls.Theme),
                children: renderthemeLists(context, themeLists, _currentTheme))
          ],
        ),
      ),
    );
  }
}

7、Http 请求

1. 安装 dio

dependencies:
  dio: ^3.0.10

2. 配置 dio

pages/HttpUtils.dart

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

// 原始请求方式
//  void getHttp() async {
//    try {
//      Response response = await Dio().get("http://81.69.13.123:7001/api/getUsersTestDataAll");
//      print(response);
//    } catch (e) {
//      print(e);
//    }
//  }

class DioUtils {
  static const String BASE_URL = "http://81.69.13.123:7001/api"; //base url
  static DioUtils _instance;
  Dio _dio;
  BaseOptions _baseOptions;

  static DioUtils getInstance() {
    if (_instance == null) {
      _instance = new DioUtils();
    }
    return _instance;
  }

  // dio初始化配置
  DioUtils() {
    //请求参数配置
    _baseOptions = new BaseOptions(
      baseUrl: BASE_URL,
      connectTimeout: 5000,
      receiveTimeout: 5000,
      headers: {
        //预设好的header信息 需要配置请求的header可在此处配置
        "testHeader": "bb"
      },
      //请求的Content-Type,默认值是[ContentType.json]. 也可以用ContentType.parse("application/x-www-form-urlencoded")
      contentType: "application/x-www-form-urlencoded",
      //表示期望以那种格式(方式)接受响应数据。接受三种类型 `json`, `stream`, `plain`, `bytes`. 默认值是 `json`,
      responseType: ResponseType.json,
    );

    //创建dio实例
    _dio = new Dio(_baseOptions);

    //可根据项目需要选择性的添加请求拦截器
    _dio.interceptors.add(
      InterceptorsWrapper(onRequest: (RequestOptions requestions) async {
        //此处可网络请求之前做相关配置,比如会所有请求添加token,或者userId
//        requestions.queryParameters["token"] = "testtoken123443423";
//        requestions.queryParameters["userId"] = "123456";
//        print('-----请求参数--' + requestions.queryParameters.toString());
        return requestions;
      }, onResponse: (Response response) {
        //此处拦截工作在数据返回之后,可在此对dio请求的数据做二次封装或者转实体类等相关操作
//        print('------ $response');
        return response;
      }, onError: (DioError error) {
        //处理错误请求
        return error;
      }),
    );
  }

  // get请求

  get(url, {data, options}) async {
    print('get request path ------$url-------请求参数--$data');
    Response response;
    try {
      response = await _dio.get(url, queryParameters: data, options: options);
      debugPrint('get result ---${response.data}');
    } on DioError catch (e) {
      print('请求失败---错误类型${e.type}--错误信息${e.message}');
    }
    return response.data;
  }

  // Post请求

  post(url, {data, options}) async {
    print('post request path ------$url-------请求参数$data');
    Response response;
    try {
      response = await _dio.post(url, queryParameters: data, options: options);
      print('post result ---${response.data}');
    } on DioError catch (e) {
      print('请求失败---错误类型${e.type}--错误信息${e.message}');
    }

    return response.data;
  }
}

3. 配置接口路径

pages/apiUrls.dart

// 接口
class ApiUrls {
  static const String BASE_URL = 'http://81.69.13.123:7001/api';

  // 全部用户数据
  static String getUsersTestDataAllApi() {
    return '/getUsersTestDataAll';
  }
}

4. 配置接口

pages/https.dart

import './HttpUtils.dart';
import './apiUrls.dart';

class Https {
  // 获取全部用户数据
  static getUsersTestDataAll() async {
    Map<String, dynamic> result =
        await DioUtils().get(ApiUrls.getUsersTestDataAllApi());
    return result;
  }
}

5. 页面请求

pages/home/index.dart

import 'package:flutter/material.dart';
import 'package:flutter_template/generated/l10n.dart';
import 'package:flutter_template/http/https.dart';

class Home extends StatefulWidget {
  @override
  _Home createState() => new _Home();
}

class _Home extends State<Home> {
  String requestData = 'null'; // 局部 state

  @override
  Widget build(BuildContext context) {
    final intls = S.of(context);

    // 请求方法
    onGetUsersTestDataAll() {
      Https.getUsersTestDataAll().then((res) {
        // setState 更改 state
        setState(() {
          requestData = res['data'].toString();
        });
      });
    }

    print(requestData);

    return new Scaffold(
      appBar: new AppBar(
        title: new Text(intls.Home),
      ),
      body: new Center(
        child: new Column(
          children: <Widget>[
            new FlatButton(
              color: Colors.blue,
              highlightColor: Colors.redAccent[700],
              splashColor: Colors.grey,
              shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.circular(20.0)),
              child: Text(
                intls.jumpDemoPage('Demo'),
                style: TextStyle(color: Colors.white),
              ),
              onPressed: () {
                Navigator.pushNamed(context, '/demo');
              },
            ),
            new FlatButton(
              color: Colors.blue,
              highlightColor: Colors.redAccent[700],
              splashColor: Colors.grey,
              shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.circular(20.0)),
              child: Text(
                intls.mockRequest,
                style: TextStyle(color: Colors.white),
              ),
              onPressed: onGetUsersTestDataAll,
            ),
            new Container(
                width: 200,
                height: 200,
                decoration: BoxDecoration(border: Border.all(width: 0.5)),
                child: new Text(requestData))
          ],
        ),
      ),
    );
  }
}

后言

以上均是此时实践下来的总结,只是提供一种整套的解决方案,不一定最佳,且 flutter 目前还不太成熟,变化快。望各位多多指教,提供更佳的解决方案,再修改完善。🤣🤣🤣

项目地址 (github.com/lEnGKX/flut…)