likes
comments
collection
share

Flutter实战-搭建微信项目(五)

作者站长头像
站长
· 阅读数 7

最终效果

Flutter实战-搭建微信项目(五)

索引点击回调

经过上篇,我们把简单的布局已经弄好了,但是点击索引条的时候ListView的cell并不会偏移。那么此时我们这里来实现下:点击索引的时候,我们需要把当前点击的值传递给上一层friend_page页面,这里控制着所有的Widget, 那我们就在IndexBar里面新增一个回调

class IndexBar extends StatefulWidget {
  final void Function(String str) indexBarCallBack;
  IndexBar({required this.indexBarCallBack});

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

onVerticalDragDownonVerticalDragUpdate�的时候调用

onVerticalDragUpdate: (DragUpdateDetails details) {
   widget.indexBarCallBack(getIndexWord(context, details.globalPosition));
},

同时,在FriendPage这个页面初始化构造IndexBar的时候传递这个indexBarCallBack

索引点击屏幕滚动

在Flutter中把当前的控制器移动一定的偏移量有一个函数

  Future<void> animateTo(
    double offset, {
    required Duration duration,
    required Curve curve,
  }) async {
    assert(_positions.isNotEmpty, 'ScrollController not attached to any scroll views.');
    await Future.wait<void>(<Future<void>>[
      for (int i = 0; i < _positions.length; i += 1) _positions[i].animateTo(offset, duration: duration, curve: curve),
    ]);
  }

所以我们需要计算出点击了侧边索引之后,主屏幕需要移动的偏移量,这个Index和对应的偏移量我们可以使用一个Map字典来保存

  // 字典,里面用来存放item和高度对应
  final Map _groupOffsetMap = {
    INDEX_WORDS[0]: 0.0, // 搜索
    INDEX_WORDS[1]: 0.0, // 五角星 点击这两个的时候屏幕不用滚动
  };

之前在initState函数中计算出了排序之后的_listData,我们可以通过对这个数据源的indexLetter逐个计算,当下一个与上一个的indexLetter相同的时候,这个便宜量是一个_cellHeight的大小,当下一个与上一个的indexLetter不相同的时候,这个偏移量就是_cellheight + _cellGroupTitleHeight

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    _viewController = ScrollController();
    _listData..addAll(datas)..addAll(datas);
    _listData.sort((Friends a, Friends b) {
      return a.indexLetter!.compareTo(b.indexLetter!);
    });
    // 第三个开始
    var _groupOffset = _cellHeight * _headerData.length;
    for (int i = 0; i < _listData.length; i++) {
      if (i < 1) {
        _groupOffsetMap.addAll({_listData[i].indexLetter: _groupOffset});
        _groupOffset += _cellGroupHeight + _cellHeight;
      } else if (_listData[i].indexLetter == _listData[i - 1].indexLetter) {
        _groupOffset += _cellHeight;
      } else {
        _groupOffsetMap.addAll({_listData[i].indexLetter: _groupOffset});
        _groupOffset += _cellGroupHeight + _cellHeight;
      }
    }
  }

这样操作了之后,这个_groupOffsetMap里面的每个字母和偏移量就一一对应好了,打印来观察下: Flutter实战-搭建微信项目(五)

当然这里的_groupOffsetMap[indexStr]也需要判空一下,毕竟有些首字母可能没有。

IndexBar(indexBarCallBack: (String str) {
   print('点击了$str');
   print('$_groupOffsetMap');
   if (_groupOffsetMap[str] != null) { // 注意啦 这里有个问题
          _viewController!.animateTo(_groupOffsetMap[str],
          duration: Duration(microseconds: 100),
          curve: Curves.easeIn);
    } ;
 })

当我像上面那样写的时候,一直提示 type 'null' is not a subtype of type 'string' of 'function result',最后查了下资料,这个Map初始化的时候默认的是<dynamic,dynamic>运行时类型,所以此时判断!= null时就会编译报错,解决的办法是直接类型转换new Map<String, double>.from(_groupOffsetMap)

Flutter实战-搭建微信项目(五)

气泡浮窗

分析:这里点击到索引条的时候会出现一个气泡的图案,这个气泡可以看做是一个Image + Text构成的Stack布局, 文字可通过获取当前的index拿到,这个上篇就已经实现了。图片的话是一个固定的气泡切图,通过动态控制这个Stack的显示和隐藏来达到效果,这里唯一比较难一点的是计算这个偏移量。

我们在Positioned中新增一个Row布局,第一个child是一个Stack,第二个child就是index_bar,先在中心(0,0)固定显示A

Container(
  width: 100,
  color: Colors.red,
  child: Stack(
         //  alignment: Alignment(-0.2, 0),
           children: [
             Container(
               child: Image.asset('images/气泡.png'),
               width: 60,
              ),
              Container(
                child: Text('A',style: TextStyle(fontSize: 35, color: Colors.white)),
              )
           ],
         ),
),

Flutter实战-搭建微信项目(五)

位置有点偏左,调整一下alignment,我这里使用的是alignment: Alignment(-0.2, 0)

Flutter实战-搭建微信项目(五)

修改这个stack在Container中的alignment,移动到底部alignment: Alignment(0, 1.1)

Flutter实战-搭建微信项目(五)

所以上下的间距是2.2,每一个index的偏移量就是 2.2/索引总个数 * 当前的index -1.1.此时三个控制变量的条件都齐全了

  double _indexBarOffsetY = 0.0; // -1.1到1.1之间的偏移量  中心点的是0
  bool _indexBarHidden = true; // 是否隐藏
  String _indexBarText = 'A'; // 当前正在显示的字母

在Gesture中更新状态:

Container(
  child: GestureDetector(
    onVerticalDragDown: (DragDownDetails details) {
      int index = getIndexWord(context, details.globalPosition);
      widget.indexBarCallBack(INDEX_WORDS[index]);
      setState(() {
        _backColor = Color.fromRGBO(1, 1, 1, 0.5);
        _textColor = Colors.white;
        _indexBarHidden = false;
        _indexBarOffsetY = 2.2 / INDEX_WORDS.length * index - 1.1;
        _indexBarText = INDEX_WORDS[index];
      });
    },
    onVerticalDragEnd: (DragEndDetails details) {
      setState(() {
        _backColor = Color.fromRGBO(1, 1, 1, 0);
        _textColor = Colors.grey;
        _indexBarHidden = true;
      });
    },
    onVerticalDragUpdate: (DragUpdateDetails details) {
      int index = getIndexWord(context, details.globalPosition);
      widget.indexBarCallBack(INDEX_WORDS[index]);
      setState(() {
        _indexBarHidden = false;
        _indexBarOffsetY = 2.2 / INDEX_WORDS.length * index - 1.1;
        _indexBarText = INDEX_WORDS[index];
      });
    },
    child: Container(
      width: 20,
      child: Column(children: _widgetData),
      color: _backColor,
    ),
  ),
)

好了,到这里索引指示器就正式告一段落了。