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

  • Our last article achieved the index bar display and click state change, next we want to achieve the index bar bubble display and address book linkage.

1. Figure out what text to click or drag

The idea is to return the corresponding letter by calculating the position of each letter in the index bar. We get the current widget’s box from the context, the RenderBox is the size of the index bar, and we use globalToLocal to convert the external coordinates to the current control’s coordinate system. Just like when we convert the child view point from the global coordinate point to the parent view coordinate, we don’t have to convert.

onVerticalDragUpdate: (DragUpdateDetails details){
  setState(() {
    RenderBox box = context.findRenderObject() as RenderBox ;
    print('$box.globalToLocal(details.localPosition).dy');
    print('Gesture moving position:$details');

  });
},
Copy the code

Get the y value, the distance (x,y) from globalToLocal’s current position to the origin of the widget (upper left corner of the widget). Pass the height of each character according to the offset

/ / drag
onVerticalDragUpdate: (DragUpdateDetails details){
  setState(() {
    RenderBox box = context.findRenderObject() as RenderBox ;
    double y =  box.globalToLocal(details.localPosition).dy;
    / / the item level
    var itemHeight = screenHeight(context)/2 / INDEX_WORDS.length;
    int index = y ~/ itemHeight;
    print('Selects number one$indexA ');

  });
},
Copy the code

This will also get an error if it goes beyond the screen array

We also need to set the index range using clamp from 0 to index_words.length-1 to get the corresponding subscript and finally extract STR from the array. Let’s pull out the method

// Get the character of the selected Item!!
String getIndex(BuildContext context, Offset globalPosition) {
  // Get the widget box before clicking
  RenderBox box = context.findRenderObject() as RenderBox ;
  // Get the y value, the distance (x,y) from the origin of the widget (top left corner of the widget)
  double y = box.globalToLocal(globalPosition).dy;
  // Figure out the height of the character
  var itemHeight = screenHeight(context) / 2 / INDEX_WORDS.length;
  // Count the number of items
  int index = (y ~/ itemHeight).clamp(0, INDEX_WORDS.length - 1);
  return INDEX_WORDS[index];
}
Copy the code

2. Index bar callback

We’re calling back a click on the return character in the index bar, and we need the high speed outside of the ListView, so we want a callback, and we define a callback method.

class CloumnIndexBar extends StatefulWidget {

  final void Function(String str) indexBarCallBack;
  CloumnIndexBar({required this.indexBarCallBack});

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

We pass this character around as we click and move

/ / drag
onVerticalDragUpdate: (DragUpdateDetails details){
  setState(() {
  var str =  getIndex(context, details.globalPosition);
  widget.indexBarCallBack(str);
  print(str);

  });
},
/ / click
onVerticalDragDown: (DragDownDetails details){
  setState(() {
    var str =  getIndex(context, details.globalPosition);
    widget.indexBarCallBack(str);
    print('Gesture into position:$details');
    _backgroundColor = Color.fromRGBO(1.1.1.0.5);
    _textColor = Colors.white;
  });

},
Copy the code

We get a callback at creation time

ListView scrolling requires a controller, so we need to create it at the beginning, which is defined directly here

final ScrollController _scrollController =  ScrollController();
Copy the code

We use animateTo of _scrollController for scrolling, so let’s do a 300 test here

child:Stack(
  children: [
    ListView.builder(itemBuilder: _itemBuilder,
      itemCount: _headerData.length+_listDatas.length,
      controller: _scrollController,

    ),
    CloumnIndexBar(indexBarCallBack: (String str){

      _scrollController.animateTo(300, duration: Duration(microseconds: 100), curve: Curves.easeIn);

      print('is selected$str'); },) ",Copy the code

So we can figure out where our letters correspond.

3. Calculate the offset

The height of the cell and the height of the head we defined earlier

double _cellHeight = 54.5;
double _groupHeight = 30.0;
Copy the code

Then we define a dictionary in which each letter contains the corresponding offset


final Map _groupOffsetMap = {
  INDEX_WORDS[0] :0.0,
  INDEX_WORDS[1] :0.0};Copy the code

Here the first two are fixed, so there’s no offset. We can’t put it in itemBulid when we do the calculation, it only loads when we slide it, so we do the calculation in initState after the data is processed.

var _groupOffset = _cellHeight * _headerData.length;// Default location
// Enter the loop to calculate the position of each head. In the dictionary
for (int i = 0; i < _listDatas.length; i++) {
  if (i < 1) {
    // The first cell must have a head!
    _groupOffsetMap.addAll({_listDatas[i].indexLetter: _groupOffset});
    // add _groupOffset
    _groupOffset += _cellHeight + _groupHeight;
  } else if (_listDatas[i].indexLetter == _listDatas[i - 1].indexLetter) {
    // Add the height of the Cell
    _groupOffset += _cellHeight;
  } else {
    _groupOffsetMap.addAll({_listDatas[i].indexLetter: _groupOffset});
    // add _groupOffset_groupOffset += _cellHeight + _groupHeight; }}Copy the code

We showed it in the previous callback, and we added a judgment in case it didn’t exist

if(_groupOffsetMap[str] ! =null) {
  _scrollController.animateTo(_groupOffsetMap[str],
      duration: Duration(microseconds: 100),
      curve: Curves.easeIn);
}
print('is selected$str');
Copy the code

4. Indicator display

The bubble to the left of the indicator can be treated as an integral part of the indicator, so we can use row for layout.

Let’s adjust the width of the index bar and the overall width so thatIn the middlethe

Let’s go ahead and set the one on the leftindicatorMostly bubbles and text so we can usestackOn the parcel

Let’s reposition it, center it

We know that the coordinates in flutter are1 to 10 in the middle, so we can usealignmentTo represent theThe scope of

I’m going to go a little bit further down here and it’s going to be in the range of y(-1.1, 1.1)

We define three variables,Figure out where -1.1 is at 1.1 based on index

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

We’re going to hide it when we click or drag it and we’re going to change this string that was returned to index

Widget build(BuildContext context) {
  final List<Widget> _words = [];

  for(int i = 0; i<INDEX_WORDS.length; i++){ _words.add(Expanded(child: Text(INDEX_WORDS[i],style: TextStyle(fontSize:10,color: _textColor),)));

  }
  return Positioned(
    top: screenHeight(context)/8,
    width: 120,
    right: 0,
    height: screenHeight(context)/2,
    child:
     Container(
       child:
       Row(
         children: [
           Container(

             alignment: Alignment(0,_indicatorOffsetY),
             width: 100,

             child: _indicatorHidden ? null : Stack(
               alignment: Alignment(0.2.0),
               children: [
                 Image(
                   image: AssetImage('images/bubbles. PNG'),
                   width: 60,
                 ),
                 Text(_indicatorText,
                   style: TextStyle(fontSize: 35, color: Colors.white),
                 )
               ],
             ),
           ),
           GestureDetector(
             / / drag
             onVerticalDragUpdate: (DragUpdateDetails details){
               setState(() {
                 int  index  =  getIndex(context, details.globalPosition);
                 widget.indexBarCallBack(INDEX_WORDS[index]);
                 setState(() {
                   _indicatorOffsetY = 2.2 / INDEX_WORDS.length * index - 1.1;
                   _indicatorText = INDEX_WORDS[index];
                   _indicatorHidden = false;
                 });

               });
             },
             / / click
             onVerticalDragDown: (DragDownDetails details){
               setState(() {
                 int  index  =  getIndex(context, details.globalPosition);
                 widget.indexBarCallBack(INDEX_WORDS[index]);
                 print('Gesture into position:$details');

                 _indicatorOffsetY = 2.2 / INDEX_WORDS.length * index - 1.1;
                 _indicatorText = INDEX_WORDS[index];
                 _indicatorHidden = false;
                 _backgroundColor = Color.fromRGBO(1.1.1.0.5);
                 _textColor = Colors.white;
               });

             },
             / / leave
             onVerticalDragEnd:(DragEndDetails details){
               setState(() {
                 _backgroundColor = Color.fromRGBO(1.1.1.0);
                 _textColor = Colors.black;
                 _indicatorHidden = true;

               });

             },
             child:
             Container(
               width: 20,
               color:_backgroundColor,
               child:
               Column(
                 children: _words,
               ),
             ),
           )
         ],
       ),
     )
  );
}
Copy the code

The end result: