前言:在前几天的一个回家的晚上,我路过了一家专门领养流浪宠物的小家,我在那里待了2个多小时,我为这些动物感到难过,包括🐱和🐕,那一刻我就想到,许多动物需要一个新家,我就想编写一个app,用于帮助这些动物(虽然很困难哈哈😝)
话不多说,先上效果图:是不是很炫酷~
源码地址:https://github.com/taxze6/flutter_rehoming(希望给点点star⭐)
本文分析重点:
- 基于GetX封装,MVP架构
- 检测是否是第一次登录,是否展示引导页
- 引导页的登录动画处理(封装处理)
- 首页底部特效导航栏(暂无分析,原因有兴趣大家可以看文章的最后)
- 其他的动画效果(暂无分析,原因有兴趣大家可以看文章的最后)
使用插件:
#状态管理 + 路由管理
get: 4.2.0
# 本地存储 + 图片缓存
shared_preferences: ^2.0.6
cached_network_image: ^3.0.0
1.实现基于Getx封装,MVP架构
mvp的全称为Model-View-Presenter,Model提供数据,View负责显示,Controller/Presenter负责逻辑的处理。
举个例子:
使用Getx进行封装:
数据存放:
class SplashModel {
late String Background;
late String SplashText;
late String SplashButtonText;
BuildContext? context = Get.context;
///初始化数据
SplashModel() {
Background = "images/splash/Splash.png";
SplashText = "Rehoming and adoption. You find each other.";
SplashButtonText = "Let's Start";
}
}
在controller中定义SplashModel,使用数据:
class SplashController extends GetxController {
final SplashModel splashModelState = SplashModel();
}
在view中使用控制器:
final controller = Get.put(SplashController());
final state = Get.find<SplashController>().splashModelState;
//使用:
Text(state.SplashText)
2.检测是否是第一次登录,是否展示引导页
这里就需要使用全局信息配置了,然后使用shared_preferences本地持久化
简单的定义用户信息:
UserLoginResponseModel userLoginResponseModelFromJson(String str) => UserLoginResponseModel.fromJson(json.decode(str));
String userLoginResponseModelToJson(UserLoginResponseModel data) => json.encode(data.toJson());
class UserLoginResponseModel {
UserLoginResponseModel({
this.token,
this.name,
this.id,
});
String? token;
String? name;
int? id;
factory UserLoginResponseModel.fromJson(Map<String, dynamic> json) => UserLoginResponseModel(
token: json["token"],
name: json["name"],
id: json["id"],
);
Map<String, dynamic> toJson() => {
"token": token,
"name": name,
"id": id,
};
}
全局配置怎么去做呢?
定义一个Global文件
class Global{}
在这个文件中我们可以进行app的全局配置
例如:
/// 用户配置
static UserLoginResponseModel? profile = UserLoginResponseModel(token: null);
///是否是第一次打开
static bool isFirstOpen = false;
///是否登录
static bool isOfflineLogin = false;
重点来啦!定义一个init方法用于初始化:
static Future init() async{
// 运行初始
WidgetsFlutterBinding.ensureInitialized();
//本地存储初始化
await LoacalStorage.init();
// 读取离线用户信息
var _profileJSON = LoacalStorage().getJSON(STORAGE_USER_PROFILE_KEY);
if (_profileJSON != null) {
profile = UserLoginResponseModel.fromJson(_profileJSON);
isOfflineLogin = true;
}
// android 状态栏为透明的沉浸
if (Platform.isAndroid) {
SystemUiOverlayStyle systemUiOverlayStyle = const SystemUiOverlayStyle(statusBarColor: Colors.transparent);
SystemChrome.setSystemUIOverlayStyle(systemUiOverlayStyle);
}
isFirstOpen = !LoacalStorage().getBool(STORAGE_DEVICE_ALREADY_OPEN_KEY);
if (isFirstOpen) {
LoacalStorage().setBool(STORAGE_DEVICE_ALREADY_OPEN_KEY, true);
}
}
之后我们就可以在main文件中初始化这配置文件
void main() => Global.init().then((value) => runApp(MyApp()));
然后我们就可以进行判断啦:
Obx(() => Scaffold(
body: controller.isloadWelcomePage.isTrue
? SplashPage() //引导页
: Global.isOfflineLogin
? HomePage() //首页
: LoginPage(), //登录页
)
3.引导页的登录动画处理(封装处理)
在上方我们已经进行了SplashModel的数据定义
那么让我们来看看动画吧,因为时MVP架构,所以我们关于AnimationController的处理要放到controller中,我们使controller继承于SingleGetTickerProviderMixin。
class SplashController extends GetxController
with SingleGetTickerProviderMixin {
AnimationController? animationController;
}
然后再初始化动画(记得销毁哦):
@override
void onInit() {
super.onInit();
animationController = AnimationController(
duration: const Duration(milliseconds: 1500), vsync: this);
animationController!.forward();
}
@override
void onClose() {
// TODO: implement onClose
super.onClose();
animationController!.dispose();
}
按钮动画(view):
Widget Splash_Button(BuildContext context) => ScaleTransition(
//动画处理
scale: Tween<double>(begin: 0, end: 1).animate(CurvedAnimation(
parent: controller.animationController!,
curve: const Interval(0.3, 0.6, curve: Curves.fastOutSlowIn),
)),
child: GestureDetector(
onTap: () {
controller.getTo();
},
child: Container(
//按钮
),
),
);
就是这么的简单,如果觉得不够详细可以自己下拉源码查看~
关于引导页的背景,我们这里有个优化的点,提前缓存图片: 在controller中onInit中缓存:
@override
void onInit() {
super.onInit();
_precacheImage(splashModelState.Background, splashModelState.context!);
}
void _precacheImage(String imageUrl, BuildContext context) {
precacheImage(AssetImage(imageUrl), context);
}