Bootstrap

Flutter第十四弹 抽屉菜单效果

目标:

1.怎么构建抽屉菜单效果?

2.抽屉菜单怎么定制?

一、抽屉菜单

侧滑抽屉菜单效果

1.1 抽屉菜单入口

Flutter 的脚手架Scaffold,默认提供了抽屉菜单效果入口。

主页面采用一个简单的页面,侧滑菜单首先使用一个Image,先看看侧滑效果。 

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:myflutter/drawer/widget/main_drawer.dart';

void main() => runApp(DrawerApp());

/**
 * 构建抽屉菜单APP
 *
 * @author zhouronghua
 * @time 2024/6/20 上午9:47
 */
class DrawerApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text("抽屉菜单"),
        ),
        drawer: Drawer(
          /// 自定义抽屉菜单Widget
          child: MainDrawer(),
        ),
        body: new MainPage(),
      ),
    );
  }
}

/**
 * 主页面
 *
 * @author zhouronghua
 * @time 2024/6/20 上午10:02
 */
class MainPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // 主页面简单一个容器
    return Container(
      child: Text("我是主页面"),
    );
  }
}

显示效果:

 1.2 定制抽屉菜单

抽屉菜单显示用户未登录状态,允许用户点击,点击跳转登录页。

添加用户头像Header,和收藏菜单项

import 'package:flutter/material.dart';

class MainDrawer extends StatefulWidget {
  @override
  _MainDrawerState createState() {
    return _MainDrawerState();
  }
}

class _MainDrawerState extends State<MainDrawer> {
  /// 用户名:标记用户是否登录
  late String _userName;

  @override
  Widget build(BuildContext context) {
    // 顶部用户信息
    Widget userHeader = DrawerHeader(
        decoration: BoxDecoration(
          color: Colors.orangeAccent,
        ),
        child: InkWell(
          /// 纵向视图
          child: Column(
            /// 设置内边距
            children: [
              Padding(
                padding: EdgeInsets.only(bottom: 20.0),
                // 采用一个圆形图像
                child: CircleAvatar(
                  backgroundImage: AssetImage('assets/images/logo.png'),
                  /// 设置图片半径
                  radius: 40.0,
                ),
              ),
              Text("请先登录", style: TextStyle(fontSize: 20.0)),
            ],
          ),
        ));

    return ListView(
      // 列表项子项
      children: [
        /// 用户头
        userHeader,
        /// 收藏
        InkWell(
          onTap: () => {},
          child: ListTile(
            leading: Icon(Icons.favorite),
            title: Text('收藏列表', style: TextStyle(fontSize: 16.0)),
          ),
        ),
      ],
    );
  }
}

 

assets的图像可能加载不出来。

怎么解决呢?

1.2.1 解决assets图片加载不出来

添加assets图片,需要注册一下图片。

1)注册图片资源

在项目pubspec.yaml 中,将需要使用的图片进行注册

2)引用图片

路径就是 assets/图片路径。

                // 采用一个圆形图像
                child: CircleAvatar(
                  backgroundImage: AssetImage('assets/images/logo.png'),
                  /// 设置图片半径
                  radius: 40.0,
                ),

热重载项目,可以看到图片已经加载出来了。

1.3 抽屉菜单点击事件监听

InkWell 对应的点击事件:onTap

点击用户头像,如果没有登录,则导航跳转登录页面。

 // 顶部用户信息
    Widget userHeader = DrawerHeader(
        decoration: BoxDecoration(
          color: Colors.orangeAccent,
        ),
        child: InkWell(
          /// 点击事件
          onTap: () {
            /// 点击跳转登录页面
            if (_userName == null) {
              /// 用户未登录跳转登录页面
              Navigator.push(context, MaterialPageRoute(builder: (context) {
                return LoginPage();
              }));
              debugPrint("open login page");
            }
          },

          /// 纵向视图
          child: Column(
            /// 设置内边距
            children: [
              Padding(
                padding: EdgeInsets.only(bottom: 20.0),
                // 采用一个圆形图像
                child: CircleAvatar(
                  backgroundImage: AssetImage('assets/images/logo.png'),
                  /// 设置图片半径
                  radius: 40.0,
                ),
                // child: ClipRRect(
                //   borderRadius: BorderRadius.circular(40.0),
                //   child: Image.network(
                //     'http://gips0.baidu.com/it/u=3602773692,1512483864&fm=3028&app=3028&f=JPEG&fmt=auto?w=960&h=1280',
                //     fit: BoxFit.cover,
                //   ),
                // ),
              ),
              Text("请先登录", style: TextStyle(fontSize: 20.0)),
            ],
          ),
        ));

二、用户登录页面

用户登录页,输入用户名+密码。

2.1 Form表单

Flutter提供了表单组件,可以定制表单内容。

 登录需要输入用户名,输入密码,以及登录按钮,因此可以采用一个列表结构展示。


import 'package:flutter/material.dart';

class LoginPage extends StatefulWidget {
  @override
  LoginPageState createState() {
    return LoginPageState();
  }
}

class LoginPageState extends State<LoginPage> {
  final _formKey = GlobalKey<FormState>();
  FocusNode _pwdNode = new FocusNode();
  late String? _username = null, _password = null;
  bool _isObscure = true;
  late Color? _pwdIconColor = null;


  @override
  void dispose() {
    _pwdNode.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("登录"),
        ),
        body: Form(
            key: _formKey,
            child: ListView(
              padding: EdgeInsets.symmetric(horizontal: 22.0),
              children: <Widget>[
                _buildUserName(),
                _buildPwd(),
                _buildLogin(),
                _buildRegister(),
              ],
            )));
  }

  Widget _buildRegister() {
    return Padding(
      padding: EdgeInsets.only(top: 10.0),
      child: Row(
        ///孩子居中对齐
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Text('没有账号?'),
          GestureDetector(
            child: Text(
              '点击注册',
              style: TextStyle(color: Colors.green),
            ),
            onTap: () async {
              ///进入注册
              // Navigator.push(context, MaterialPageRoute(builder: (_) {
              //   return RegisterPage();
              // }));
            },
          ),
        ],
      ),
    );
  }

  Widget _buildLogin() {
    return Container(
      height: 45.0,
      margin: EdgeInsets.only(top: 18.0, left: 8.0, right: 8.0),
      child: RaisedButton(
        child: Text(
          '登录',
          style: TextStyle(fontSize: 18.0, color: Colors.white),
        ),
        color: Theme.of(context).primaryColor,
        onPressed: _doLogin,
      ),
    );
  }

  Widget _buildPwd() {
    return TextFormField(
      focusNode: _pwdNode,
      ///是否隐藏
      obscureText: _isObscure,
      validator: (String? value) {
        if (value?.trim().isEmpty == true) {
          return '请输入密码';
        }
        _password = value;
      },
      textInputAction: TextInputAction.done,
      onEditingComplete: _doLogin,
      decoration: InputDecoration(
          labelText: '密码',

          ///输入框尾部图标
          suffixIcon: IconButton(
              icon: Icon(
                Icons.remove_red_eye,
                color: _pwdIconColor,
              ),
              onPressed: () {
                setState(() {
                  _isObscure = !_isObscure;

                  ///密码隐藏 图标颜色控制
                  _pwdIconColor = (_isObscure
                      ? Colors.grey
                      : Theme.of(context).iconTheme.color)!;
                });
              })),
    );
  }

  Widget _buildUserName() {
    return TextFormField(
      autofocus: true,
      decoration: InputDecoration(
        labelText: '用户名',
      ),
      initialValue: _username,
      /// 从注册返回username
      ///设置键盘回车为下一步
      textInputAction: TextInputAction.next,
      onEditingComplete: () {
        ///点击下一步
        FocusScope.of(context).requestFocus(_pwdNode);
      },
      validator: (String? value) {
        if (value?.trim().isEmpty == true) {
          return '请输入用户名';
        }
        _username = value;
      },
    );
  }

  void _doLogin() async {
    _pwdNode.unfocus();

    ///输入的内容通过验证
    if (_formKey.currentState?.validate() == true) {
      //TODO 执行登录方法
      // var result = await Api.login(_username, _password);
      // if (result['errorCode'] == -1) {
      //   Toast.show(result['errorMsg'], context,
      //       duration: Toast.LENGTH_LONG, gravity: Toast.BOTTOM);
      // } else {
      //   AppManager.eventBus.fire(LoginEvent(_username));
      //   Navigator.pop(context);
      // }
    }
  }
}

2.2 输入框 

用户名是一个输入框,采用 TextFormField 控件。

  Widget _buildUserName() {
    return TextFormField(
      autofocus: true,
      decoration: InputDecoration(
        labelText: '用户名',
      ),
      initialValue: _username,
      /// 从注册返回username
      /// 设置键盘回车为下一步
      textInputAction: TextInputAction.next,
      onEditingComplete: () {
        /// 软键盘点击下一步
        FocusScope.of(context).requestFocus(_pwdNode);
      },
      validator: (String? value) {
        if (value?.trim().isEmpty == true) {
          return '请输入用户名';
        }
        _username = value;
      },
    );
  }

2.2.1 属性说明 

  •  autofocus:是否自动获得焦点
  • decoration:控件修饰。创建用于的边框、标签、图标和样式的束
    装饰“材质设计”文本字段。
  • initialValue:输入框初始值
  • textInputAction:设置键盘回车为下一步
  • onEditingComplete:编辑完成时的回调。软件盘点击下一步进入密码输入框获得焦点
  • validator:表单字段有效性校验。如果的内容为空的话,提示“请输入用户名”。点击“登录”的时候,校验输入的用户名内容。这个相当于输入内容的校验逻辑。return 返回的就是错误提示内容。

 

;