“This is the fifth day of my participation in the Gwen Challenge in November. Check out the details: The Last Gwen Challenge in 2021.”

The final result

Index click callback

After the previous article, we had a simple layout, but the ListView cell does not shift when the index bar is clicked. When we click on the index, we need to pass the value of the current click to the upper-level friend_page page, which controls all the widgets, so we add a callback to the IndexBar

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

  @override
  _IndexBarState createState() => _IndexBarState();
}
Copy the code

Called when onVerticalDragDown and onVerticalDragUpdate

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

Also, the indexBarCallBack is passed when the FriendPage initializes the Construction of the IndexBar

Index click screen scroll

There is a function in a Flutter to move the current controller by a certain offset

  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),
    ]);
  }
Copy the code

So we need to calculate the offset that the main screen needs to move after clicking the side Index. This Index and its offset can be saved using a Map dictionary

  // a dictionary is used to store items and heights
  final Map _groupOffsetMap = {
    INDEX_WORDS[0] :0.0./ / search
    INDEX_WORDS[1] :0.0.// The screen does not scroll when the pentagram clicks on both
  };
Copy the code

In the initState function, we calculated the sorted _listData. We can calculate the indexLetter of the data source one by one. When the next indexLetter is the same as the last indexLetter, this is the size of _cellHeight. When the next indexLetter is different from the previous one, the offset is _cellHeight + _cellGroupTitleHeight

  @override
  void initState() {
    // TODO: implement initState
    super.initState(); _viewController = ScrollController(); _listData.. addAll(datas).. addAll(datas); _listData.sort((Friends a, Friends b) {returna.indexLetter! .compareTo(b.indexLetter!) ; });// The third start
    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; }}}Copy the code

So once you’ve done that, this_groupOffsetMapEach letter in it corresponds to the offset one by one. Print it to observe:

_groupOffsetMap[indexStr] also needs to be null, because some initial letters may not be present.

IndexBar(indexBarCallBack: (String str) {
   print('click on the$str');
   print('$_groupOffsetMap');
   if(_groupOffsetMap[str] ! =null) { // Notice there is a problem here_viewController! .animateTo(_groupOffsetMap[str], duration:Duration(microseconds: 100), curve: Curves.easeIn); }; })Copy the code

Type ‘null’ is not a subtype of type ‘string’ of ‘function result’ when I write as above, I keep saying type ‘null’ is not a subtype of type ‘string’ of ‘function result’. This Map is initialized with the

runtime type by default. New Map

. From (_groupOffsetMap)
,>
,dynamic>

Bubbles floating window

Analysis: When clicking the index bar, a bubble pattern will appear, which can be regarded as a Stack layout composed of Image + Text. The Text can be obtained by obtaining the current index, which has been achieved in the previous article. The image is a fixed bubble slice, which is achieved by dynamically controlling the Stack’s display and hiding. The only difficulty here is calculating the offset.

The first child is A Stack, and the second child is index_bar, which is fixed in the centre of (0,0) showing A

Container(
  width: 100,
  color: Colors.red,
  child: Stack(
         / / alignment, alignment (- 0.2, 0),
           children: [
             Container(
               child: Image.asset('images/bubbles. PNG'),
               width: 60,
              ),
              Container(
                child: Text('A',style: TextStyle(fontSize: 35, color: Colors.white)),
              )
           ],
         ),
),
Copy the code

The position is slightly to the left. Adjust the alignment. I used alignment here: alignment (-0.2, 0)

Change the stack alignment in the Container and move it to the bottom alignment: Alignment(0, 1.1)

So the spacing is 2.2, and the offset of each index is 2.2/ total number of indexes * the current index -1.1. At this point, the conditions of the three control variables are complete

  double _indexBarOffsetY = 0.0; // The center of the offset between -1.1 and 1.1 is 0
  bool _indexBarHidden = true; // Whether to hide
  String _indexBarText = 'A'; // The letter currently being displayed
Copy the code

Update status in 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,
    ),
  ),
)
Copy the code

Well, this is where the index indicator officially comes to an end.