本软件基于Flutter开发,运用GETX状态管理框架实现,支持多语言。
一、运行样例
二、功能
三、插件集合
- 状态管理框架
- 网络请求
- dio: ^4.0.0
- dio_cookie_manager: ^2.0.0
- cookie_jar: ^3.0.1
- 下拉刷新
- flutter_easyrefresh: ^2.2.1
- 动态获取权限
- permission_handler: ^6.1.1
- 自定义弹窗
- 支持空安全轮播图
- flutter_swiper_null_safety: ^1.0.2
- 重力传感器
- 列表左右滑动
- 图片选择器
- 设备信息
- AES加密
- 极光推送
- 本地缓存
- shared_preferences: ^2.0.5
- 获取应用信息
- 应用缓存操作
- 加载网页
四、通用代码
1. 状态栏颜色
Widget build(BuildContext context) {
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle.light,
child: Scaffold()
)
}
2. 使用十六进制颜色
colors_utils.dart
import 'dart:ui' show Color;
/**
* @Author: Jingluo
* @Gitee: <https://gitee.com/jingluoonline/>
* @Email: jingluoonline@163.com
* @Description: 根据传入的值转换成十六进制
* @Date: 2021/7/17
*/
class ColorsUtil {
/// 十六进制颜色,
/// hex, 十六进制值,例如:0xffffff,
/// alpha, 透明度 [0.0,1.0]
static Color hexToColor(String s) {
// 如果传入的十六进制颜色值不符合要求,返回默认值
if (s.length != 7 || int.tryParse(s.substring(1, 7), radix: 16) == null) {
s = '#999999';
}
return new Color(int.parse(s.substring(1, 7), radix: 16) + 0xFF000000);
}
}
color_extension.dart
import 'dart:ui';
import 'colors_utils.dart';
/**
* @Author: Jingluo
* @Gitee: <https://gitee.com/jingluoonline/>
* @Email: jingluoonline@163.com
* @Description: 自定义标识符号
* @Date: 2021/7/17
*/
extension ColorExtension on String {
Color get c => ColorsUtil.hexToColor(this);
}
app_colors.dart
import 'package:flutter/material.dart';
import 'color_extension.dart';
/**
* @Author: Jingluo
* @Gitee: <https://gitee.com/jingluoonline/>
* @Email: jingluoonline@163.com
* @Description: 颜色库
* @Date: 2021/7/17
*/
// ignore: non_constant_identifier_names
final Color c_EB4B8B = '#EB4B8B'.c;
3. 自适应高宽
screen_util.dart
import 'package:flutter/material.dart';
import 'dart:ui' as ui show window;
/**
* @Author: Jingluo
* @Gitee: <https://gitee.com/jingluoonline/>
* @Email: jingluoonline@163.com
* @Description: 自适应高宽
* @Date: 2021/7/17
*/
///默认设计稿尺寸(单位 dp or pt)
double _designW = 360.0;
double _designH = 640.0;
double _designD = 3.0;
/**
* 配置设计稿尺寸(单位 dp or pt)
* w 宽
* h 高
* density 像素密度
*/
/// 配置设计稿尺寸 屏幕 宽,高,密度。
/// Configuration design draft size screen width, height, density.
void setDesignWHD(double? w, double? h, {double? density = 3.0}) {
_designW = w ?? _designW;
_designH = h ?? _designH;
_designD = density ?? _designD;
}
/// Screen Util.
class ScreenUtil {
double _screenWidth = 0.0;
double _screenHeight = 0.0;
double _screenDensity = 0.0;
double _statusBarHeight = 0.0;
double _bottomBarHeight = 0.0;
double _appBarHeight = 0.0;
MediaQueryData? _mediaQueryData;
static final ScreenUtil _singleton = ScreenUtil();
static ScreenUtil getInstance() {
_singleton._init();
return _singleton;
}
_init() {
MediaQueryData mediaQuery = MediaQueryData.fromWindow(ui.window);
if (_mediaQueryData != mediaQuery) {
_mediaQueryData = mediaQuery;
_screenWidth = mediaQuery.size.width;
_screenHeight = mediaQuery.size.height;
_screenDensity = mediaQuery.devicePixelRatio;
_statusBarHeight = mediaQuery.padding.top;
_bottomBarHeight = mediaQuery.padding.bottom;
_appBarHeight = kToolbarHeight;
}
}
/// screen width
/// 屏幕 宽
double get screenWidth => _screenWidth;
/// screen height
/// 屏幕 高
double get screenHeight => _screenHeight;
/// appBar height
/// appBar 高
double get appBarHeight => _appBarHeight;
/// screen density
/// 屏幕 像素密度
double get screenDensity => _screenDensity;
/// status bar Height
/// 状态栏高度
double get statusBarHeight => _statusBarHeight;
/// bottom bar Height
double get bottomBarHeight => _bottomBarHeight;
/// media Query Data
MediaQueryData? get mediaQueryData => _mediaQueryData;
/// screen width
/// 当前屏幕 宽
static double getScreenW(BuildContext context) {
MediaQueryData mediaQuery = MediaQuery.of(context);
return mediaQuery.size.width;
}
/// screen height
/// 当前屏幕 高
static double getScreenH(BuildContext context) {
MediaQueryData mediaQuery = MediaQuery.of(context);
return mediaQuery.size.height;
}
/// screen density
/// 当前屏幕 像素密度
static double getScreenDensity(BuildContext context) {
MediaQueryData mediaQuery = MediaQuery.of(context);
return mediaQuery.devicePixelRatio;
}
/// status bar Height
/// 当前状态栏高度
static double getStatusBarH(BuildContext context) {
MediaQueryData mediaQuery = MediaQuery.of(context);
return mediaQuery.padding.top;
}
/// status bar Height
/// 当前BottomBar高度
static double getBottomBarH(BuildContext context) {
MediaQueryData mediaQuery = MediaQuery.of(context);
return mediaQuery.padding.bottom;
}
/// 当前MediaQueryData
static MediaQueryData getMediaQueryData(BuildContext context) {
MediaQueryData mediaQuery = MediaQuery.of(context);
return mediaQuery;
}
/// 仅支持纵屏。
/// returns the size after adaptation according to the screen width.(unit dp or pt)
/// 返回根据屏幕宽适配后尺寸(单位 dp or pt)
/// size 单位 dp or pt
static double getScaleW(BuildContext context, double size) {
if (getScreenW(context) == 0.0) return size;
return size * getScreenW(context) / _designW;
}
/// 仅支持纵屏。
/// returns the size after adaptation according to the screen height.(unit dp or pt)
/// 返回根据屏幕高适配后尺寸 (单位 dp or pt)
/// size unit dp or pt
static double getScaleH(BuildContext context, double size) {
if (getScreenH(context) == 0.0) return size;
return size * getScreenH(context) / _designH;
}
/// 仅支持纵屏。
/// returns the font size after adaptation according to the screen density.
/// 返回根据屏幕宽适配后字体尺寸
/// fontSize 字体尺寸
static double getScaleSp(BuildContext context, double fontSize) {
if (getScreenW(context) == 0.0) return fontSize;
return fontSize * getScreenW(context) / _designW;
}
/// Orientation
/// 设备方向(portrait, landscape)
static Orientation getOrientation(BuildContext context) {
MediaQueryData mediaQuery = MediaQuery.of(context);
return mediaQuery.orientation;
}
/// 仅支持纵屏。
/// returns the size after adaptation according to the screen width.(unit dp or pt)
/// 返回根据屏幕宽适配后尺寸(单位 dp or pt)
/// size 单位 dp or pt
double getWidth(double size) {
return _screenWidth == 0.0 ? size : (size * _screenWidth / _designW);
}
/// 仅支持纵屏。
/// returns the size after adaptation according to the screen height.(unit dp or pt)
/// 返回根据屏幕高适配后尺寸(单位 dp or pt)
/// size unit dp or pt
double getHeight(double size) {
return _screenHeight == 0.0 ? size : (size * _screenHeight / _designH);
}
/// 仅支持纵屏
/// returns the size after adaptation according to the screen width.(unit dp or pt)
/// 返回根据屏幕宽适配后尺寸(单位 dp or pt)
/// sizePx unit px
double getWidthPx(double sizePx) {
return _screenWidth == 0.0
? (sizePx / _designD)
: (sizePx * _screenWidth / (_designW * _designD));
}
/// 仅支持纵屏。
/// returns the size after adaptation according to the screen height.(unit dp or pt)
/// 返回根据屏幕高适配后尺寸(单位 dp or pt)
/// sizePx unit px
double getHeightPx(double sizePx) {
return _screenHeight == 0.0
? (sizePx / _designD)
: (sizePx * _screenHeight / (_designH * _designD));
}
/// 仅支持纵屏。
/// returns the font size after adaptation according to the screen density.
/// 返回根据屏幕宽适配后字体尺寸
/// fontSize 字体尺寸
double getSp(double fontSize) {
if (_screenDensity == 0.0) return fontSize;
return fontSize * _screenWidth / _designW;
}
/// 兼容横/纵屏。
/// 获取适配后的尺寸,兼容横/纵屏切换,可用于宽,高,字体尺寸适配。
/// Get the appropriate size, compatible with horizontal/vertical screen switching, can be used for wide, high, font size adaptation.
double getAdapterSize(double dp) {
if (_screenWidth == 0 || _screenHeight == 0) return dp;
return getRatio() * dp;
}
/// 适配比率。
/// Ratio.
double getRatio() {
return (_screenWidth > _screenHeight ? _screenHeight : _screenWidth) /
_designW;
}
/// 兼容横/纵屏。
/// 获取适配后的尺寸,兼容横/纵屏切换,适应宽,高,字体尺寸。
/// Get the appropriate size, compatible with horizontal/vertical screen switching, can be used for wide, high, font size adaptation.
static double getAdapterSizeCtx(BuildContext context, double dp) {
Size size = MediaQuery.of(context).size;
if (size == Size.zero) return dp;
return getRatioCtx(context) * dp;
}
/// 适配比率。
/// Ratio.
static double getRatioCtx(BuildContext context) {
Size size = MediaQuery.of(context).size;
return (size.width > size.height ? size.height : size.width) / _designW;
}
/// 获取高度占位
SizedBox getHeightBox(double height){
return SizedBox(height: height, width: 1,);
}
/// 获取宽度占位
SizedBox getWidthBox(double width){
return SizedBox(height: 1, width: width,);
}
}
size_extension.dart
import 'screen_util.dart';
/**
* @Author: Jingluo
* @Gitee: <https://gitee.com/jingluoonline/>
* @Email: jingluoonline@163.com
* @Description: 自定义宽高别名
* @Date: 2021/7/17
*/
extension SizeExtension on num {
/// 适配宽高
double get dp => ScreenUtil.getInstance().getAdapterSize(this.toDouble());
/// 适配字体
double get sp => ScreenUtil.getInstance().getAdapterSize(this.toDouble());
/// 屏幕宽高的百分比
double get wp => ScreenUtil.getInstance().screenWidth * this.toDouble();
/// 屏幕宽高的百分比
double get hp => ScreenUtil.getInstance().screenHeight * this.toDouble();
}
example.dart
Text(
"自适应字体宽高,字符串过长隐藏",
style: TextStyle(
color: Colors.white,
fontSize: 16.dp
),
overflow: TextOverflow.ellipsis,
)
4. Getx扩展方法
get_extension.dart
import 'package:flutter/material.dart';
import 'package:get/get_core/src/get_interface.dart';
/**
* @Author: Jingluo
* @Gitee: <https://gitee.com/jingluoonline/>
* @Email: jingluoonline@163.com
* @Description: 高度和宽度占位
* @Date: 2021/7/17
*/
extension GetExtension on GetInterface {
/// 获取高度占位
SizedBox getHeightBox(double height){
return SizedBox(height: height, width: 1,);
}
/// 获取宽度占位
SizedBox getWidthBox(double width){
return SizedBox(height: 1, width: width,);
}
}
example.dart
Get.getHeightBox(10.dp)
五、第三方使用
1. 极光推送(jpush_flutter)
import 'package:jpush_flutter/jpush_flutter.dart';
/**
* @Author: Jingluo
* @Gitee: <https://gitee.com/jingluoonline/>
* @Email: jingluoonline@163.com
* @Description: 极光推送,获取注册ID和设置别名
* @Date: 2021/7/17
*/
Future<String> initJPush(uid) async{
var registerId;
JPush jpush = new JPush();
jpush.addEventHandler(
// 接收通知回调方法。
onReceiveNotification: (Map<String, dynamic> message) async {
print("flutter 接受通知回调: $message");
},
// 点击通知回调方法。
onOpenNotification: (Map<String, dynamic> message) async {
print("flutter 点击通知回调: $message");
},
// 接收自定义消息回调方法。
onReceiveMessage: (Map<String, dynamic> message) async {
print("flutter 接收自定义消息回调: $message");
},
);
///配置应用 Key
jpush.setup(
appKey: "jiguangAppKey",
channel: "theChannel",
production: false,
/// 设置是否打印 debug 日志
debug: true,
);
if(GetPlatform.isIOS){
jpush.applyPushAuthority(
new NotificationSettingsIOS(
sound: true,
alert: true,
badge: true
)
);
}
jpush.getRegistrationID().then((rid) {
print("获取注册的id:$rid");
registerId = rid;
});
jpush.setAlias("test_$uid").then((map) {
print("别名设置成功");
});
return registerId ?? "1";
}
- 注意??
- 当是IOS应用时,需要请求通知权限。
- 通知方式有注册ID和别名两种方式,根据后台需要来设置。
2. 缓存文件操作(path_provider)
获取缓存文件
import 'dart:io';
import 'package:path_provider/path_provider.dart';
/**
* @Author: Jingluo
* @Gitee: <https://gitee.com/jingluoonline/>
* @Email: jingluoonline@163.com
* @Description: 获取应用缓存文件及计算大小
* @Date: 2021/7/17
*/
///加载缓存
Future<String> loadCache() async {
Directory tempDir = await getTemporaryDirectory();
double value = await _getTotalSizeOfFilesInDir(tempDir);
// tempDir.list(followLinks: false,recursive: true).listen((file){
// //打印每个缓存文件的路径
// print(file.path);
// });
return _renderSize(value); // _cacheSizeStr用来存储大小的值
}
/// 循环计算文件的大小(递归)
Future<double> _getTotalSizeOfFilesInDir(final FileSystemEntity file) async {
if (file is File && await file.exists()) {
int length = await file.length();
return double.parse(length.toString());
}
if (file is Directory && await file.exists()) {
final List<FileSystemEntity> children = file.listSync();
double total = 0;
for (final FileSystemEntity child in children)
total += await _getTotalSizeOfFilesInDir(child);
return total;
}
return 0;
}
/// 格式化缓存文件大小
_renderSize(double value) {
if (value == 0.0) {
return "0.00B";
}
List<String> unitArr = []
..add('B')
..add('K')
..add('M')
..add('G');
int index = 0;
while (value > 1024) {
index++;
value = value / 1024;
}
String size = value.toStringAsFixed(2);
return size + unitArr[index];
}
清除缓存
import 'dart:io';
import 'package:path_provider/path_provider.dart';
/**
* @Author: Jingluo
* @Gitee: <https://gitee.com/jingluoonline/>
* @Email: jingluoonline@163.com
* @Description: 清空缓存文件
* @Date: 2021/7/17
*/
/// 清理缓存
void clearCacheMethod() async {
Directory tempDir = await getTemporaryDirectory();
//删除缓存目录
await delDir(tempDir);
await loadCache();
toastShow('清除缓存成功');
}
///递归方式删除目录
Future<Null> delDir(FileSystemEntity file) async {
if(await file.exists()){
if (file is Directory) {
final List<FileSystemEntity> children = file.listSync();
for (final FileSystemEntity child in children) {
await delDir(child);
}
}
await file.delete();
loadCache();
}
}
3. 获取设备信息(device_info)
import 'dart:io';
import 'package:device_info/device_info.dart';
/**
* @Author: Jingluo
* @Gitee: <https://gitee.com/jingluoonline/>
* @Email: jingluoonline@163.com
* @Description: 获取设备信息,用来唯一标识用户或统计和分析用户数据。
* @Date: 2021/7/17
*/
Future<void> getAppInfo() async {
DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
if(Platform.isAndroid){
AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
print('设备机型${androidInfo.model}');
print('设备版本${androidInfo.version.toString()}');
print('设备Id${androidInfo.androidId}');
} else if(Platform.isIOS){
IosDeviceInfo iosInfo = await deviceInfo.iosInfo;
print('设备机型${iosInfo.model}');
print('设备系统名${iosInfo.systemName}');
print('设备版本${iosInfo.systemVersion}');
print('设备Id${iosInfo.identifierForVendor}');
}
}
4. 获取应用信息(package_info)
import 'package:package_info/package_info.dart';
/**
* @Author: Jingluo
* @Gitee: <https://gitee.com/jingluoonline/>
* @Email: jingluoonline@163.com
* @Description: 获取应用信息,用来统计用户在每个版本数量及对版本进行分析优化。
* @Date: 2021/7/17
*/
PackageInfo.fromPlatform().then((PackageInfo packageInfo) {
print("版本号:${packageInfo.version}");
print("包名称:${packageInfo.packageName}");
print("应用名称:${packageInfo.appName}");
print("构建版本号:${packageInfo.buildNumber}");
});
5. 自定义弹窗(fluttertoast)
import 'package:fluttertoast/fluttertoast.dart';
/// toast信息
Fluttertoast.showToast(
msg: msg,
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
timeInSecForIosWeb: 1,
backgroundColor: c_69,
textColor: c_FF,
fontSize: 16.0
)
描述:自定义弹窗,居中弹出。
6. 数据缓存(shared_preferences)
setData.dart
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setInt("id", 123);
prefs.setString("content", "学习");
getDate.dart
SharedPreferences prefs = await SharedPreferences.getInstance();
id = prefs.getInt("id")!;
content = prefs.getString("content")!;
描述:通常用来对数据进行缓存,如用户信息数据,避免来回请求后台接口,提高性能。
7. 图像选择(image_picker)
import 'package:image_picker/image_picker.dart';
/**
* @Author: Jingluo
* @Gitee: <https://gitee.com/jingluoonline/>
* @Email: jingluoonline@163.com
* @Description: 图像选择并获取本地图片地址,通常用来更换头像或上传图片证明等。
* @Date: 2021/7/18
*/
PickedFile? image = await ImagePicker().getImage(
source: ImageSource.gallery,
);
/// 本地图片路径
String path = image!.path;
/// 图片名称
var name = path.substring(path.lastIndexOf("/") + 1, path.length);
/// 生成FormData格式
FormData formData = new FormData.fromMap({
"file": await MultipartFile.fromFile(path,filename: name)
});
8. 下拉刷新(flutter_easyrefresh)
import 'package:flutter_easyrefresh/easy_refresh.dart';
/**
* @Author: Jingluo
* @Gitee: <https://gitee.com/jingluoonline/>
* @Email: jingluoonline@163.com
* @Description: 下拉和上拉刷新,多种下拉刷新样式,可去查看`[<https://pub.flutter-io.cn/packages/flutter_easyrefresh#-readme-tab->](<https://pub.flutter-io.cn/packages/flutter_easyrefresh#-readme-tab->)`。
* @Date: 2021/7/19
*/
EasyRefresh.custom(
header: DeliveryHeader(),
onRefresh: () async {
toastShow("刷新成功");
},
slivers: [
SliverToBoxAdapter(child: Text(""),)),
]
)
9. 重力感应(sensors_plus)
testSensorsPage.dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:sensors_plus/sensors_plus.dart';
import 'export.dart'; /// 本地封装的一些方法,如dp,静态资源等
/**
* @Author: Jingluo
* @Gitee: <https://gitee.com/jingluoonline/>
* @Email: jingluoonline@163.com
* @Description: 重力感应示例,具体实现效果需要自己去定义。
* @Date: 2021/7/17
*/
class TestSensorsPage extends StatefulWidget {
@override
_TestSensorsPageState createState() => _TestSensorsPageState();
}
class _TestSensorsPageState extends State<TestSensorsPage> {
late StreamSubscription<AccelerometerEvent> _streamSubscription;
AccelerometerEvent? acceleration;
late Timer _timer;
GlobalKey anchorKey = GlobalKey();
var dx = 0.0 , dy = 0.0;
@override
void initState() {
super.initState();
_streamSubscription =
accelerometerEvents.listen((AccelerometerEvent event) {
acceleration = event;
});
_timer = Timer.periodic(const Duration(milliseconds: 10), (_) {
if(acceleration != null){
var dxNow = acceleration!.x.abs() < 1.0
? dx : dx - acceleration!.x;
var dyNow = acceleration!.y.abs() < 1.0
? dy : dy + acceleration!.y;
setState(() {
dx = dxNow;
dy = dyNow;
});
}
});
}
@override
Widget build(BuildContext context) {
return Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: Transform.translate(
key: anchorKey,
offset: Offset(dx, dy),
child: GestureDetector(
child: Image.asset(
R.imagesPlanIconAdd,
width: 24.dp,
),
),
),
);
}
}
10. 列表左右滑动(flutter_slidable)
import 'package:flutter_slidable/flutter_slidable.dart';
import 'export.dart'; /// 本地封装的一些方法,如dp,静态资源等
/**
* @Author: Jingluo
* @Gitee: <https://gitee.com/jingluoonline/>
* @Email: jingluoonline@163.com
* @Description: 列表左右滑动,可自定义左右侧按钮
* @Date: 2021/7/17
*/
SlidableController _slidableController = SlidableController();
/// 滑动通用模板
slidableTemplate(index, context{
return Slidable(
actionPane: SlidableDrawerActionPane(),//滑出选项的面板 动画
actionExtentRatio: 0.25,
controller: _slidableController,
child: GestureDetector(
onTap: ()=>{},
child: Padding(
padding: EdgeInsets.only(right: 8.dp),
child: Container(
width: double.infinity,
height: double.infinity,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10.dp),
),
child: Row(
children: [
Get.getWidthBox(15.dp),
Expanded(child: Text("任务$index")),
Get.getWidthBox(15.dp),
],
),
),
),
),
/// 左侧按钮列表
actions: <Widget>[
Container(
height: double.infinity,
decoration: BoxDecoration(
color: c_f77f88,
borderRadius: BorderRadius.all(Radius.circular(10.0))
),
child: Center(
child: singleTextWeight("分享", c_FF, 16.dp),
)
),
],
/// 右侧按钮列表
secondaryActions: <Widget>[
Container(
height: double.infinity,
decoration: BoxDecoration(
color: c_f77f88,
borderRadius: BorderRadius.all(Radius.circular(10.0))
),
child: Center(
child: singleTextWeight("修改", c_FF, 16.dp),
)
),
Container(
height: double.infinity,
decoration: BoxDecoration(
color: c_80b3f7,
borderRadius: BorderRadius.all(Radius.circular(10.0))
),
child: Center(
child: singleTextWeight("删除", c_FF, 16.dp),
)
),
],
);
}
|