简体中文 | English
GetXScaffold 快速开发脚手架在 GetX 框架和一些常用插件的基础上,构建了一套完整的快速开发模板。其中包括新增了部分常用功能的全局方法、常用的扩展方法和各种工具类、部分常用组件的封装、简单易用的对话框、二次封装的 Dio 网络请求工具、二次封装的 GetxController、二次封装的应用主题和国际化实现等。GetXScaffold 是对以上这些内容的过度封装,包括一些组件的扩展方法会违背 Flutter 本身的开发规范,改变你的开发习惯。所以本脚手架单纯为了提高开发效率,减少重复代码,减少开发成本。如果您是刚接触 Flutter 开发并还处在学习过程中的话,并不推荐您使用该脚手架。以下只是部分功能的使用示例,建议您通过示例项目或者源码了解全部使用方法。
Version | Flutter 版本 |
---|---|
0.0.3 | 3.19.5 |
0.0.4 | 3.22.2 |
0.1.2 | 3.24.1 |
0.2.0 | 3.24.1 |
- 最近新项目的使用中,发现这 Material3 实在没法用,各种配色问题莫名其妙。从 0.2.0 开始推荐使用 Material2 重新适配,具体主题设置参考 Example。
- 适配 Flutter SDK 版本到 3.24.1
- 修复 ButtonX.icon iconSize 参数不生效的问题
- 新增滑动验证码 Dialog
- HttpService 新增错误拦截和请求拦截
- EncryptUtil ExString 新增 sha1 sha256 sha512 散列算法
- 新增随机数生成和 Nonce 生成的全局方法
- refreshAppui 延时 300ms 刷新,处理主题更新后显示不正常的问题
- Android 端修改主题自动修改底部导航栏颜色
cd example
flutter pub get
flutter run
# 如果提示 Error: Type 'UnmodifiableUint8ListView' not found.请更新win32库
flutter pub upgrade win32
Flutter 工程中 pubspec.yaml 文件里加入以下依赖:
dependencies:
getx_scaffold: ^0.0.1
# 如果您的项目中需要使用国际化,请添加以下依赖
flutter_localizations:
sdk: flutter
在 main.dart 中初始化 GetXScaffold:
import 'package:getx_scaffold/getx_scaffold.dart';
void main() async {
WidgetsBinding widgetsBinding = await init(
isDebug: kDebugMode,
logTag: 'GetxScaffold',
supportedLocales: TranslationLibrary.supportedLocales,
);
runApp(
GetxApp(
// 设计稿尺寸 单位:dp
designSize: const Size(390, 844),
// Getx Log
enableLog: kDebugMode,
// 默认的跳转动画
defaultTransition: Transition.rightToLeft,
// 主题模式
themeMode: GlobalService.to.themeMode,
// 主题
theme: AppTheme.light,
// Dark主题
darkTheme: AppTheme.dark,
// 国际化配置
locale: GlobalService.to.locale,
translations: TranslationLibrary(),
fallbackLocale: TranslationLibrary.fallbackLocale,
supportedLocales: TranslationLibrary.supportedLocales,
localizationsDelegates: TranslationLibrary.localizationsDelegates,
// AppTitle
title: 'GetxScaffold',
// 首页
home: const HomePage(),
// Builder
builder: (context, widget) {
// do something....
return widget!;
},
),
);
}
GetxApp 是 GetMaterialApp 嵌套了 ScreenUtilInit 对全局进行屏幕适配,通过 GlobalService 可以方便的实现多主题和多语言的切换。
最近新项目的使用中,发现这 Material3 实在没法用,各种配色问题莫名其妙。从 0.2.0 开始推荐使用 Material2 重新适配,具体主题设置参考 Example。
import 'package:flutter/material.dart';
class AppTheme {
static const String fontMontserrat = 'Montserrat';
static const Color themeColor = Color.fromARGB(255, 11, 107, 47);
static const Color darkThemeColor = Color.fromARGB(255, 12, 16, 121);
/// 亮色主题样式
static ThemeData light = ThemeData(
useMaterial3: false,
fontFamily: fontMontserrat,
colorScheme: ColorScheme.fromSeed(
seedColor: themeColor,
brightness: Brightness.light,
),
appBarTheme: const AppBarTheme(
backgroundColor: Colors.white,
foregroundColor: Color.fromARGB(200, 0, 0, 0),
centerTitle: true,
titleTextStyle: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Color.fromARGB(200, 0, 0, 0),
),
),
);
/// 暗色主题样式
static ThemeData dark = ThemeData(
useMaterial3: false,
fontFamily: fontMontserrat,
colorScheme: ColorScheme.fromSeed(
seedColor: darkThemeColor,
brightness: Brightness.dark,
),
appBarTheme: const AppBarTheme(
centerTitle: true,
titleTextStyle: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
bottomAppBarTheme: const BottomAppBarTheme(
color: Color.fromARGB(255, 34, 34, 34),
),
);
}
以上是主题示例,GetXScaffold 的所有内置组件均遵循 Material3 设计规范。如果你使用 colorScheme 定义了主题颜色,那么你可以通过以下方法使用所有的主题颜色:
ThemeColor.onPrimary
ThemeColor.onSecondary
ThemeColor.onSurface
......
GetXScaffold 再次简化的国际化实现,您需要创建 TranslationLibrary 并实现以下方法:
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:getx_scaffold/getx_scaffold.dart';
import 'locales/locale_en.dart';
import 'locales/locale_es.dart';
import 'locales/locale_zh.dart';
class TranslationLibrary extends Translations {
// 默认语言 Locale(语言代码, 国家代码)
static const fallbackLocale = Locale('zh', 'CN');
static const supportedLocales = [
Locale('zh', 'CN'),
Locale('en', 'US'),
Locale('es', 'ES'),
];
@override
Map<String, Map<String, String>> get keys => {
'zh': zh,
'en': en,
'es': es,
};
static const localizationsDelegates = [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
];
}
在 main.dart GetxApp 中加入以下代码:
// 国际化配置
locale: GlobalService.to.locale,
translations: TranslationLibrary(),
fallbackLocale: TranslationLibrary.fallbackLocale,
supportedLocales: TranslationLibrary.supportedLocales,
localizationsDelegates: TranslationLibrary.localizationsDelegates,
GetXScaffold 的所有页面使用 GetView 加 GetxController 视图与逻辑分离的开发方式。这里注意,所有 GetxController 必须混入 BaseControllerMixin,以相应全局刷新。
import 'package:flutter/material.dart';
import 'package:example/common/langs/index.dart';
import 'package:getx_scaffold/getx_scaffold.dart';
import 'index.dart';
class HomePage extends GetView<HomeController> {
const HomePage({super.key});
// 主视图
Widget _buildView() {
return <Widget>[
ListTile(
title: Text(TextKey.zhuTi.tr),
onTap: () {
Get.to(() => const ThemePage());
},
),
ListTile(
title: Text(TextKey.yuYan.tr),
onTap: () {
Get.to(() => const LanguagePage());
},
),
]
.toListView(
separator: const DividerX(),
)
.scrollbar()
.safeArea();
}
Widget _buildFloatingActionButton() {
return FloatingActionButton(
onPressed: () {},
child: const Icon(Icons.info),
).padding(all: 20.w);
}
@override
Widget build(BuildContext context) {
return GetBuilder<HomeController>(
init: HomeController(),
//这里的id需要与HomeController中的builderId一致
id: 'home',
builder: (_) {
//双击退出
return DoublePressBackWidget(
child: Scaffold(
appBar: AppBar(
title: const Text("GetxScaffold"),
centerTitle: true,
elevation: 1,
),
floatingActionButton: _buildFloatingActionButton(),
body: _buildView(),
),
);
},
);
}
}
import 'package:getx_scaffold/getx_scaffold.dart';
class HomeController extends GetxController with BaseControllerMixin {
@override
String get builderId => 'home';
HomeController();
@override
void onInit() {
super.onInit();
//刷新ui
updateUi();
//返回
back();
//延时退出
delayedBack();
}
/// 是否监听生命周期事件
@override
bool get listenLifecycleEvent => true;
/// listenLifecycleEvent设置为true时,会调用以下生命周期方法
@override
void onDetached() {
log('onDetached');
}
@override
void onHidden() {
log('onHidden');
}
@override
void onInactive() {
log('onInactive');
}
@override
void onPaused() {
log('onPaused');
}
@override
void onResumed() {
log('onResumed');
}
}
/// 获取当前时间戳(Millisecond)
int getTimeStamp({bool isSecond = false})
/// 获取当前时间戳(Second)
int getTimeStampSecond()
/// 获取当前日期字符串
String getNowDateString()
/// 获取当前日期时间字符串
String getNowDateTimeString()
/// 获取当前时间字符串
String getNowTimeString()
/// 判断设备是否连接网络
Future<bool> isNetworkAvailable()
/// 判断设备是否连接移动网络
Future<bool> isConnectedToMobile()
/// 判断设备是否连接WiFi
Future<bool> isConnectedToWiFi()
/// 显示Toast
void showToast(String msg)
/// 显示成功Toast
void showSuccessToast(String msg)
/// 显示提示Toast
void showInfoToast(String msg)
/// 显示警告Toast
void showWarningToast(String msg)
/// 显示错误Toast
void showErrorToast(String msg)
/// 显示loading
void showLoading([String? msg])
/// 显示错误
void showError([String? msg])
/// 显示提示
void showInfo(String msg)
/// 隐藏loading
void dismissLoading()
/// 延时执行
void delayed(int milliseconds, Function() callback)
/// 监听事件总线
StreamSubscription<T> eventListen<T>(
void Function(T)? onData, {
Function? onError,
void Function()? onDone,
bool? cancelOnError,
})
/// 发送事件总线
sendEvent<T>(T event)
/// 刷新App所有页面
void refreshAppui()
/// 统一Log输出
void log(String log, [String? tag])
/// 切换主题模式
void changeThemeMode(ThemeMode themeMode)
/// 更改语言
void changeLanguage(Locale locale)
/// 打开网页
void openWebPage(String url, {LaunchMode mode = LaunchMode.platformDefault})
/// 拨打电话
void callPhone(String phoneNumber)
/**
* 需要在App启动后调用
*/
/// 获取包信息
Future<PackageInfo> getPackageInfo()
/// 获取AppName
Future<String> getAppName()
/// 获取PackageName
Future<String> getPackageName()
/// 获取Version
Future<String> getVersion()
/// 获取BuildNumber
Future<String> getBuildNumber()
/// 获取设备信息
Future<BaseDeviceInfo> getDeviceInfo()
// 获取设备名称
Future<String?> getDeviceName()
// 获取系统版本
Future<String?> getDeviceSystemVersion()
/// 隐藏输入法
void hideKeyboard()
/// Change status bar Color and Brightness
Future<void> setStatusBarColor()
/// Dark Status Bar
void setDarkStatusBar()
/// Light Status Bar
void setLightStatusBar()
/// This function will show status bar
Future<void> showStatusBar()
// Enter FullScreen Mode (Hides Status Bar and Navigation Bar)
void enterFullScreen()
// Unset Full Screen to normal state (Now Status Bar and Navigation Bar Are Visible)
void exitFullScreen()
/// This function will hide status bar
Future<void> hideStatusBar()
/// Set orientation to portrait
void setOrientationPortrait()
/// Set orientation to landscape
void setOrientationLandscape()
/// SharedPreferences:
/// Add a value in SharedPref based on their type - Must be a String, int, bool, double, Map<String, dynamic> or StringList
Future<bool> setValue(String key, dynamic value, {bool print = true})
/// Returns List of Keys that matches with given Key
List<String> getMatchingSharedPrefKeys(String key)
/// Returns a StringList if exists in SharedPref
List<String>? getStringListAsync(String key)
/// Returns a Bool if exists in SharedPref
bool getBoolAsync(String key, {bool defaultValue = false})
/// Returns a Double if exists in SharedPref
double getDoubleAsync(String key, {double defaultValue = 0.0})
/// Returns a Int if exists in SharedPref
int getIntAsync(String key, {int defaultValue = 0})
/// Returns a String if exists in SharedPref
String getStringAsync(String key, {String defaultValue = ''})
/// Returns a JSON if exists in SharedPref
Map<String, dynamic> getJSONAsync(String key,{Map<String, dynamic>? defaultValue})
/// remove key from SharedPref
Future<bool> removeKey(String key)
/// clear SharedPref
Future<bool> clearSharedPref()
/// 申请权限
Future<bool> requestPermission({
required Permission permission,
String? title,
String? confirmText,
required String message,
required String error,
})
/// 申请相机权限
Future<bool> requestCameraPermission()
/// 申请相册权限
Future<bool> requestPhotosPermission()
/// 申请蓝牙权限
Future<bool> requestBluetoothPermission()
/// 是否为空或null
bool get isEmptyOrNull => _isEmptyOrNull();
/// 是否不为空或null
bool get isNotEmptyOrNull => !_isEmptyOrNull();
/// 格式化时间字符串
String? dateFormat(String pattern)
/// 格式化时间字符串为日期
String? toDateString()
/// 格式化时间字符串为日期时间
String? toDateTimeString()
/// 格式化时间字符串为时间
String? toTimeString()
/// 获取DateTime对象
DateTime? getDateTime({bool? isUtc})
/// MD5加密
String? md5()
/// Base64编码
String? encodeBase64()
/// Base64解码
String? decodeBase64()
//转为金额字符串
String? toPrice(
int amount, {
MoneyFormats format = MoneyFormats.NORMAL,
MoneyUnit unit = MoneyUnit.NORMAL,
})
/// 转为int类型
int? toInt({int defValue = 0})
/// 转为double类型
double? toDouble({double defValue = 0})
/// 转为num类型
num? toNumber({num defValue = 0})
/// 判断是否为手机号(简易验证)
bool isMobileSimple()
/// 判断是否为手机号(严格验证)
bool isMobileExact()
/// 判断是否为座机号码
bool isTel()
/// 判断是否为身份证号码
bool isIDCard18()
/// 判断是否成年
bool isAdult()
/// 判断是否为Email
bool isEmail()
/// 判断是否为Url
bool isURL()
/// 判断是否为IP
bool isIP()
/// 根据格式将时间戳(Milliseconds)格式化日期
String? dateFormat(String pattern, {bool isSecond = false})
/// 将时间戳(Milliseconds)格式化日期
String? toDateString({bool isSecond = false})
/// 将时间戳(Milliseconds)格式化日期时间
String? toDateTimeString({bool isSecond = false})
/// 将时间戳(Milliseconds)格式化时间
String? toTimeString({bool isSecond = false})
/// 将字节转为容量单位
String? toFileSize({int decimals = 0})
/// 时间戳(Milliseconds)距离当前的时间
String? getTimeDifference({bool isShowDay = true, bool isSecond = false})
/// 时间戳(Milliseconds)距离当前的时间描述
String? getTimeDifferenceDescription({bool isSecond = false})
/// 转为金额字符串
String? toPrice(
int amount, {
MoneyFormats format = MoneyFormats.NORMAL,
MoneyUnit unit = MoneyUnit.NORMAL,
})
/// 加 (精确相加,防止精度丢失).
/// add (without loosing precision).
double? add(num value)
/// 减 (精确相减,防止精度丢失).
/// subtract (without loosing precision).
double? subtract(num value)
/// 乘 (精确相乘,防止精度丢失).
/// multiply (without loosing precision).
double? multiply(num value)
/// 除 (精确相除,防止精度丢失).
/// divide (without loosing precision).
double? divide(num value)
/// 间距
Widget spacing()
/// 转 Wrap
Widget toWrap();
/// 转 Column
Widget toColumn();
/// 转 Row
Widget toRow();
/// 转 ListView
Widget toListView()
// 转 Stack
Widget toStack();
// 使用示例:
Widget _buildView() {
return <Widget>[
ListTile(
title: Text(TextKey.genSuiXiTong.tr),
trailing: GlobalService.to.themeMode == ThemeMode.system
? const Icon(Icons.check)
: null,
onTap: () {
changeThemeMode(ThemeMode.system);
},
),
ListTile(
title: Text(TextKey.liangSeZhuTi.tr),
trailing: GlobalService.to.themeMode == ThemeMode.light
? const Icon(Icons.check)
: null,
onTap: () {
changeThemeMode(ThemeMode.light);
},
),
ListTile(
title: Text(TextKey.anSeZhuTi.tr),
trailing: GlobalService.to.themeMode == ThemeMode.dark
? const Icon(Icons.check)
: null,
onTap: () {
changeThemeMode(ThemeMode.dark);
},
),
].toListView(
separator: const DividerX(),
);
}
/// 控制组件隐藏显示
Widget visibility();
/// 比例布局
Widget aspectRatio();
/// 卡片布局
Widget card();
/// 居中布局
Widget center();
/// 裁剪圆形
Widget clipOval();
/// 裁剪矩形
Widget clipRect();
/// 裁剪圆角
Widget clipRRect();
/// 阴影
Widget elevation();
/// expand
Widget expand();
/// 缩放布局
Widget fittedBox();
/// 弹性布局
Widget flexible();
/// 百分比布局
Widget fractionallySizedBox();
/// 限制盒子 最大宽高
Widget limitedBox();
/// 偏移
Widget offstage();
/// 透明度
Widget opacity();
/// 溢出
Widget overflow();
/// Stack布局 位置
Widget positioned();
/// 墨水纹
Widget inkWell();
/// 涟漪
Widget ripple();
/// 比例缩放
Widget scale();
/// 滚动视图
Widget scrollable();
/// 滚动条
Widget scrollbar();
/// Transforms Matrix4
Widget transform();
/// Translate 变化位置
Widget translate();
/// 约束
Widget constrained();
/// 约束宽高
Widget tight();
/// 约束宽度
Widget width();
/// 约束高度
Widget height();
/// 取消父级约束
Widget unConstrained();
/// 安全区域
Widget safeArea();
/// 对齐
Widget align();
/// 对齐 上左边
Widget alignTopLeft();
/// 对齐 顶部居中
Widget alignTopCenter();
/// 对齐 上右边
Widget alignTopRight();
/// 对齐 左边
Widget alignCenterLeft();
/// 对齐 中间
Widget alignCenter();
/// 对齐 右边
Widget alignCenterRight();
/// 对齐 下左边
Widget alignBottomLeft();
/// 对齐 底部
Widget alignBottomCenter();
/// 对齐 下右边
Widget alignBottomRight();
/// 盒子装饰器
Widget decorated();
/// 背景颜色
Widget backgroundColor();
/// 边框
Widget border();
/// 阴影
Widget boxShadow();
/// 手势
Widget gestures();
/// 点击
Widget onTap();
/// 内间距
Widget padding();
/// Sliver 内间距
Widget sliverPadding();
/// 内间距 纵向
Widget sliverPaddingVertical(double val);
/// 内间距 横向
Widget sliverPaddingHorizontal(double val);
/// 内间距 上
Widget sliverPaddingTop(double val);
/// 内间距 下
Widget sliverPaddingBottom(double val);
/// 内间距 左
Widget sliverPaddingLeft(double val);
/// 内间距 右
Widget sliverPaddingRight(double val);
/// SliverToBoxAdapter
Widget sliver();
GetXScaffold 并不是一个 UI 组件库,里面仅封装了最常用的一些组件并优化了使用方法。如有其他需求请按需引入其他组件库。
import 'package:example/common/langs/index.dart';
import 'package:flutter/material.dart';
import 'package:getx_scaffold/common/index.dart';
import 'package:getx_scaffold/getx_scaffold.dart';
import 'index.dart';
class BaseWidgetsPage extends GetView<BaseWidgetsController> {
const BaseWidgetsPage({super.key});
// 主视图
Widget _buildView() {
return <Widget>[
TextX.displayLarge('DisplayLarge'),
TextX.displayMedium('DisplayMedium'),
TextX.displaySmall('DisplaySmall'),
TextX.headlineLarge('HeadlineLarge'),
TextX.headlineMedium('HeadlineMedium'),
TextX.headlineSmall('HeadlineSmall'),
TextX.titleLarge('TitleLarge'),
TextX.titleMedium('TitleMedium'),
TextX.titleSmall('TitleSmall'),
TextX.bodyLarge('BodyLarge'),
TextX.bodyMedium('BodyMedium'),
TextX.bodySmall('BodySmall'),
TextX.labelLarge('LabelLarge'),
TextX.labelMedium('LabelMedium'),
TextX.labelSmall('LabelSmall'),
const TextX('Weight 100', weight: FontWeight.w100),
const TextX('Weight 200', weight: FontWeight.w200),
const TextX('Weight 300', weight: FontWeight.w300),
const TextX('Weight 400', weight: FontWeight.w400),
const TextX('Weight 500', weight: FontWeight.w500),
const TextX('Weight 600', weight: FontWeight.w600),
const TextX('Weight 700', weight: FontWeight.w700),
const TextX('Weight 800', weight: FontWeight.w800),
const TextX('Weight 900', weight: FontWeight.w900),
//可点击文本
RichTextX(children: [
TextSpanItem(
'简易',
),
TextSpanItem('可点击', onTap: () {
showInfoToast('onClick!');
}),
TextSpanItem(
'文本测试',
),
]).padding(top: 10.w),
//TextTag
<Widget>[
const TextTag('Text Tag'),
const TextTag(
'Text Tag Outline',
outline: true,
),
const TextTag(
'Text Tag Orange',
color: Colors.orange,
),
].toWrap(spacing: 10.w).padding(top: 10.w),
//Button
<Widget>[
ButtonX(
'General',
dot: true,
icon: Icons.info,
onPressed: () {},
),
ButtonX.primary(
'Primary',
onPressed: () {},
),
ButtonX.outline(
'Outline',
onPressed: () {},
),
ButtonX.icon(
Icons.add,
badge: controller.number.toString(),
onPressed: () {
controller.increment();
},
),
ButtonX.text(
'Text',
onPressed: () {},
),
ButtonX.text(
'Small Text Button',
textColor: Colors.orange,
textSize: 12.sp,
textWeight: FontWeight.bold,
minSize: Size.zero,
innerPadding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 5.h),
borderRadius: 3.r,
onPressed: () {},
),
].toWrap(spacing: 10.w).padding(top: 10.w),
//Icon
//Antd图标库:https://www.iconfont.cn/collections/detail?spm=a313x.collections_index.i1.d9df05512.40943a815QJHPE&cid=9402
<Widget>[
IconX.icon(
AntdIcon.project,
size: 40.sp,
dot: controller.showDot,
).onTap(() {
controller.updateDot();
}),
IconX.icon(
Icons.add_circle_sharp,
size: 40.sp,
badge: controller.number.toString(),
).onTap(() {
controller.increment();
}),
IconX.svg(
'assets/svgs/icon1.svg',
size: 40.sp,
badge: 'New',
),
IconX.image(
'assets/icons/ic_launcher_adaptive_dark.png',
size: 40.sp,
).backgroundColor(Colors.orange).clipRRect(all: 8.r),
].toWrap(spacing: 10.w).padding(top: 10.w),
//SpinKit
//https://pub-web.flutter-io.cn/packages/flutter_spinkit
<Widget>[
SpinKitFoldingCube(
color: ThemeColor.primaryContainer,
size: 22.sp,
).padding(all: 10.w),
20.w.spacing(),
SpinKitDoubleBounce(
color: ThemeColor.primaryContainer,
size: 30.sp,
).padding(all: 10.w),
20.w.spacing(),
SpinKitFadingCircle(
color: ThemeColor.primaryContainer,
size: 30.sp,
).padding(all: 10.w),
20.w.spacing(),
SpinKitRipple(
color: ThemeColor.primaryContainer,
size: 30.sp,
).padding(all: 10.w),
].toRow().padding(top: 20.w),
//lottie
//https://pub-web.flutter-io.cn/packages/lottie
Lottie.asset(
'assets/lottie/error.json',
package: pluginPackageName,
width: 0.7.sw,
).padding(top: 10.w),
//image
ImageX.url(
'https://i0.hdslb.com/bfs/archive/ac72ae36271a6970f92b1de485e6ae6c9e4c1ebb.jpg',
width: 0.7.sw,
radius: 5.r,
).padding(top: 10.w),
]
.toColumn(crossAxisAlignment: CrossAxisAlignment.start)
.padding(all: 10.w, bottom: 50.w)
.scrollable()
.scrollbar()
.width(1.sw);
}
//导航栏
Widget _buildNavigationBar() {
return NavigationX(
currentIndex: controller.pageIndex, // 当前选中的tab索引
onTap: (index) {
controller.pageIndex = index;
controller.updateUi();
}, // 切换tab事件
items: [
NavigationItemModel(
label: '首页',
icon: AntdIcon.home,
selectedIcon: AntdIcon.home_fill,
dot: true,
),
NavigationItemModel(
label: '日历',
icon: AntdIcon.calendar,
selectedIcon: AntdIcon.calendar_fill,
badge: '18',
),
NavigationItemModel(
label: '设置',
icon: AntdIcon.setting,
selectedIcon: AntdIcon.setting_fill,
),
NavigationItemModel(
label: '设置',
icon: AntdIcon.setting,
selectedIcon: AntdIcon.setting_fill,
),
],
);
}
@override
Widget build(BuildContext context) {
return GetBuilder<BaseWidgetsController>(
init: BaseWidgetsController(),
id: 'baseWidgets',
builder: (_) {
return Scaffold(
extendBody: false,
resizeToAvoidBottomInset: false,
appBar: AppBar(title: Text(TextKey.jiChuZuJian.tr), elevation: 1),
bottomNavigationBar: _buildNavigationBar(),
body: SafeArea(
child: _buildView(),
),
);
},
);
}
}
.....
内置了加载中,网络错误,空数据三种状态,通过控制器进行切换。每种状态的 Widget 可以自定义。
import 'package:example/common/langs/index.dart';
import 'package:flutter/material.dart';
import 'package:getx_scaffold/getx_scaffold.dart';
import 'index.dart';
class LoadContainerPage extends GetView<LoadContainerController> {
const LoadContainerPage({super.key});
// 主视图
Widget _buildView() {
return <Widget>[
TextX.titleLarge('Page contents'),
ButtonX(
'Show Load Error',
onPressed: () => controller.onError(),
).width(double.infinity).padding(top: 30.h, horizontal: 50.w),
ButtonX(
'Show Load Empty',
onPressed: () => controller.onEmpty(),
).width(double.infinity).padding(top: 10.h, horizontal: 50.w),
].toColumn(mainAxisSize: MainAxisSize.min).center();
}
@override
Widget build(BuildContext context) {
return GetBuilder<LoadContainerController>(
init: LoadContainerController(),
id: 'loadContainer',
builder: (_) {
return Scaffold(
appBar: AppBar(title: Text(TextKey.zhuTi.tr), elevation: 1),
body: SafeArea(
child: LoadContainer(
controller: controller.loadController!,
onReLoad: controller.onLoad,
child: _buildView(),
),
),
);
},
);
}
}
import 'package:getx_scaffold/getx_scaffold.dart';
class LoadContainerController extends GetxController with BaseControllerMixin {
@override
String get builderId => 'loadContainer';
LoadController? loadController = LoadController();
@override
void onInit() {
super.onInit();
onLoad();
}
@override
void onClose() {
super.onClose();
loadController?.dispose();
loadController = null;
}
void onLoad() {
loadController?.loading();
delayed(3000, () => loadController?.complete());
}
void onEmpty() {
loadController?.loading();
delayed(3000, () => loadController?.empty());
}
void onError() {
loadController?.loading();
delayed(3000, () => loadController?.error());
}
}
脚手架中引入了 AntDesign 图标库,可以直接通过 AntdIcon.xxx 使用。查看全部图标
AntdIcon.home
AntdIcon.home_fill
......
// 显示确认弹窗
DialogX.to.showConfirmDialog();
// 显示通知弹窗
DialogX.to.showNoticeDialog();
// 显示提示弹窗
DialogX.to.showPromptDialog();
// 显示菜单弹窗
DialogX.to.showMenuDialog();
整合了 common_utils 库和 nb_utils 库中的常用工具类,并补全了 RSAUtils 等工具类。
- DateUtil : 日期转换格式化输出。
- EncryptUtil : 异或对称加/解密,md5 加密,Base64 加/解密。
- JsonUtil : 简单封装 json 字符串转对象。
- JwtDecoder : jwt 解析。
- LogUtil : 全局 log 控制。
- MoneyUtil : 精确转换,元转分,分转元,支持格式输出。
- NumUtil : 保留 x 位小数, 精确加、减、乘、除, 防止精度丢失。
- ObjectUtil : 判断对象是否为空(String List Map),判断两个 List 是否相等。
- RegexUtil : 正则验证手机号,身份证,邮箱等等。
- RSAUtils : RSA 加密解密验签。
- TextUtil : 银行卡号每隔 4 位加空格,每隔 3 三位加逗号,隐藏手机号等等。
- TimelineUtil : 时间轴。
- TimerUtil : 倒计时,定时任务。
GetXScaffold 对 Dio 进行了二次封装,提供了更简洁的调用方式和完整的请求日志。通过 HttpService 进行调用。
// 设置BaseURL
HttpService.to.setBaseUrl('https://api.vvhan.com');
// 设置Authorization
HttpService.to.setAuthorization('1234567890');
HttpService.to.clearAuthorization();
// 统一的响应处理,这里返null则正常通过,返回Error字符串则会拦截该请求并弹出错误提示。这里可以对403等错误进行处理。
HttpService.to.setOnResponseHandler(
(response) async {
try {
var result = BaseModel.fromJson(response.data);
if (result.success == true) {
return null;
} else {
return '服务器异常';
}
} on Exception catch (e) {
e.printInfo();
return '服务器异常';
}
},
);
// 取消统一响应处理
HttpService.to.setOnResponseHandler(null);
// get请求
showLoading();
var response = await HttpService.to.get(
'/api/wallpaper/acg?type=json',
);
if (response != null) {
dismissLoading();
var result = BaseModel.fromJson(response.data);
showToast(result.url ?? '');
}
// 取消请求
HttpService.to.cancel();
国内 Android 应用市场大部分都要求在申请权限前,要弹窗提示用户申请权限的原因。GetXScaffold 封装了这部分的逻辑,可以通过全局方法直接调用。并且将相册权限这种在不同 SDK 版本有差异的权限封装成了一个方法,简化申请权限流程。在使用前请在对应平台的配置文件设置对应权限。 参考:permission_handler
import 'package:example/common/langs/index.dart';
import 'package:flutter/material.dart';
import 'package:getx_scaffold/getx_scaffold.dart';
import 'index.dart';
class PermissionPage extends GetView<PermissionController> {
const PermissionPage({super.key});
// 主视图
Widget _buildView() {
return <Widget>[
ListTile(
title: const Text('申请相机权限'),
onTap: () async {
if (await requestCameraPermission()) {
showSuccessToast('已获取相机权限');
}
},
),
ListTile(
title: const Text('申请相册权限'),
onTap: () async {
if (await requestPhotosPermission()) {
showSuccessToast('已获取相册权限');
}
},
),
ListTile(
title: const Text('申请麦克风权限'),
onTap: () async {
var result = await requestPermission(
permission: Permission.microphone,
message: '我们申请使用您设备的麦克风权限,用于拍摄录音',
error: '请授权麦克风权限',
);
if (result) {
showSuccessToast('已获取麦克风权限');
}
},
),
].toListView(
separator: const DividerX(),
);
}
@override
Widget build(BuildContext context) {
return GetBuilder<PermissionController>(
init: PermissionController(),
id: 'permission',
builder: (_) {
return Scaffold(
appBar:
AppBar(title: Text(TextKey.shenQingQuanXian.tr), elevation: 1),
body: SafeArea(
child: _buildView(),
),
);
},
);
}
}