语言设置
效果如下
实现方法
核心:通过跨组件状态共享provider,更改MaterialAPP的locale属性。
接下来我们以
MaterialApp
类为入口的应用来说明如何支持国际化。大多数应用程序都是通过
MaterialApp
为入口,但根据低级别的WidgetsApp
类为入口编写的应用程序也可以使用相同的类和逻辑进行国际化。MaterialApp
实际上也是WidgetsApp
的一个包装。Flutter 官方鼓励我们在写 Flutter 应用的时候直接从 MaterialApp 开始,原因是 MaterialApp
为我们集成好了很多 Material Design 所必须的控件,如AnimatedThemen、GridPager 等,另外还通过MaterialApp 配置了全局路由,方便进行页面的切换
准备工作:首先需要在pubspec.yaml中添加如下依赖
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
provider: ^4.3.1
默认情况下,Flutter SDK中的组件仅提供美国英语本地化资源(主要是文本)。要添加对其他语言的支持,应用程序须添加一个名为“flutter_localizations”的包依赖,然后还需要在MaterialApp
中进行一些配置。
provider基本用法;1、新建状态管理类。 2、顶层注册状态管理类。3、状态组件绑定 。4、状态变更。
1、第一步
新建一个currentLocale.dart文件创建CurrentLocale model类(即我们要共享的状态)
import 'package:flutter/material.dart';
class CurrentLocale with ChangeNotifier
{
Locale _locale = const Locale('zh','CN');
Locale get value => _locale;
void setLocale(locale)
{
_locale = locale;
notifyListeners();//通知依赖的Widget
}
}
2、第二步
在main.dart文件main方法里注册顶层共享数据。注意导入provider包和新建的currentLocale.dart(时常注意别忘了导入用到的包或类不然会报错)
void main() {
//2、注册顶层共享数据
return runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (context)=>ThemeModel()),
ChangeNotifierProvider(create: (context)=>CurrentLocale())//此是语言状态注册
],
child: MyApp(),
)
);
}
MultiProvider用来注册你需要共享的状态,其中CurrentLocale是语言设置相关的状态是我们需要的,ThemeModel是主题设置的状态,上一篇主题设置blog有说,此处不管它,可以新建一个themeModel类,也可以删了它,如果你也想了解主题推荐新建一个然后可以看下上篇文章主题设置。
import 'package:flutter/material.dart';
/*1.创建theme数据Model
这里的 Model 实际上就是我们的状态,它不仅储存了我们的数据模型,而且还包含了更改数据的方法,并暴露出它想要暴露出的数据
*/
class ThemeModel with ChangeNotifier {
String _theme = 'blue';
String get value => _theme;
void setTheme(color) async
{
_theme = color;
print(_theme);
notifyListeners();
}
}
3、第三步
实现Localizations,我们需要实现两个类:一个Localizations
类一个Delegate
类,其中Localizations
类中主要实现提供了本地化值(即你需要国际化翻译的那些数据),
定义完 Localizations
以后,我们就需要想这么一个问题,这个类是谁负责初始化呢?答案自然不是我们自己主动去初始化,而是需要一个Delegate
的类来完成,Delegate
是一个抽象类,需要我们去实现它
新建localizations.dart文件,创建一个Localizations类-DemoLocalizations
import 'package:flutter/material.dart';
class DemoLocalizations {
final Locale locale;
DemoLocalizations(this.locale);
//本demo只对四个文本进行了国际化,包括主界面标题...
static Map<String, Map<String, String>> _localizedValues = {
'en': {
'task title': 'Yshow',
'set title': 'Setting',
'theme set': 'Theme Settings',
'language set':'Language Settings'
},
'zh': {
'task title': '易米八一',
'set title': '设置',
'theme set': '主题设置',
'language set':'语言设置'
}
};
get taskTitle{
return _localizedValues[locale.languageCode]['task title'];
}
get setTitle{
return _localizedValues[locale.languageCode]['set title'];
}
get themeSet{
return _localizedValues[locale.languageCode]['theme set'];
}
get langSet{
return _localizedValues[locale.languageCode]['language set'];
}
// 这个特殊的Localizations.of()表达式会经常使用,这样可以直接调用便捷方法
static DemoLocalizations of(BuildContext context) {
return Localizations.of<DemoLocalizations>(context, DemoLocalizations);
}
}
DemoLocalizations
中会根据当前的语言来返回不同的文本,如title
,我们可以将所有需要支持多语言的文本都在此类中定义。DemoLocalizations
的实例将会在Delegate类的load
方法中创建。
新建delegate.dart文件,创建一个delegate类-
DemoLocalizationsDelegate
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'localizations.dart';
class DemoLocalizationsDelegate extends LocalizationsDelegate<DemoLocalizations>{
const DemoLocalizationsDelegate();
@override
bool isSupported(Locale locale) {
return ['en','zh'].contains(locale.languageCode);
}
@override
Future<DemoLocalizations> load(Locale locale) {
return new SynchronousFuture<DemoLocalizations>(new DemoLocalizations(locale));
}
@override
bool shouldReload(LocalizationsDelegate<DemoLocalizations> old) {
return false;
}
static DemoLocalizationsDelegate delegate = const DemoLocalizationsDelegate();
}
shouldReload
的返回值决定当Localizations组件重新build时,是否调用load
方法重新加载Locale资源。一般情况下,Locale资源只应该在Locale切换时加载一次,不需要每次在Localizations
重新build时都加载,所以返回false
即可。可能有些人会担心返回false
的话在APP启动后用户再改变系统语言时load
方法将不会被调用,所以Locale资源将不会被加载。事实上,每当Locale改变时Flutter都会再调用load
方法加载新的Locale,无论shouldReload
返回true
还是false
。
4、第四步
关键的一步
首先是在main.dart文件中的MyApp类中将语言状态CurrentLocale与locale绑定,MaterialApp的locale的值默认是手机系统语言设置,我们将其与我们的currentLocale状态绑定来控制它。
然后指定MaterialApp
的localizationsDelegates
和supportedLocales
:
localizationsDelegates
列表中的元素是生成本地化值集合的工厂。GlobalMaterialLocalizations.delegate
为Material 组件库提供的本地化的字符串和其他值,它可以使Material 组件支持多语言。GlobalWidgetsLocalizations.delegate
定义组件默认的文本方向,从左到右或从右到左,这是因为有些语言的阅读习惯并不是从左到右,比如如阿拉伯语就是从右向左的。
supportedLocales
也接收一个Locale数组,表示我们的应用支持的语言列表,在本例中我们的应用只支持美国英语和中文简体两种语言。
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
//3、状态组件绑定,将语言状态与组件绑定
return Consumer2<ThemeModel,CurrentLocale>( //语言设置1:语言状态获取方式,暂时只看CurrentLocale
builder: (context,themeModel,currentLocale,child)
{
return MaterialApp(
localizationsDelegates: [
// 本地化的代理类
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
DemoLocalizationsDelegate.delegate
],
locale: currentLocale.value,//语言设置2
supportedLocales: [
const Locale('zh', 'CN'), // 中文简体
const Locale('en', 'US'), // 美国英语
//其它Locales
],
theme: AppTheme.getThemeData(themeModel.value),
onGenerateTitle: (context){
return DemoLocalizations.of(context).taskTitle;
},
initialRoute: welcomeRoute,//欢迎页
routes: routes //注册路由
);
},
);
}
}
关键是语言设置标注的两处,Consumer2是获取你注册的状态的一种方式,因为还有一个主题设置状态所以是2。如果你上面选择的是删除,没有新建一个themeModel那这里要改成Consumer,如下
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
//3、状态组件绑定,将语言状态与组件绑定
return Consumer<CurrentLocale>( //语言设置1:语言状态获取方式
builder: (context,currentLocale,child)
{
return MaterialApp(
localizationsDelegates: [
// 本地化的代理类
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
DemoLocalizationsDelegate.delegate
],
locale: currentLocale.value,//语言设置2
supportedLocales: [
const Locale('zh', 'CN'), // 中文简体
const Locale('en', 'US'), // 美国英语
//其它Locales
],
onGenerateTitle: (context){
return DemoLocalizations.of(context).taskTitle;
},
initialRoute: welcomeRoute,//欢迎页
routes: routes //注册路由
);
},
);
}
}
然后通过locale我们可以手动指定APP的locale
我们现在需要先注册DemoLocalizationsDelegate
类,然后在需要国际化文本的地方通过DemoLocalizations.of(context)
来动态获取当前Locale文本。如上面的title,其它需要国际化的地方都通过此方法获取对应文本。
这是设置页面
appBar: AppBar(
title:Text(DemoLocalizations.of(context).setTitle),
centerTitle: true,
),
此处DemoLocalizations.of(context).setTitle调用的就是DemoLocalizations里的文本
最后只需要在MaterialApp的localizationsDelegates
列表中添加我们的Delegate实例即可完成注册
常见问题:为什么对title国际化要使用onGenerateTitle?
MaterialApp
有一个title
属性,用于指定APP的标题。在Android系统中,APP的标题会出现在任务管理器中。所以也需要对title
进行国际化。但是问题是很多国际化的配置都是在MaterialApp
上设置的,我们无法在构建MaterialApp
时通过Localizations.of
来获取本地化资源,所以我们需要设置一个onGenerateTitle
回调。在我下面贴出的参考文章中有详细解释。
5、第五步
在你设置语言的地方调用另一种获取状态的方式Provider.of(context)调用内部设置方法改变语言状态。比如我是在设置页面
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../provider/themeModel.dart';
import '../provider/locak_storage.dart';
import '../provider/currentLocale.dart';
import '../intl/localizations.dart';
class Set extends StatefulWidget {
@override
_SetState createState() => _SetState();
}
class _SetState extends State<Set> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title:Text(DemoLocalizations.of(context).setTitle),
centerTitle: true,
),
body: ListView(
children: [
InkWell(
child:Card(
child: ListTile(
title: Text(DemoLocalizations.of(context).themeSet),
trailing: Icon(Icons.arrow_right),
),
),
onTap: () async
{
int i = await showDialog<int>(
context: context,
builder: (BuildContext context) {
return SimpleDialog(
title: const Text('请选择主题'),
children: <Widget>[
SimpleDialogOption(
onPressed: () {
// 返回1
Navigator.pop(context, 1);
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 6),
child: const Text('蓝色'),
),
),
SimpleDialogOption(
onPressed: () {
// 返回2
Navigator.pop(context, 2);
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 6),
child: const Text('紫色'),
),
),
SimpleDialogOption(
onPressed: () {
// 返回2
Navigator.pop(context, 3);
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 6),
child: const Text('粉色'),
),
),
SimpleDialogOption(
onPressed: () {
// 返回2
Navigator.pop(context, 4);
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 6),
child: const Text('深粉'),
),
),
],
);
});
if (i != null) {
if(i==1)
{
Provider.of<ThemeModel>(context,listen: //更改主题false).setTheme('blue');
}
else if(i==2)
{
Provider.of<ThemeModel>(context,listen: false).setTheme('purple');
}
else if(i==3)
{
Provider.of<ThemeModel>(context,listen: false).setTheme('pink');
}
else if(i==4)
{
Provider.of<ThemeModel>(context,listen: false).setTheme('deeppink');
}
}
},
),
InkWell(
child:Card(
child: ListTile(
title: Text(DemoLocalizations.of(context).langSet),
trailing: Icon(Icons.arrow_right),
),
),
onTap: () async
{
int i = await showDialog<int>(
context: context,
builder: (BuildContext context) {
return SimpleDialog(
title: const Text('请选择语言'),
children: <Widget>[
SimpleDialogOption(
onPressed: () {
// 返回1
Navigator.pop(context, 1);
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 6),
child: const Text('中文简体'),
),
),
SimpleDialogOption(
onPressed: () {
// 返回2
Navigator.pop(context, 2);
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 6),
child: const Text('美国英语'),
),
),
],
);
});
if (i != null) {
if(i==1)
{
Provider.of<CurrentLocale>(context,listen: false).setLocale(const Locale('zh',"CH"));//更改语言
}
else
{
Provider.of<CurrentLocale>(context,listen: false).setLocale(const Locale('en',"US"));
}
}
},
)
],
),
);
}
}
参考文章:(更多专业的理论解释可通过下面两个优秀文章理解,此篇博客着重实现效果)
https://book.flutterchina.club/chapter13/multi_languages_support.html
https://www.jianshu.com/p/8356a3bc8f6c
源码地址
https://github.com/Yimi81/Flutter-ThemeAndLang