Flutter初识(二):登录页、首页、退出登录页面

前言:

        语法与Java很像,所以大多地方我就不注释了,有问题随时讨论,一起学习一起进步,不要只能靠卷。

一、主页(main.dart)改造

        main.dart是执行的主文件,生成空项目时就存在,我们首先把它原先的代码全部删除,然后让他指向登录页面(LoginPage),上代码:

import 'package:flutter/material.dart';

import 'page/login_page.dart'; // 登录页面

void main() {
    runApp(MyApp())
}

// StatelessWidget 用于那些状态不变的Widget
// StatefulWidget 用于那些状态可能会改变的Widget,并且需要管理其状态。
class MyApp extends StatelessWidget {
    const MyApp({super.key});

    @override
    Widget build(BuildContext context) {
        return MaterialApp(
            title: 'Test Demo',
            theme: ThemeData(
              primarySwatch: Colors.blue, // 主题颜色
            ),
            home: const LoginPage(), // 直接打开登录页
        );
    }
}

其中:

  1. StatelessWidget 用于那些状态不变的Widget
  2. StatefulWidget 用于那些状态可能会改变的Widget,并且需要管理其状态

二、登录页(login_page.dart)

        在lib文件夹下新建一个page文件夹,在page文件夹下新建一个页面:login_page.dart。先上效果图吧,毕竟我也是根据这个图设计的。

功能拆分:

  1. 登录页(我这边用的标题栏展示此功能,所以要额外设置标题栏颜色与字体颜色大小)
  2. App测试(这个没啥好说的,Text搞定)
  3. 中间的白色部分(使用Container包裹即可,css直接写)
  4. 图片展示(我使用的svg图片,中间遇到了问题,后面再说)
  5. 输入框与按钮(这个也没啥好说的)
  6. 登录验证(我这边仅进行最简单的空值验证并做出提示)
  7. 登录数据存储
  8. 首页跳转

需要准备的引用插件:

  1. 引用文件是:pubspec.yaml
  2. 支持svg展示的插件:flutter_svg: ^2.0.10
  3. 图片引用:
  4. 插件引用之后需要执行命令安装生效,直接打开 Ternimal 输入 flutter pub get 即可

下面直接上代码:

import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';

import 'home_page.dart';

class LoginPage extends StatefulWidget {
  const LoginPage({super.key});

  @override
  _LoginPageState createState() => _LoginPageState();
}

class _LoginPageState extends State<LoginPage> {
  final _formKey = GlobalKey<FormState>();
  final _usernameController = TextEditingController();
  final _passwordController = TextEditingController();
  final FocusNode _usernameFocusNode = FocusNode();
  final FocusNode _passwordFocusNode = FocusNode();
  String? _usernameError;
  String? _passwordError;
  bool _isObscure = true;
  final _borderRadius = 10.0; // 圆角角度

  @override
  void initState() {
    super.initState();
    _usernameFocusNode.addListener(() {
      if (!_usernameFocusNode.hasFocus) {
        _validateUsername();
      }
    });

    _passwordFocusNode.addListener(() {
      if (!_passwordFocusNode.hasFocus) {
        _validatePassword();
      }
    });
  }

  // 用户验证
  void _validateUsername() {
    setState(() {
      if (_usernameController.text.isEmpty) {
        _usernameError = "用户名不能为空";
      } else {
        _usernameError = null;
      }
    });
  }

  // 密码验证
  void _validatePassword() {
    setState(() {
      if (_passwordController.text.isEmpty) {
        _passwordError = "密码不能为空";
      } else {
        _passwordError = null;
      }
    });
  }

  // 登录点击事件
  void _login() async {
    if (_formKey.currentState?.validate() ?? false) {
      _validateUsername();
      _validatePassword();

      final userName = _usernameController.text;
      final password = _passwordController.text;

      if (_usernameError == null && _passwordError == null) {
        // 底部消息弹框
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('$userName 已登录')),
        );

        // 登录数据存储
        final prefs = await SharedPreferences.getInstance();
        prefs.setString('userName', userName);

        // 跳转到下一个页面
        // Navigator.push(
        //   context,
        //   MaterialPageRoute(builder: (context) => HomePage(userName: username)),
        // );

        // 跳转后替换整个导航堆栈,无返回按钮
        Navigator.pushAndRemoveUntil(
          context,
          MaterialPageRoute(
            builder: (context) => HomePage(userName: userName),
          ),
          (route) => false, // 移除前置路由
        );
      }
    }
  }

  // hex颜色
  Color _hexToColor(String hexString) {
    final buffer = StringBuffer();
    if (hexString.length == 7 || hexString.length == 9) buffer.write('FF');
    buffer.write(hexString.replaceFirst('#', ''));
    return Color(int.parse(buffer.toString(), radix: 16));
  }

  @override
  Widget build(BuildContext context) {
    // 获取屏幕的宽度
    // double screenWidth = MediaQuery.of(context).size.width;

    return Scaffold(
      appBar: AppBar(
        title: Text(
          "登录页",
          style: const TextStyle(
            fontWeight: FontWeight.normal,
            fontSize: 14,
          )
        ),
        backgroundColor: _hexToColor('#037cfd'), // 标题栏颜色
        foregroundColor: Colors.white,
        centerTitle: true,
      ),
      backgroundColor: _hexToColor('#037cfd'), // 修改外部背景颜色
      body: Padding(
        padding: const EdgeInsets.all(40.0), // 设置内边距
        child: Column(
          // mainAxisAlignment: MainAxisAlignment.center, // 垂直居中
          // crossAxisAlignment: CrossAxisAlignment.center, // 水平居中
          children: [
            Text(
              "App测试",
              style: const TextStyle(fontSize: 30, fontWeight: FontWeight.bold, color: Colors.white),
            ),
            const SizedBox(height: 20),
            Container(
              padding: const EdgeInsets.all(16.0),
              // width: screenWidth * 0.75, // 宽度设置0.75
              decoration: BoxDecoration(
                color: Colors.white, // 内部颜色
                borderRadius: BorderRadius.circular(8.0),
                boxShadow: [
                  BoxShadow(
                    color: Colors.black.withOpacity(0.1),
                    spreadRadius: 5,
                    blurRadius: 7,
                    offset: const Offset(0, 3),
                  ),
                ],
              ),
              child: Form(
                key: _formKey,
                child: Column(
                  children: [
                    SvgPicture.asset(
                      'assets/images/login.svg'
                    ),
                    const SizedBox(height: 16),
                    TextFormField(
                      controller: _usernameController,
                      focusNode: _usernameFocusNode, // 失焦事件
                      style: const TextStyle(fontSize: 12, color: Colors.black87), //文字大小、颜色
                      //输入组件
                      decoration: InputDecoration(
                        labelText: "用户名",
                        // hintText: 'Username',
                        errorText: _usernameError,
                        prefixIcon: Icon(Icons.person, color: _hexToColor('#7ba8c0')),
                        fillColor: Colors.grey[80], //背景颜色,
                        filled: true, //必须设置为true,fillColor才有效
                        isCollapsed: true, //相当于高度包裹的意思,必须设置为true
                        contentPadding: const EdgeInsets.symmetric(
                            horizontal: 15, vertical: 15), //内容内边距
                        enabledBorder: OutlineInputBorder(
                          //设置输入框可编辑时的边框样式
                            borderRadius: BorderRadius.circular(_borderRadius), //输入框圆角
                            borderSide:
                            const BorderSide(color: Colors.white, width: 0) //输入框白色隐藏
                        ),
                        border: OutlineInputBorder(
                          borderRadius: BorderRadius.circular(_borderRadius), //输入框圆角
                        ),
                        focusedBorder: OutlineInputBorder(
                          borderRadius: BorderRadius.circular(_borderRadius), //输入框圆角
                          borderSide: BorderSide(color: _hexToColor('#7ba8c0'), width: 2.0),
                        ),
                      ),
                      // validator: (value) {
                      //   if (value == null || value.isEmpty) {
                      //     return 'Please enter your username';
                      //   }
                      //   return null;
                      // },
                    ),
                    const SizedBox(height: 16),
                    TextFormField(
                      controller: _passwordController,
                      focusNode: _passwordFocusNode, // 失焦事件
                      obscureText: _isObscure,
                      decoration: InputDecoration(
                        labelText: "密码",
                        errorText: _passwordError,
                        prefixIcon: Icon(Icons.lock, color: _hexToColor('#7ba8c0'),),
                        fillColor: Colors.grey[80], //背景颜色,必须结合filled: true,才有效
                        filled: true, //必须设置为true,fillColor才有效
                        isCollapsed: true, //相当于高度包裹的意思,必须设置为true
                        contentPadding: const EdgeInsets.symmetric(
                            horizontal: 15, vertical: 15), //内容内边距,影响高度
                        enabledBorder: OutlineInputBorder(
                            borderRadius: BorderRadius.circular(_borderRadius), //输入框圆角
                            borderSide: const BorderSide(color: Colors.white, width: 0) //输入框白色隐藏
                        ),
                        border: OutlineInputBorder(
                          borderRadius: BorderRadius.circular(_borderRadius), //输入框圆角
                          borderSide: const BorderSide(color: Colors.white, width: 0),
                        ),
                        focusedBorder: OutlineInputBorder(
                          borderRadius: BorderRadius.circular(_borderRadius), //输入框圆角
                          borderSide: BorderSide(color: _hexToColor('#7ba8c0'), width: 2.0),
                        ),
                        suffixIcon: IconButton(
                          icon: Icon(
                            !_isObscure ? Icons.visibility : Icons.visibility_off,
                          ),
                          onPressed: () {
                            setState(() {
                              _isObscure = !_isObscure;
                            });
                          },
                        ),
                      ),
                      // validator: (value) {
                      //   if (value == null || value.isEmpty) {
                      //     return 'Please enter your password';
                      //   }
                      //   return null;
                      // },
                    ),
                    const SizedBox(height: 24),
                    ElevatedButton(
                      // onPressed: () {
                      //   if (_formKey.currentState?.validate() ?? false) {
                      //     // Perform login action
                      //     ScaffoldMessenger.of(context).showSnackBar(
                      //       const SnackBar(content: Text('Logging in...')),
                      //     );
                      //   }
                      // },
                      onPressed: _login,
                      style: ElevatedButton.styleFrom(
                        backgroundColor: _hexToColor('#037cfd'), //修改按钮背景色
                        foregroundColor: Colors.white, //文本的颜色
                        minimumSize: const Size(double.infinity, 50), // 修改按钮大小
                        shape: RoundedRectangleBorder(
                          borderRadius: BorderRadius.circular(_borderRadius), // 修改按钮圆角
                        ),
                      ),
                      child: Text("登录"),
                    ),
                  ],
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

注意点:

        flutter使用svg库时提示The <style> element is not implemented in this library。

        这是svg中带有style标签导致的,我们使用的插件不支持,去除style即可,我采用的是这位大佬的方式:flutter使用svg库时提示The <style> element is not implemented in this library的解决方法 - yongfengnice - 博客园 (cnblogs.com)

三、首页(home_page.dart)

先上效果图:

        

功能拆分:

  1. 这个真没啥好说的,主要就是配置页面使用子组件

代码:

import 'package:flutter/material.dart';
import 'profile_page.dart';

class HomePage extends StatefulWidget {
  final String userName;

  HomePage({required this.userName});

  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  int _selectedIndex = 0; // 默认选第一个

  final List<Widget> _pages = [
    Center(child: Text('主页')),
    Center(child: Text('搜索页')),
    ProfilePage(), // 配置页面子组件
  ];

  // 选中事件
  void _onItemTapped(int index) {
    setState(() {
      _selectedIndex = index;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('欢迎, ${widget.userName}'),
      ),
      body: _pages[_selectedIndex],
      bottomNavigationBar: BottomNavigationBar(
        items: const <BottomNavigationBarItem>[
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            label: '主页',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.search),
            label: '搜索',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.person),
            label: '配置',
          ),
        ],
        currentIndex: _selectedIndex,
        selectedItemColor: Colors.blue, // 选中颜色
        unselectedItemColor: Colors.grey, // 未选中颜色
        onTap: _onItemTapped, // 选中事件
      ),
    );
  }
}

四、配置页面(profile_page.dart)展示与退出登录 

照例先上效果图(比较简陋。。。):

        

功能拆分:

  1. 用户头像有就展示,没有展示一个默认icon做头像(默认图片头像的代码在注释里面)
  2. 退出登录

上代码吧:

import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';

import 'login_page.dart';

class ProfilePage extends StatefulWidget {
  @override
  _ProfilePageState createState() => _ProfilePageState();
}

class _ProfilePageState extends State<ProfilePage> {
  final String? _profilePictureUrl = null; // 头像
  String _userName = ''; // 用户名
  final String _userId = 'TEST-001'; // 工号
  final String _address = 'XX市 XX区 XXX'; // 地址

  @override
  void initState() {
    super.initState();
    _loadUserInfo();
  }

  // 获取用户信息
  _loadUserInfo() async {
    final prefs = await SharedPreferences.getInstance();
    setState(() {
      _userName = prefs.getString('userName') ?? '未知';
    });
  }

  // 退出登录
  void _logout(BuildContext context) {
    Navigator.pushReplacement(
      context,
      MaterialPageRoute(
        builder: (context) => const LoginPage(),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: [
            // 用户头像
            // CircleAvatar(
            //   radius: 50,
            //   backgroundImage: _profilePictureUrl != null
            //       ? NetworkImage(_profilePictureUrl)
            //       : const AssetImage('assets/default_avatar.png') as ImageProvider, // 默认图片头像
            //   backgroundColor: Colors.grey[200], // 设置头像背景色
            // ),
            CircleAvatar(
              radius: 50,
              backgroundColor: Colors.grey[200], // 设置头像背景色
              child: _profilePictureUrl != null && _profilePictureUrl.isNotEmpty
                  ? ClipOval(
                      child: Image.network(
                        _profilePictureUrl,
                        fit: BoxFit.cover,
                        width: 100,
                        height: 100,
                      ),
                    )
                  : Icon(
                      Icons.person,
                      size: 50,
                      color: Colors.grey[700], // 设置图标颜色
                    ),
            ),
            const SizedBox(height: 16),
            // 用户姓名
            Text(
              _userName,
              style: const TextStyle(
                fontSize: 24,
                fontWeight: FontWeight.bold,
              ),
            ),
            const SizedBox(height: 8),
            // 用户识别码(ID)
            Text(
              '工号: $_userId',
              style: TextStyle(
                fontSize: 16,
                color: Colors.grey[600],
              ),
            ),
            const SizedBox(height: 16),
            // 用户地址
            Text(
              '地址: $_address',
              style: const TextStyle(
                fontSize: 16,
              ),
            ),
            const Spacer(), // 占据剩余的空间
            // 退出登录按钮
            ElevatedButton(
              onPressed: () => _logout(context),
              style: ElevatedButton.styleFrom(
                backgroundColor: Colors.red, //修改按钮背景色
                foregroundColor: Colors.white, //文本的颜色
                minimumSize: const Size(double.infinity, 50), // 修改按钮大小
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.circular(10.0), // 修改按钮圆角
                ),
              ),
              child: Text('退出登录'),
            ),
          ],
        ),
      ),
    );
  }
}

结语:

        至此结束,感谢观看。

        下一节为国际化配置,也是我目前已完成的最终进度。后续有什么想要研究实现的可以留言讨论一起研究哈。

        PS:这三篇写完我就继续黑吗喽了,这次就不立flag了

  • 7
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值