美文网首页
第八章:Flutter之wechatDemo(通讯录页面下)

第八章:Flutter之wechatDemo(通讯录页面下)

作者: Mr姜饼 | 来源:发表于2021-05-17 16:53 被阅读0次

    本节继续为大家完善通讯录页面
    1.增加标签头部
    2.增加索引栏目
    .3.改进通讯录按索引显示,并且去掉重复的标签头
    4.改进标签索引Bar,增加回调,
    5.listview根据回调来跳转到相应的标签栏
    6.为标签栏添加可见视图,滑动或者点击的时候显示,结束拖动或者点击的时候消失

    1.增加标签头部

    修改Cell,给每一个cell添加一个默认的标签头部,先不去判断第二个相同的不显示

     Widget build(BuildContext context) {
        return Container(
          //height: 85,
          color: Colors.white,
          child: Column(
            children: [
              //分组标签(有数据显示,无数据不显示)
              (groupTitle != null) ? Container(
                height: 30,
                color: wechatThemeColor,
                width: ScreenWidth(context),
                padding: EdgeInsets.only(left: 10),
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.start,
                  crossAxisAlignment: CrossAxisAlignment.center,
                  children: [
                    Text('A',style: TextStyle(fontSize: 16),),
                  ],
                ),
              ) : Container(),
              Container(
                height: 54.5,
                child: Row(
                  children: [
                    Container(
                      margin: EdgeInsets.only(left: 10),
                      width: 35,
                      height: 35,
                      child: (imageUrl != null) ? NetworkImage(imageUrl) : Image.asset(locationImageUrl),
                    ),
                    Container(
                      margin: EdgeInsets.only(left: 10),
                      child: Text(name,style: TextStyle(fontSize: 16,color: Colors.black),),
                    ),],),),//cell内容
              //分割线
              Container( height: 0.5,color: Color.fromRGBO(220, 220, 220, 1), child: Row(children: [Container(width: 50,color: Colors.white,),],),),
            ],
          ));}
    }
    

    改变数据源

    final List<Friends> _friendsDatas = [
        Friends(locationImageUrl: 'images/ice.png',name: '张三',indexChar: 'Z'),
        Friends(locationImageUrl: 'images/ice.png',name: '李四',indexChar: 'L'),
        Friends(locationImageUrl: 'images/ice.png',name: '王二',indexChar: 'W'),
        Friends(locationImageUrl: 'images/ice.png',name: '麻子',indexChar: 'M'),
        Friends(locationImageUrl: 'images/ice.png',name: '安娜',indexChar: 'A'),
        Friends(locationImageUrl: 'images/ice.png',name: '黄飞鸿',indexChar: 'H'),
        Friends(locationImageUrl: 'images/ice.png',name: '霍元甲',indexChar: 'H'),
        Friends(locationImageUrl: 'images/ice.png',name: '周杰伦',indexChar: 'Z'),
        Friends(locationImageUrl: 'images/ice.png',name: '林俊杰',indexChar: 'L'),
        Friends(locationImageUrl: 'images/ice.png',name: '王力宏',indexChar: 'W'),
        Friends(locationImageUrl: 'images/ice.png',name: '欧豪',indexChar: 'O'),
        Friends(locationImageUrl: 'images/ice.png',name: '大张伟',indexChar: 'D'),
      ];
    

    run

    26.png
    2.增加索引栏目

    右侧的标签分组栏为悬浮视图,所以我们改造下

      Widget build(BuildContext context) {
        return Scaffold(
          appBar:...省略,
          body:Stack(
            children: [
              ListView.builder(itemBuilder: _cellForRow,itemCount: _headDatas.length + _friendsDatas.length,),
              Positioned(
                child: IndexCharBar(),
                right: 0,
                top: ScreenHeight(context)/8,
                height: ScreenHeight(context)/2,
                width: 40,
              ),
            ],
          )
        );
      }
    

    新建friends_indexchar_bar.dart

    import 'package:flutter/material.dart';
    
    class IndexCharBar extends StatefulWidget {
      @override
      _IndexCharBarState createState() => _IndexCharBarState();
    }
    
    class _IndexCharBarState extends State<IndexCharBar> {
      @override
      Widget build(BuildContext context) {
        return Container(
          color: Colors.red,
        );
      }
    }
    

    run

    继续改进:为indexBar创建索引,根据传进来的通讯录文字来创建

    class IndexCharBar extends StatefulWidget {
    
      final List<Friends> friendsDatas;
    
      IndexCharBar({
        this.friendsDatas,//根据传进来的数据来创建索引
      });
      
      @override
      _IndexCharBarState createState() => _IndexCharBarState();
    }
    

    数据处理(处理数据、创建索引视图)

    class _IndexCharBarState extends State<IndexCharBar> {
    
      final List<Widget> widgets = [];
    
      final List<String> chars = [];
    
      //处理数据
      void handleFriendsDatas(){
        //先排序,按照字母的先后顺序来排序(A,B,C,D......)
        List<Friends> newDatas = [];
        newDatas.addAll(widget.friendsDatas);
        newDatas.sort(
          (Friends a,Friends b){
             return a.indexChar.compareTo(b.indexChar);
          }
        );
        //去重,得到Chars
        for(int i = 0;i <  newDatas.length ; i++){
          Friends friend =  newDatas[i];
          if(!chars.contains(friend.indexChar)){
            chars.add(friend.indexChar);
          }
        }
      }
    
      void initWidgets(){
        for(int i = 0 ; i < chars.length ; i++)
        widgets.add(
            Expanded(
              child: Container(
                width: 40,
                alignment: Alignment.center,
                child:  Text(chars[i],style: TextStyle(color: _textColor)),
              ),
            ),
        );
      }
    
      @override
      void initState() {
        // TODO: implement initState
        super.initState();
        //处理数据
        handleFriendsDatas();
        initWidgets();
      }
    
    
      @override
      Widget build(BuildContext context) {
        return Container(
          color: Colors.red,
          child: Column(
            mainAxisAlignment: MainAxisAlignment.spaceAround,
            children: widgets,
          ),
        );
      }
    }
    

    run

    28.png
    3.改进通讯录按索引显示,并且去掉重复的标签头

    为通讯录数据进行排序

    void handleDatas(){
        _friendsDatas.sort((Friends a,Friends b){
              return a.indexChar.compareTo(b.indexChar);
            }
        );
      }
    
      @override
      void initState() {
        // TODO: implement initState
        super.initState();
        //处理数据
        handleDatas();
      }
    
    

    run

    29.png

    多增加点假数据

    void handleDatas(){
        final List<Friends> tmpDatas = [];
        tmpDatas..addAll(_friendsDatas)..addAll(_friendsDatas);
        _friendsDatas.addAll(tmpDatas);
        _friendsDatas.sort((Friends a,Friends b){
              return a.indexChar.compareTo(b.indexChar);
            }
        );
      }
    

    run

    30.png

    ,去掉重复头

    Widget _cellForRow(BuildContext context , int index){
        if(index < _headDatas.length){
          return FriendsCell(locationImageUrl: _headDatas[index].locationImageUrl,name: _headDatas[index].name,);
        }else{
          //如果当前cell的indexChar与前一个相同的话,则不显示,去掉重复的标签头
          int currentIndex = index - _headDatas.length;
          int fontIndex =  currentIndex - 1;
          if(currentIndex > 0 ){
            if(_friendsDatas[currentIndex].indexChar == _friendsDatas[fontIndex].indexChar){
              return FriendsCell(locationImageUrl: _friendsDatas[index-_headDatas.length].locationImageUrl,name: _friendsDatas[index-_headDatas.length].name);
            }
          }
          return FriendsCell(locationImageUrl: _friendsDatas[index-_headDatas.length].locationImageUrl,name: _friendsDatas[index-_headDatas.length].name,groupTitle: _friendsDatas[index-_headDatas.length].indexChar,);
        }
      }
    

    run

    31.png
    4.改进标签索引Bar,增加回调,点击标签栏上面的索引,或者滑动,都能知道点击或者滑到了哪个索引。

    添加点击效果,点击的时候呈现背景色,且文字颜色为白色,还原时,背景色消失,文字颜色变为黑色

    image.png

    用GestureDetector将Bar包装起来,并且添加点击事件

    child: GestureDetector(
            onVerticalDragDown:(DragDownDetails details){
              setState(() {
                _bgColor =  Color.fromRGBO(1, 1, 1, 0.5);
                _textColor = Colors.white;
              });
            } ,
            onVerticalDragEnd:(DragEndDetails details){
              setState(() {
                _bgColor =  Color.fromRGBO(1, 1, 1, 0);
                _textColor = Colors.black;
              });
            } ,
            onVerticalDragUpdate: (DragUpdateDetails details){
              setState(() {
                _bgColor =  Color.fromRGBO(1, 1, 1, 0.5);
                _textColor = Colors.white;
              });
            },
            child: Column(
              mainAxisAlignment: MainAxisAlignment.spaceAround,
              children: widgets,
            ),
          ),
    

    计算点击或者滑动的索引

       onVerticalDragUpdate: (DragUpdateDetails details){
              //全局转标转化,转化局部左边
              RenderBox box =   context.findRenderObject();
              var local = box.globalToLocal(details.globalPosition);
              //print('${local.dy}');
              //获取每一个索引的高度
              double charhight = ScreenHeight(context) / 2 / chars.length;
              int index = 0 ;
              if(local.dy < 0){//滑出最顶部了
              }else if(local.dy > ScreenHeight(context) / 2){//滑出最底部了
                index =  chars.length - 1;
              }else{
                index =  local.dy ~/  charhight;
              }
              print('当前点击的是${chars[index]}');
              setState(() {
                _bgColor =  Color.fromRGBO(1, 1, 1, 0.5);
                _textColor = Colors.white;
              });
            },
    

    其他地方也要用到,所以方法抛出来

     String findIndexChar(BuildContext context , Offset point){
        //全局转标转化,转化局部坐标
        RenderBox box =   context.findRenderObject();
        var local = box.globalToLocal(point);
        //print('${local.dy}');
        //获取每一个索引的高度
        double charhight = ScreenHeight(context) / 2 / chars.length;
        int index = 0 ;
        if(local.dy < 0){//滑出最顶部了
        }else if(local.dy > ScreenHeight(context) / 2){//滑出最底部了
          index =  chars.length - 1;
        }else{
          index =  local.dy ~/  charhight;
        }
        return chars[index];
      }
    

    抛出回调,告知listview,我选择了什么索引

    修改构造方法,添加回调参数
    class IndexCharBar extends StatefulWidget {
    
      final List<Friends> friendsDatas;
      var selectCharBlock = (String indexChar){};
    
      //构造方法
      IndexCharBar({
        this.friendsDatas,//根据传进来的数据来创建索引
        this.selectCharBlock,
      });
    
      @override
      _IndexCharBarState createState() => _IndexCharBarState();
    }
    
    

    传值

    onVerticalDragDown:(DragDownDetails details){
              String slectedChar = findIndexChar(context ,details.globalPosition);
              if(widget.selectCharBlock != null){
                widget.selectCharBlock(slectedChar);
              }
              setState(() {
                _bgColor =  Color.fromRGBO(1, 1, 1, 0.5);
                _textColor = Colors.white;
              });
            } ,
            onVerticalDragUpdate: (DragUpdateDetails details){
              String slectedChar = findIndexChar(context ,details.globalPosition);
              if(widget.selectCharBlock != null){
                widget.selectCharBlock(slectedChar);
              }
              setState(() {
                _bgColor =  Color.fromRGBO(1, 1, 1, 0.5);
                _textColor = Colors.white;
              });
            },
    

    run

    image.png
    5.最后一步:listview根据回调来跳转到相应的标签栏

    分析:首先我们先计算滑动到每个标签索引所需要滑动的距离

    核心代码:计算每个索引的内容高度

      //listview的滑动器
      final ScrollController _scrollController = ScrollController();
      //记录每个索引内容的高度
      final Map _charIndexMap = Map();
      
      void handleDatas(){
        final List<Friends> tmpDatas = [];
        tmpDatas..addAll(_friendsDatas)..addAll(_friendsDatas);
        _friendsDatas.addAll(tmpDatas);
        _friendsDatas.sort((Friends a,Friends b){
              return a.indexChar.compareTo(b.indexChar);
            }
        );
        //头部距离
        double headerheight = 55.0 * _headDatas.length;
        double tmpHeight = headerheight;
        //计算每个索引所占的高度
        for (int i = 0 ; i < _friendsDatas.length ; i++){
          Friends friend = _friendsDatas[i];
          if(!_charIndexMap.containsKey(friend.indexChar)){//遇到了新的索引,则把前面的相加起来
            if(i > 0){//新出来的要加上头部
              tmpHeight += 30;//加上头部
            }
            _charIndexMap[friend.indexChar] = tmpHeight;
            tmpHeight += 55.0;//加上一个cell的高度
          }else{
            tmpHeight += 55.0;
          }
        }
        print('计算出来的map是$_charIndexMap');
      }
    
    Widget build(BuildContext context) {
        return Scaffold(
          appBar: ...省略,
          body:Stack(
            children: [
              ListView.builder(itemBuilder: _cellForRow,itemCount: _headDatas.length + _friendsDatas.length,controller: _scrollController,),
              Positioned(
                child: IndexCharBar(friendsDatas: _friendsDatas,selectCharBlock: (String idx){
                  double  offset = _charIndexMap[idx];
                  print('传过来的值是${idx}+将要滑动到${offset}距离');
                  _scrollController.animateTo(offset, duration:Duration(milliseconds:200) , curve: Curves.easeIn);
                },),
                right: 0,
                top: ScreenHeight(context)/8,
                height: ScreenHeight(context)/2,
                //bottom: ScreenWidth(context)/4,
                width: 40,
              ),
            ],
          )
        );
      }
    

    优化:当要滑动的offset大于listview内容的总高度,则滑到最底部即可

    待补充....
    

    优化:为标签栏添加可见视图,滑动或者点击的时候显示,结束拖动或者点击的时候消失

    优化方法:
      Map findIndexChar(BuildContext context , Offset point){
        //全局转标转化,转化局部坐标
        RenderBox box =   context.findRenderObject();
        var local = box.globalToLocal(point);
        //print('${local.dy}');
        //获取每一个索引的高度
        double charhight = ScreenHeight(context) / 2 / chars.length;
        int index = 0 ;
        if(local.dy < 0){//滑出最顶部了
        }else if(local.dy > ScreenHeight(context) / 2){//滑出最底部了
          index =  chars.length - 1;
        }else{
          index =  local.dy ~/  charhight;
        }
        Map map = Map();
        map['index'] = index;
        map['char'] =  chars[index];
        return map;
      }
    
    GestureDetector(
                onVerticalDragDown:(DragDownDetails details){
                  String slectedChar = findIndexChar(context ,details.globalPosition)['char'];
    
                  if(widget.selectCharBlock != null){
                    widget.selectCharBlock(slectedChar);
                  }
                  setState(() {
                    _selectIndex =  findIndexChar(context ,details.globalPosition)['index'];
                  });
                } ,
                onVerticalDragUpdate: (DragUpdateDetails details){
                  String slectedChar = findIndexChar(context ,details.globalPosition)['char'];
                  if(widget.selectCharBlock != null){
                    widget.selectCharBlock(slectedChar);
                  }
                  setState(() {
                    _selectIndex =  findIndexChar(context ,details.globalPosition)['index'];
                  });
                },
                onVerticalDragEnd:(DragEndDetails details){
                  setState(() {
                    _selectIndex =  -999;
                  });
                } ,
    
     double showOffsetY(int index){
        double charhight = ScreenHeight(context) / 2 / chars.length;
        return charhight * index;
      }
    
    image.png

    总结:本节内容有点多,嘿嘿,但是还是很满足,算是比较复杂的一个页面了 完结✿✿ヽ(°▽°)ノ✿

    相关文章

      网友评论

          本文标题:第八章:Flutter之wechatDemo(通讯录页面下)

          本文链接:https://www.haomeiwen.com/subject/lndjjltx.html