Flutter 多语言、主题切换之GetX库
多语言、主题切换之GetX库
- 前言
- 正文
- 一、配置项目
- 二、模拟UI
- 三、语言配置
- ① 常量键
- ② 语言配置文件
- ③ 配置
- 四、持久化
- 五、切换语言
- ① my_home.dart
- ② home.dart
- ③ mine_controller.dart
- ④ language_setting_controller.dart
- ⑤ language_setting.dart
- ⑥ mine.dart
- 六、切换主题
- ① 配置文件
- ② 更改主题
- 七、源码
前言
关于GetX库前面我们讲述了状态管理的使用,实际上GetX是非常强大的,功能很多,本篇文章中我们将介绍GetX的多语言切换和主题切换等功能。
正文
为了让你更清晰的知道,这里我会结合实际开发中的一些操作方式和使用方式,让你可以更好用在自己的项目上。
一、配置项目
首先创建项目
然后好之后打开pubspec.yaml文件,在dependencies下面添加一个get:,如下图所示:
然后点击窗口上的Pub get,已经依赖下载,如下图所示:
这里的依赖已经下载好了,项目配置完成,下面开始写代码。
二、模拟UI
做戏做全套,为了让你感觉这是一个实际的项目我们就按照实际的项目的UI来做,让你身临其境,只不过缺点就是我需要写一部分与标题内容无关的UI代码,见谅。
先说说我们要做什么,首先我们需要做一个底部导航栏,里面有两项内容,首页和我的,首页我们可以什么都不写,而我的里面你可以直接将多语言的设置加在里面亦或者是再加一层进去,弄一个设置页面,在设置页面中设置多语言。
那么下面我们首先来写首页,在lib下创建一个home文件夹,然后里面创建一个home.dart,代码如下所示:
import 'package:flutter/material.dart'; class Home extends StatelessWidget { const Home({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('主页'), ), body: Center( child: Text('主页'), )); } }
代码非常简单,就一个标题和文字内容,下面我们创建另一个mine文件夹,里面创建一个mine.dart,代码如下所示:
import 'package:flutter/material.dart'; class Mine extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('我的'), ), body: Center( child: Text('我的'), )); } }
然后再创建一个,my_home文件夹,里面创建一个my_home_controller.dart
import 'package:get/get.dart'; class MyHomeController extends GetxController { var currentIndex = 0.obs; void changeIndex(int index) { currentIndex.value = index; } }
这里面就是对于当前切换页面的记录和更新,下面在my_home文件夹下创建一个my_home.dart,里面装载我们前面写好的两个页面,完成切换的工作,代码如下所示:
import 'package:flutter/material.dart'; import 'package:get/get.dart'; import '../home/home.dart'; import '../mine/mine.dart'; import 'my_home_controller.dart'; class MyHomePage extends StatelessWidget { MyHomePage({super.key}); final controller = Get.put(MyHomeController()); @override Widget build(BuildContext context) { return Scaffold( body: Obx(() { switch (controller.currentIndex.value) { case 0: return const Home(); case 1: return Mine(); default: return const Home(); } }), bottomNavigationBar: Obx(() => BottomNavigationBar( currentIndex: controller.currentIndex.value, onTap: (index) => controller.changeIndex(index), items: const [ BottomNavigationBarItem(icon: Icon(Icons.home), label: '首页'), BottomNavigationBarItem(icon: Icon(Icons.person), label: '我的'), ])), ); } }
上面属于GetX的常规使用,然后就是在body中根据当前的item下标来确定显示什么内容,底部的bottomNavigationBar中的内容也是如此,onTap表示点击item是的index,items里面就是对应的Item了,目前来说我们没有使用多语言的,只是做了UI,最后一步就是修改main.dart,装载我们写好的这个MyHomePage ,代码如下所示:
import 'package:flutter/material.dart'; import 'my_home/my_home.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), useMaterial3: true, ), home: MyHomePage(), ); } }
下面我们运行一下:
切换一下底部的导航栏,没有问题的话我们就可以来写这个多语言切换了。
三、语言配置
在lib下创建一个language文件夹,文件夹下创建一个local.dart文件,里面代码如下所示:
① 常量键
class Local { static const String home = 'home';//首页 static const String mine = 'mine';//我的 static const String languageSetting = 'languageSet';//多语言设置 static const String followerSystemLanguage = 'followerSystemLanguage';//跟随系统语言 static const String simplifiedChinese = 'simplifiedChinese';//简体中文 }
这里代码很简单就是一些常量,用来确定我们需要引用的文字资源键。
② 语言配置文件
下面我们在language目录下创建一个messages.dart文件,里面代码如下所示:
import 'package:get/get.dart'; import 'local.dart'; class Messages extends Translations { @override Map get keys => { 'zh_CN': { Local.home: '主页', Local.mine: '我的', Local.languageSetting: '多语言设置', Local.followerSystemLanguage: '跟随系统语言', Local.simplifiedChinese: '简体中文', }, 'en_US': { Local.home: 'Home', Local.mine: 'Mine', Local.languageSetting: 'Multilingual setup', Local.followerSystemLanguage: 'Follower system Language', Local.simplifiedChinese: 'Simplified Chinese', }, }; }
通过使用继承Get的Translations ,进行键值的切换,zh_CN是中文,en_US是英文,里面通过键获取对应的值,那么这一部分我们就写好了,后续如果有新的字符添加进来就依葫芦画瓢。
③ 配置
最后一步就是配置进去了,打开main.dart文件,修改如下所示:
import 'package:flutter/material.dart'; import 'language/messages.dart'; import 'my_home/my_home.dart'; import 'package:get/get.dart'; void main() async { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return GetMaterialApp( title: 'Flutter Demo', translations: Messages(), locale: Get.deviceLocale, fallbackLocale: Locale("zh", "CN"), theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), useMaterial3: true, ), home: MyHomePage(), ); } }
这里首先将MaterialApp改成了GetMaterialApp,然后配置相关的属性值如下所示:
translations: Messages(), locale: Get.deviceLocale, fallbackLocale: Locale("zh", "CN"),
注意这里的locale: Get.deviceLocale,,这表示当前的语言环境是跟随系统语言的,后面这里还需要更改的,因为我们需要做持久化,假如我们支持3钟设置:跟随系统语言、中文、英文。当我们第一次打开App时,默认是跟随系统语言,而我们切换为英文之后再重新打开App,发现没有变化,这是因为我们没有更改这个locale的属性值,因此就涉及到持久化存储了,你想到了什么呢?我想到了Android的SP,之前我们介绍过Hive,这里我也将使用Hive。
四、持久化
下面我们首先在pubspec.yaml中增加配置如下所示:
然后点击Pub get,下载依赖。下载好之后,在lib下创建一个app_box.dart文件,代码如下所示:
import 'package:hive_flutter/hive_flutter.dart'; class AppBox { static final AppBox shared = AppBox(); // 声明盒子 final _box = Hive.box('appBox'); // 语言 int get language => _box.get('language') ?? 0; set language(int value) => _box.put('language', value); // 主题 int get theme => _box.get('theme') ?? 0; set theme(int value) => _box.put('theme', value); }
是不是感觉代码很熟悉呢?下面我们就要配置一下这个appBox,否则会报错,在main.dart中配置,代码如下所示:
import 'package:flutter/material.dart'; import 'package:hive_flutter/hive_flutter.dart'; import 'package:study_language_theme/app_box.dart'; import 'language/messages.dart'; import 'my_home/my_home.dart'; import 'package:get/get.dart'; void main() async { await Hive.initFlutter(); await Hive.openBox('appBox'); runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); Locale? getLocale() { Locale? appLocale; switch (AppBox.shared.language) { case 0: //跟隨系統 appLocale = Get.deviceLocale; break; case 1: //简体中文 appLocale = const Locale('zh', 'CN'); break; case 2: //English appLocale = const Locale('en', 'US'); break; default: appLocale = Get.deviceLocale; break; } return appLocale; } @override Widget build(BuildContext context) { return GetMaterialApp( title: 'Flutter Demo', translations: Messages(), locale: getLocale(), fallbackLocale: Locale("zh", "CN"), theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), useMaterial3: true, ), home: MyHomePage(), ); } }
首先在main()函数中进行Hive的初始化配置和盒子的配置,同时增加了一个getLocale()函数,在里面就根据键获取对应的语言设置下标,根据下标设置语言模式,最后再改变locale: getLocale(),这样就完成了持久化的处理,但是还没有全部完成,这里是取值,我们还需要存值,存值在哪里?就在设置语言哪里,下面来写这一步。
五、切换语言
① my_home.dart
现在为了是我们的切换语言生效,在我们之前直接使用字符串的地方,现在就需要更改为Local中的键了,首先我们修改一下my_home.dart中的代码,如下图所示:
这里就将,“首页”改成了Local.home.tr,这里的tr就是用于切换语言后自动改变的,记得要加上去,通过如果加了tr,则对应的外部组件不能使用const关键字,请注意。
② home.dart
下面再改动一下home.dart
③ mine_controller.dart
最后我们修改mine,在此之前先在mine中增加一个mine_controller.dart,代码如下:
import 'package:get/get.dart'; import 'package:study_language_theme/app_box.dart'; import 'package:study_language_theme/language/local.dart'; import 'package:flutter/widgets.dart'; class MineController extends GetxController with WidgetsBindingObserver { final languageStr = Local.followerSystemLanguage.tr.obs; RxInt languageIndex = 0.obs; @override void onInit() { super.onInit(); WidgetsBinding.instance.addObserver(this); getLanguage(); } @override onClose() { super.onClose(); WidgetsBinding.instance.removeObserver(this); } @override void didChangeAppLifecycleState(AppLifecycleState state) { super.didChangeAppLifecycleState(state); getLanguage(); } //获取语言 void getLanguage() { switch (AppBox.shared.language) { case 0: languageStr.value = Local.followerSystemLanguage.tr; break; case 1: languageStr.value = Local.simplifiedChinese.tr; break; case 2: languageStr.value = 'English'; break; default: languageStr.value = Local.followerSystemLanguage.tr; break; } } //切换语言 void changeLanguage(int index) { languageIndex.value = index; } }
这里通过Getx处理页面显示的语言设置项目,切换语言和获取语言。
④ language_setting_controller.dart
在lib下新建一个settings文件夹,里面创建一个用于切换语言的language_setting_controller.dart,代码如下所示:
import 'package:flutter/widgets.dart'; import 'package:get/get.dart'; import 'package:study_language_theme/app_box.dart'; import '../language/local.dart'; class LanguageSettingsController extends GetxController { final currentLanguageIndex = 0.obs; List languageList = [ Local.followerSystemLanguage.tr, '简体中文', 'English', ]; @override void onInit() { super.onInit(); currentLanguageIndex.value = AppBox.shared.language; } ///切换语言 void changeLanguage(int languageIndex) { Locale? appLocale; switch (languageIndex) { case 0: //跟隨系統 appLocale = Get.deviceLocale; break; case 1: //简体中文 appLocale = const Locale('zh', 'CN'); break; case 2: //English appLocale = const Locale('en', 'US'); break; default: appLocale = Get.deviceLocale; break; } //保存到本地 AppBox.shared.language = languageIndex; Get.updateLocale(appLocale!); } }
在changeLanguage()函数中,保存切换的语言下标,然后更新语言配置。
⑤ language_setting.dart
在settings下创建一个language_setting.dart,代码如下所示:
import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:study_language_theme/language/local.dart'; import 'language_setting_controller.dart'; ///语言设置页面 class LanguageSettingPage extends StatelessWidget { LanguageSettingPage({Key? key}) : super(key: key); final controller = Get.put(LanguageSettingsController()); @override Widget build(BuildContext context) { return Scaffold( backgroundColor: const Color(0xFFF7F9FF), appBar: AppBar( centerTitle: true, title: Text( Local.languageSetting.tr, style: const TextStyle(color: Colors.black), ), backgroundColor: Colors.white, leading: const CupertinoNavigationBarBackButton(color: Colors.black)), body: Container( padding: const EdgeInsets.only( top: 10, ), child: ListView.separated( itemBuilder: (context, index) { return Obx(() => Container( padding: const EdgeInsets.only(left: 16, right: 16), color: Colors.white, height: 50, child: InkWell( onTap: () { controller.changeLanguage(index); Get.back(result: index); }, child: Row( children: [ Expanded(child: Text(controller.languageList[index])), Visibility( visible: controller.currentLanguageIndex.value == index, child: const Icon(Icons.check_rounded, color: Colors.blue)) ], ), ), )); }, separatorBuilder: (context, index) { return Container( height: 2, color: Colors.white70, ); }, itemCount: controller.languageList.length), ), ); } }
在这个语言设置页面中,主要的内容就是一个列表,用于点击Item切换语言项,切换之后返回上一个页面,同时传值过去,
⑥ mine.dart
最后我们改动一下mine.dart,如下所示:
import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:study_language_theme/language/local.dart'; import 'package:study_language_theme/mine/mine_controller.dart'; import '../settings/language_setting.dart'; class Mine extends StatelessWidget { Mine({super.key}); final controller = Get.put(MineController()); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(Local.mine.tr), ), body: Container( color: Colors.white54, padding: const EdgeInsets.only(top: 20), child: Container( padding: const EdgeInsets.all(16), color: Colors.white, child: Row( children: [ Expanded( child: Text(Local.languageSetting.tr), ), InkWell( onTap: () { Get.to(() => LanguageSettingPage())?.then((value) { controller.getLanguage(); }); }, child: Row( children: [ Text(controller.languageStr.value, style: const TextStyle(fontSize: 12),), const Icon(Icons.chevron_right) ], ), ) ], ), ))); } }
到此为止,我们的代码就改的差不多了,下面就可以运行了,效果如下图所示:
六、切换主题
切换主题相对来说,简单很多。
① 配置文件
首先我们在lib下创建一个custom_theme.dart,里面代码如下:
import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; ///浅色模式 ThemeData lightTheme = ThemeData.light().copyWith( primaryColor: Colors.blue, splashColor: Colors.white12, appBarTheme: AppBarTheme( systemOverlayStyle: SystemUiOverlayStyle.dark, elevation: 0, backgroundColor: ThemeData.light().scaffoldBackgroundColor, iconTheme: const IconThemeData(color: Colors.black), ), dividerColor: Colors.white38, scaffoldBackgroundColor: ThemeData.light().scaffoldBackgroundColor, backgroundColor: Colors.white, iconTheme: const IconThemeData( color: Colors.black, ), bottomNavigationBarTheme: const BottomNavigationBarThemeData( selectedItemColor: Colors.black, unselectedItemColor: Colors.grey ), ); ///深色模式 ThemeData darkTheme = ThemeData.dark().copyWith( appBarTheme: AppBarTheme( systemOverlayStyle: SystemUiOverlayStyle.light, elevation: 0, backgroundColor: ThemeData.dark().scaffoldBackgroundColor, iconTheme: const IconThemeData(color: Colors.white), ), dividerColor: Colors.black38, scaffoldBackgroundColor: ThemeData.dark().scaffoldBackgroundColor, backgroundColor: Colors.black, iconTheme: const IconThemeData( color: Colors.white, ), bottomNavigationBarTheme: const BottomNavigationBarThemeData( selectedItemColor: Colors.white, unselectedItemColor: Colors.white24 ), );
这里面定义了浅色模式和深色模式两种,这也是现在App大部分会做的功能,里面定义了标题栏、脚手架背景、图标主题、底部导航栏在不同模式下的颜色设置。
然后我们去修改main.dart中的内容:
@override Widget build(BuildContext context) { return GetMaterialApp( title: 'Flutter Demo', translations: Messages(), locale: getLocale(), fallbackLocale: Locale("zh", "CN"), theme: lightTheme, darkTheme: darkTheme, themeMode: AppBox.shared.theme == 0 ? MediaQuery.of(context).platformBrightness == Brightness.dark ? ThemeMode.dark : ThemeMode.light : AppBox.shared.theme == 1 ? ThemeMode.light : ThemeMode.dark, home: MyHomePage(), ); }
主要是修改themeMode的值,记得导包import 'custom_theme.dart';。
② 更改主题
然后在settings下创建对应更换主题页面,首先我们创建一个theme_setting_controller.dart,里面代码如下所示:
import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:study_language_theme/app_box.dart'; import 'package:study_language_theme/custom_theme.dart'; import '../language/local.dart'; class ThemeSettingsController extends GetxController { final currentThemeIndex = 0.obs; List themeList = [ Local.followerSystemTheme.tr, Local.lightMode.tr, Local.darkMode.tr, ]; @override void onInit() { super.onInit(); currentThemeIndex.value = AppBox.shared.theme; } ///切换主题 void changeTheme(BuildContext context, int themeIndex) { ThemeData themeData; switch (themeIndex) { case 0: //跟隨系統 themeData = MediaQuery.of(context).platformBrightness == Brightness.dark ? darkTheme : lightTheme; break; case 1: //浅色模式 themeData = lightTheme; break; case 2: //深色模式 themeData = darkTheme; break; default: themeData = MediaQuery.of(context).platformBrightness == Brightness.dark ? darkTheme : lightTheme; break; } //保存到本地 AppBox.shared.theme = themeIndex; Get.changeTheme(themeData); } }
和切换语言没有什么太大的区别,同样最后使用 Get.changeTheme切换主题,下面我们要写页面了,在settings下创建theme_setting.dart,里面代码如下所示:
import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:study_language_theme/language/local.dart'; import 'theme_setting_controller.dart'; ///主题设置页面 class ThemeSettingPage extends StatelessWidget { ThemeSettingPage({Key? key}) : super(key: key); final controller = Get.put(ThemeSettingsController()); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( centerTitle: true, title: Text( Local.themeSetting.tr, ),), body: Container( padding: const EdgeInsets.only( top: 10, ), child: ListView.separated( itemBuilder: (context, index) { return Obx(() => Container( padding: const EdgeInsets.only(left: 16, right: 16), height: 50, child: InkWell( onTap: () { controller.changeTheme(context,index); Get.back(result: index); }, child: Row( children: [ Expanded(child: Text(controller.themeList[index])), Visibility( visible: controller.currentThemeIndex.value == index, child: const Icon(Icons.check_rounded, color: Colors.blue)) ], ), ), )); }, separatorBuilder: (context, index) { return const Divider(); }, itemCount: controller.themeList.length), ), ); } }
这里的代码和之前的设置语言页面差不多,区别就是我将所有的背景颜色都去掉了,这样才能时候切换后的效果切换,之前用到背景颜色的地方你都需要更改一下,最后我们修改一下mine_controller.dart和mine.dart的代码,先是mine_controller.dart,代码如下所示:
import 'package:get/get.dart'; import 'package:study_language_theme/app_box.dart'; import 'package:study_language_theme/language/local.dart'; import 'package:flutter/widgets.dart'; class MineController extends GetxController with WidgetsBindingObserver { final languageStr = Local.followerSystemLanguage.tr.obs; final themeStr = Local.followerSystemTheme.tr.obs; @override void onInit() { super.onInit(); WidgetsBinding.instance.addObserver(this); getLanguage(); getTheme(); } @override onClose() { super.onClose(); WidgetsBinding.instance.removeObserver(this); } @override void didChangeAppLifecycleState(AppLifecycleState state) { super.didChangeAppLifecycleState(state); getLanguage(); } @override void didChangePlatformBrightness() { super.didChangePlatformBrightness(); getTheme(); } //获取语言 void getLanguage() { switch (AppBox.shared.language) { case 0: languageStr.value = Local.followerSystemLanguage.tr; break; case 1: languageStr.value = Local.simplifiedChinese.tr; break; case 2: languageStr.value = 'English'; break; default: languageStr.value = Local.followerSystemLanguage.tr; break; } } //获取主题 void getTheme() { switch (AppBox.shared.theme) { case 0: themeStr.value = Local.followerSystemTheme.tr; break; case 1: themeStr.value = Local.lightMode.tr; break; case 2: themeStr.value = Local.darkMode.tr; break; default: themeStr.value = Local.followerSystemTheme.tr; break; } } }
相比之前增加了获取最新主题的文字描述,切换主题后改变文字描述,然后修改mine_controller.dart,代码如下所示:
import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:study_language_theme/language/local.dart'; import 'package:study_language_theme/mine/mine_controller.dart'; import 'package:study_language_theme/settings/theme_setting.dart'; import '../settings/language_setting.dart'; class Mine extends StatelessWidget { Mine({super.key}); final controller = Get.put(MineController()); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(Local.mine.tr), ), body: Container( padding: const EdgeInsets.only(top: 20), child: Container( padding: const EdgeInsets.all(16), child: Column(children: [ Row( children: [ Expanded( child: Text(Local.languageSetting.tr), ), InkWell( onTap: () { Get.to(() => LanguageSettingPage())?.then((value) { controller.getLanguage(); }); }, child: Row( children: [ Obx(() => Text(controller.languageStr.value, style: const TextStyle(fontSize: 12),)), const Icon(Icons.chevron_right) ], ), ) ], ), SizedBox(height: 10,), Row( children: [ Expanded( child: Text(Local.themeSetting.tr), ), InkWell( onTap: () { Get.to(() => ThemeSettingPage())?.then((value) { controller.getTheme(); }); }, child: Row( children: [ Obx(() => Text(controller.themeStr.value, style: const TextStyle(fontSize: 12),),), const Icon(Icons.chevron_right) ], ), ) ], ) ],), ))); } }
到这里为止,我们就修改好了,现在我们运行一下看看效果。
本文就写到这里了,如果你发现文章代码达不到效果图的效果,那么应该是没有贴完整,那么你可以通过源码去进行对照修改。
七、源码
源码地址:study_language_theme