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

Simple address book list page we have achieved, today we will achieve the index bar address book; We all know that the address book index bar floats above the list, on the right side of the screen; So, the way we build is going to change;

Build index bar

We need to use Stack to build the entire interface so that the index bar appears above the list:

Displays index bar data

Let’s display the following information on the index bar:

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

The index bar is displayed vertically, so it’s obvious that Column needs to be used for layout. We’ll create the Widget array for Column’s children:

final List<Widget> _indexList = []; // Index bar Widget array

@override
  void initState() {
    super.initState(); .Article / / index
    for (int i = 0; i < INDEX_WORDS.length; i++) {
      _indexList.add(Text(INDEX_WORDS[i], style: const TextStyle(fontSize: 12))); }}Copy the code

To do this, we place all widgets that need to be displayed in _indexList. We simply assign _indexList to the children of Column:

Index bar layout optimization

Currently, all index letters are displayed at the top and are too close to each other, so we can use Expanded to wrap Text around them and let them adapt to the layout:

Article / / index
for (int i = 0; i < INDEX_WORDS.length; i++) {
  _indexList.add(
      Expanded(
        child: Text(
          INDEX_WORDS[i],
          style: const TextStyle(fontSize: 12),),)); }Copy the code

At this point, re-render the interface as follows:

Let’s change the position of the index bar as follows:

For ease of operation and expansion, we extracted the IndexBar and put it in IndexBar. The complete code is as follows:

This will allow us to use IndexBar directly in the Stack and facilitate subsequent extensions;

The status of the index bar changed

Next, change the state of the index bar in different situations: default transparent background, black font, click or slide on the background black, white font;

We define two colors, a background color and a font color:

  Color _bgColor = const Color.fromRGBO(1.1.1.0.0); // Background color is transparent by default
  Color _textColor = Colors.black; // The font color defaults to black
Copy the code

Next, when the gesture is triggered, change two colors:

At this point, we notice that the background color changes when the gesture is triggered, but the font color does not. This is because the initialization of the Text control for our font is in the initState method, which is only called when the interface is created. SetState triggers the build method, so we should put the loop to create Text in build, and the final code is as follows:

The effect is as follows:

Note that the _indexList definition should also be included in the build, otherwise the list will be rendered once every time the array is not recreated and elements will be added to the list.

Determines the index position of the mouse

Now that we’ve almost finished indexing the bar, what if we knew what letter was clicked on when we clicked? We note whether the gesture event is parametric:

DragDownDetails({
  this.globalPosition = Offset.zero,
  Offset? localPosition,
})
Copy the code

So what do these two parameters mean? Let’s print it out:

We can confirm by printing information that these two are coordinate positions:

  • globalPosition: Global position of the current click point;
  • localPosition: Position of the current click point in the current part;

LocalPosition can also be computed using globalPosition:

Then we can determine which character is clicked:

We say operations here are encapsulated as methods:

String getIndexString(BuildContext context, Offset localPosition) {
  // Click on the y coordinate of the current widget
  double y = localPosition.dy;
  // Calculate the height of the character
  var itemHeight = screenHeight(context) / 2 / INDEX_WORDS.length;
  // Count the number of characters
  int index = y ~/ itemHeight; // ~/
  // The currently selected character
  String str = INDEX_WORDS[index];
  return str;
}
Copy the code

There is a problem, however, with an error if we slide beyond the index bar:

Therefore, our index value range is limited:

String getIndexString(BuildContext context, Offset localPosition) {
  // Click on the y coordinate of the current widget
  double y = localPosition.dy;
  // Calculate the height of the character
  var itemHeight = screenHeight(context) / 2 / INDEX_WORDS.length;
  // Count the number of characters
  int index = (y ~/ itemHeight).clamp(0, INDEX_WORDS.length - 1); // ~/ indicates that the integer is limited to [0, length-1].
  // The currently selected character
  String str = INDEX_WORDS[index];
  return str;
}
Copy the code

The index bar associates with the contact list

When we click on the IndexBar to scroll the contact list to the corresponding position, we need to keep an interface for IndexBar:

// Click the index character callback;
final void Function(String str)? indexBarCallBack;
IndexBar({this.indexBarCallBack});
Copy the code

Then call back to this function when IndexBar triggers the gesture event:

This way, when using IndexBar, we can get the clicked character in its constructor:

Then, if we want the ListView to scroll synchronously, we need to use the ListView property controller. We define a _scrollController property and initialize it in initState:

Then assign _scrollController to the Controller property of the ListView:

So, if we need to scroll the ListView, we just need to get _scrollController and scroll; As follows:

After clicking the index character, scroll the ListView to 260 pixels, for a total animation time of 1 second, Curves. EaseIn means that the animation starts and ends fast and is slow in the middle; The effect is as follows:

Now that we’re at a fixed value, as long as we can figure out where each group is, we can click on the character and scroll to a certain group;

Calculates the position of index groups

If we want to scroll to the position of the group, we need to calculate the position information in advance. We create a Map to store the corresponding relationship between each group and its height:

/* Stores the group of characters and their corresponding height */
final Map _groupOffsetMap = {
  INDEX_WORDS[0] :0.0,
  INDEX_WORDS[1] :0.0};Copy the code

0 and 1 are not letters, they don’t need to scroll, so the height is 0; Then loop through the height of each packet header in initState:

final double _rowHeight = 51;
final double _groupHeaderHeight = 30;

// Loop to calculate the character position of each group header and place it in the map
var _groupOffset = _rowHeight * _headerList.length; // The height of the head four
for (int i = 0; i < _listDatas.length; i++) {
  if (i < 1) {
    // The first must have a head
    _groupOffsetMap.addAll(
        {_listDatas[i].indexLetter: _groupOffset}); // The first head height is 51 * 4
    // Change the offset position
    _groupOffset += _rowHeight + _groupHeaderHeight;
  } else if (_listDatas[i].indexLetter == _listDatas[i - 1].indexLetter) { // If the two indexLetters are the same, do not save them;
    // Add offset instead of map
    _groupOffset += _rowHeight;
  } else {
    _groupOffsetMap.addAll({_listDatas[i].indexLetter: _groupOffset});
    // Change the offset position_groupOffset += _rowHeight + _groupHeaderHeight; }}Copy the code

While scrolling, extract the height from the Map and then scroll to a specific position:

The running effect is as follows:

Index bar indicator

If we need to display the index bar indicator, we need to change the index bar layout:

We add the bubble, and by default display an A, centered in the bubble:

The text is displayed in the center of the bubble using the Stack alignment attribute to adjust the position. Next is to control the position of the bubble display, warning us to try, the display area of the upper and lower ends of the bubble is as follows:

In addition to controlling the position and text of the indicator, we also need to determine the hidden and displayed state of the indicator. We define three variables:

double _indicatorY = 0.0; // The indicator defaults to Y
String _indicatorTitle = 'A'; // The indicator text defaults to A
bool _indicatorHidden = true; // Indicator display and hide Hide by default
Copy the code

Return the index value of the selected character:

/* Gets the selected character index */
int getIndex(BuildContext context, Offset localPosition) {
  // Click on the y coordinate of the current widget
  double y = localPosition.dy;
  // Calculate the height of the character
  var itemHeight = screenHeight(context) / 2 / INDEX_WORDS.length;
  // Count the number of characters
  int index = (y ~/ itemHeight).clamp(0, INDEX_WORDS.length - 1); Clamp clamp clamp clamp clamp clamp clamp clamp clamp clamp clamp clamp
  return index;
}
Copy the code

The final indicator code is as follows:

The running effect is as follows:

At this point, the address book interface effect has been fully realized;