毕业设计——基于LLM控制决策的机械臂 (Flutter上位机软件部分2——切换软件语言、主题)
本文介绍了如何在Flutter项目中实现语言和主题切换功能。通过Provider库实现数据和视图的自动绑定,创建ThemeModel类管理主题颜色,使用ChangeNotifierProvider在顶层提供全局数据。同时演示了简单的国际化实现思路,通过LanguageModel管理多语言文本。文章包含代码示例和实现效果展示,展示了点击按钮动态切换主题颜色的功能。
上一篇文章初始化了项目,本文来实现语言和主题的切换功能。
如何让数据更新反馈到视图中
先让我们回顾一下上一篇的计数器例程的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上位机软件部分我会完成主页、设置页和蓝牙设备管理页的功能编写。
更多推荐



所有评论(0)