美文网首页
第八章: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