在上一篇《Flutter中使用GetX从0到1搭建自己的项目框架(二)》中,我们学习了如何注册路由和使用路由的相关方法。今天,我们将继续深入,探讨如何构建页面。页面是应用程序中最基本的组件之一,它展示了用户界面的内容和交互。在Flutter中,使用GetX框架可以轻松构建页面,并实现页面间的状态管理和数据传递。让我们开始编写一个简单的页面。
下面我们就以登录页面为例展示如何使用GetX 的Controller来构建一个mvvm模式的应用。
先来看一下Example的项目目录是如何划分的:
在应用中有很多不同的业务模块我们统称为module,在每个module下我们又为具体的业务划分了login/controller login/view login/model
下面我们一起来看一下登录模块是怎么实现的;
login/view/login_page.dart
class LoginPage extends EdenBaseWidget<LoginController> with LoginView {
LoginPage({Key? key}) : super(key: key);
@override
String toolbarTitle() {
return "Login";
}
void _onLogin() {
showLoading(controller.onLogin()).then((value) {
if (value == true) {
EdenRoute.back();
} else {}
});
}
@override
Widget buildBody(BuildContext context, LoginController _controller) {
return renderView(context, _controller, _onLogin);
}
}
LoginPage中继承了flutter_eden中EdenBaseWidget<T>父类;
在EdenBaseWidget<T>中我们对GetX GetView<T>进行了封装;
页面的具体实现在GetBuilder<T>中:
@override
Widget build(BuildContext context) {
return GetBuilder<T>(
init: controller,
builder: (controller) {
return Scaffold(
appBar: appToolbar(context),
body: buildBody(context, controller),
backgroundColor: getBackgroundColor(),
floatingActionButton: floatingActionButton(),
bottomNavigationBar: bottomNavigationBar(),
);
});
}
来看一下具体的代码实现:
abstract class EdenBaseWidget<T extends EdenBaseController> extends GetView<T> {
EdenBaseWidget({Key? key}) : super(key: key);
///toolbar title
String? _toolbarTitle;
///hide toolbar
bool hideToolbar() {
return false;
}
/// hide toolbar arrow
bool hideToolbarArrowBack() {
return false;
}
///tool title
String toolbarTitle();
Color? getBackgroundColor() {
return kIsDark ? backgroundColor : backgroundLightColor;
}
void setToolbarTitle(String? toolbarTitle) {
_toolbarTitle = toolbarTitle;
}
/// toolbar title color
Color? _toolbarTitleColor;
Color? get toolbarTitleColor => _toolbarTitleColor;
void setToolbarTitleColor(Color? titleColor) {
_toolbarTitleColor = titleColor;
}
/// toolbar background color
Color? _toolbarBackgroundColor =
kIsDark ? toolBarbgColor : toolBarbgLightColor;
Color? get toolbarBackgroundColor => _toolbarBackgroundColor;
void setToolbarBackgroundColor(Color? backgroundColor) {
_toolbarBackgroundColor = backgroundColor;
}
List<Widget>? toolbarActions() {
return [];
}
///toolbar arrow back
Widget toolbarArrowBack() {
return IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () {},
);
}
///
PreferredSizeWidget? appToolbar(BuildContext context) {
return hideToolbar()
? null
: ToolbarWidget(
centerTitle: true,
backgroundColor: toolbarBackgroundColor,
hideBackArrow: hideToolbarArrowBack(),
title: _toolbarTitle ?? toolbarTitle(),
color: toolbarTitleColor,
actions: toolbarActions(),
fontSize: 35.rpx,
fontWeight: FontWeight.w500,
);
}
///build body
Widget buildBody(BuildContext context, T _controller);
///bottom navigation bar
Widget? bottomNavigationBar() {
return null;
}
Widget? floatingActionButton() {
return null;
}
@override
Widget build(BuildContext context) {
return GetBuilder<T>(
init: controller,
builder: (controller) {
return Scaffold(
appBar: appToolbar(context),
body: buildBody(context, controller),
backgroundColor: getBackgroundColor(),
floatingActionButton: floatingActionButton(),
bottomNavigationBar: bottomNavigationBar(),
);
});
}
}
EdenBaseWidget<T>它继承自 GetView<T>需要是 EdenBaseController 或其子类封装了一个通用的基础 Widget,提供了一些常用的功能和配置项,用于构建应用程序中的页面。
主要特点和方法包括:
_toolbarTitle:工具栏的标题。hideToolbar():是否隐藏工具栏。hideToolbarArrowBack():是否隐藏工具栏的返回箭头。toolbarTitle():工具栏的标题文本。_toolbarTitleColor:工具栏标题的颜色。_toolbarBackgroundColor:工具栏的背景颜色。toolbarActions():工具栏的操作按钮。toolbarArrowBack():工具栏的返回箭头按钮。appToolbar(BuildContext context):构建应用程序的工具栏部分。buildBody(BuildContext context, T _controller):构建页面的主体部分。bottomNavigationBar():底部导航栏部分。floatingActionButton():浮动操作按钮部分。build(BuildContext context):构建整个页面的方法。
该抽象类的目的是为了提供一个可扩展的基础 Widget,供具体页面继承并实现其中的抽象方法,以定制化页面的功能和外观。
通过使用该抽象类,可以减少重复的代码,统一页面的样式和布局,提高代码的复用性和可维护性。
LoginPage实现了EdenBaseWidget<LoginController> 的buildBody()方法,并混入了LoginView的renderView()方法
@override
Widget buildBody(BuildContext context, LoginController _controller) {
return renderView(context, _controller, _onLogin);
}
LoginView登录页面的具本实现:
class LoginView {
Widget renderView(
BuildContext context, LoginController _controller, Function() onLogin) {
return SingleChildScrollView(
child: form(context, _controller, onLogin),
);
}
///
Widget form(
BuildContext context, LoginController _controller, Function() onLogin) {
return Padding(
padding: const EdgeInsets.all(dimens.margin),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const SizedBox(height: 20),
const LogoWidget(
small: true,
),
const SizedBox(height: 20),
InputWidget(
placeholder: "login",
// value: snapshot.data,
onChanged: (value) {
_controller.setUsername(value);
},
),
const SizedBox(height: 10),
InputWidget(
placeholder: "password",
// value: snapshot.data,
onChanged: (value) {
_controller.setPassword(value);
},
),
const SizedBox(height: 20),
Align(
alignment: Alignment.centerRight,
child: TextButton(
onPressed: () {},
child: const TextWidget(
text: "sign up",
),
)),
const SizedBox(height: 12),
SizedBox(
width: double.infinity,
child: ButtonWidget(
label: "login",
onPressed: () {
onLogin();
},
),
),
],
),
),
);
}
}
接下来我们来看一下Login模块比较重要LoginController类是怎么实现的:
class LoginController extends EdenBaseController {
final ILoginRespository loginRespository;
LoginController({required this.loginRespository});
String? _username;
String? _password;
String? get username => _username;
String? get password => _password;
@override
void init() {}
void setUsername(String? username) {
_username = username;
}
void setPassword(String? password) {
_password = password;
}
Future<bool?> onLogin() async {
FocusScope.of(Get.context!).requestFocus(FocusNode());
print("onLogin-username=$username -password=$password");
return await loginRespository.doLogin(username, password).then((value) {
return _loginSuccess(value);
}).catchError((error) {
return _loginFailure(error);
}).then((value) async {
print("auth refresh=$value");
if (value == true) {
await Get.find<AuthService>().init();
AccountController.to.update();
}
return value;
});
}
bool? _loginSuccess(LoginModel? login) {
print("loginModel=${login?.toJson()}");
if (login?.resCode == 200) {
StorageHelper.set(StorageKeys.token, login?.data?.accessToken);
StorageHelper.set(Constants.USER_INFO, login?.toRawJson());
return true;
} else {
EdenSnackbar("${login?.message}", title: "Message");
return false;
}
}
bool _loginFailure(dynamic error) {
print("error=$error");
EdenSnackbar(error.toString(), title: "Error");
return false;
}
@override
void dealloc() {}
}
它继承自 EdenBaseController
主要功能:
ILoginRespository loginRespository:用于处理登录相关操作的仓库接口。- 构造函数
LoginController({required this.loginRespository}):接收一个loginRespository参数,并将其赋值给类的成员变量。 _username和_password:保存用户名和密码的私有变量。username和password:通过 getter 方法获取用户名和密码的值。init()方法:重写基类的init()方法,在初始化阶段执行逻辑(这里为空)。setUsername(String? username)和setPassword(String? password):设置用户名和密码的方法。onLogin()方法:执行登录操作的逻辑。在该方法中,先隐藏键盘,然后打印用户名和密码。接着调用loginRespository的doLogin方法进行登录,处理登录成功和失败的情况,并返回一个Future<bool?>对象。在登录成功后,保存用户的访问令牌和其他信息,并返回true,否则返回false。_loginSuccess(LoginModel? login)方法:处理登录成功的情况。如果登录的返回码为 200,将用户的访问令牌和其他信息存储在本地,并返回true,否则显示登录失败的提示,并返回false。_loginFailure(dynamic error)方法:处理登录失败的情况。显示错误提示,并返回false。dealloc()方法:重写基类的dealloc()方法,在销毁阶段执行逻辑(这里为空)。
该 LoginController 类的目的是作为登录页面的控制器,处理登录逻辑和状态,并与仓库层进行交互,实现登录功能。
EdenBaseController的实现:
abstract class EdenBaseController extends GetxController {
bool? _isRequest = false;
bool? get isRequest => _isRequest;
Future futureDelayed(
Future computation, {
int seconds = 1,
}) {
return Future.delayed(
Duration(
seconds: seconds,
),
() => computation);
}
@override
void onInit() {
super.onInit();
init();
if (isRequest == true) {
showLoading(doRequest(true));
}
}
@override
void onClose() {
super.onClose();
dealloc();
}
void init();
void dealloc();
Future doRequest(bool isPull) async {}
void hideKeyboard() {
FocusScope.of(kContext!).requestFocus(FocusNode());
}
///
S edenFind<S>({String? tag}) => Get.find<S>(tag: tag);
}
EdenBaseController 它继承自 GetxController。
主要功能:
_isRequest:表示是否正在进行请求的私有变量,默认为false。isRequest:通过 getter 方法获取_isRequest的值。futureDelayed(Future computation, {int seconds = 1}):延迟执行给定的计算任务,返回一个Future对象。onInit()方法:重写基类的onInit()方法,在初始化阶段执行逻辑。调用init()方法进行初始化,并根据isRequest的值显示加载状态。onClose()方法:重写基类的onClose()方法,在关闭阶段执行逻辑。调用dealloc()方法进行清理操作。init()方法:抽象方法,需要在具体的子类中实现。用于执行初始化逻辑。dealloc()方法:抽象方法,需要在具体的子类中实现。用于执行清理操作。doRequest(bool isPull)方法:抽象方法,需要在具体的子类中实现。用于执行请求操作,具体的实现可以根据参数isPull判断是下拉刷新还是加载更多。hideKeyboard()方法:隐藏键盘的方法。edenFind<S>({String? tag})方法:通过 GetX 的Get.find()方法获取指定类型的实例对象。可以通过指定tag来获取特定的实例。
EdenBaseWidget 和 EdenBaseController 是用于构建基于 GetX 状态管理的 Widget 和 Controller 的抽象类:
EdenBaseWidget:
- EdenBaseWidget 是一个抽象类,用于创建具有一致性和可定制外观的 Widget。
- 提供了设置工具栏标题、背景色、文字颜色等属性的方法,以便根据需要进行定制。
- 可以通过重写方法 hideToolbar() 和 hideToolbarArrowBack() 来控制是否隐藏工具栏和返回箭头。
- 通过实现 toolbarTitle() 方法来设置工具栏标题,或通过 setToolbarTitle() 方法动态设置标题。
- 可以通过重写 toolbarActions() 方法添加工具栏操作按钮。
- 提供了 appToolbar() 方法来构建应用程序的顶部工具栏,可以根据需求设置背景色、标题颜色和其他属性。
- 通过重写 buildBody() 方法来构建主体内容,可以根据数据状态和业务需求自定义 UI 元素。
- 支持设置底部导航栏和浮动操作按钮。
- 提供了生命周期方法 init() 和 dealloc(),用于在 Widget 的生命周期中执行初始化和资源释放操作。
EdenBaseController:
- EdenBaseController 是一个抽象类,用于管理基于 GetX 的数据状态和业务逻辑。
- 继承自 GetX 的 GetxController 类,提供了强大的状态管理功能。
- 可以定义数据状态,例如 isRequest 属性用于表示是否处于请求状态。
- 提供了 futureDelayed() 方法,用于延迟执行异步操作,方便处理延迟加载等场景。
- 在 onInit() 方法中执行初始化操作,可以在此处调用 init() 方法进行一些初始化设置。
- 在 onClose() 方法中执行资源释放操作,可以在此处调用 dealloc() 方法释放相关资源。
- 提供了 hideKeyboard() 方法,用于隐藏键盘。
- 可以通过 edenFind() 方法来获取依赖对象,方便在 Controller 中访问其他对象或服务。
通过继承 EdenBaseWidget 和 EdenBaseController,开发人员可以实现具体的 Widget 和 Controller,并根据项目需求来定制和扩展其功能。这种抽象类的使用方式可以提高代码的重用性、可维护性和可测试性,同时也有助于保持代码结构的一致性和清晰性。
由于篇幅原因今天就分享到这里,后续会连载!
如果您对此项目感兴趣可以加入一起维护开发!
开源项目地址:flutter_eden
希望对您有所帮助谢谢!!