This is the 15th day of my participation in the November Gwen Challenge. Check out the event details: The last Gwen Challenge 2021

The index of article

There is too much functionality here so create a new file to write the index bar. The index bar is superimposed on top of the Listview, so the body in the moments page should use the Stack. Then add the index bar.

body: Stack( children: [ Container( child: ListView.builder( itemBuilder: _itemForRow, itemCount: Datas.length + _headerdata.length,), color: weChatThemColor,), // IndexBar(),Copy the code

Add index bar data

Const INDEX_WORDS = [' 🔍 ', 'being', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I' and 'J' and 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' ];Copy the code

Create a list of widgets

  final List<Widget> words = [];

Copy the code

Add widgets in initState

for (int i = 0; i < INDEX_WORDS.length; i++) { words.add( Expanded(child: Text(INDEX_WORDS[i],style: TextStyle(fontSize: 10,color: Colors.grey),),) ); }Copy the code

Then write the index bar interface in build so that the index bar can be displayed.

Jam (right: 0.0, top: screenHeight(context)/8, height: screenHeight(context)/2, width: 30, child: Column( children: words, ), );Copy the code

The index bar also needs to capture which one is selected, so you define two attributes first.

Color _bkColor = color.fromrgbo (1, 1, 1, 0.0); Color _textColor = Colors.black;Copy the code

Then Column is wrapped with the GestureDetector.

Change the text color of the text created previously in initState to _textColor. Change the color of Column to _bkColor after wrapping it in Container.

Listen on onVerticalDragDown and onVerticalDragEnd at GestureDetector, then setState changes the background color and text color.

OnVerticalDragDown: (DragDownDetails details){setState(() {_bkColor = const color.fromrgbo (1, 1, 1, 0.5); _textColor = Colors.white; }); }, onVerticalDragEnd: (DragEndDetails details){setState(() {_bkColor = const color.fromrgbo (1, 1, 1, 0.0); _textColor = Colors.black; }); },Copy the code

The background color changes, but the text color does not, because the text color is in initState. So interface related items should not be placed in data related areas, otherwise bugs will occur, so words and loops will be placed in build.

Then listen on the onVerticalDragUpdate at the GestureDetector, use context.findRenderObject to find the nearest widget, and use globalToLocal to calculate the y value from the current click to the widget’s origin. And then figure out the height of the character, figure out what item it is, and then you get the letter that you click on.

OnVerticalDragUpdate: (DragUpdateDetails details){RenderBox box = context.findRenderObject() as RenderBox; Double y = box. GlobalToLocal (details.globalPosition).dy; double y = box. Var itemHeight = screenHeight(context) / 2 / index_words.length; Int index = (y ~/ itemHeight).clamp(0, INDEX_WORDS.length-1); print('${INDEX_WORDS[index]}'); },Copy the code

Encapsulate the steps inside as a method

String getIndex (BuildContext Context,Offset globalPosition) {RenderBox box = context.findRenderObject() as RenderBox; Double y = box. GlobalToLocal (globalPosition).dy; double y = box. Var itemHeight = screenHeight(context) / 2 / index_words.length; Int index = (y ~/ itemHeight).clamp(0, INDEX_WORDS.length-1); return INDEX_WORDS[index]; }Copy the code

onVerticalDragUpdate

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

I need to return the data to the address book page, so I’ll write a callback here.

  final void Function(String str)? indexBarCallBack;
  IndexBar({this.indexBarCallBack});
Copy the code

Call indexBarCallBack inside onVerticalDragUpdate.

onVerticalDragUpdate: (DragUpdateDetails details) { if (widget.indexBarCallBack ! = null) { widget.indexBarCallBack! ( getIndex(context, details.globalPosition)); }},Copy the code

Pass in the indexBarCallBack using IndexBar outside

IndexBar(indexBarCallBack: (String str){
      
        }),
Copy the code

The next step is to scroll according to the letter clicked, so you need to calculate the scrolling distance of each letter. I need to declare a ScrollController

   ScrollController _scrollController =  ScrollController();
Copy the code

Set it to controller of _scrollController.

 controller:  _scrollController,
Copy the code

Then declare map, _cellHeight, and _groupHeight

  final Map _groupOffsetMap = {};
    final double _cellHeight = 54.5;
    double _groupHeight = 30;
Copy the code

Then calculate the scrolling distance in initState.

var _groupOffset = _cellHeight * _headerData.length; // Calculate the position of each header through the loop and put it into the dictionary. for (int i = 0; i < _listDatas.length; I ++) {if (I < 1){// The first cell must have a header _groupoffsetmap.addall ({_listDatas[I].indexLetter:_groupOffset}); _groupOffset += _cellHeight + _groupHeight; } else if (_listDatas[i].indexLetter == _listDatas[i-1].indexLetter ) { _groupOffset += _cellHeight; } else { _groupOffsetMap.addAll({_listDatas[i].indexLetter:_groupOffset}); _groupOffset += _cellHeight + _groupHeight; }}Copy the code

You can then scroll through the IndexBar callback.

 IndexBar(indexBarCallBack: (String str){
          if (_groupOffsetMap[str] != null) {
            _scrollController.animateTo(
                _groupOffsetMap[str], duration: Duration(microseconds: 100),
                curve: Curves.easeIn);
          }
        }),
Copy the code

For the left side of the bubble, wrap the Indexbar of the Indexbar.

Then add images and text to the Container.

Container(alignment (0,-1.1), width: 100, color: Colors. Red, child: Stack(alignment: 0,-1.1) Alignment(-0.2, 0), children: [Image(Image: AssetImage('images/ bubble.png '), width: 60,), Text('A', style: TextStyle( fontSize: 35, color: Colors.white, ), ), ], ), ),Copy the code

You then need to transform the bubble text depending on where you click, and hide the bubble when you release it, so you declare three properties.

Double _indicatorY = 0.0; String _indicatorText = "A"; bool _indicatorHidden = true;Copy the code

Change getIndex to return index only, then change _indicatorY and _indicatorText in onVerticalDragDown and onVerticalDragUpdate, and set _indicatorHidden to false. And then inside onVerticalDragEnd set _indicatorHidden to true.

onVerticalDragDown: (DragDownDetails details) { int index = getIndex(context, details.globalPosition); if (widget.indexBarCallBack ! = null) { widget.indexBarCallBack! ( INDEX_WORDS[index]); } setState(() {_indicatorY = 2.2 / index_words.length * index - 1.1; _indicatorText = INDEX_WORDS[index]; _indicatorHidden = false; _bkColor = const color.fromrgbo (1, 1, 1, 0.5); _textColor = Colors.white; }); }, onVerticalDragUpdate: (DragUpdateDetails details) { int index = getIndex(context, details.globalPosition); if (widget.indexBarCallBack ! = null) { widget.indexBarCallBack! ( INDEX_WORDS[index]); } setState(() {_indicatorY = 2.2 / index_words.length * index - 1.1; _indicatorText = INDEX_WORDS[index]; _indicatorHidden = false; }); }, onVerticalDragEnd: (DragEndDetails details) {setState(() {_bkColor = const color.fromrgbo (1, 1, 1, 0.0); _textColor = Colors.black; _indicatorHidden = true; }); },Copy the code

Then modify the bubble side and return null if _indicatorHidden is true, otherwise return the bubble and the position of the bubble changes as _indicatorY changes.

Container( alignment: Alignment(0,_indicatorY), width: 100, color: Colors.red, child: _indicatorHidden ? Null: Stack(alignment: alignment (-0.2, 0), children: [Image(Image: AssetImage('images/ bubble.png ')), width: 60, ), Text( _indicatorText, style: TextStyle( fontSize: 35, color: Colors.white, ), ), ], ), ),Copy the code