flutter国际化、主题配置、视频播放器UI、扫码功能、水波纹问题
1、国际化
1.1、 使用到的插件如下,添加到pubspec.yaml中,vscode会自动下载,你也可以通过命令下载flutter pub get
。
dart
# 国际化支持
flutter_localizations:
sdk: flutter
intl: ^0.19.0
1.2、安装编译器插件 Flutter Intl(Android Studio安装也是直接搜就可以,不习惯用Android Studio就懒得试了)。
1.3、执行命令,创建必要文件。
vscode 点击顶部输入框输入命令
输入 >Flutter Intl: Initialize
后点击回车,等待初始化完成
初始化成功会默认创建如下两个文件夹
generated文件夹不需要修改
l10n文件夹默认只有 intl_en.arb 文件,即默认英文
通过命令添加其它语言 >Flutter Intl: Add locale
,回车继续输入 zh_CN
回车确认即可
执行完命令就会创建新的 arb文件
1.4、使用
- .arb文件内容,.arb文件内容和 json 格式一致。以 key:value 的形式存储。
- 在需要使用的地方通过
S.of(context).home
即可拿到,注意导入相关的包和确保context
能安全的拿到。
json
{
"home": "Home",
"setting": "Setting",
"video": "Video",
"food": "Food",
"internationalization": "Internationalization",
"theme": "Theme",
"icon": "Icon",
"icons": "Icons",
"about": "About",
"login": "Login"
}
1.5、结合 shared_preferences 持久化存储和 provider 状态管理实现持久化和实时更新。
- 创建 localization_info.dart 文件:
- provider用法可以去官网查一下,视频播放器UI封装时就介绍过了,这里就懒得介绍,直接看实现。
dart
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
class LocalizationInfo with ChangeNotifier {
String _localeKey = 'zh_CN';
String get localeKey => _localeKey;
// 初始化
LocalizationInfo() {
initLanguage();
}
// 设置语言
void setLocaleKey(String locale) async {
final prefs = await SharedPreferences.getInstance();
_localeKey = locale;
prefs.setString('_localeKey', locale);
notifyListeners(); // 通知监听者
}
// 初始化语言
void initLanguage() async {
final prefs = await SharedPreferences.getInstance();
_localeKey = prefs.getString('_localeKey') ?? 'zh_CN';
notifyListeners();
}
}
- 注入 provider,在main.dart中注入。
- 同时,直接在 main.dart中使用。
_getLocale
方法通过不同的值返回不一样的 Locale。_getLocale
封装的比较简单粗暴,可自行封装成通用的方法。
dart
Locale _getLocale(String localeKey) {
switch (localeKey) {
case 'zh_CN':
return const Locale('zh', 'CN');
case 'en':
return const Locale('en');
default:
return const Locale('zh', 'CN');
}
}
- 创建一个
localizations_page.dart
页面来设置语言
dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:wallpaper/components/appbar_base.dart';
import 'package:wallpaper/generated/l10n.dart';
import 'package:wallpaper/themes/localization_info.dart';
class LocalizationsPage extends StatefulWidget {
const LocalizationsPage({super.key});
@override
State<LocalizationsPage> createState() => _LocalizationsPageState();
}
// ignore: camel_case_types
class localItem {
final String title;
final String localeKey;
const localItem(this.title, this.localeKey);
}
class _LocalizationsPageState extends State<LocalizationsPage> {
@override
Widget build(BuildContext context) {
final localization = Provider.of<LocalizationInfo>(context);
List<localItem> localList = [
const localItem('中文', 'zh_CN'),
const localItem('English', 'en'),
];
return Scaffold(
appBar: AppbarBase(title: S.of(context).internationalization),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Flex(
direction: Axis.vertical,
spacing: 8,
children: [
for (var item in localList)
Material(
color: Theme.of(context).colorScheme.primaryContainer,
borderRadius: BorderRadius.circular(8),
clipBehavior: Clip.antiAlias,
child: ListTile(
title: Text(item.title),
// 右侧图标
trailing: localization.localeKey == item.localeKey
? Icon(
Icons.check,
color: Theme.of(context).colorScheme.primary,
)
: null,
onTap: () {
localization.setLocaleKey(item.localeKey);
},
)),
],
),
),
);
}
}
- 只要通过
localization.setLocaleKey()
设置语言就会通知main.dart
重新加载语言,并且会做持久化处理,下次打开软件时会自动加载设置好的语言。
2、主题
- 主题和国际化配置基本上如出一辙,都是切换、持久存储、实时更新。
- 使用到的插件也都是 shared_preferences 和 provider , 这两个非常好用和简单、建议学一下。
自定义主题文件 theme.dart
,colorScheme
还有很多属性都能自定义,可以自己查一下,基本上下面这些属性已经完全够用了,我们可以通过 Theme.of(context).colorScheme.primaryContainer
使用定义好的颜色。
dart
import 'package:flutter/material.dart';
ThemeData lightMode = ThemeData(
brightness: Brightness.light,
colorScheme: ColorScheme.light(
surface: Colors.grey.shade200,
onSurface: Colors.grey.shade900,
primary: const Color.fromARGB(255, 0, 94, 255),
primaryContainer: Colors.white,
secondary: Colors.grey.shade300,
inversePrimary: Colors.grey.shade800,
shadow: const Color.fromARGB(112, 80, 80, 80),
),
);
ThemeData darkMode = ThemeData(
brightness: Brightness.dark,
colorScheme: ColorScheme.dark(
surface: Colors.grey.shade900,
onSurface: Colors.grey.shade100,
primary: Colors.deepPurpleAccent,
primaryContainer: const Color.fromARGB(255, 23,23,23),
secondary: const Color.fromARGB(107, 66, 66, 66),
inversePrimary: Colors.grey.shade200,
shadow: const Color.fromARGB(62, 75, 75, 75)),
);
创建 theme_provider.dart
文件设置主题,里面注释已经很详细了,也没啥难的逻辑,我就直接介绍怎么使用了。
dart
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:wallpaper/themes/theme.dart';
class ThemeProvider extends ChangeNotifier {
ThemeData _themeData = lightMode;
bool _followSystem = false;
bool _isInitialized = false;
VoidCallback? _brightnessListener;
ThemeData get themeData => _themeData;
bool get isDarkMode => _themeData == darkMode;
bool get followSystem => _followSystem;
ThemeProvider() {
_initTheme();
}
// 初始化主题(异步)
Future<void> _initTheme() async {
if (_isInitialized) return;
final prefs = await SharedPreferences.getInstance();
_followSystem = prefs.getBool('followSystem') ?? false;
if (_followSystem) {
_themeData = _getSystemTheme();
} else {
final isDark = prefs.getBool('isDark') ?? false;
_themeData = isDark ? darkMode : lightMode;
}
_addSystemThemeListener();
_isInitialized = true;
notifyListeners();
}
// 系统主题监听
void _addSystemThemeListener() {
_brightnessListener = () {
if (_followSystem) {
_themeData = _getSystemTheme();
notifyListeners();
}
};
WidgetsBinding.instance.platformDispatcher.onPlatformBrightnessChanged =
_brightnessListener;
}
// 清理资源
@override
void dispose() {
WidgetsBinding.instance.platformDispatcher.onPlatformBrightnessChanged =
null;
super.dispose();
}
// 统一保存配置
Future<void> _savePreferences() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('isDark', isDarkMode);
await prefs.setBool('followSystem', _followSystem);
}
// 切换主题
void toggleTheme() {
_followSystem = false;
_themeData = isDarkMode ? lightMode : darkMode;
_savePreferences();
notifyListeners();
}
// 设置浅色主题
void setLightTheme() {
_followSystem = false;
_themeData = lightMode;
_savePreferences();
notifyListeners();
}
// 设置深色主题
void setDarkTheme() {
_followSystem = false;
_themeData = darkMode;
_savePreferences();
notifyListeners();
}
// 跟随系统主题
void setFollowSystem() {
_followSystem = true;
_themeData = _getSystemTheme();
_savePreferences();
notifyListeners();
}
// 获取系统主题
ThemeData _getSystemTheme() {
final brightness =
WidgetsBinding.instance.platformDispatcher.platformBrightness;
return brightness == Brightness.dark ? darkMode : lightMode;
}
}
-
在
main.dart
中注入ThemeProvider
-
直接在
main.dart
中使用主题 -
因为上面国际化时已经使用了
Consumer
,设置主题可以采用Provider.of<ThemeProvider>(context).themeData
的方式直接设置,详细用法见官网。 -
创建一个
theme_page.dart
页面来设置主题
dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:wallpaper/components/appbar_base.dart';
import 'package:wallpaper/themes/theme_provider.dart';
class ThemePage extends StatefulWidget {
const ThemePage({super.key});
@override
State<ThemePage> createState() => _ThemePageState();
}
class _ThemePageState extends State<ThemePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppbarBase(title: '主题设置'),
body: Consumer<ThemeProvider>(builder: (context, provider, child) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Flex(
direction: Axis.vertical,
spacing: 10,
children: [
SizedBox(
child: Material(
borderRadius: BorderRadius.circular(10),
clipBehavior: Clip.antiAlias,
color: Theme.of(context).colorScheme.primaryContainer,
child: InkWell(
onTap: () {
provider.setLightTheme();
},
child: Row(
children: [
Radio(
value: !provider.isDarkMode &&
!provider.followSystem,
groupValue: true,
onChanged: (value) {
provider.setLightTheme();
}),
Text('浅色模式')
],
),
),
),
),
Material(
borderRadius: BorderRadius.circular(10),
clipBehavior: Clip.antiAlias,
color: Theme.of(context).colorScheme.primaryContainer,
child: InkWell(
onTap: () {
provider.setDarkTheme();
},
child: Row(
children: [
Radio(
value:
provider.isDarkMode && !provider.followSystem,
groupValue: true,
onChanged: (value) {
provider.setDarkTheme();
}),
Text('暗色模式')
],
),
),
),
Material(
borderRadius: BorderRadius.circular(10),
clipBehavior: Clip.antiAlias,
color: Theme.of(context).colorScheme.primaryContainer,
child: InkWell(
onTap: () {
provider.setFollowSystem();
},
child: Row(
children: [
Radio(
value: provider.followSystem,
groupValue: true,
onChanged: (value) {
provider.setFollowSystem();
}),
Text('跟随系统')
],
),
),
),
],
),
);
}));
}
}
- 同理只要设置了主题就会实时更新和存储主题,下次打开时自动读取存储的主题。
3、视频播放器UI
视频播放器UI,没啥大的变化,稍微调整了边距。
4、扫码功能
扫码功能,美化UI,简单添加提示和刷新功能。
5、水波纹问题
问题:
- 不生效问题。
- 跳转页面后,水波纹动画不会继续执行,需返回页面后才会执行执行,体验非常糟糕。
解决:
- 拿
InkWell
举例,水波纹需要Material
或者Scaffold
来承载,如果使用Container
等包裹,水波纹可能就会失效。 - 通过自定义跳转动画使水波纹能够执行。可以自定义跳转动画,并且水波纹不会停止播放,非常nice。
dart
Navigator.push(
context,
PageRouteBuilder(
settings: RouteSettings(name: ''), // 保留路由名称
pageBuilder: (_, animation, __) =>
list[i].route!, // 替换为你的目标页面
transitionsBuilder: (_, animation, __, child) {
// 示例:从右向左滑动动画
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(1.0, 0.0),
end: Offset.zero,
).animate(CurvedAnimation(
parent: animation,
curve: Curves.easeInOut,
)),
child: child,
);
},
transitionDuration:
const Duration(milliseconds: 300), // 动画时长
),
);
6、项目地址
- 使用的是本地
gradle-8.3-all.zip