GetX是Flutter的轻便而强大的解决方案,它结合了高性能状态管理,智能依赖注入和快速实用的路由管理。
更新时间:4.18
关键词:StatelessWidget、.obs、Obx()
[ 例:一个加法按钮 —— 使用.obs变量和Obx对象构建响应式控件 ]
下面这个例子,是基于可观察的变量和控件Obx构建的一个简单视图,点击按钮可以改变页面中的数字;
该例子表明GetX框架可在页面未进行setState()刷新的情况下,实现由build方法构建的控件的状态更新。
import 'package:flutter/material.dart';
import 'package:get/get.dart';
// ignore: must_be_immutable
class GetXPage extends StatelessWidget {
var count = 0.obs;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Obx(() => Text("$count"))
),
floatingActionButton: FloatingActionButton(
onPressed: () => count++,
child: Icon(Icons.add),
),
);
}
}
如果没有使用GetX框架(即变量count无后缀.obs 或Text没有包含在控件Obx中),则点击按钮+,屏幕中的数字不会增加;这是由于,方法build仅执行了一次,onPressed所引起的 count值的变化 并没有进入控件Text,因而屏幕中的数字显示不会变化。
[ 编写步骤 ]
注:使用动态类型的可观察变量无法用于有类型要求的构造传参;
关键词:.obs、GetX()
[ 例:使用别人家的按钮(一) —— 基于可观察变量的视图-逻辑分离 ]
使用GetX框架可以加载并使用非当前类的控制器来更新当前视图控件的状态,实现 视图与逻辑的分离;
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class Controller {
var count = 0.obs;
void increment() {
count++;
}
}
class TestGetX extends StatelessWidget {
final controller = Controller();
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Padding(
padding: EdgeInsets.all(170),
child: GetX(builder: (_) => Text('clicks: ${controller.count}')),
),
FloatingActionButton(
child: Icon(Icons.add),
onPressed: () => controller.increment(),
),
],
),
);
}
}
通过导包本身即能够在他类构造实例以访问类中的变量和方法;但如果没有使用GetX框架,方法increment所引起的 count的值变化,将无法传递更新到控件,实现视图状态刷新;除了构造实例创建控制器外,该例子的实现还需要将相应变量声明为 “可观察的” 。
[ 编写步骤 ]
1、在控制器中定义 可观察变量 ;
2、将 包含可观察变量的视图控件作为 控件GetX 的子控件;
3、使用控制器更新变量的值,观察视图控件的 状态响应和变化;
注:使用控件GetX的前提是,声明变量可观察,并非定义继承GetxController及使用Get.put进行注入;
关键词:GetxController、update、Get.put()、GetBuilder
()
[ 例:使用别人家的按钮(二) —— 使用GetBuilder构建视图-逻辑分离的响应式控件 ]
使用GetBuilder实现响应控件与使用GetX的差别在于:后者基于变量可观察,而前者则通过 加载指定控制器 来更新控件状态;
import 'package:get/get.dart';
class Controller extends GetxController {
var count = 0;
void increment() {
count++;
update();
}
}
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'test_controller.dart';
class TestGetBuilder extends StatelessWidget {
final controller = Get.put(Controller());
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(170.0),
child: GetBuilder(
builder: (_) => Text(
'clicks: ${controller.count}',
)),
),
FloatingActionButton(
child: Icon(Icons.add),
onPressed: () => controller.increment(),
),
],
),
);
}
}
通过GetBuilder实现响应式控件,控制器必须继承自GetxController,所定义的目标状态变量之后无需后缀".obs ",但需要定义方法update;并且加载指定控制器,不仅需要使用Get.put进行注入,而且GetBuilder还需要通过指定泛型绑定目标注入的控制器。
[ 编写步骤 ]
关键词:Get.put、Get.find
[ 例:最直观的跨组件状态通信 —— 在这个页面触发事件,在另一个页面状态变化 ]
我们在例子-使用别人家的按钮(二) 中,已了解到Get x框架中的Get.put可以用来注入非当前类的控制器;使用GetBuilder构建响应式控件,不仅需要通过Get.put进行控制器注入,还要求控制器必须作为GetxController实现类实例,且控件GetBuilder必须指定了泛型才能够真正获取到控制器,实现在当前视图使用另一个类中定义的业务逻辑对控件状态进行变化和更新;
但Get.find使这些步骤得到了魔术般的简化:使用Get.find能够发现并获取到已经通过Get.put注入的控制器,它不仅并不要求控制器来自GetxController的实现类,也无须使用Obx、GetX、GetBuilder等控件 —— 通过Get.find获取的控制器可以直接应用于当前Widget下的所有子路由,实现真正意义上的跨组件状态通信。
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class Controller {
var count = 0;
}
class FirstPage extends StatelessWidget {
final controller = Get.put(Controller());
@override
Widget build(BuildContext context) {
return RaisedButton(
child: Text('Next Route'),
onPressed: () {
controller.count++;
},
);
}
}
class SecondPage extends StatelessWidget {
final Controller ctrl = Get.find();
@override
Widget build(context) {
return Scaffold(body: Center(child: Text("${ctrl.count}")));
}
}
class HomePage extends StatelessWidget {
@override
Widget build(context) => Scaffold(
body: SlidePage(),
);
}
// ignore: must_be_immutable
class SlidePage extends StatelessWidget {
List pages = [FirstPage(), SecondPage()];
@override
Widget build(BuildContext context) {
return PageView.custom(
scrollDirection: Axis.vertical,
childrenDelegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return pages[index];
},
childCount: 2,
),
);
}
}
使用Get.find的局限在于,它必须应用在两个或两个以上的widget中,若在同一widget中将无法实现视图的状态响应和刷新,这是因为,Get.find的使用前提是:获取已经通过Get.put在另一个widget完成了注入的控制器。
[ 编写步骤 ]
1、定义业务逻辑类Controller
2、使用 Get.put 注入控制器,通过控制改变count数据
使用StatelessWidget创建界面
创建控制器:在Get.put()构造Controller实例controller,使后者对当下所有子路由可用;
3、使用 Get.find 获取控制器,共享其中的数据状态
调用Get.find从另一个Widget中即页面FirstPage中,发现已注入的控制器;
Get作为GetInterface子类私有对象的引用,调用父类拓展中的find,等同于一个GetInstance调用find
通过find方法初始化控制器,并将其保存为ctrl;然后通过ctrl共享变量count中的数据状态;
4、实现跨页面的状态联动:创建滑动页面,点击页面FirstPage中的Button,页面SecondPgae中展示的数值将发生变化;
[ 例:使用Get.to实现路由跳转 ]
import 'package:flutter/material.dart';
import 'package:get/get.dart';
void main() {
runApp(GetMyApp());
}
// 使用Get.to来进行页面路由,需要在GetMaterialApp下构建控件树
class GetMyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GetMaterialApp(
home: GetPage(),
// SlidePage(),
);
}
}
// 创建Get式可路由页面
class GetPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 通过RaisedButton按钮onPressed事件触发跳转
return RaisedButton(
child: Text('Next Route'),
onPressed: () {
// 将目标调整页面作为参数对象传入Get.to;
Get.to(TargetPage());
},
);
}
}
// 创建目标跳转页
class TargetPage extends StatelessWidget {
@override
Widget build(context) {
return Scaffold(body: Center(child: Text("TargetPage")));
}
}
[ 整合例:使用Get.to + Get.put + Get.find 实现页面跳转计数 ]
import 'package:flutter/material.dart';
import 'package:get/get.dart';
void main() {
runApp(MyApp());
// testPrint();
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GetMaterialApp(
home: GetPageTwo(),
// SlidePage(),
);
}
}
class Controller extends GetxController {
var count = 0;
}
class GetPageTwo extends StatelessWidget {
final controller = Get.put(Controller());
@override
Widget build(BuildContext context) {
return RaisedButton(
child: Text('Next Route'),
onPressed: () {
controller.count++;
Get.to(Second());
},
);
}
}
class Second extends StatelessWidget {
final Controller ctrl = Get.find();
@override
Widget build(context) {
return Scaffold(body: Center(child: Text("${ctrl.count}")));
}
}
Bindings是拥有可伸缩应用程序的第一步;
Bindings是管理依赖注入的类,将依赖注入抽离出widget树,使视图层更纯粹地用于布置widget控件,使代码更整洁、有条理;
注入在Bindings的控制器,允许任何没有上下文的地方被访问;
打开页面上的Bindings文件,将可以清楚地看将注入页面的内容;
import 'package:get/get.dart';
class HomeBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => Controller());
}
}
使用作为私有实例GetInstance的引用Get,能够调用其父类GetInterface拓展中的方法lazyPut
[ 编写步骤 ]
GetView,是具有“ controller”属性的StatelessWidget;
在可伸缩应用程序中使用GetView创建界面;
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'home_controller.dart';
class Home extends GetView {
@override
Widget build(context) => Scaffold(
appBar: AppBar(title: Text("counter")),
body: Center(
child: Obx(() => Text("${controller.count}")),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: controller.increment,
));
}
[ 编写步骤 ]
将GetMaterialApp设置为widget视图顶端;
控件GetPage可以用来构造一个能够设置具体依赖注入绑定的命名路由页面;
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../home_view.dart';
import '../home_bindings.dart';
void main() {
runApp(GetMaterialApp(
initialRoute: '/home',
getPages: [
GetPage(name: '/home', page: () => Home(), binding: HomeBinding()),
],
));
}
[ 编写步骤 ]
1、定义业务逻辑类Controller
import 'package:get/get.dart';
class HomeController extends GetxController {
var count = 0.obs;
void increment() => count++;
}
2、定义Bindings类,注入依赖
import 'package:get/get.dart';
import 'home_controller.dart';
class HomeBinding implements Bindings {
@override
void dependencies() {
Get.lazyPut(() => HomeController());
}
}
3、使用GetView创建界面
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'home_controller.dart';
class Home extends GetView {
@override
Widget build(context) => Scaffold(
appBar: AppBar(title: Text("counter")),
body: Center(
child: Obx(() => Text("${controller.count}")),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: controller.increment,
));
}
4、将GetMaterialApp设置为widget视图顶端;
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../home_view.dart';
import '../home_bindings.dart';
void main() {
runApp(GetMaterialApp(
initialRoute: '/home',
getPages: [
GetPage(name: '/home', page: () => Home(), binding: HomeBinding()),
],
));
}
官方文档例
import 'package:flutter/material.dart';
import 'package:get/get.dart';
void main() {
runApp(GetMaterialApp(
// It is not mandatory to use named routes, but dynamic urls are interesting.
initialRoute: '/home',
defaultTransition: Transition.native,
translations: MyTranslations(),
locale: Locale('pt', 'BR'),
getPages: [
//Simple GetPage
GetPage(name: '/home', page: () => First()),
// GetPage with custom transitions and bindings
GetPage(
name: '/second',
page: () => Second(),
customTransition: SizeTransitions(),
binding: SampleBind(),
),
// GetPage with default transitions
GetPage(
name: '/third',
transition: Transition.cupertino,
page: () => Third(),
),
],
));
}
class MyTranslations extends Translations {
@override
Map> get keys => {
'en': {
'title': 'Hello World %s',
},
'en_US': {
'title': 'Hello World from US',
},
'pt': {
'title': 'Olá de Portugal',
},
'pt_BR': {
'title': 'Olá do Brasil',
},
};
}
class Controller extends GetxController {
int count = 0;
void increment() {
count++;
// use update method to update all count variables
update();
}
}
class First extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.add),
onPressed: () {
Get.snackbar("Hi", "I'm modern snackbar");
},
),
title: Text("title".trArgs(['John'])),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
GetBuilder(
init: Controller(),
// You can initialize your controller here the first time. Don't use init in your other GetBuilders of same controller
builder: (_) => Text(
'clicks: ${_.count}',
)),
RaisedButton(
child: Text('Next Route'),
onPressed: () {
Get.toNamed('/second');
},
),
RaisedButton(
child: Text('Change locale to English'),
onPressed: () {
Get.updateLocale(Locale('en', 'UK'));
},
),
],
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
Get.find().increment();
}),
);
}
}
class Second extends GetView {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('second Route'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
GetX(
// Using bindings you don't need of init: method
// Using Getx you can take controller instance of "builder: (_)"
builder: (_) {
print("count1 rebuild");
return Text('${_.count1}');
},
),
GetX(
builder: (_) {
print("count2 rebuild");
return Text('${controller.count2}');
},
),
GetX(builder: (_) {
print("sum rebuild");
return Text('${_.sum}');
}),
GetX(
builder: (_) => Text('Name: ${controller.user.value.name}'),
),
GetX(
builder: (_) => Text('Age: ${_.user.value.age}'),
),
RaisedButton(
child: Text("Go to last page"),
onPressed: () {
Get.toNamed('/third', arguments: 'arguments of second');
},
),
RaisedButton(
child: Text("Back page and open snackbar"),
onPressed: () {
Get.back();
Get.snackbar(
'User 123',
'Successfully created',
);
},
),
RaisedButton(
child: Text("Increment"),
onPressed: () {
Get.find().increment();
},
),
RaisedButton(
child: Text("Increment"),
onPressed: () {
Get.find().increment2();
},
),
RaisedButton(
child: Text("Update name"),
onPressed: () {
Get.find().updateUser();
},
),
RaisedButton(
child: Text("Dispose worker"),
onPressed: () {
Get.find().disposeWorker();
},
),
],
),
),
);
}
}
class Third extends GetView {
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(onPressed: () {
controller.incrementList();
}),
appBar: AppBar(
title: Text("Third ${Get.arguments}"),
),
body: Center(
child: Obx(() => ListView.builder(
itemCount: controller.list.length,
itemBuilder: (context, index) {
return Text("${controller.list[index]}");
}))),
);
}
}
class SampleBind extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => ControllerX());
}
}
class User {
User({this.name = 'Name', this.age = 0});
String name;
int age;
}
class ControllerX extends GetxController {
final count1 = 0.obs;
final count2 = 0.obs;
final list = [56].obs;
final user = User().obs;
updateUser() {
user.update((value) {
value.name = 'Jose';
value.age = 30;
});
}
/// Once the controller has entered memory, onInit will be called.
/// It is preferable to use onInit instead of class constructors or initState method.
/// Use onInit to trigger initial events like API searches, listeners registration
/// or Workers registration.
/// Workers are event handlers, they do not modify the final result,
/// but it allows you to listen to an event and trigger customized actions.
/// Here is an outline of how you can use them:
/// made this if you need cancel you worker
Worker _ever;
@override
onInit() {
/// Called every time the variable $_ is changed
_ever = ever(count1, (_) => print("$_ has been changed (ever)"));
everAll([count1, count2], (_) => print("$_ has been changed (everAll)"));
/// Called first time the variable $_ is changed
once(count1, (_) => print("$_ was changed once (once)"));
/// Anti DDos - Called every time the user stops typing for 1 second, for example.
debounce(count1, (_) => print("debouce$_ (debounce)"),
time: Duration(seconds: 1));
/// Ignore all changes within 1 second.
interval(count1, (_) => print("interval $_ (interval)"),
time: Duration(seconds: 1));
}
int get sum => count1.value + count2.value;
increment() => count1.value++;
increment2() => count2.value++;
disposeWorker() {
_ever.dispose();
// or _ever();
}
incrementList() => list.add(75);
}
class SizeTransitions extends CustomTransition {
@override
Widget buildTransition(
BuildContext context,
Curve curve,
Alignment alignment,
Animation animation,
Animation secondaryAnimation,
Widget child) {
return Align(
alignment: Alignment.center,
child: SizeTransition(
sizeFactor: CurvedAnimation(
parent: animation,
curve: curve,
),
child: child,
),
);
}
}
官方学习文档:https://pub.flutter-io.cn/packages/get#about-get
编程小白,如有谬误,欢迎指出,由衷感谢!