美文网首页FlutteriOS开发Flutter探索
iOS开发Flutter探索-实现一个IndexBar索引选择器

iOS开发Flutter探索-实现一个IndexBar索引选择器

作者: 泽泽伐木类 | 来源:发表于2020-06-18 23:12 被阅读0次

    前言

    本篇依然是针对Flutter中UI界面的实操。我们通过实现一个类似于iOS下UITableView 右侧的索引条的一个小部件,来加深对之前内容的学习。
    部件中主要涉及到的有:
    Stack(),
    Positioned(),
    Column(),
    Expanded(),
    GestureDetector(),
    ListView.builder()
    ScrollController(),
    Alignment()
    .......
    这里先说几个比较关键的东西:

    • Alignment(x,y):是控制容器内子部件的偏移量,取值范围是-1~1,当小于-1或者大于1时,偏移就超出了容器,这也是实现IndexBar 放大气泡能上下移动的关键
    • Expanded():多个被它包住的子部件会在容器内平分区域,如果只有一个被包裹的子部件,默认沾满整个容器
    • ScrollController():滚动控制器,它并非独立存在,而是需要传递给ListView.buidler()controller属性,换句话说就是ListView.buidler()自己无法完成滚动等操作,而是委托给了ScrollController()。比如在ios开发中我们可以通过调用UITableView.scrollToRowAtIndexPath:atScrollPosition:animated:来主动触发滚动,而这里需要委托ScrollController()来间接完成。
      GestureDetector():让一个部件具有事件响应能力,内部有很多的回调函数,包括单击,长按,拖拽等。

    先上一张效果图:


    效果图.gif

    布局分析

    首先当前ListView()ZZIndexBar````需要一个Stack()布局,使得ZZIndexBar压在ListView()上层,在ZZIndexBar内部,先是一个Row布局,左边是气泡框,右边是字母列表,字母列表内部又是一个Column布局。结构也比较简单;字母列表需要一个GestureDetector()```包裹起来达到响应事件的能力。

    • 已上课程页面
    @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('已上课程'),
            actions: [
              GestureDetector(
                child: Container(
                  margin: EdgeInsets.only(right: 10),
                  child: Icon(Icons.add),
                ),
                onTap: (){
                },
              ),
            ],
          ),
          body: Container(
            child: Stack(
              children: [
                ListView.builder(  //具备复用能力的ListView()
                  itemCount: _headerData.length+_listData.length,
                  itemBuilder: _cellForRowAtIndex,
                  controller: _scrollController, //给ListView设置一个委托控制器(ScrollController),间接操作滚动行为等
                ),
                ZZIndexBar(
                  indexBarCallBack: (String str){
                    //这里响应后 我们通过Str换算出ListView需要偏移的位置
                    //并通过ScrollController来间接操作ListView的滚动行为
                    print('====='+str);
                    //_groupOffsetMap 维护了一个根据str找到具体的offset值的Map 就不赘述了
                    if (_groupOffsetMap[str] != null) {
                      //animateTo()来发生滚动偏移
                      _scrollController.animateTo(
                        _groupOffsetMap[str],   //偏移的值
                        duration: Duration(milliseconds: 10), //动画持续时长
                        curve: Curves.easeIn,  //动效执行曲线
                      );
                    }
                  },
                )
              ],
            )
          ),
        );
      }
    }
    
    • ZZIndexBar
    @override
      Widget build(BuildContext context) {
        List<Widget> words = [];
        for (int i = 0; i < INDEX_WORDS.length; i++) {
          words.add(Expanded(
            child: Container(
              child: Text(
                INDEX_WORDS[i],
                style: TextStyle(color: Colors.white, fontSize: 10),
              ),
            ),
          ));
        }
        return Positioned(
            right: 0,
            top: ScreenHeight(context) / 8,
            height: ScreenHeight(context) / 2,
            width: 120,
            child: Row(
              children: [
                Container(
                  alignment: Alignment(0.0,_offsetY),  //上下移动的核心
                  width: 100,
    //              color: Colors.red,
                  child: _poHidden == true
                      ? null
                      : Stack(
                          alignment: Alignment(-0.2, 0),  //对齐方式
                          children: [
                            Image(
                              image: AssetImage('images/ppo.png'),
                              width: 60,
                            ),
                            Text(
                              _indexText,
                              style: TextStyle(fontSize: 35, color: Colors.white),
                            )
                          ],
                        ),
                ),
                GestureDetector(
                  onVerticalDragDown: (DragDownDetails details) {
                    //垂直方向 touchesBegin:
                   //details.globalPosition 获取当前拖动的屏幕坐标
                    //details.localPosition  获取当前context的坐标
                    int index = getIndex(context, details.localPosition);
                    if(index != _selectorIndex){
                      _selectorIndex = index;
                      setState(() {
                        //callback 回调
                        widget.indexBarCallBack(INDEX_WORDS[index]);
                      });
                    }
                    //UI显示
                    setState(() {
                      _indexText = INDEX_WORDS[index];
                      _offsetY = 2.2 / (INDEX_WORDS.length - 1) * index - 1.1;
                      _poHidden = false;
                    });
                  },
                  onVerticalDragUpdate: (DragUpdateDetails details) {
                    //垂直方向 touchesMove:
                    int index = getIndex(context, details.localPosition);
                    if(index != _selectorIndex){
                      _selectorIndex = index;
                      setState(() {
                        widget.indexBarCallBack(INDEX_WORDS[index]);
                      });
                    }
                    //UI显示
                    setState(() {
                      _indexText = INDEX_WORDS[index];
                      _offsetY = 2.2 / (INDEX_WORDS.length - 1) * index - 1.1;
                      _poHidden = false;
                    });
                  },
                  onVerticalDragEnd: (DragEndDetails details) {
                    //垂直方向 touchesEnd:
                    //UI显示
                    _poHidden = true;
                    _selectorIndex = -1;
                    setState(() {});
                  },
                  child: Container(
                    width: 20,
                    color: Color.fromRGBO(1, 1, 1, 0.3),
                    child: Column(
                      children: words,
                    ),
                  ),
                )
              ],
            ));
      }
    

    Positioned()通常用在Stack()中来控制一个容器的显示位置。
    onVerticalDragDown,onVerticalDragUpdate,onVerticalDragEndGestureDetector() 的具体触摸回调函数,我们在这3个阶段做了一些逻辑处理来控制UI的显示效果。通过indexBarCallBack将事件结果传递出去。
    这里主要看一下
    int getIndex(BuildContext context,Offset localPosition)

    //根据屏幕坐标来换算获取字母索引值
    int getIndex(BuildContext context,Offset localPosition){
    
      double y =  localPosition.dy;
      //每一个Item的高度
      var itemHeigth = ScreenHeight(context) /2 /INDEX_WORDS.length;
      //'~/'相除取整  'clamp'取值范围
      int index = (y ~/ itemHeigth).clamp(0, INDEX_WORDS.length);
      return index;
    }
    

    外部通过scrollController 实现联动

    ZZIndexBar(
          indexBarCallBack: (String str){
          //这里响应后 我们通过Str换算出ListView需要偏移的位置
          //并通过ScrollController来间接操作ListView的滚动行为
          print('====='+str);
          //_groupOffsetMap 维护了一个根据str找到具体的offset值的Map 就不赘述了
          if (_groupOffsetMap[str] != null) {
              //animateTo()来发生滚动偏移
              _scrollController.animateTo(
                  _groupOffsetMap[str],   //偏移的值
                  duration: Duration(milliseconds: 10), //动画持续时长
                  curve: Curves.easeIn,  //动效执行曲线
              );
          }
        },
    )
    

    总结

    依然不存在任何难点,没什么可说的。。。。。🙃

    相关文章

      网友评论

        本文标题:iOS开发Flutter探索-实现一个IndexBar索引选择器

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