上一篇文章初始化了项目,本文来实现语言和主题的切换功能。

如何让数据更新反馈到视图中

先让我们回顾一下上一篇的计数器例程的homepage部分

import 'package:flutter/material.dart';
class HomePage extends StatefulWidget {
  const HomePage({super.key, required this.title});
  final String title;
  @override
  State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
  int _counter = 0;
  void _incrementCounter() {
    setState(() {//使用setState来通知视图数据发生了变化
      _counter++;
    });
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text('You have pushed the button this many times:'),
            Text(
              '$_counter',	//这里引用了数据变量
              style: Theme.of(context).textTheme.headlineMedium,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ), 
    );
  }
}

通过这个例程可以知道在Flutter中我们可以通过在StatefulWidget的状态中使用内部带有setState()的方法来通知窗口对数据进行更新。不过缺点明显,我不可以跨组件共享这些数据。
所以我要使用Provider这个库完成此功能。它可以提供数据和视图的自动绑定以及跨组件共享。

安装Provider依赖

shift + ctrl + ` 打开一个终端,输入命令

flutter pub add provider

或者在项目根目录下的pubspec.yaml添加

dependencies:
	provider: ^6.1.5+1

Provider 的使用

首先我们需要一个存储数据和处理业务的model类,并且它需要使用with关键字来获取ChangeNotifier的父类方法。这里我创建了一个ThemeModel类,存储背景颜色数据,修改背景颜色的方法以及一个测试用的可以变动背景颜色的方法。
创建的文件内容
model/
├── index.dart (用于export所有Model类的文件,以后这个文件的内容就不放出了)
└── theme_model.dart

theme_model.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class ThemeModel with ChangeNotifier {  //使用with关键字直接获取父类的方法
  late Color _backgroundColor;  //定义背景颜色的Color变量,late关键字是告诉Flutter这个变量我稍后再初始化,这样就不会因为未初始化报错。变量名带前缀 '_' 表名该变量是私有的
  ThemeModel({Color backgroundColor = Colors.blue}){
    _backgroundColor=backgroundColor;
  }//构造函数初始化颜色内容
  
  void setBackgroundColor(Color color){
    if(color == _backgroundColor) return;
    _backgroundColor = color;
    notifyListeners();//只要执行了此方法,那么所有通过provider获取的此model的widget都会进行刷新
  }
  void changeBackgroundColor(){//测试函数,让颜色在蓝色和紫色之间变换
    print("changeBackgroundColor");
    Color color;
    if(_backgroundColor == Colors.blue) color = Colors.purple;
    else color=Colors.blue;
    setBackgroundColor(color);
  }
  Color get backgroundColor => _backgroundColor;//使用get关键字的箭头函数,可以用成员的方式调用方法,所以获取这个私有的变量可以使用themeModel.backgroundColor
}

存储数据的东西有了,让我们在修改一下main.dart 和 homepage.dart 让视图使用provider提供的model类。

main.dart

import 'package:flutter/material.dart';
import './app.dart';
import 'package:provider/provider.dart';
import 'model/index.dart';
void main() => runApp(
  ChangeNotifierProvider(
    create: (_)=>ThemeModel(),
    child: const App(),
  )//使用ChangeNotifierProvider包裹app,并且提供一个唯一的位于顶层的全局ThemeModel变量
);

homepage.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../model/index.dart';
class HomePage extends StatefulWidget {
  const HomePage({super.key, required this.title});
  final String title;
  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  int _counter = 0;
  @override
  Widget build(BuildContext context) {
    var themeMode=Provider.of<ThemeModel>(context);//使用Provider.of 来获取对应类型的Model全局数据
    return Scaffold(
      appBar: AppBar(
        backgroundColor: themeMode.backgroundColor,//使用通过Provider获取的model类数据,实现视图和模型的自动绑定,这里我让AppBar中的背景使用model类中的数据
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text('You have pushed the button this many times:'),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: themeMode.changeBackgroundColor,//使用model里的方法,把数据和业务都解耦给model类
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ), 
    );
  }
}

运行的效果
在这里插入图片描述
这样一个更换主题的基础实现就做完了。

国际化(切换软件的语言)

有了上文例子,不难想到使用proivder和model类的方式来在软件里面动态的切换语言。所以来简单实现一下。

简单的实现

创建新的文件language_model.dart
language_model.dart

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

class LanguageModel with ChangeNotifier{
  List<String> titles=["Flutter demo title","Flutter 测试标题"];//数组存储一段文本的不同语言的文本样式
  List<String> texts =["Flutter demo text","Flutter 测试文本"];
  bool isEnglish=true;
  void ChangeLanguage(){
    isEnglish=!isEnglish;
    notifyListeners();
  }
  String get title => isEnglish?titles[0]:titles[1];  //使用?:三目运算符来获取当前语言模式下的文本
  String get text =>isEnglish?texts[0]:texts[1];
}

现在我们有多个model了,main.dart也需要修改,让他可以包含多个model类

main.dart

import 'package:flutter/material.dart';
import './app.dart';
import 'package:provider/provider.dart';
import 'model/index.dart';
void main() => runApp(
  MultiProvider(//使用MutilProvider 就可以避免嵌套,一次性注入所有provider
    providers: [
      ChangeNotifierProvider(create: (_)=>ThemeModel()),
      ChangeNotifierProvider(create: (_)=>LanguageModel())
    ],
    child:  const App(),
  )
);

再对使用的文本数据内容修改为LanguageModel类中的数据,把这些文本数据都解耦到LanguageModel中。并且让button调用LanguageModel里面的方法来切换语言。

app.dart

import 'package:flutter/material.dart';
import 'route/index.dart';
import 'model/index.dart';
import 'package:provider/provider.dart';
class App extends StatelessWidget {
  const App({super.key});
  @override
  Widget build(BuildContext context) {
    var languageModel = Provider.of<LanguageModel>(context); //老样子使用provider获取数据
    return
       MaterialApp(
        title: languageModel.title,
        home:  HomePage(title: languageModel.title), //使用languageModel中的数据
      ); 
  }
}

homepage.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../model/index.dart';
class HomePage extends StatefulWidget {
  const HomePage({super.key, required this.title});
  final String title;
  @override
  State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
  int _counter = 0;
  @override
  Widget build(BuildContext context) {
    var themeMode=Provider.of<ThemeModel>(context);
    var languageModel=Provider.of<LanguageModel>(context);
    return Scaffold(
      appBar: AppBar(
        backgroundColor: themeMode.backgroundColor,
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(languageModel.text),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: languageModel.ChangeLanguage,//修改成LanguageModel中改变语言的方法
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ), 
    );
  }
}

最后效果:
在这里插入图片描述

使用Intl进行国际化

只是单纯的使用provider和model类的组合实现国际化,在文本多,语言种类多的时候,显然不方便。

所以我使用Intl库来对其进行优化

安装前置依赖

在vscode侧边栏选择扩展,然后搜索flutter intl,并安装
在这里插入图片描述
安装完成后按下shift + ctrl +p 打开命令面板,输入flutter intl 选择initialize这一项按下enter
在这里插入图片描述
此命令会自动在Lib文件夹中生成以下内容
generated/ //此文件夹为插件自动生成修改,我们不需要管理
├── intl
│ ├── messages_all.dart
│ └── messages_en.dart
└── l10n.dart
l10n/
└── intl_en.arb //存储语言为en的文本文件

打开生成的dart文件,我们发现还需要手动安装一下intl库的依赖
在这里插入图片描述
shift +ctrl +` 打开终端输入以下命令安装

flutter pub add intl

还需要添加flutter_localizations的依赖,但是它无法简单的通过pub add 命令安装,需要在pubspec.yaml中添加。
pubspec.yaml

dependencies:
	...
	flutter_localizations:
    	sdk: flutter
intl初始化以及使用

在当前的l10n文件夹中,我们看到只有一个en语言的arb文件,还需要一个中文的arb文件体现国际化。
再按下shift + ctrl +p 打开命令面板,输入flutter intl: 选择add locale

在这里插入图片描述
输入想要添加的语言,中文是 zh
在这里插入图片描述
然后插件自动创建了 zh 相对应的文件。
在这里插入图片描述
在arb中以键值对的方式把各语言的文本输入进去

intl_en.arb

{
    "title":"Flutter demo title",
    "text" :"hi {name}"
}

intl_zh.arb

{
    "title":"Flutter 测试标题",
    "text" :"你好 {name}"
}

其中被{}包裹的name是动态参数,可以在使用中动态的传入值然后呈现。
保存后插件会自动更新对应messages文件。

文本已经写好,接下来我们在app.dart中注册国际化的代理,这些代理的作用是异步地把本地化的资源加载到widget中

app.dart

import 'package:flutter/material.dart';
import 'route/index.dart';
import 'model/index.dart';
import 'package:provider/provider.dart';
import 'generated/l10n.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
class App extends StatelessWidget {
  const App({super.key});
  @override
  Widget build(BuildContext context) {
    var languageModel = Provider.of<LanguageModel>(context);
    return
       MaterialApp(
        localizationsDelegates: const [
          S.delegate, // 你的应用本地化代理,就是我们刚刚自己的业务文本的代理
          GlobalMaterialLocalizations.delegate, // Material组件库的本地化,把flutter组件的内置文本比如说对话框的确认/取消文字、日期选择器的月份名称等进行本地化
          GlobalWidgetsLocalizations.delegate, // 基础Widget(如文字方向)的本地化,有些语言的书写方向和很多语言是不同的,比如希伯来语,所以文字方向对于国际化很重要
          GlobalCupertinoLocalizations.delegate, // Cupertino(iOS风格)组件库的本地化,如果在IOS平台上运行没有这个代理,那么可能会因为找不到IOS本地化资源而崩溃
        ],
        supportedLocales: S.delegate.supportedLocales, //支持的本地化列表
        locale: Locale('en'),  // 先上个英语的
        home:  HomePage(), //homepage这里原先是在app输入标题,后续我都把那些东西放在了它自己的文件里面了。
      ); 
  }
}

然后再修改一下homepage.dart里面显示的文本

homepage.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../model/index.dart';
import '../generated/l10n.dart';
class HomePage extends StatefulWidget {
  const HomePage({super.key}); 
  @override
  State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
  int _counter = 0;
  @override
  Widget build(BuildContext context) {
    var themeMode=Provider.of<ThemeModel>(context);
    var languageModel=Provider.of<LanguageModel>(context);
    var s = S.of(context); //获取本地化的业务文本数据
    return Scaffold(
      appBar: AppBar(
        backgroundColor: themeMode.backgroundColor,
        title: Text(s.title), //不带参数的title数据
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(s.text("flutter")),//带参数的要传入参数
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: languageModel.ChangeLanguage,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ), 
    );
  }
}

成功显示了在arb文件中写入的业务文本
在这里插入图片描述

动态切换语言类型

由于文本数据已经被解耦到arb文件中了,所以我们的LanguageModel只需要存储可用语言的列表,当前语言的类型和修改语言的方法了。

LanguageModel.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../generated/l10n.dart';
class LanguageModel with ChangeNotifier{
  LanguageModel(){
    
  }
  
  int  _currentPostion=0;   //当前选择的语言的数组下标
  List<Locale> _supportLocales = S.delegate.supportedLocales;//获取本地化列表
  bool Init(List<Locale> locales){  //提供了一个重新初始化的方法
    if(locales.isEmpty){
      print('locale is empty');
      return true;
    }
    _supportLocales=locales;
    _currentPostion=0;
    return false;
  }
  void setLocale(int position){   //设置当前语言的数组下标
    if(position >= _supportLocales.length){
      print('setLocale fail: position >= supportLocales.length');
      return;
    }
    if(position<0){
      print('setLocale fail: position < 0');
      return;
    }
    _currentPostion=position;
    notifyListeners(); //做出更改,通知使用的widget进行刷新
  }
  void ChangeLanguage(){//一个变化当前语言的测试方法
    if(_currentPostion==0) setLocale(1);
    else setLocale(0);
  }

  Locale get currentLocale => _supportLocales[_currentPostion];
  List<Locale> get supportLocales => _supportLocales; 
}

接下来只要把App中的locale参数变成由provider提供的languageModel的方法就可以了。
app.dart

import 'package:flutter/material.dart';
import 'route/index.dart';
import 'model/index.dart';
import 'package:provider/provider.dart';
import 'generated/l10n.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
class App extends StatelessWidget {
  const App({super.key});
  @override
  Widget build(BuildContext context) {
    var languageModel = Provider.of<LanguageModel>(context);
    return
       MaterialApp(
        ...
        locale: languageModel.currentLocale,
        ...
      ); 
  }
}

最后实现的效果
在这里插入图片描述

总结

主要使用了provider来对数据和视图进行自动绑定,让数据变化反馈到视图中,以及实现了数据业务和视图内容的解耦和数据的跨组件共享。国际化在provider的基础上,使用了intl库对国际化内容代码的自动管理和动态切换。
下一篇Flutter上位机软件部分我会完成主页、设置页和蓝牙设备管理页的功能编写。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐