美文网首页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索引选择器

    前言 本篇依然是针对Flutter中UI界面的实操。我们通过实现一个类似于iOS下UITableView 右侧的索...

  • js提取汉字首字母完成indexBar功能

    indexBar 项目开发中经常会使用到indexBar点击索引栏时,会自动跳转到对应的IndexAnchor锚点...

  • IOS开发问题索引(四)

    全系列文章索引: IOS开发问题索引(一) IOS开发问题索引(二) IOS开发问题索引(三) IOS开发问题索引...

  • IOS开发问题索引(八)

    全系列文章索引: IOS开发问题索引(一) IOS开发问题索引(二) IOS开发问题索引(三) IOS开发问题索引...

  • IOS开发问题索引(七)

    全系列文章索引: IOS开发问题索引(一) IOS开发问题索引(二) IOS开发问题索引(三) IOS开发问题索引...

  • IOS开发问题索引(六)

    全系列文章索引: IOS开发问题索引(一) IOS开发问题索引(二) IOS开发问题索引(三) IOS开发问题索引...

  • IOS开发问题索引(五)

    全系列文章索引: IOS开发问题索引(一) IOS开发问题索引(二) IOS开发问题索引(三) IOS开发问题索引...

  • IOS开发问题索引(九)

    全系列文章索引: IOS开发问题索引(一) IOS开发问题索引(二) IOS开发问题索引(三) IOS开发问题索引...

  • IOS开发问题索引(二)

    全系列文章索引: IOS开发问题索引(一) IOS开发问题索引(二) IOS开发问题索引(三) IOS开发问题索引...

  • IOS开发问题索引(三)

    全系列文章索引: IOS开发问题索引(一) IOS开发问题索引(二) IOS开发问题索引(三) IOS开发问题索引...

网友评论

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

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