Flutter从入门到奔溃(五):撸一些UI交互以及动态页面

5,185 阅读6分钟

[toc]

Flutter从入门到奔溃(五):撸一些UI交互以及动态页面

前记

我们之前粗略介绍了基础以及布局:

Flutter从入门到奔溃(一):撸一个登录界面

Flutter从入门到奔溃(二):撸一个个人界面

Flutter从入门到奔溃(三):撸一个App基础框架

# Flutter从入门到奔溃(四):撸一个包含列表刷新以及网络请求的首页

总算是脱离了无聊的静态页面,涉及到了一部分网络交互,今天我们接着进行UI交互的学习(因为开源中国的api感觉不太好用,我们改用wananzhuo的api,这个也是我们安卓狗练手的必备项目了,这里感谢鸿洋大神)。

页面交互

登录界面

image.png

登录界面使用我们之前绘制的静态页面,在这里我们进行UI交互:

  1. 拿到用户输入的数据
  2. 对数据进行必要的验证
  3. 提交用户数据到后台
  4. 根据接口成功与否进行页面交互以及数据更新

接下来我们分步进行上述操作:

拿到用户输入的数据

我们页面是用了基础的TextField,所以我们通过controller来进行数据获取,(如果是用form表单的格式的话,还有另外一种方式,在onsave方法里面保存,这里按下不表)。

 onPressed: () {
 _postLogin(
          _userNameController.text, _userPassController.text);
    },

我们很容易可以知道上述代码的作用是通过2个对应的controller拿到了username,userpass2个参数,并把他们作为参数提供给了私有方法*_postLogin*。

对数据进行必要的验证

这里对数据并没有太大的要求,只要是非空,我们就默认是有效数据,所以我们做了简单的判断:

_postLogin(String userName, String userPassword) {
    if (userName.isNotEmpty && userPassword.isNotEmpty) {
		//    do some   
    } else {
      TsUtils.showShort('请输入用户名和密码');
    }
  }

这里我们稍微讲下使用的一个插件:** fluttertoast: ^2.0.7**,很明显就是一个android的Toast方法,(这里还有另外一种做法是用原生提供桥接,让flutter调用原生方法进行交互,这里也按下不表,后续我们更新这部分的内容),

import 'package:fluttertoast/fluttertoast.dart';
class TsUtils{
  static showShort(String msg){
    Fluttertoast.showToast(
        msg: msg,
        toastLength: Toast.LENGTH_SHORT,
        gravity: ToastGravity.CENTER,
        timeInSecForIos: 1,
        bgcolor: "#63CA6C",
        textcolor: '#ffffff'
    );
  }
}

提交用户数据到后台

玩安卓的api接口是典型的三段式接口:

{
    "data": ...,
    "errorCode": 0,
    "errorMsg": ""
}
  1. 判断成功与否用errorCode
  2. 显示信息用errorMsg
  3. 拿数据用data

所以我们对应进行了比较拙略的封装:

//  post请求
  static Future<Map> post(String url,
      {Map<String, String> params, bool saveCookie = false}) async {
    if (params == null) {
      params = new Map();
    }
    String _url = Api.BASE_URL + url;
    if (OsApplication.cookie != null) {
      params['Cookie'] = OsApplication.cookie;
    }
    http.Response res = await http.post(_url, body: params);
    return _dealWithRes(res, saveCookie: saveCookie);
  }
static Map<String, dynamic> _dealWithRes(var res, {bool saveCookie}) {
    if (res.statusCode == 200) {
      var cookie = res.headers['set-cookie'];
      if (saveCookie) {
        SpUtils.saveCookie(cookie);
        OsApplication.cookie = cookie;
      }
      String body = res.body;
      var jsonStr = json.decode(body);
      print('the jsonStr is $jsonStr');
      int errCode = jsonStr['errorCode'];
      if (errCode == 0) {
        var data = jsonStr['data'];
        return data;
      } else {
        TsUtils.showShort(jsonStr['errorMsg']);
        return null;
      }
    } else {
      TsUtils.showShort('您的网络好像不太好哟~~~///(^v^)\\\~~~');
      return null;
    }
  }

这里需要注意的是一个**cookie **,我们用来进行登录的凭证,这里的思路是:

  1. 登录的时候拿到cookie,保存到OsApplication类中,并且持久化
  2. 启动app的时候从持久化中获取,保存到OsApplication类中,
  3. 调用接口的时候根据是否要保存,重新进行保存,并携带cookie传到后台 (存在一个问题是内存被杀死后,要重新从持久化中获取)

我们完整的登录方法应该是:

_postLogin(String userName, String userPassword) {
    if (userName.isNotEmpty && userPassword.isNotEmpty) {
      Map<String, String> params = new Map();
      params['username'] = userName;
      params['password'] = userPassword;
      Http.post(Api.USER_LOGIN, params: params,saveCookie: true).then((result) {
        SpUtils.map2UserInfo(result).then((userInfoBean){
          if(userInfoBean!=null){
            OsApplication.eventBus.fire(new LoginEvent(userInfoBean.username));
            SpUtils.saveUserInfo(userInfoBean);
            Navigator.pop(context);
          }
        });
      });
    } else {
      TsUtils.showShort('请输入用户名和密码');
    }
  }

这里我们是不是看到了一个熟悉的名词呢=> eventBus,hahahahahahahahhahahahahhaha,它是我们下一个步骤的主角。

根据接口成功与否进行页面交互以及数据更新

我们登录后,要怎么通知其他页面,我已经登录了呢?

  1. 把栈里所有activity都出栈,重新new出新的带登录信息的activity再压栈进去
  2. 通过其他手段通知需要更新状态的页面:爷爷我登录了,你赶紧地更新页面!!

我觉得第二种比较划算!而在安卓中,我们可以通过原生的广播,第三方的EventBus来实现,而在flutter,我们可以考虑用插件event_bus: ^1.0. 1来实现。

它的实现方式和安卓版的类似:

  1. 写event类
  2. 事件源发出event
  3. 接受源接受event,并作出对应处理

登录事件源发出消息

    OsApplication.eventBus.fire(new LoginEvent(userInfoBean.username));

个人中心接受源接收消息

OsApplication.eventBus.on<LoginEvent>().listen((event) {
      setState(() {
        if (event != null && event.userName != null) {
          userName = event.userName;
          userAvatar = 'http://www.wanandroid.com/resources/image/pc/logo.png';
        } else {
          userName = null;
          userAvatar = null;
        }
      });
    });

设置页面-退出登录发送logout事件源

这里不能说用户一点退出就立马噌噌噌地退出了,要有一个交互的过程--AlertDialog

_showDialog() {
    showDialog(
        builder: (context) => new AlertDialog(
              title: new Text('提示'),
              content: new Text('是否要退出登录'),
              actions: <Widget>[
                new FlatButton(
                    onPressed: () {
                      Navigator.pop(context);
                    },
                    child: new Text('取消')),
                new FlatButton(
                    onPressed: () {
                      SpUtils.cleanUserInfo();
                      OsApplication.eventBus.fire(new LoginEvent(null));
                      Navigator.pop(context);
                    },
                    child: new Text('是的'))
              ],
            ),
        context: context);
  }

在确定按钮中,我们清除了用户信息(包括保存保存于内存和持久化的userName,token,id,cookie), 并且发出了一个null的event,由接受源代码可以知道,会显示未登录状态。

丑丑的体系页面

UI “鉴赏”

接下来做好准备!!! 你将会受到视觉的冲击!!! 一大波钢铁直男的粗糙审美将会冲击你! 我很自豪地认为要是有审美选丑比赛,我肯定可以夺得第一!

image.png
image.png
image.png

页面拆解

一级页面

一级页面没有什么难度,我们仍然有多种方案来实现它:

  1. listView
  2. CustomScrollView
  3. ScrollView

这里我们选用CustomScrollView,具体代码可以: 体系页面 如果有人有兴趣的话,可以试试自己动手写一个呢。

二级页面

二级页面用安卓来实现肯定就是:tabLayout+ViewPager,有趣的是flutter只用一个控件就可以实现了DefaultTabController

 @override
  Widget build(BuildContext context) {
    widgetsUtils = new WidgetsUtils(context);
    return new Scaffold(
      appBar: new AppBar(
        title: widgetsUtils.getAppBar(_title),
        iconTheme: new IconThemeData(color: Colors.white),
      ),
      body: new DefaultTabController(
        child: new Scaffold(
            appBar: new TabBar(
              isScrollable: true,
              tabs: _initTabs(),
            ),
            body: new TabBarView(children: _initBody())),
        length: classList.length,
      ),
    );
  }

其中的TabBar类似于tabLayout; 其中的TabBarView类似于ViewPager;

这里有一个需要注意的点,TabBarView的children每次划到的时候都会重新走一次initState(),而如果我们在那里请求接口的话,就会每次都请求一次,这样无论是UI还是性能还是体验都不是我们要的 而解决方案是在children(也就是类似fragment)的State加上 with AutomaticKeepAliveClientMixin

class _SystemChildPageState extends State<SystemChildPage>
    with AutomaticKeepAliveClientMixin{

  @override
  bool get wantKeepAlive => true;
}

见名知意,这个是用于标志是否保持状态的tag。

三级

三级没了... 就一个webview

总结

陈词

草草地说,好像也没什么好写了,接下去做的都是重复性的劳动:

  1. 接接口
  2. 写数据
  3. 画UI

但是

但是flutter不止这么些可以玩的,动画,2端交互,其其他他零零总总的,还有很多好玩的控件,嗯...接下来继续慢慢玩flutter。

互勉

一起玩吧