FlutteriOS开发Flutter探索

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

2020-06-18  本文已影响0人  泽泽伐木类

前言

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

先上一张效果图:


效果图.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,  //动效执行曲线
                  );
                }
              },
            )
          ],
        )
      ),
    );
  }
}
@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,  //动效执行曲线
          );
      }
    },
)

总结

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

上一篇 下一篇

猜你喜欢

热点阅读