【06】flutter完成注册页面-密码登录-手机短信验证-找回密码相关页面-并且实现静态跳转打包demo做演示-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈

简介: 【06】flutter完成注册页面-密码登录-手机短信验证-找回密码相关页面-并且实现静态跳转打包demo做演示-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈

【06】flutter完成注册页面-密码登录-手机短信验证-找回密码相关页面-并且实现静态跳转打包demo做演示-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈

章节内容【06】

flutter完成注册页面-密码登录-手机短信验证-找回密码相关页面-并且打包demo做演示

开发背景

上篇我们做了自定义组件,本文继续完善注册相关页面并且实现跳转

闲话不多,开源仓库地址,可以观摩已经写好的代码:

https://gitee.com/youyacao/ff-flutter

demo下载

实战开始


上一篇我们在完成后发现个报错问题,

Target of URI doesn't exist: 'package:ff_flutter/lib/widgets/pinkbutton.dart'.
Try creating the file referenced by the URI, or try using a URI for a file that does exist.

这是提示目录中没有找到pinkbutton.dart文件,相关报错还有很多,但是优雅草卓伊凡的路径是正确的,其实就是package:ff_flutter识别不了了。

这是因为包依赖问题:

如果 ff_flutter 是一个自定义包,确保它已经在 pubspec.yaml 中正确声明。
运行 flutter pub get 更新依赖。

dependencies:
  ff_flutter:
    path: ../ff_flutter  # 根据实际情况调整路径

清理和重建项目:

运行 flutter clean 清理构建缓存。
运行 flutter pub get 获取最新依赖。
重新启动 IDE 或编辑器以确保所有更改生效。

可是当我们运行调试的时候还是报错了,这时候我们再来看

发现问题了,name应该是 name: ff_flutter 才对。

一切就绪后还是报错,依赖也是也正确的,终于我测试,把lib目录去掉,就成功了

想了一下,那么为啥呢,原来我定义目录已经是path: ../ff_flutter 那么package:ff_flutter 已经就到了lib 目录下了,我再去加lib目录那当然要出错啦,问题解决,我们进行下一步

有了注册页面我们做登录页面

新建login.dart

这个应该是登录页面了,但是login画错了,而且下面有切换用户密码登录,那么这里就是短信登录,因此我反馈给ui了 让去整改下,其次login文件名改为smslogin这样会方便知道。

观察了下样式基本一致因此我们复制注册页面过来,这里有很多地方细节都需要修改,特别是社交登录下方那个是图片才可以实现,我们先增加一下login按钮点击 后跳转到 smslogin.dart

要给 login 按钮增加点击跳转到 smslogin.dart 页面的功能,你需要在 onPressed 回调中使用 Navigator 进行页面跳转,我们需要以下步骤

在注册页面修改登录按钮的代码:

BlackButton(
  label: 'login',
  onPressed: () {
    // 登录按钮点击事件
    logger.info('登录按钮被点击');
    Navigator.push(
      context,
      MaterialPageRoute(builder: (context) => SmsLoginScreen()),
    );
  },
),

并且增加引入:

import 'package:ff_flutter/screens/smslogin.dart'; // 假设 smslogin.dart 文件位于 screens 目录下

在smslogin.dart页面增加

// smslogin.dart
class SmsLoginScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('SMS Login'),
      ),
      body: Center(
        child: const Text('SMS Login Screen'),
      ),
    );
  }
}

完成后,但是我们得到一个警告

Constructors for public widgets should have a named 'key' parameter.
Try adding a named parameter to the constructor.

大意是

Flutter 建议为公共小部件的构造函数添加一个命名的 key 参数。为了符合这个建议,你需要在 SmsLoginScreen 和 RegisterScreen 的构造函数中添加 Key 参数。

那么扩展知识又来了

扩展知识

在Flutter中,为小部件的构造函数添加一个命名的 key 参数有以下几个主要作用:

1. 唯一标识小部件

每个小部件都可以通过 key 参数在树结构中唯一标识。这在重建部分树时特别有用,因为它有助于Flutter引擎高效地更新和重用小部件,而不是销毁和重建它们。

2. 保持状态

当你需要保持某个状态时(例如在列表中拖动排序项目),key 参数可以确保小部件在重建时保持其状态。例如,在一个可变顺序的列表中,如果每个项目都有唯一的 key,那么在列表项被重新排列时,它们的状态仍能正确保持。

3. 控制小部件重建

key 参数可以帮助Flutter引擎决定是否需要重建小部件。通过比较 key 值,Flutter可以在更新UI时更智能地选择重建哪些部分,从而提高性能。

代码示例

以下是如何为小部件添加一个命名的 key 参数的示例

import 'package:flutter/material.dart';
class CustomWidget extends StatelessWidget {
  final String title;
  const CustomWidget({Key? key, required this.title}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Container(
      child: Text(title),
    );
  }
}

在上面的示例中,CustomWidget 有一个可选的 key 参数,并在构造函数中使用 super(key: key) 进行初始化。当你创建这个小部件的实例时,可以传递一个 key

CustomWidget(
  key: ValueKey('unique_key'),
  title: 'My Custom Widget',
)

ok 我们照做,

Key 类型

在Flutter中,有几种不同类型的 Key,你可以根据具体需求选择使用:

ValueKey<T>: 通过值来唯一标识小部件,适用于简单数据类型(如字符串或数字)。

ObjectKey: 通过对象来唯一标识小部件,适用于复杂数据类型。

UniqueKey: 保证每次创建时都唯一,适用于需要绝对唯一性的场景(但不能用于状态保持)。

也就是说,接下来需要在 register_screen.dart 中为 RegisterScreen 添加一个 key 值 reg,在 smslogin.dart 中为 SmsLoginScreen 添加一个 key 值 slogin。

以下是修改后的代码

在顶部加入

const RegisterScreen({Key? key}) : super(key: key); // 添加 key 参数

在注册处:

key: const Key('reg'), // 添加 key 值 'reg'

在登录按钮处:

MaterialPageRoute(builder: (context) => SmsLoginScreen(key: const Key('slogin'))), // 添加 key 值 'slogin'

注册页面我们点击登录成功跳转到了登录页面,成功实现了 跳转,本来打算本篇幅 写完所有注册页面,但是看来过长 需要下一篇了,其次有个大一点的原因是注册逻辑有问题,因此我们先去做其他页面。

完整页面供参考,

注册页面

import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import 'package:ff_flutter/widgets/pinkbutton.dart'; // 引入 PinkButton
import 'package:ff_flutter/widgets/blackbutton.dart'; // 引入 BlackButton
import 'package:ff_flutter/screens/smslogin.dart'; // 假设 smslogin.dart 文件位于 screens 目录下
class RegisterScreen extends StatefulWidget {
  const RegisterScreen({Key? key}) : super(key: key); // 添加 key 参数
  @override
  State<RegisterScreen> createState() => _RegisterScreenState();
}
class _RegisterScreenState extends State<RegisterScreen> {
  // 示例国家地区号列表
  final List<String> countryCodes = ['+1', '+86', '+91', '+44', '+33'];
  // 默认选择的国家地区号
  String selectedCountryCode = '+1';
  // Checkbox 状态
  bool _agreedToTerms = false;
  @override
  Widget build(BuildContext context) {
    final logger = Logger('RegisterScreen');
    logger.info('Building RegisterScreen');
    return Scaffold(
      key: const Key('reg'), // 添加 key 值 'reg'
      backgroundColor: const Color(0xFF1E1E1E), // 设置背景色为 #1E1E1E
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              "Free Friend",
              style: TextStyle(
                color: Colors.white,
                fontSize: 61.87,
                fontFamily: "PingFang SC",
                fontWeight: FontWeight.w800,
              ),
            ),
            const SizedBox(height: 16.0),
            const Text(
              "Please register your account",
              style: TextStyle(
                color: Colors.white,
                fontSize: 32,
                fontFamily: "PingFang SC",
                fontWeight: FontWeight.w800,
              ),
            ),
            const SizedBox(height: 16.0),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,  // 使用 spaceBetween 对齐方式
              children: [
                Flexible(
                  flex: 1,  // 给 DropdownButtonFormField 分配一部分空间
                  child: DropdownButtonFormField<String>(
                    value: selectedCountryCode,
                    onChanged: (String? newValue) {
                      if (newValue != null) {
                        setState(() {
                          selectedCountryCode = newValue;
                        });
                      }
                    },
                    items: countryCodes.map<DropdownMenuItem<String>>((String value) {
                      return DropdownMenuItem<String>(
                        value: value,
                        child: Text(
                          value,
                          style: const TextStyle(color: Colors.white), // 设置文字颜色为 FFFFFF
                        ),
                      );
                    }).toList(),
                    decoration: const InputDecoration(
                      labelText: '选择国家地区号',
                      border: OutlineInputBorder(),
                    ),
                    style: const TextStyle(
                      fontSize: 16,
                      color: Colors.white, // 设置文字颜色为 FFFFFF
                    ),
                    dropdownColor: const Color(0xFF1E1E1E), // 设置弹窗背景色为 #1E1E1E
                  ),
                ),
                const SizedBox(width: 8.0),
                const Expanded(
                  flex: 2,  // 给 TextField 分配更多的空间
                  child: TextField(
                    decoration: InputDecoration(
                      labelText: '请输入手机号',
                      border: OutlineInputBorder(),
                      hintStyle: TextStyle(color: Color(0xffa9a9a9)),
                    ),
                    style: TextStyle(color: Colors.white), // 设置输入文字颜色为 FFFFFF
                    keyboardType: TextInputType.phone,
                  ),
                ),
              ],
            ),
            const SizedBox(height: 16.0),
            const TextField(
              decoration: InputDecoration(
                labelText: '请输入密码',
                hintStyle: TextStyle(color: Color(0xffa9a9a9)),
                border: OutlineInputBorder(),
              ),
              obscureText: true,
              style: TextStyle(color: Colors.white), // 设置输入文字颜色为 FFFFFF
            ),
            Row(
              mainAxisSize: MainAxisSize.min,
              mainAxisAlignment: MainAxisAlignment.start,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: [
                Checkbox(
                  value: _agreedToTerms,
                  onChanged: (bool? value) {
                    setState(() {
                      _agreedToTerms = value ?? false;
                    });
                  },
                ),
                const SizedBox(width: 20),
                const Text(
                  "You agree to our Terms",
                  style: TextStyle(
                    color: Colors.white,
                    fontSize: 32,
                    fontFamily: "PingFang SC",
                    fontWeight: FontWeight.w500,
                  ),
                ),
              ],
            ),
            const SizedBox(height: 24.0),
            PinkButton(
              label: 'Register',
              onPressed: () {
                // 注册按钮点击事件
                logger.info('注册按钮被点击');
              },
            ),
            const SizedBox(height: 8.0),
            Expanded(
              child: Align(
                alignment: Alignment.bottomCenter,
                child: Column(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    const Text(
                      "Already have an account?",
                      style: TextStyle(
                        color: Colors.white,
                        fontSize: 32,
                        fontFamily: "PingFang SC",
                        fontWeight: FontWeight.w800,
                      ),
                    ),
                    const SizedBox(height: 8.0),
                  BlackButton(
  label: 'login',
  onPressed: () {
    // 登录按钮点击事件
    logger.info('登录按钮被点击');
    Navigator.push(
      context,
       MaterialPageRoute(builder: (context) => SmsLoginScreen(key: const Key('slogin'))), // 添加 key 值 'slogin'
    );
  },
),
                  ],
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

登录页面

import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import 'package:ff_flutter/widgets/pinkbutton.dart'; // 引入 PinkButton
import 'package:ff_flutter/widgets/blackbutton.dart'; // 引入 BlackButton
// smslogin.dart
class SmsLoginScreen extends StatelessWidget {
  const SmsLoginScreen({Key? key}) : super(key: key); // 添加 key 参数
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('SMS Login'),
      ),
      body: Center(
        child: const Text('SMS Login Screen'),
      ),
    );
  }
}
class RegisterScreen extends StatefulWidget {
  const RegisterScreen({super.key});
  @override
  State<RegisterScreen> createState() => _RegisterScreenState();
}
class _RegisterScreenState extends State<RegisterScreen> {
  // 示例国家地区号列表
  final List<String> countryCodes = ['+1', '+86', '+91', '+44', '+33'];
  // 默认选择的国家地区号
  String selectedCountryCode = '+1';
  // Checkbox 状态
  bool _agreedToTerms = false;
  @override
  Widget build(BuildContext context) {
    final logger = Logger('RegisterScreen');
    logger.info('Building RegisterScreen');
    return Scaffold(
      backgroundColor: const Color(0xFF1E1E1E), // 设置背景色为 #1E1E1E
      // appBar: AppBar(
      //   title: const Text(
      //     'Free Friend',
      //     style: TextStyle(
      //       fontSize: 24.0, // 设置字体大小
      //       fontFamily: 'PingFang SC', // 设置字体为 PingFang SC
      //     ),
      //   ),
      //   centerTitle: true, // 居中标题
      // ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              "Free Friend",
              style: TextStyle(
                color: Colors.white,
                fontSize: 61.87,
                fontFamily: "PingFang SC",
                fontWeight: FontWeight.w800,
              ),
            ),
            const SizedBox(height: 16.0),
            const Text(
              "Please login your account",
              style: TextStyle(
                color: Colors.white,
                fontSize: 32,
                fontFamily: "PingFang SC",
                fontWeight: FontWeight.w800,
              ),
            ),
            const SizedBox(height: 16.0),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,  // 使用 spaceBetween 对齐方式
              children: [
                Flexible(
                  flex: 1,  // 给 DropdownButtonFormField 分配一部分空间
                  child: DropdownButtonFormField<String>(
                    value: selectedCountryCode,
                    onChanged: (String? newValue) {
                      if (newValue != null) {
                        setState(() {
                          selectedCountryCode = newValue;
                        });
                      }
                    },
                    items: countryCodes.map<DropdownMenuItem<String>>((String value) {
                      return DropdownMenuItem<String>(
                        value: value,
                        child: Text(
                          value,
                          style: const TextStyle(color: Colors.white), // 设置文字颜色为 FFFFFF
                        ),
                      );
                    }).toList(),
                    decoration: const InputDecoration(
                      labelText: '选择国家地区号',
                      border: OutlineInputBorder(),
                    ),
                    style: const TextStyle(
                      fontSize: 16,
                      color: Colors.white, // 设置文字颜色为 FFFFFF
                    ),
                    dropdownColor: const Color(0xFF1E1E1E), // 设置弹窗背景色为 #1E1E1E
                  ),
                ),
                const SizedBox(width: 8.0),
                const Expanded(
                  flex: 2,  // 给 TextField 分配更多的空间
                  child: TextField(
                    decoration: InputDecoration(
                      labelText: '请输入手机号',
                      border: OutlineInputBorder(),
                      hintStyle: TextStyle(color: Color(0xffa9a9a9)),
                    ),
                    style: TextStyle(color: Colors.white), // 设置输入文字颜色为 FFFFFF
                    keyboardType: TextInputType.phone,
                  ),
                ),
              ],
            ),
            const SizedBox(height: 16.0),
            const TextField(
              decoration: InputDecoration(
                labelText: '请输入密码',
                hintStyle: TextStyle(color: Color(0xffa9a9a9)),
                border: OutlineInputBorder(),
              ),
              obscureText: true,
              style: TextStyle(color: Colors.white), // 设置输入文字颜色为 FFFFFF
            ),
            Row(
              mainAxisSize: MainAxisSize.min,
              mainAxisAlignment: MainAxisAlignment.start,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: [
                Checkbox(
                  value: _agreedToTerms,
                  onChanged: (bool? value) {
                    setState(() {
                      _agreedToTerms = value ?? false;
                    });
                  },
                ),
                const SizedBox(width: 20),
                const Text(
                  "You agree to our Terms",
                  style: TextStyle(
                    color: Colors.white,
                    fontSize: 32,
                    fontFamily: "PingFang SC",
                    fontWeight: FontWeight.w500,
                  ),
                ),
              ],
            ),
            const SizedBox(height: 24.0),
            PinkButton(
              label: 'Register',
              onPressed: () {
                // 注册按钮点击事件
                logger.info('注册按钮被点击');
              },
            ),
            const SizedBox(height: 8.0),
            Expanded(
              child: Align(
                alignment: Alignment.bottomCenter,
                child: Column(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    const Text(
                      "Already have an account?",
                      style: TextStyle(
                        color: Colors.white,
                        fontSize: 32,
                        fontFamily: "PingFang SC",
                        fontWeight: FontWeight.w800,
                      ),
                    ),
                    const SizedBox(height: 8.0),
                    BlackButton(
                      label: 'login',
                      onPressed: () {
                        // 登录按钮点击事件
                        logger.info('登录按钮被点击');
                      },
                    ),
                  ],
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

其次我改了下名字,register_screen.dart改为register.dart,其次打包了apk 供下载给大家看。

目录
相关文章
|
3月前
|
缓存 移动开发 JavaScript
如何优化UniApp开发的App的启动速度?
如何优化UniApp开发的App的启动速度?
652 139
|
3月前
|
移动开发 JavaScript weex
UniApp开发的App在启动速度方面有哪些优势和劣势?
UniApp开发的App在启动速度方面有哪些优势和劣势?
382 137
|
3月前
|
数据采集 JavaScript 前端开发
开发比分App?你缺的不是程序员
开发体育比分App,关键不在代码,而在懂体育、懂数据、懂用户。明确定位、理清需求、选好数据源,再找专业的产品、数据与技术人才协同,才能少走弯路。程序员最后入场,效率最高。
248 154
|
4月前
|
移动开发 小程序 Android开发
基于 uni-app 开发的废品回收类多端应用功能与界面说明
本文将对一款基于 uni-app 开发的废品回收类多端应用,从多端支持范围、核心功能模块及部分界面展示进行客观说明,相关资源信息也将一并呈现。
176 0
|
6月前
|
人工智能 文字识别 小程序
旅游社用什么工具收报名 + 资料?不开发 App 也能自动收集信息
本文探讨了旅游行业中报名信息收集的常见痛点及解决方案,重点介绍了二维码表单工具在提升信息收集效率、简化操作流程方面的优势。通过对比多种工具,分析其适用场景与实际应用逻辑,为一线旅游从业者提供高效、低成本的执行参考。
|
7月前
|
容器
HarmonyOS NEXT仓颉开发语言实战案例:外卖App
仓颉语言实战分享,教你如何用仓颉开发外卖App界面。内容包括页面布局、导航栏自定义、搜索框实现、列表模块构建等,附完整代码示例。轻松掌握Scroll、List等组件使用技巧,提升HarmonyOS应用开发能力。
|
3月前
|
移动开发 前端开发 Android开发
【02】建立各项目录和页面标准化产品-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【02】建立各项目录和页面标准化产品-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
347 12
【02】建立各项目录和页面标准化产品-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
|
3月前
|
移动开发 JavaScript 应用服务中间件
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
345 5
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
|
6月前
|
Rust 安全 前端开发
Github 轻松斩获30k+ Star,桌面应用开发太丝滑啦,Tauri框架能重塑桌面App开发?别错过,抓紧上车
Tauri 是一个基于 Rust 的开源框架,用于构建轻量级、高性能、安全的跨平台桌面及移动应用。它利用系统 WebView 渲染前端界面,后端由 Rust 编写,具备出色的性能和安全性。相比 Electron,Tauri 应用体积更小、启动更快,且默认权限更安全。它支持 React、Vue、Svelte 等主流前端框架,并提供自动更新、CLI 工具链、资源注入优化等功能,适用于生产力工具、开发者工具、数据分析、AI 应用等多种场景。目前 Tauri 在 GitHub 上已获得超过 30,000 Star,社区活跃,是现代桌面应用开发的理想选择。
719 0

热门文章

最新文章