flutter入门之实现登陆页面的记住历史登录账号功能

    【原创不易,转载请注明出处:https://blog.csdn.net/email_jade/article/details/86220592

    切换账号在移动app中应用的场景非常多,记住账号功能在Android或者IOS中有大量的开源代码,今天,我们就用flutter来实现一个能够记录历史登录账号的Demo吧,以下是效果演示:

《flutter入门之实现登陆页面的记住历史登录账号功能》

    实现思路:

    1. shared_preferences 用来做账号密码的保存

    对于账号密码的保存,注意的有两点,一是要有序保存,比如,最后一次登录的账号,要在下次进入的时候直接展示到输入框中,二是要进行去重操作,假如要登录的账号跟之前是同一个,那么不再新增记录了。考虑到顺序,因此我采用的是List,而去重,采用的是List的contain函数,不过注意,这要重载bean的==运算符,不得不说,相对于Java,Dart新增了运算符的重载,使用起来更加方便了。

   对于数据的操作,代码详见下:

import 'package:flutter_login/bean/User.dart';
import 'package:shared_preferences/shared_preferences.dart';

///数据库相关的工具
class SharedPreferenceUtil {
  static const String ACCOUNT_NUMBER = "account_number";
  static const String USERNAME = "username";
  static const String PASSWORD = "password";

  ///删掉单个账号
  static void delUser(User user) async {
    SharedPreferences sp = await SharedPreferences.getInstance();
    List<User> list = await getUsers();
    list.remove(user);
    saveUsers(list, sp);
  }

  ///保存账号,如果重复,就将最近登录账号放在第一个
  static void saveUser(User user) async {
    SharedPreferences sp = await SharedPreferences.getInstance();
    List<User> list = await getUsers();
    addNoRepeat(list, user);
    saveUsers(list, sp);
  }

  ///去重并维持次序
  static void addNoRepeat(List<User> users, User user) {
    if (users.contains(user)) {
      users.remove(user);
    }
    users.insert(0, user);
  }

  ///获取已经登录的账号列表
  static Future<List<User>> getUsers() async {
    List<User> list = new List();
    SharedPreferences sp = await SharedPreferences.getInstance();
    int num = sp.getInt(ACCOUNT_NUMBER) ?? 0;
    for (int i = 0; i < num; i++) {
      String username = sp.getString("$USERNAME$i");
      String password = sp.getString("$PASSWORD$i");
      list.add(User(username, password));
    }
    return list;
  }

  ///保存账号列表
  static saveUsers(List<User> users, SharedPreferences sp){
    sp.clear();
    int size = users.length;
    for (int i = 0; i < size; i++) {
      sp.setString("$USERNAME$i", users[i].username);
      sp.setString("$PASSWORD$i", users[i].password);
    }
    sp.setInt(ACCOUNT_NUMBER, size);
  }
}

    2. 将保存的账号进行展示

    对于账号的展示,熟悉Android的朋友都知道,这种情况采用PopupWindow比较合适,我最开始也是这样写的,在flutter里面,使用的是showMenu,不过遇到了一个大坑,就是历史账号展示有最大宽度的限制,导致布局看起来特别难看,参考framework的源代码:

   popup_menu.dart

.......

const double _kMenuMaxWidth = 5.0 * _kMenuWidthStep;
const double _kMenuMinWidth = 2.0 * _kMenuWidthStep;

.......


final Widget child = ConstrainedBox(
      constraints: const BoxConstraints(
        minWidth: _kMenuMinWidth,
        maxWidth: _kMenuMaxWidth,
      ),


.......

    于是,我改变思路,顶层采用Stack布局,账号展示采用Offstage镶嵌ListView布局,详见:

《flutter入门之实现登陆页面的记住历史登录账号功能》

    历史账号的展示覆盖在登陆页面之上,采用Offstage布局,可以随时显示和隐藏。这里还会遇到一个问题,就是历史账号展示的位置及大小,在flutter中,我们可以通过globalKey.currentContext.findRenderObject()来测量已有控件的大小及位置,因此,我们可以测量账号输入框的大小及位置。对于历史账号而言,位置top,可以经过输入框的y坐标+输入框的height计算得来,位置left和right,可以通过屏幕的宽和自身的宽计算而来,自身的宽度可以与账号输入框的宽度保持一致,而历史记录的高度,可以看成是items的高度和Divide的高度和,这里可以经过计算得来。由于采用的是ListView,也不用考虑高度超出屏幕范围的处理了。

    完成布局后,剩下的事情就比较简单了,就是账号的删除与新增,注意处理数据边界问题。

    这里还用到了package_info这个包,主要用来读取app的版本号的。

    布局代码详见下:

import 'package:flutter/material.dart';
import 'package:flutter_login/bean/User.dart';
import 'package:flutter_login/util/SharedPreferenceUtil.dart';
import 'package:package_info/package_info.dart';
import 'package:flutter/rendering.dart' show debugPaintSizeEnabled;

void main() {
  debugPaintSizeEnabled = false; //调试用
  return runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: LoginPage(),
    );
  }
}

class LoginPage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return _LoginPageState();
  }
}

class _LoginPageState extends State<LoginPage> {
  GlobalKey _globalKey = new GlobalKey(); //用来标记控件
  String _version; //版本号
  String _username = ""; //用户名
  String _password = ""; //密码
  bool _expand = false; //是否展示历史账号
  List<User> _users = new List(); //历史账号

  @override
  void initState() {
    super.initState();
    _getVersion();
    _gainUsers();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      resizeToAvoidBottomPadding: false,
      body: Stack(
        children: <Widget>[
          Center(
            child: Container(
              width: 500,
              child: Flex(direction: Axis.vertical, children: <Widget>[
                Expanded(
                  child: Container(
                    child: Icon(
                      Icons.account_balance,
                      size: 100,
                    ),
                  ),
                  flex: 3,
                ),
                _buildUsername(),
                _buildPassword(),
                _buildLoginButton(),
                Expanded(
                  child: Container(
                    padding: EdgeInsets.only(bottom: 20),
                    alignment: AlignmentDirectional.bottomCenter,
                    child: Text("版本号:$_version"),
                  ),
                  flex: 2,
                ),
              ]),
            ),
          ),
          Offstage(
            child: _buildListView(),
            offstage: !_expand,
          ),
        ],
      ),
    );
  }

  ///构建账号输入框
  Widget _buildUsername() {
    return TextField(
      key: _globalKey,
      decoration: InputDecoration(
        border: OutlineInputBorder(borderSide: BorderSide()),
        contentPadding: EdgeInsets.all(8),
        fillColor: Colors.white,
        filled: true,
        prefixIcon: Icon(Icons.person_outline),
        suffixIcon: GestureDetector(
          onTap: () {
            if (_users.length > 1 || _users[0] != User(_username, _password)) {
              //如果个数大于1个或者唯一一个账号跟当前账号不一样才弹出历史账号
              setState(() {
                _expand = !_expand;
              });
            }
          },
          child: _expand
              ? Icon(
                  Icons.arrow_drop_up,
                  color: Colors.red,
                )
              : Icon(
                  Icons.arrow_drop_down,
                  color: Colors.grey,
                ),
        ),
      ),
      controller: TextEditingController.fromValue(
        TextEditingValue(
          text: _username,
          selection: TextSelection.fromPosition(
            TextPosition(
              affinity: TextAffinity.downstream,
              offset: _username == null ? 0 : _username.length,
            ),
          ),
        ),
      ),
      onChanged: (value) {
        _username = value;
      },
    );
  }

  ///构建密码输入框
  Widget _buildPassword() {
    return Container(
      padding: EdgeInsets.only(top: 30),
      child: TextField(
        decoration: InputDecoration(
          border: OutlineInputBorder(borderSide: BorderSide()),
          fillColor: Colors.white,
          filled: true,
          prefixIcon: Icon(Icons.lock),
          contentPadding: EdgeInsets.all(8),
        ),
        controller: TextEditingController.fromValue(
          TextEditingValue(
            text: _password,
            selection: TextSelection.fromPosition(
              TextPosition(
                affinity: TextAffinity.downstream,
                offset: _password == null ? 0 : _password.length,
              ),
            ),
          ),
        ),
        onChanged: (value) {
          _password = value;
        },
        obscureText: true,
      ),
    );
  }

  ///构建历史账号ListView
  Widget _buildListView() {
    if (_expand) {
      List<Widget> children = _buildItems();
      if (children.length > 0) {
        RenderBox renderObject = _globalKey.currentContext.findRenderObject();
        final position = renderObject.localToGlobal(Offset.zero);
        double screenW = MediaQuery.of(context).size.width;
        double currentW = renderObject.paintBounds.size.width;
        double currentH = renderObject.paintBounds.size.height;
        double margin = (screenW - currentW) / 2;
        double offsetY = position.dy;
        double itemHeight = 30.0;
        double dividerHeight = 2;
        return Container(
          decoration: BoxDecoration(
            color: Colors.white,
            borderRadius: BorderRadius.circular(5.0),
            border: Border.all(color: Colors.blue, width: 2),
          ),
          child: ListView(
            itemExtent: itemHeight,
            padding: EdgeInsets.all(0),
            children: children,
          ),
          width: currentW,
          height: (children.length * itemHeight +
              (children.length - 1) * dividerHeight),
          margin: EdgeInsets.fromLTRB(margin, offsetY + currentH, margin, 0),
        );
      }
    }
    return null;
  }

  ///构建历史记录items
  List<Widget> _buildItems() {
    List<Widget> list = new List();
    for (int i = 0; i < _users.length; i++) {
      if (_users[i] != User(_username, _password)) {
        //增加账号记录
        list.add(_buildItem(_users[i]));
        //增加分割线
        list.add(Divider(
          color: Colors.grey,
          height: 2,
        ));
      }
    }
    if (list.length > 0) {
      list.removeLast(); //删掉最后一个分割线
    }
    return list;
  }

  ///构建单个历史记录item
  Widget _buildItem(User user) {
    return GestureDetector(
      child: Container(
        child: Flex(
          direction: Axis.horizontal,
          children: <Widget>[
            Expanded(
              child: Padding(
                padding: EdgeInsets.only(left: 5),
                child: Text(user.username),
              ),
            ),
            GestureDetector(
              child: Padding(
                padding: EdgeInsets.only(right: 5),
                child: Icon(
                  Icons.highlight_off,
                  color: Colors.grey,
                ),
              ),
              onTap: () {
                setState(() {
                  _users.remove(user);
                  SharedPreferenceUtil.delUser(user);
                  //处理最后一个数据,假如最后一个被删掉,将Expand置为false
                  if (!(_users.length > 1 ||
                      _users[0] != User(_username, _password))) {
                    //如果个数大于1个或者唯一一个账号跟当前账号不一样才弹出历史账号
                    _expand = false;
                  }
                });
              },
            ),
          ],
        ),
      ),
      onTap: () {
        setState(() {
          _username = user.username;
          _password = user.password;
          _expand = false;
        });
      },
    );
  }

  ///构建登录按钮
  Widget _buildLoginButton() {
    return Container(
      padding: EdgeInsets.only(top: 30),
      width: double.infinity,
      child: FlatButton(
        onPressed: () {
          //提交
          SharedPreferenceUtil.saveUser(User(_username, _password));
          SharedPreferenceUtil.addNoRepeat(_users, User(_username, _password));
        },
        child: Text("登录"),
        color: Colors.blueGrey,
        textColor: Colors.white,
        highlightColor: Colors.blue,
      ),
    );
  }

  ///获取版本号
  void _getVersion() async {
    PackageInfo.fromPlatform().then((PackageInfo packageInfo) {
      setState(() {
        _version = packageInfo.version;
      });
    });
  }

  ///获取历史用户
  void _gainUsers() async {
    _users.clear();
    _users.addAll(await SharedPreferenceUtil.getUsers());
    //默认加载第一个账号
    if (_users.length > 0) {
      _username = _users[0].username;
      _password = _users[0].password;
    }
  }
}

 

     GitHub地址:https://github.com/jadennn/flutter_login

     

     flutter很好,路还很长,让我们一起奋斗前行!

 

 

    原文作者:Hirabbit_jaden
    原文地址: https://blog.csdn.net/email_jade/article/details/86220592
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞