flutter入門之實現登陸頁面的記住歷史登錄賬號功能

    切換賬號在移動app中應用的場景非常多,記住賬號功能在Android或者IOS中有大量的開源代碼,今天,我們就用flutter來實現一個能夠記錄歷史登錄賬號的Demo吧,以下是效果演示:

    實現思路:

    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佈局,詳見:

    歷史賬號的展示覆蓋在登陸頁面之上,採用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很好,路還很長,讓我們一起奮鬥前行!

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章