preface

Like contacts, contact list, city selection lists and other data more long list page, we often pay attention to the product design will be on the right side of the page area to provide a list of the vertical index of letters, for users to click on the select fast positioning to the specified index position in the long list, so that the users quickly locate to oneself to filter the data, so as to improve the user experience, Today we will take the list of cities as an example to analyze the experience effect of Flutter

Technical implementation analysis

  • The layout of the city list uses the ListView nested ListView. The outer ListView is responsible for displaying the letter information of the current city group, and the inner ListView is responsible for displaying all cities in the current alphabetic index group
  • The right vertical alphabetic index list is displayed by ListView. When clicking one of the right alphabetic index, dynamically calculate the corner index value of the current alphabetic index in the city list, and then calculate the height value of the character index to be clicked in the city listScrollControllerLet the city list automatically locate to the calculated height position, so as to achieve, click the letter so dynamic positioning city list position linkage effect

Results the following

I first put the effect of the relevant code posted for your reference, and then the code involved in the calculation of the location of the core code to do a detailed explanation, code involved in the network request, as well as the network tool class encapsulation will not be repeated here, Readers can check out my previous blog about getting started with Flutter DIO for details, or find the source code directly on Github and read github.com/xiedong11/f…

The core code

/ * * *@descSelect the City region linkage index page *@author xiedong
 * @date2020-04-30. * /

class PhoneCountryCodePage extends StatefulWidget {
  @override
  State<StatefulWidget> createState(a) => PageState();
}

class PageState extends State<PhoneCountryCodePage> {
  var GET_PHONE_COUNTRY_CODE_URL =
      "https://raw.githubusercontent.com/xiedong11/flutter_app/master/static/phoneCode.json";
  List<String> letters = [];
  List<PhoneCountryCodeData> data;

  ScrollController _scrollController = ScrollController();
  int _currentIndex = 0;

  @override
  void initState(a) {
    super.initState();
    getPhoneCodeDataList();
  }

  getPhoneCodeDataList() async {
    var response = await DioUtils.getInstance().get(GET_PHONE_COUNTRY_CODE_URL);

    var resultEntity = new PhoneCountryCodeEntity.fromJson(json.decode(response));

    if(resultEntity.code==200) {this.setState(() {
        data = resultEntity.data;
        for (int i = 0; i < data.length; i++) { letters.add(data[i].name.toUpperCase()); }}); }}@override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Urban Area Selection"),
        centerTitle: true,
      ),
      body: Stack(
        children: <Widget>[
          data == null || data.length == 0
              ? Text("")
              : Padding(
                  padding: EdgeInsets.only(left: 20),
                  child: ListView.builder(
                      controller: _scrollController,
                      itemCount: data.length,
                      itemBuilder: (BuildContext context, int index) {
                        return Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: <Widget>[
                            PhoneCodeIndexName(data[index].name.toUpperCase()),
                            ListView.builder(
                                itemBuilder:
                                    (BuildContext context, int index2) {
                                  return Container(
                                    height: 46,
                                    child: GestureDetector(
// behavior: HitTestBehavior.translucent,
                                      child: Padding(
                                        padding:
                                            EdgeInsets.symmetric(vertical: 10),
                                        child: Row(
                                          children: <Widget>[
                                            Text(
                                                "${data[index].listData[index2].name}",
                                                style: TextStyle(
                                                    fontSize: 16,
                                                    color: Color(0xff434343))),
                                            Margin(width: 10),
                                            Text(
                                              "+${data[index].listData[index2].code}",
                                              style: TextStyle(
                                                  fontSize: 16,
                                                  color: Color(0xffD6D6D6)), ) ], ), ), onTap: () { Navigator.of(context).pop( data[index].listData[index2].code); },),); }, itemCount: data[index].listData.length, shrinkWrap:true,
                                physics:
                                    NeverScrollableScrollPhysics()) // Disable the sliding event),]); }), ), Align( alignment:new FractionalOffset(1.0.0.5),
            child: SizedBox(
              width: 25,
              child: Padding(
                padding: EdgeInsets.only(top: 20),
                child: ListView.builder(
                  itemCount: letters.length,
                  itemBuilder: (BuildContext context, int index) {
                    return GestureDetector(
                      child: Text(
                        letters[index],
                        style: TextStyle(color: Colors.black),
                      ),
                      onTap: () {
                        setState(() {
                          _currentIndex = index;
                        });
                        var height = index * 45.0;
                        for (int i = 0; i < index; i++) {
                          height += data[i].listData.length * 46.0; } _scrollController.jumpTo(height); }); }, ((), (()], ((), ((); }}class PhoneCodeIndexName extends StatelessWidget {
  String indexName;

  PhoneCodeIndexName(this.indexName);

  Widget build(BuildContext context) {
    return Container(
      height: 45,
      child: Padding(
        child: Text(indexName,
            style: TextStyle(fontSize: 20, color: Color(0xff434343))),
        padding: EdgeInsets.symmetric(vertical: 10),),); }}Copy the code

Json data mapping entity classes

class PhoneCountryCodeEntity {
  int code;
  List<PhoneCountryCodeData> data;
  String message;

  PhoneCountryCodeEntity({this.code, this.data, this.message});

  PhoneCountryCodeEntity.fromJson(Map<String, dynamic> json) {
    code = json['code'];
    if (json['data'] != null) {
      data = new List<PhoneCountryCodeData>();
      (json['data'] as List).forEach((v) {
        data.add(new PhoneCountryCodeData.fromJson(v));
      });
    }
    message = json['message'];
  }

  Map<String, dynamic> toJson(a) {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['code'] = this.code;
    if (this.data ! =null) {
      data['data'] = this.data.map((v) => v.toJson()).toList();
    }
    data['message'] = this.message;
    returndata; }}class PhoneCountryCodeData {
  List<PhoneCountryCodeDataListdata> listData;
  String name;

  PhoneCountryCodeData({this.listData, this.name});

  PhoneCountryCodeData.fromJson(Map<String, dynamic> json) {
    if (json['listData'] != null) {
      listData = new List<PhoneCountryCodeDataListdata>();
      (json['listData'] as List).forEach((v) {
        listData.add(new PhoneCountryCodeDataListdata.fromJson(v));
      });
    }
    name = json['name'];
  }

  Map<String, dynamic> toJson(a) {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    if (this.listData ! =null) {
      data['listData'] = this.listData.map((v) => v.toJson()).toList();
    }
    data['name'] = this.name;
    returndata; }}class PhoneCountryCodeDataListdata {
  String code;
  String name;
  int id;
  String groupCode;

  PhoneCountryCodeDataListdata({this.code, this.name, this.id, this.groupCode});

  PhoneCountryCodeDataListdata.fromJson(Map<String, dynamic> json) {
    code = json['code'];
    name = json['name'];
    id = json['id'];
    groupCode = json['groupCode'];
  }

  Map<String, dynamic> toJson(a) {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['code'] = this.code;
    data['name'] = this.name;
    data['id'] = this.id;
    data['groupCode'] = this.groupCode;
    returndata; }}Copy the code

Blog Json data test address is: raw.githubusercontent.com/xiedong11/f… Readers testing their own JSON data format should parse the entity class for their own JSON data format to ensure that the data is properly bound to the view.

The difficulties in analysis

At the beginning of the article, when clicking a certain letter index on the right, dynamically calculate the corner index value of the current letter index in the city list, and then calculate the height value of the clicked character index in the city list. ScrollController makes the city list automatically locate to the calculated height position, so as to achieve. Click on the letter to dynamically position the city list position.

By analyzing the above figure, we can conclude that when we click on the specified position to be located, we only need to calculate the cumulative height value of all items before the current clicked alphabetic index. To complete the linkage effect, slide scrollController.jumpto (double Value) to the specified height in the ListView.

Here, we can easily calculate the height accumulative value that we need to slide by walking through the data values we get from the network

The core code is as follows

       var height = index * 45.0;  //45.0 Height of letter grouping
       for (int i = 0; i < index; i++) {
           height += data[i].listData.length * 46.0;   //46.0 Height of each Item
             }
       _scrollController.jumpTo(height);
Copy the code

The entire implementation of Flutter is now complete. For the complete project and code, see the Journey to Flutter Advancements column