状态管理框架 Get的使用
目录
状态管理框架 Get的使用
GetMaterialApp
路由的注册
路由的跳转
middlewares的使用
组件使用 defaultDialog bottomSheet snackbar
状态刷新有很多种方式
ValueBuilder
Obx 基础使用
是时候引入GetxController, 也是Get里面的常用的
GetX
优化控制器的使用 put
优化控制器的使用find
优化控制器的使用 懒加载lazyput
GetView
接下来GetConnect就是网络请求的使用
GetConnect
GetConnectStatemixin
GetConnectDio
切换主题
多语言切换
常使用的Getx自带的API
不得不说,GetX使你的代码量减少了许多, 一个obs. 一个obx. 搞定你的数据监听刷新, 给你不一样的简洁。
GetMaterialApp
return GetMaterialApp(
title: 'Flutter Demo',
getPages: MyRoutes.routes, //注册路由
// initialRoute: MyHomePage.route, //该属性, 设置有有问题, 在自定义转场动画的时候, 跳转的时候右边会黑屏
unknownRoute: MyRoutes.notFoundPage, //默认404的路由地址
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const MyHomePage(),
);
// initialRoute: MyHomePage.route,//该属性,在自定义转场动画的时候, 跳转的时候右边会黑屏
路由的注册
class MyRoutes {
static List<GetPage> routes = [
//正常注册
GetPage(name: MySettingPage.route, page: () => const MySettingPage()),
GetPage(name: MyHomePage.route, page: () => const MyHomePage(),
children: [
//children 这里可以注册子页面, 一级, 二级, 三级, 页面都可以
GetPage(name: MyProductPage.route, page: () => const MyProductPage(),
children: [
GetPage(name: MyProductDetailPage.route, page: () => const MyProductDetailPage()),
GetPage(name: MySettingPage.route, page: () => const MySettingPage()),
GetPage(name: MySettingPage.routeID, page: () => const MySettingPage()),
]),
]),
];
}
路由的跳转
//使用路由String跳转, 这样写, 注册的时候 /MyProductPage 必须要在 MyHomePage 的children: [ 里面] 如上展示
Get.toNamed("/MyHomePage/MyProductPage");
Get.toNamed("/MyHomePage/MyProductPage/MySettingPage");
//直接跳转对应的页面
Get.to(const MyProductPage());
//使用路由跳转并传值
Get.toNamed("/MyHomePage/MyProductPage/MySettingPage?id==345");
//返回上一个页面, 并传值, 接收参数的 var result = await Get.toNamed("/MyHomePage/MyProductPage/MySettingPage?id==345");
Get.back(result: {"success": true}),
//特殊路由传值 需要定义路由: "/MySettingPage/:id";, 才能使用下方跳转传值
Get.toNamed("/MyHomePage/MyProductPage/MySettingPage/8910");
//跳转后, 消除上页面的堆栈
Get.off(const MyProductDetailPage());
Get.offNamed(MyProductDetailPage.route);
//跳转后, 消除所有的堆栈
Get.offAll(const MyProductDetailPage());
Get.offAllNamed(MyProductDetailPage.route);
middlewares的使用
一个类似拦截器的功能,可以传入多个,需要自定研究下优先级。
GetPage(name: MyMinePage.route, page: () => const MyMinePage(), middlewares: [ MyRouteAuthMiddleware()]),
class MyRouteAuthMiddleware extends GetMiddleware {
@override
RouteSettings? redirect(String? route) {
// 加入 AuthService 这里可以判断下用户是否登录, 如果true
return super.的方法
//否则去都登陆页面
Future.delayed(const Duration(seconds: 1), () => Get.snackbar("提示", "请先登录APP"));
return const RouteSettings(name: MyLoginPage.route);
}
}
组件使用 defaultDialog bottomSheet snackbar
Get.snackbar
Get.bottomSheet
Get.defaultDialog
自行调用, 没有难度
状态刷新有很多种方式
ValueBuilder
爱了, 爱了, 直接导入头文件, 只刷新该作用域的.
ValueBuilder<List<String>?>(
initialValue: const ["A", "B", "C"],
builder: (value, updateFn) {
return Column(children: [
Text("List -> $value"),
ElevatedButton(
onPressed: () {
List<String> newList = [...value!]; // 使用扩展运算符来创建列表的浅拷贝
newList.add("${newList.length}");
// newList.removeAt(1); // 移除索引为1的元素
newList.shuffle();//元素随机
updateFn(newList); // 使用新列表更新状态
},
child: const Text('ValueBuilder -> add'),
)
]);
}),
Obx 基础使用
比如在Widget, 声明一个属性, 使用的时候使用Obx(()=>{}) 包裹一下,然后在其他地方点击,修改count值就OK了, 因为增加了obs, 就成了, count.value++ 来修改值
//Int
RxInt count = 0.obs;
var count1 = 0.obs;
Obx(() => Column(
children: [
Text("int -> $count"),
Text("int -> $count1"),
])
//刷新
updateCount() {
count.value++;
count1.value++;
}
在监听list. map. 枚举 的时候要注意, 一定要从新赋值才会刷新
//刷新枚举
void updateViewState(ViewState newValue) {
viewState.value = newValue; // 更新枚举值
}
//刷新list
updateList() {
list[0] = "BMW";
list1[0] = "BMW";
}
//刷新Map
updateMap() {
map["Audi"] = "BMW";
map1["Audi"] = "BMW";
}
//刷新List中的Model
updateModels() {
models[0] = GetXModels(name: "BMW");
models1[0] = GetXModels(name: "BMW");
}
//刷新Model
updateModel() {
model.value = GetXModels(name: "BMW");
model1.value = GetXModels(name: "BMW");
}
使用的地方一地个要加上Obx()
是时候引入GetxController, 也是Get里面的常用的
GetX<MyGetxController>
可以包裹引用Controller值的地方, 进行局部刷新, 还有声明周期的回调 推荐 ☆☆☆
//要创建控制器Controller
class MyPutController extends GetxController {}
//在widget的build方法中
final MyGetxController getxController = MyGetxController();
//使用例子
ListTile(
title: const Text("update list"),
subtitle: GetX<MyGetxController>(
init: getxController,
initState: (_) {},
builder: (_) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('我要买${getxController.list.toString()}'),
Text('我要买${getxController.list1.toString()}'),
],
);
},
),
onTap: () {
//list, 要修改list里面的元素, 才会去更新UI
getxController.updateList();
},
),
优化控制器的使用 put
//在Widget的build方法里面我们注册一个controller 推荐 ☆☆☆
final MyPutController countController = Get.put<MyPutController>(MyPutController());
在使用过程中使用Obx(())包裹即可,
ListTile(
title: const Text("update Count"),
subtitle: Obx(() => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('count = ${countController.count.value}'),
Text('count = ${countController.count1.value}'),
],
)),
onTap: () {
countController.updateCount();
},
),
优化控制器的使用find
//在put过MyProductController()的Widget的build方法里面即二级页面, 三级页面....子页面
final MyProductController productController = Get.find<MyProductController>();
在使用过程中使用Obx(())包裹即可, 也可以使用GetX<MyProductController>build 也可以
ListTile(
title: Text("传值 Get.arguments; = ${details.toString()} parameters == ${parameters.toString()}"),
subtitle: Obx(() => Text(productController.myProductList.length.toString())),
onTap: () {
productController.addProduct(MyProduct(name: 'iPhone 16', description: 'APPle 设备', price: 8199));
productController.addCount();
}),
优化控制器的使用 懒加载lazyput
这里就要引出Binding的使用了
class MyGetLazyPutBinding implements Bindings {
@override
void dependencies() {
Get.lazyPut(() => MyGetLazyPutController());
}
}
// MyGetLazyPutController 上面说的controller. 必须创建, 切继承GetxController
//注册路由的时候
GetPage(name: MyLazyPutPage.route, page: () => const MyLazyPutPage(),binding: MyGetLazyPutBinding()),
//在Widget页面的时候就,不用调用Getx.put的方法, 会自动Put
//直接去find找到该Controller
final MyGetLazyPutController getLazyPutController = Get.find<MyGetLazyPutController>();
//在使用过程中使用Obx(())包裹即可, 也可以使用GetX<MyProductController>build 也可以
//建议自己撸一遍代码, 会印象更加深刻
GetView
这个更加简单, 省略put, find 的步骤, 不过还是要一个Controller的
//创建Widget的时候,需要增加一个泛型
class MyGetViewPage extends GetView<MyGetLazyPutController>
//上面的路由注册, 还是要注册一个Controller
GetPage(name: MyLazyPutPage.route, page: () => const MyLazyPutPage(),binding: MyGetLazyPutBinding()),
//在widget中, 就自带了controller的变量 ,可以翻看源码中
abstract class GetView<T> extends StatelessWidget {
const GetView({Key? key}) : super(key: key);
final String? tag = null;
T get controller => GetInstance().find<T>(tag: tag)!;
@override
Widget build(BuildContext context);
}
//就会知道, 内部帮忙find_Controller
接下来GetConnect就是网络请求的使用
GetConnect
这里跟官网的不太一样, 官网我感觉有点麻烦, 创建那么多的文件, 其实就是请求层, 数据层, 页面布局划分就好了
//1.我们先继承GetConnect 设置下你的请求相关配置
//如: baseUrl headers 以及其他设置
class MyBaseHttp extends GetConnect {
@override
void onInit() {
httpClient.baseUrl = "xxxxxx";
// 请求拦截
httpClient.addRequestModifier<void>((request) {
Map<String, String> headerMap = {
"os-type": GetPlatform.isIOS ? "ios" : "android",
"timestamp": DateTime.now().microsecondsSinceEpoch.toString(),
};
request.headers.addAll(headerMap);
return request;
});
// 响应拦截
httpClient.addResponseModifier((request, response) {
return response;
});
}
}
//2.然后继承 MyBaseHttp, 创建你的serviceController
class MyServiceController extends MyBaseHttp {
//获取数据, 自定义数据
Future<MyGetConnectModel> getContent() async {
Response response = await get('/route/external_link.json'); //这里去请求的, 有post. put. 自己看下API
if (response.statusCode == 200) {
debugPrint(response.bodyString);
return MyGetConnectModel(userId: 1, id: 2, title: 'title', body: 'body');
} else {
debugPrint(response.bodyString);
return MyGetConnectModel(userId: 1, id: 2, title: 'title', body: 'body');
}
}
}
//3.创建页面的controller
class MyGetConnectController extends GetxController {
// 获取实例
final MyServiceController serviceController = Get.find<MyServiceController>();
// model
late MyGetConnectModel getConnectModel;
//监听状态, 这里是一个枚举, 这里你也可以监听其他的,比如model的变化
Rx<ViewState> viewState = ViewState.normal.obs;
getData() async {
updateViewState(ViewState.loading);
getConnectModel = await serviceController.getContent();
updateViewState(ViewState.normal);
}
void updateViewState(ViewState newValue) {
viewState.value = newValue; // 更新枚举值
}
}
//4.注册
class MyGetContentBuinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => MyGetConnectService());
Get.lazyPut(() => MyGetConnectController());
}
}
//路由binding
GetPage(name: MyGetContentPage.route, page: () => const MyGetContentPage(),binding: MyGetContentBuinding()),
//5.创建Widget,布局
方式1: class MyGetContentPage extends GetView<MyGetConnectController> {}
方式2: class MyHomePage extends StatelessWidget{
final controller = Get.find<MyGetConnectController>();}
//数据刷新
GetX<MyGetConnectController>(initState: (state) {
controller.getData();
}, init: controller,
builder: ((_) {
debugPrint("controller.viewState.value: ${controller.viewState.value}")
switch (controller.viewState.value) {
case ViewState.loading:
return const Center(
child: CircularProgressIndicator(),
);
default:
return _buildListView(controller.getConnectModel);
}
})),
或者
Obx(()=>)
更新值的时候, 需要 controller哟
GetConnectStatemixin
//跟上面基本一致, 这里说下不同点
//创建页面控制器的时候需要增加监听的类型数据
class MyGetConnectStateMixinController extends GetxController with StateMixin<List<MyGetConnectModel>> { }
//List<MyGetConnectModel>就是我要监听的数据类型
//创建页面方式
class MyGetConnectStateMixinPage extends GetView<MyGetConnectStateMixinController> {}
也可以使用find的方式找到该 MyGetConnectStateMixinController
然后通过controller.obx 进行监听处理, 有多种状态,
final bool isLoading;
final bool isError;
final bool isSuccess;
final bool isEmpty;
final bool isLoadingMore;
final String? errorMessage;
// 是在页面的MyGetConnectStateMixinController 控制器里面进行定义的.相关代码
@override
void onInit() {
fetchList(); //获取数据
super.onInit();
}
// 拉取数据列表
Future<void> fetchList() async {
// 获取数据, 也可以处理好处理传过来,
final Response response = await connectStateMixinService.getContent(); //通过find找到该控制器 connectStateMixinService, 是继承上面的MyBaseHttp的请求类控制器
// 判断请求是否有错误
if (response.hasError) {
// 改变数据,传入错误状态,在ui中会处理这些错误
//返回状态的定义
// change(null, status: RxStatus.error(response.statusText));
// change(null, status: RxStatus.empty());
} else {
// 成功,自定义数据,改变状态为成功
List<MyGetConnectModel> dataList = [];
for (var i = 0; i < 10; i++) {
MyGetConnectModel getConnectModel = MyGetConnectModel(userId: 12312, id: 231231, title: "title", body: "body");
dataList.add(getConnectModel);
}
change(dataList, status: RxStatus.success());
}
//页面布局Widget, 根据不同的状态, 可以写不同的结果
controller.obx(
(state) {
return ListView.separated(
itemCount: state!.length,
itemBuilder: (context, index) {
final MyGetConnectModel getConnectModel = state[index];
return ListTile(
onTap: () {},
title: Text(getConnectModel.title),
trailing: Text("\$${getConnectModel.body}"),
);
},
separatorBuilder: (BuildContext context, int index) {
return const Divider();
},
);
},
onError: (error) {
return Center(
child: Text(error.toString()),
);
},
onLoading: const SizedBox(
width: double.infinity,
height: double.infinity,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
CircularProgressIndicator(),
SizedBox(height: 10),
Text(
"疯狂加载中...",
style: TextStyle(color: Colors.blue, fontSize: 16),
),
],
),
),
onEmpty: const Center(
child: Text("没有数据"),
),
));
GetConnectDio
需要自行导入Dio的框架
把上面的 GetConnect 部分中的请求部分, 更换为Dio请求, 然后使用Rx定义,监听的变量。
//在Widget中, 通过Obx, 或者GetX<MyGetConnectController> 去刷新数据即可
class MyGetConnectDioPage extends GetView<MyGetConnectDioController>
切换主题
return Scaffold(
appBar: AppBar(
title: const Text("切换主题"),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Get.changeTheme(Get.isDarkMode ? ThemeData.light() : ThemeData.dark());
},
child: Text("是否黑色主题 -> ${Get.isDarkMode}"),
)),
);
多语言切换
class MyTranslationService extends Translations {
static Locale? get locale => Get.deviceLocale;
static const fallbackLocale = Locale('en', 'US');
@override
Map<String, Map<String, String>> get keys => {
'en_US': enUS,
'zh_Hans': zhHans,
'zh_HK': zhHK,
};
}
//enUS zhHans zhHK 要去创建文件 如:
const Map<String, String> zhHans = {
'title': '这是标题',
'login': '登录用户 @name,邮箱账号 @email',
};
GetMaterialApp下:
locale: MyTranslationService.locale,
fallbackLocale: MyTranslationService.fallbackLocale,
translations: MyTranslationService(),
// 使用
Text(
"title -> ${'title'.tr}"),
Text(
"login -> ${'login'.trParams({'name': 'xxx', 'email': 'xxx@gmail.com'})}"),
//使用tr. trParams 来处理多语言
//设置多语音
var locale = const Locale('zh', 'HK');
Get.updateLocale(locale);
常使用的Getx自带的API
Get.arguments
//给出以前的路由名称
Get.previousRoute
// 给出要访问的原始路由,例如,rawRoute.isFirst()
Get.rawRoute
// 允许从GetObserver访问Rounting API。
Get.routing
// 检查 snackbar 是否打开
Get.isSnackbarOpen
// 检查 dialog 是否打开
Get.isDialogOpen
// 检查 bottomsheet 是否打开
Get.isBottomSheetOpen
// 删除一个路由。
Get.removeRoute()
//反复返回,直到表达式返回真。
Get.until()
// 转到下一条路由,并删除所有之前的路由,直到表达式返回true。
Get.offUntil()
// 转到下一个命名的路由,并删除所有之前的路由,直到表达式返回true。
Get.offNamedUntil()
//检查应用程序在哪个平台上运行。
GetPlatform.isAndroid
GetPlatform.isIOS
GetPlatform.isMacOS
GetPlatform.isWindows
GetPlatform.isLinux
GetPlatform.isFuchsia
//检查设备类型
GetPlatform.isMobile
GetPlatform.isDesktop
//所有平台都是独立支持web的!
//你可以知道你是否在浏览器内运行。
//在Windows、iOS、OSX、Android等系统上。
GetPlatform.isWeb
// 相当于.MediaQuery.of(context).size.height,
//但不可改变。
Get.height
Get.width
// 提供当前上下文。
Get.context
// 在你的代码中的任何地方,在前台提供 snackbar/dialog/bottomsheet 的上下文。
Get.contextOverlay
// 注意:以下方法是对上下文的扩展。
// 因为在你的UI的任何地方都可以访问上下文,你可以在UI代码的任何地方使用它。
// 如果你需要一个可改变的高度/宽度(如桌面或浏览器窗口可以缩放),你将需要使用上下文。
context.width
context.height
// 让您可以定义一半的页面、三分之一的页面等。
// 对响应式应用很有用。
// 参数: dividedBy (double) 可选 - 默认值:1
// 参数: reducedBy (double) 可选 - 默认值:0。
context.heightTransformer()
context.widthTransformer()
/// 类似于 MediaQuery.of(context).size。
context.mediaQuerySize()
/// 类似于 MediaQuery.of(context).padding。
context.mediaQueryPadding()
/// 类似于 MediaQuery.of(context).viewPadding。
context.mediaQueryViewPadding()
/// 类似于 MediaQuery.of(context).viewInsets。
context.mediaQueryViewInsets()
/// 类似于 MediaQuery.of(context).orientation;
context.orientation()
///检查设备是否处于横向模式
context.isLandscape()
///检查设备是否处于纵向模式。
context.isPortrait()
///类似于MediaQuery.of(context).devicePixelRatio。
context.devicePixelRatio()
///类似于MediaQuery.of(context).textScaleFactor。
context.textScaleFactor()
///查询设备最短边。
context.mediaQueryShortestSide()
///如果宽度大于800,则为真。
context.showNavbar()
///如果最短边小于600p,则为真。
context.isPhone()
///如果最短边大于600p,则为真。
context.isSmallTablet()
///如果最短边大于720p,则为真。
context.isLargeTablet()
///如果当前设备是平板电脑,则为真
context.isTablet()
///根据页面大小返回一个值<T>。
///可以给值为:
///watch:如果最短边小于300
///mobile:如果最短边小于600
///tablet:如果最短边(shortestSide)小于1200
///desktop:如果宽度大于1200
context.responsiveValue<T>()
状态管理框架 Provider 和 Get 的Likes, 各有千秋