iOS开发Flutter探索-实现一个iOS-Grouped风格的tableView(7)

前言

大家都知道,在Flutter框架中,渲染并不像ReactNative或者Veex等通过JSCore来映射成原生组件,而是有自己的一套渲染引擎,这也是Flutter的强大之处。来上一张Flutter架构图:


来源Flutter中文网.png

从图上可以看出,我们是在Framework层通过Dart语法进行开发,再往下层就是基于C++的一些引擎依赖。因此,如果我们想在Flutter中使用像iOS-Grouped风格的TableView就需要使用Widget来自己构建了。
今天我们就以微信通讯录页面UI来做实现一个Grouped风格的ListView。先看下效果图:


效果图.gif

开始

  • 布局分析
    因为有section的存在,在iOS下是很好实现的,但是在Flutter中并没有相应的API或者回调来配置这部分,所有我把这部分归类到Cell中,然后通过数据来控制Section的显示。如图:


    Simulator Screen Shot - iPhone 11 - 2020-06-18 at 15.21.07.png

整个cell外层通过一个Column()布局拆分成Section+Content两部分,然后Content部分用Row()布局拆分成leftIcon和nickName部分,因为nickName 部分底部还有一跟分割线,所有nickName部分可以通过一个一个Column()布局分为上下两个部分。(还是那句话,UI布局思路非常多且灵活,以上只是个人的思路)。

  • 开始造
    这里还是基于之前创建的一个Tabbar项目,这里我们使用ListView.builder(),这个是可是实现Cell复用的ListView,内部需要一个itemCount(就是numberOfRowsInSection:)和itemBuilder(是一个callback函数,类似于cellForRowAtIndexPath:);我这里把这个callback的实现写在了外面的_cellForRowAtIndex方法中,这个方法在滚动列表时,也会实时回调,跟cellForRowAtIndexPath一模一样,一个需要返回一个UITableViewCell,一个需要返回一个Widget.
    以下是build()部分的代码:
@override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('泽泽2'),
      ),
      body: Container(
        child: ListView.builder(  //具备复用能力的ListView()
          itemCount: _headerData.length+_listData.length,   // 数组个数
          itemBuilder: _cellForRowAtIndex,  //复用回调函数
      ),
      ),
    );
  }

_itemForRow()代码块:

 //cell复用回调
  Widget _cellForRowAtIndex(BuildContext context, int index){
      //头部cell
      if(index < _headerData.length){
        FriendModel model = _headerData[index];
        return CustomCell(
            imageAssets: model.imageUrl,
            name: model.name,
        );
      }
      //其他cell
      return CustomCell(
        imageUrl: _listData[index - 4].imageUrl,
        name: _listData[index - 4].name,
        groupTitle: _listData[index - 4].indexLetter,
      );
  }

自定义Cell custom_cell.dart部分:

import 'package:flutter/material.dart';

class CustomCell extends StatefulWidget {
  final String imageUrl;
  final String name;
  final String groupTitle;
  final String imageAssets;

  const CustomCell({
    Key key,
    this.imageUrl,
    this.name,
    this.groupTitle,
    this.imageAssets,
  }) : super(key: key);

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

class _CustomCellState extends State {
  @override
  Widget build(BuildContext context) {
    return Container(
      child: Column(
        children: [
          Container(
            alignment: Alignment.centerLeft,
            padding: EdgeInsets.only(left: 10),
            height: widget.groupTitle != null ? 30 : 0,
            color: Color.fromRGBO(236, 237, 237, 1.0),
//            child: Text(widget.groupTitle,style: TextStyle(color: Colors.white),),
            child: widget.groupTitle != null ? Text(widget.groupTitle,style: TextStyle(color: Color.fromRGBO(76, 75, 75, 1.0),),):null,
          ),//SectionHeader
          Container(
            height: 54,
            color: Colors.white,
            child: Row(
              children: [
                Container(
                  width:34,
                  height: 34,
                  margin: EdgeInsets.all(10),
                  //设置圆角
                  decoration: BoxDecoration(
                    borderRadius: BorderRadius.circular(6.0),
                    image: DecorationImage(
                        image:widget.imageUrl != null ? NetworkImage(widget.imageUrl) : AssetImage(widget.imageAssets),
                    ),
                  ),
                ), //左图
                Container(
//                  color: Colors.blue,
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.spaceBetween, //上下对齐
                    children: [
                      Container(  //昵称
//                        color: Colors.red,
                        height:53.5,
                        width: MediaQuery.of(context).size.width - 10 - 10 - 34,
                        alignment: Alignment.centerLeft,  //文本对齐
                        child: Text(
                          widget.name,
                          style: TextStyle(fontSize: 18),
                        ),
                      ),
                      Container(  //线
                         height: 0.5,
                         width: MediaQuery.of(context).size.width - 10 - 10 - 34,
                         color: Color.fromRGBO(246, 246, 246, 1.0),

                      )
                    ],
                  ),
                ),
              ],
            ),
          ),//Contents
        ],
      ),
    );
  }
}

目前看到的效果:

Simulator Screen Shot - iPhone 11 - 2020-06-18 at 14.37.07.png

现在看到的效果就是我们定制的这个完整Cell的样子了(SectionHeader+Content),但是我们不需要每个cell都来显示这个SectionHeader,怎么弄呐?我们通过model数据来做控制SectionHeader的显示。这里在model中定义了一个groupTitle的字段,根据groupTitle 是否为 null 来控制SectionHeader的显示与隐藏;第二个问题就是mode中包含相同groupTitle的Cell,只让第一个显示SectionHeader显示,之后就不显示,直到不同的groupTitle出现。思路还是数据驱动UI的方式。
首先把列表分成了4+n,因为上面的4个cell是固定不变的,分别将model数据添加到_headerData_listData中,然后来到_cellForRowAtIndex中做一些判断:

//cell复用回调
  Widget _cellForRowAtIndex(BuildContext context, int index){
      //头部cell
      if(index < _headerData.length){
        FriendModel model = _headerData[index];
        return CustomCell(
            imageAssets: model.imageUrl,
            name: model.name,
        );
      }
      //其他cell
      //如果当前model.indexLetter(对应model中的indexLetter字段)与上一个model.indexLetter相同则不显示
      //否则就显示
      if(index > 4 && _listData[index - 4].indexLetter == _listData[index - 5].indexLetter){
        //groupTitle = null
        return CustomCell(
          imageUrl: _listData[index - 4].imageUrl,
          name: _listData[index - 4].name,
        );
      }

      return CustomCell(
        imageUrl: _listData[index - 4].imageUrl,
        name: _listData[index - 4].name,
        groupTitle: _listData[index - 4].indexLetter,
      );
  }

效果图:


Simulator Screen Shot - iPhone 11 - 2020-06-18 at 15.16.43.png

这样我们就将一个Group风格的tableView就构造完成了。

补充

  • 常用的一些变量或者常亮或者一些方法可以单独的抽离到一个dart文件中,比如获取屏幕的宽高,一些主题颜色等,比如我这里的app_config.dart文件
import 'package:flutter/material.dart';

//主题色调
final Color APP_ThemeColor = Color.fromRGBO(223, 223, 223, 1.0);
//屏幕的宽 等同于 [UIScreen mainScreen].bounds.size.width 因为这里需要一个context 所以需要定义成方法
double ScreenWidth(BuildContext context) => MediaQuery.of(context).size.width;
//屏幕的高
double ScreenHeight(BuildContext context) => MediaQuery.of(context).size.height;

使用app_config.dart

Container(  //线
     height: 0.5,
     width: ScreenWidth(context) - 10 - 10 - 34,
     color: Color.fromRGBO(246, 246, 246, 1.0),
)
  • 关于导航条(AppBar) 上如何配置左右视图(比如iOS下的UINavigationItem),这里需要用到AppBar下的actions属性,接收一个Widget数组,可以添加多个Widget,这里举例一下
    我们添加一个Icon(),然后通过GestureDetector()Icon()一个点击事件,然后通过Navigator实现页面跳转并传参。
 @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('泽泽2'),
        actions: [
          GestureDetector(
            child: Container(
              margin: EdgeInsets.only(right: 10),
              child: Icon(Icons.add),
            ),
            onTap: (){
              print('我被点击了');
              Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context){
                return AddFriend(
                  cellName: '泽泽伐木类',
                );
              }));
            },
          ),
        ],
      ),
      body: Container(
        child: ListView.builder(  //具备复用能力的ListView()
          itemCount: _headerData.length+_listData.length,
          itemBuilder: _cellForRowAtIndex,
      ),
      ),
    );
  }
}
  • flutter 中的..级联调用语法,可以对同一个对象进行一系列操作,比如在构造数据多次的调用addAll()的时候:
@override
  void initState() {     //这个方法需要在State类重写 同iOS下的viewWillAppear()
    super.initState();
    //构造下数据 .. 语法糖
    //为了多加点测试数据,对_listData进行了两次addAll(),然后根据字母进行了排序
    _listData..addAll(datas)..addAll(datas)..sort((FriendModel a,FriendModel b){
        return a.indexLetter.compareTo(b.indexLetter);
    });
  }

这里等同于

@override
  void initState() {     //这个方法需要在State类重写 同iOS下的viewWillAppear()
    super.initState();
    //构造下数据 .. 语法
    _listData.addAll(datas);
     _listData.addAll(datas);
    //按照字母进行排序 跟NSArray类似
    _listData.sort((FriendModel a,FriendModel b){
      return a.indexLetter.compareTo(b.indexLetter);
    });
  }

更多Dart语法相关的内容,会在后面的文章中介绍

总结

本篇内容大部分的内容还是关于UI相关的东西,主要就是拓展一下在iOS中常见的一些UI布局,迁移到Flutter中的一些思路;同时也简单的提到了一些Dart语法相关的东西,这个后续我们详细聊;

你可能感兴趣的:(iOS开发Flutter探索-实现一个iOS-Grouped风格的tableView(7))