Flutter项目实战(1):通用项目框架搭建

news/2024/12/29 1:26:07/文章来源:https://www.cnblogs.com/linuxAndMcu/p/18552904

下面介绍 Flutter 最基本的通用项目框架搭建,同时实现了一个登录界面图标和登录界面。

先看下效果图:

Flutter_frame_A.gif


  • 使用ScreenUtilInit自适应界面大小;
  • 使用Stack支持多个子界面在同一个全屏主界面上选择显示;
  • 使用 Get 插件实现界面之间的跳转和国际化翻译;
  • 界面都通过Transform实现了鼠标移动界面;
  • 使用Controller.dart管理所有全局变量和界面控制器;

一、项目目录结构

Flutter_frame_A.png


  • asserts\images:存放图片资源文件的目录;
  • Translation.dart:翻译文件;
  • Controller.dart:全局变量和对应控制器的定义;
  • LogoWidget.dart:入口图标界面;
  • LoginWidget.dart:登录界面;
  • main.dart:主界面;

二、代码实现与分析

2.1 pubspec.yaml

pubspec.yaml的内容如下:

dependencies:flutter:sdk: flutterflutter_screenutil: ^5.9.3get: ^4.6.5flutter:assets:- assets/images/

因为用到了 Get 插件与 ScreenUtilInit,所以需要加上这两种的依赖;另外定义了图片资源文件的路径;


2.2 Translation.dart

实现了中文简体、中文繁体和英文的语言切换,翻译文件如下所示:

import 'package:get/get.dart';// (2)自定义自己的国际化字符串 
class Translation extends Translations {@overrideMap<String, Map<String, String>> get keys => {// 1-配置中文简体'zh_CN': {'登录': '登录','用户协议未选中': '用户协议未选中','请勾选用户协议': '请勾选用户协议','用户名异常': '用户名异常','用户名为空': '用户名为空','密码异常': '密码异常','密码为空': '密码为空','用户名、密码正确': '用户名、密码正确','去登陆': '去登陆','用户': '用户','密码': '密码','同意': '同意','<服务协议>': '<服务协议>','<隐私政策>': '<隐私政策>',},// 2-配置中文繁体'zh_HK': {'登录': '登錄','用户协议未选中': '用戶協議未選中','请勾选用户协议': '請勾選用戶協議','用户名异常': '用戶名異常','用户名为空': '用戶名爲空','密码异常': '密碼異常','密码为空': '密碼爲空','用户名、密码正确': '用戶名、密碼正確','去登陆': '去登陸','用户': '用戶','密码': '密碼','同意': '同意','<服务协议>': '<服務協議>','<隐私政策>': '<隱私政策>',},// 3-配置英文'en_US': {'登录': 'Login','用户协议未选中': 'User agreement not selected','请勾选用户协议': 'Please check the user agreement',      '用户名异常': 'Abnormal username',      '用户名为空': 'The username is empty',      '密码异常': 'Password exception',      '密码为空': 'Password is empty',      '用户名、密码正确': 'The username and password are correct',      '去登陆': 'Go log in',      '用户': 'user',      '密码': 'password',      '同意': 'agree with',      '<服务协议>': '<Service Agreement>',      '<隐私政策>': '<Privacy Policy>',      }};
}

这里使用了 Get 插件方式实现国际化翻译,具体可参考:Flutter插件Get(7):实现语言的国际化 - fengMisaka - 博客园


2.3 Controller.dart

全局变量和对应控制器的定义:

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'LogoWidget.dart';
import 'LoginWidget.dart';// state只专注数据,需要使用数据,直接通过state获取
// logic只专注于触发事件交互,操作或更新数据
// view只专注UI显示// 全局状态
class GlobalState {final screenSize = const Size(1920,1080).obs; // 屏幕尺寸var language = const Locale('zh', 'CN').obs; //语言参数
}// 全局变量控制器
class GlobalController extends GetxController {// 全局变量, 内部调用final GlobalState _globalState = GlobalState();// 获取屏幕尺寸与设置屏幕尺寸的函数Size get screenSize => _globalState.screenSize.value;set screenSize(Size value) => _globalState.screenSize.value = value;  // 获取当前语言与设置当前语言的函数Locale get language => _globalState.language.value;set language(Locale language) => () {_globalState.language.value = language;Get.updateLocale(language);}();
}// 定义全局变量控制器
final GlobalController globalCtrl = Get.put(GlobalController());// 初始化通用配置
void initCommomCfg() {Get.lazyPut<LoginController>(() => LoginController());Get.lazyPut<LogoControl>(() => LogoControl());
}

2.4 LogoWidget.dart

入口图标界面:

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'LoginWidget.dart';// 状态类
class LogoState {final _offset = Offset.zero.obs; // 平移距离final _isVisable = true.obs; // 是否显示的变量final _x = 50.0.obs; // 水平方向的边距final _y = 20.0.obs; // 垂直方向的边距
}// 控制器类
class LogoControl extends LogoState {final _state = LogoState();// 控制函数实现,以下类似Offset get offset => _state._offset.value;set offset(Offset value) => _state._offset.value = value;bool get isVisable => _state._isVisable.value;set setVisable(bool val) => _state._isVisable.value = val;double get x => _state._x.value;set x(double value) => _state._x.value = value;double get y => _state._y.value;set y(double value) => _state._y.value = value;
}class LogoWidget extends StatelessWidget {LogoWidget({super.key});// 实现控制器final LogoControl logoControl = Get.find<LogoControl>();final LoginController loginControl = Get.find<LoginController>();@overrideWidget build(BuildContext context) {return Positioned (right: logoControl.x.w,top: logoControl.y.h,child: Obx(() => Transform.translate ( // 让部件在 x、y 轴上平移指定的距离offset: logoControl.offset, // 平移距离child: GestureDetector( // 手势识别组件behavior: HitTestBehavior.opaque,child: Visibility( // 是一个用于根据布尔值条件显示或隐藏小部件的控件visible: loginControl.isHidden(),maintainState: true,child: MouseRegion( // 以让鼠标移动到该组件上时光标为"选中样式"cursor: SystemMouseCursors.click, // 光标为"选中样式"child: IconButton(mouseCursor: SystemMouseCursors.click,onPressed: null,iconSize: 45.w,icon: Image.asset("assets/images/btn_logo.png"), // 显示图标),),   ),// 按压拖动回调,以支持鼠标移动界面onPanUpdate: (details) {// 通过修改平移距离变量来移动界面logoControl.offset += details.delta;},// 点击事件回调onTap: () {// 显示登录界面loginControl.show();},             ),)));}
}

2.5 LoginWidget.dart

登录界面:

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'Controller.dart';// 状态类
class LoginState {final _isHidden = true.obs; // 是否隐藏final _width = 400.0.obs; // 宽度final _height = 280.0.obs; // 高度final _offset = const Offset(0, 0).obs; // 位置final _isLogined = false.obs;     // 是否登陆完成final _x = 0.0.obs; // 水平方向的边距final _y = 0.0.obs; // 垂直方向的边距
}// 控制器类
class LoginController extends GetxController {final LoginState state = LoginState();double get width => state._width.value;set width(double value) => state._width.value = value;double get height => state._height.value;set height(double value) => state._height.value = value;Offset get offset => state._offset.value;set offset(Offset value) => state._offset.value = value;bool get isLogined => state._isLogined.value;set isLogined(bool flag) => state._isLogined.value = flag;double get x => state._x.value;set x(double value) => state._x.value = value;double get y => state._y.value;set y(double value) => state._y.value = value;  // 是否隐藏bool isHidden() {return state._isHidden.value;}// 显示void show() {state._isHidden.value = false;}// 隐藏void hide() {state._isHidden.value = true;}// 设置窗口显示/隐藏状态void setVisable(bool isVisable){state._isHidden.value = !isVisable;} // 移动void move(double x, double y) {state._offset.value = Offset(x, y);}  // 登陆按钮点击事件login(TextEditingController userNameController,TextEditingController passWordController) {var userName = userNameController.text;var passWord = passWordController.text;// 1-用户协议是否勾选if (!isChecked.value) {Get.snackbar('用户协议未选中'.tr, '请勾选用户协议'.tr, snackPosition: SnackPosition.BOTTOM);return;}// 2-用户名判断if (userName.isEmpty) {Get.snackbar('用户名异常'.tr, '用户名为空'.tr, snackPosition: SnackPosition.BOTTOM);return;}// 3-密码判断if (passWord.isEmpty) {Get.snackbar('密码异常'.tr, '密码为空'.tr, snackPosition: SnackPosition.BOTTOM);return;}Get.snackbar('用户名、密码正确'.tr, '去登陆'.tr, snackPosition: SnackPosition.BOTTOM);}// 用户协议勾选事件var isChecked = false.obs;void changeChecked(bool value) {isChecked.value = value;}
}// 登陆界面
class LoginWidget extends StatelessWidget {LoginWidget({super.key});final userNameController = TextEditingController();final passWordController = TextEditingController();final LoginController controller = Get.find<LoginController>(); // 登录界面控制器  @overrideWidget build(BuildContext context) {return Positioned (left: (globalCtrl.screenSize.width - controller.width)/2,top: (globalCtrl.screenSize.height - controller.height)/2,child: Obx(() => Transform.translate ( // 让部件在 x、y 轴上平移指定的距离offset: controller.offset, // 平移距离child: GestureDetector( // 手势识别组件,以让鼠标移动到该组件上时光标为"选中样式"behavior: HitTestBehavior.opaque,          child: Visibility( // 是一个用于根据布尔值条件显示或隐藏小部件的控件visible: !controller.isHidden(), // 控制是否显示maintainState: true,           child:Container(width: controller.width,height: controller.height,padding: const EdgeInsets.symmetric(horizontal: 0.0),decoration: const BoxDecoration(color: Colors.grey,),                 child: Column(crossAxisAlignment: CrossAxisAlignment.start,children: [// 登录界面标题栏LoginTabBar(),// 距离上一个View距离             const SizedBox(height: 12), // 下方编辑界面buildInputWidget(),],),)),// 按压拖动回调,以支持鼠标移动界面onPanUpdate: (details) {controller.offset += details.delta;}          ))));}// 下方编辑界面Widget buildInputWidget() {return Container(padding: const EdgeInsets.symmetric(horizontal: 16.0), // 两侧边距child: Column(crossAxisAlignment: CrossAxisAlignment.start,        children: <Widget>[TextField(controller: userNameController,decoration: InputDecoration(labelText: '用户'.tr),style: const TextStyle(fontSize: 16),keyboardType: TextInputType.phone,),const SizedBox(height: 12), //距离上一个View距离TextField(controller: passWordController,obscureText: true,decoration: InputDecoration(labelText: "密码".tr),style: const TextStyle(fontSize: 16),),const SizedBox(height: 12), //距离上一个View距离buildPrivacyWidget(), //隐私政策const SizedBox(height: 12), //距离上一个View距离SizedBox(width: controller.width-32,child: ElevatedButton(child: Text('登录'.tr),onPressed: () {debugPrint("ElevatedButton Click");controller.login(userNameController, passWordController);},)),const SizedBox(height: 12), //距离上一个View距离                   ],));} // 隐私协议勾选框Widget buildPrivacyWidget() {return Row(children: [Obx(() => Checkbox(value: controller.isChecked.value,onChanged: (value) => controller.changeChecked(value!))),Text('同意'.tr, style: const TextStyle(fontSize: 14)),Text('<服务协议>'.tr,style: const TextStyle(fontSize: 14, color: Colors.blue)),Text('<隐私政策>'.tr, style: const TextStyle(fontSize: 14, color: Colors.blue))],);}
}// 登录界面标题栏
class LoginTabBar extends StatelessWidget {LoginTabBar({super.key});final LoginController loginCtrl = Get.find<LoginController>();@overrideWidget build(BuildContext context) {return Container(height: 44.h,color: const Color.fromARGB(128, 20, 45, 86),child: Flex(direction: Axis.horizontal,children: <Widget>[const SizedBox(width: 6),SizedBox(width: 40.h,height: 40.h,child: Obx(() => IconButton(onPressed: () {if ( globalCtrl.language == const Locale('zh', 'CN') ){globalCtrl.language = const Locale('zh', 'HK');}else if ( globalCtrl.language == const Locale('zh', 'HK') ){globalCtrl.language = const Locale('en', 'US');}                                                  else if ( globalCtrl.language == const Locale('en', 'US') ){globalCtrl.language = const Locale('zh', 'CN');}                                                  }, icon: () {if(globalCtrl.language ==  const Locale('zh', 'CN')){return Image.asset("assets/images/btn_Chinese_jianti.png", width: 40.w, height: 40.h);}else if(globalCtrl.language == const Locale('zh', 'HK')){return Image.asset("assets/images/btn_Chinese_fanti.png", width: 40.w, height: 40.h);}else if(globalCtrl.language == const Locale('en', 'US')){return Image.asset("assets/images/btn_English.png", width: 40.w, height: 40.h);}else{return const Icon(null);}}(),padding: EdgeInsets.zero,)),),const SizedBox(width: 6),Expanded(flex: 15,child: Text("登录".tr,style: const TextStyle(color: Colors.white,fontSize: 16.0,))),SizedBox(width: 30.h,height: 30.h,child: IconButton(onPressed: () {loginCtrl.hide(); // 隐藏登录界面}, icon: Icon(Icons.close,size: 30.w,),padding: EdgeInsets.zero,),),const SizedBox(width: 6),],),);}
}

2.6 main.dart

主界面:

// ignore_for_file: prefer_const_constructors
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'Controller.dart';
import 'Translation.dart';
import 'LogoWidget.dart';
import 'LoginWidget.dart';// 主函数
main(List<String> args) {// 初始化通用配置initCommomCfg();runApp(MainApp());
}// 主界面
class MainApp extends StatelessWidget {MainApp({super.key});// 各界面的实例final LoginWidget loginWidget = LoginWidget();final LogoWidget logoWidget = LogoWidget();@overrideWidget build(BuildContext context) {return ScreenUtilInit(designSize: Size(1920, 1080),builder: (context, child) {return GetMaterialApp(// 配置GetMaterialApptranslations: Translation(), // 你的翻译locale: const Locale('zh', 'CN'), // 将会按照此处指定的语言翻译fallbackLocale: const Locale('en', 'US'), // 添加一个回调语言选项,以备上面指定的语言翻译不存在debugShowCheckedModeBanner: false,theme: ThemeData(fontFamily: "Ali"),home: Scaffold(backgroundColor: Colors.white,body: Stack( // 使用Stack以同时选择显示多个子界面在同一个主界面中alignment: Alignment.center,children: <Widget>[logoWidget,loginWidget,],),),);},);}
}

三、程序下载

程序下载:Flutter_Demo/CommonFrame-1 at main · confidentFeng/Flutter_Demo


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/836216.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Rocky安装htop

本篇抄的,放在这里防止自己忘记 两条命令: dnf install epel-release -y dnf install htop -yhtop测试: [root@localhost ~]# htop //回车后出现如下图,按q退出

数据采集与融合第四次作业

码云仓库地址 https://gitee.com/sun-jiahui22/crawl_project作业1仓库地址 https://gitee.com/sun-jiahui22/crawl_project/tree/master/作业4/实验4.1作业2的仓库地址 https://gitee.com/sun-jiahui22/crawl_project/tree/master/作业4/实验4.2作业3的仓库地址 https://gitee…

2-SQL注入渗透与攻防

1、SQL注入基础 1.1 什么是sql注入 一、SQL注入概述 二、数据库概述 1.关系型数据库 关系型数据库,存储格式可以直观的反映实体间的关系,和常见的表格比较相似 关系型数据库中表与表之间有很多复杂的关联关系的 常见的关系型数据库有MySQL、Orcale、PostgreSQL、SQL Server等…

vscode go语言注释语句黄色波浪线

go语言注释代码总是飘着黄色波浪线提示:(with optional leading article) (ST1021)go-staticcheck,非常影响观感。 经过查询发现,go-staticcheck 是一个用于 Go 代码静态分析的工具,用来检测代码中的潜在问题、代码规范以及常见的错误。本次错误信息来自 go-staticcheck 中…

1-信息收集

1.1 域名信息 whois、域名反查、ICP备案、企业信息查询 子域名收集工具:Layer子域名挖掘机等 原理:枚举、字典 域名:baidu.com 子域名:news.baidu.com、map.baidu.com、www.baidu.com、... 域名DNS信息 Domain Name Server 域名解析服务www.baidu.com --> 域名解析服务 …

springboot~jpa优雅的处理isDelete的默认值

如果多个实体类都有 isDelete 字段,并且你希望在插入时为它们统一设置默认值,可以采取以下几种方法来减少代码重复: 1. 使用基类(抽象类) 创建一个基类,其中包含 isDelete 字段和 @PrePersist 方法。然后让所有需要这个字段的实体类继承这个基类。 示例代码: import jav…

20222412 2024-2025-1 《网络与系统攻防技术》实验六实验报告

20222412 2024-2025-1 《网络与系统攻防技术》实验六实验报告 1.实验内容 主要任务:基于Metasploit框架,实现漏洞利用。 Metasploit框架(MSF)由HD Moore于2003年发布,并在2007年使用Ruby语言重写。它提供了一套完整的渗透测试框架,包括漏洞利用模块、攻击载荷、辅助模块、…

01计算机简介

001简介 计算机硬件逻辑组成

访谈李继刚:从哲学层面与大模型对话

当面访谈李继刚,看他如何理解提示工程,从哲学角度探讨提示词的“道”与“术”。相信不少人和我一样,是从“汉语新解”这段爆火提示词中知道李继刚这位“神人”的。直到看到11月4日公众号“数字生命卡兹克”对继刚做了专访文章《专访"Prompt之神"李继刚 - 我想用20…

k8s: 配置ingress的会话亲和(转载)

Ingress会话亲和,又称会话保持,粘性会话,指同一客户端的请求在一定时间内会被ingress路由到相同的pod处理. 本文控制器使用的是ingress-nginxingress默认的负载均衡策略是轮询, 验证如下 使用浏览器连续访问9次ingress 查看ingress日志, 可看到9次请求被轮询负载到不同pod处理 …

线性代数知识点复习——范数

范数(Norm) 是数学中的一个概念,用于度量向量、矩阵或张量的大小或长度。范数是向量空间上的一种函数,能够将向量映射为非负实数,表示向量的某种“长度”或“大小”。