首页 > 其他分享 >Flutter项目实战(1):通用项目框架搭建

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

时间:2024-11-18 16:07:24浏览次数:1  
标签:const final 项目 value dart state ._ Flutter 搭建

下面介绍 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: flutter
  flutter_screenutil: ^5.9.3
  get: ^4.6.5

flutter:
  assets:
    - assets/images/

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


2.2 Translation.dart

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

import 'package:get/get.dart';

// (2)自定义自己的国际化字符串 
class Translation extends Translations {
  @override
  Map<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>();

  @override
  Widget 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>(); // 登录界面控制器  

  @override
  Widget 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>();

  @override
  Widget 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();

  @override
  Widget build(BuildContext context) {
    return ScreenUtilInit(
      designSize: Size(1920, 1080),
      builder: (context, child) {
        return GetMaterialApp(
          // 配置GetMaterialApp
          translations: 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


标签:const,final,项目,value,dart,state,._,Flutter,搭建
From: https://www.cnblogs.com/linuxAndMcu/p/18552904

相关文章

  • SpingBoot创建项目
    1.IDEA创建项目创建项目,对项目命名,设置项目位置选择Maven项目选择JDK版本点击创建。 创建后的项目结构。2.配置Maven 设置-构建工具-Maven 修改主路径到maven的根目录修改依赖路径到maven安装下conf下的settings.xml配置文件自动匹配本地仓库配置。如果没有注意......
  • GPT+AI技术项目实战,打造多端智能虚拟数字人,多端智能虚拟数字人项目
    《GPT+AI技术项目实战:打造多端智能虚拟数字人》在当今数字化飞速发展的时代,虚拟数字人正逐渐从概念走向现实,并在众多领域展现出了巨大的应用潜力。借助GPT等先进的AI技术,打造多端智能虚拟数字人项目已然成为了热门且极具挑战性与创新性的探索方向。以下将深入介绍基于GPT+A......
  • node.js毕设中小企业项目管理系统的设计与实现(程序+论文)
    本系统(程序+源码+数据库+调试部署+开发环境)带文档lw万字以上,文末可获取源码系统程序文件列表开题报告内容一、选题背景在当今全球化的经济环境下,中小企业在各国经济发展中占据着重要地位。关于项目管理系统的研究,现有研究多集中于大型企业,针对中小企业项目管理系统的研究......
  • linux(统信)下搭建electron开发环境
    1.安装vscode下载地址https://vscode.download.prss.microsoft.com/dbazure/download/stable/f1a4fb101478ce6ec82fe9627c43efbf9e98c813/code-stable-x64-1731511985.tar.gz下载后拷贝压缩包到安装目录下解压缩.点击code就可以启动.2.安装githttps://git-scm.com/downlo......
  • 项目管理软件哪个好用?适合科技公司的推荐!
    在当今高速发展的科技领域,技术迭代和产品更新的节奏令人目不暇接。无论是硬件研发、软件开发,还是跨学科的科技创新项目,团队协作、任务管理和资源优化都面临前所未有的复杂性和挑战。在这样的背景下,项目管理工具作为一种高效的协作和管理手段,正在成为科技领域不可或缺的助力。科技......
  • catia零部件装配结构搭建
    catia零部件装配结构搭建ProductCatia的product文件保存装配结构和各个零部件之间的参数关系与约束关系,不保存三维实体本身。装配结构搭建通常在装配结构搭建的时候不考虑零部件之间的约束关系,只保留装配结构与位置关系(零部件的三维坐标与当前的姿态)。程序思路一般情况下,会......
  • 极限编程在项目管理中的应用,你了解吗?
    极限编程(ExtremeProgramming,XP)是一种敏捷软件开发方法论,强调通过持续的反馈、快速的迭代和强烈的团队协作来提升软件开发的效率和质量。虽然XP最初是针对软件开发过程设计的,但它的核心原则和实践也可以在项目管理中得到广泛应用。以下是极限编程在项目管理中的一些关键应用和作......
  • 私有部署、本地部署,哪种项目管理软件更适合你的项目?
    选择私有部署还是本地部署的项目管理软件,取决于你的团队需求、项目特性、以及对安全性、控制权和可扩展性的要求。下面我们来分析两者的特点,并帮助你做出合适的选择:1.私有部署(PrivateCloud/On-Premises)项目管理软件私有部署指的是将项目管理软件部署在自有的服务器或私有云......
  • 这 10 套项目,一个比一个惊艳!
    大家好,我是程序员鱼皮。首先大家不要太在意封面,那是我前段时间穿越到古代的形象(狗头)。言归正传,因为我在编程导航带大家做项目已经整整两年半了,这期间带做了10套保姆级的项目教程,也帮很多小伙伴学有所成,拿到了Offer。但随着项目数量的增多,很多新了解我们的同学就不知道如何选......
  • 项目开发的常用模版类
    mybatis的相关配置1.拦截及配置sqlimportorg.apache.ibatis.executor.statement.StatementHandler;importorg.apache.ibatis.mapping.BoundSql;importorg.apache.ibatis.mapping.ParameterMapping;importorg.apache.ibatis.plugin.*;importorg.apache.ibatis.session.......