美文网首页Flutter入门
Flutter入门五:【我的】+【通讯录】

Flutter入门五:【我的】+【通讯录】

作者: markhetao | 来源:发表于2021-02-10 21:48 被阅读0次

    Flutter入门 学习大纲

    1. 状态栏颜色
    2. 【我的】页面
    3. 【通讯录】页面

    1. 状态栏颜色

    iOS顶部状态栏白色黑色Flutter中,可以通过设置主题色的深浅,自动修改顶部状态栏颜色:

    • primaryColor: Colors.blue主题色设置为深色时,状态栏白色:
      image.png
    • primaryColor: Colors.white主题色设置为浅色时,状态栏黑色:
      image.png

    导航栏背景标题颜色分割线、通过页面appBar属性可以设置。

    appBar: AppBar(
        backgroundColor: widget._themeColor, // 导航栏背景色
        centerTitle: true, // (安卓)导航栏标题居中
        title: Text( "朋友圈", 
                    style: TextStyle(color: Colors.black), //标题颜色),
        elevation: 0.0 )// 去除分割线
    
    • main.dart完整代码:
    import 'package:flutter/material.dart';
    import 'package:wechat_demo/pages/root_page.dart';
    
    void main() => runApp(App());
    
    class App extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Wechat Demo', // 安卓需要,后台切换app时展示的名称(iOS中名称与APP名称一致)
          debugShowCheckedModeBanner: false, // 隐藏debug角标
          home: RootPage(),
          theme: ThemeData(
            primaryColor: Colors.white, // 主题色
            highlightColor: Color.fromRGBO(0, 0, 0, 0), // 去除高亮色
            splashColor: Color.fromRGBO(0, 0, 0, 0), // 去除水波纹
          ),
        );
      }
    }
    

    2.【我的】页面

    • 布局分析:


      image.png
    • 布局方式不唯一,以下仅供参考
    1. 顶部固定悬浮相机,背后使用ListView展示内容,选用Stack
    2. 头部有左右和上下多种布局,可使用RowStack结合展示,利用主轴对齐方式aligment对齐方式,实现布局要求。

    【相关知识点】

    1. 隐藏状态栏间距
    MediaQuery.removePadding(
       removeTop: true, // 移除顶部内边距
       context: context, // 指定上下文
       child: XXX),
    
    1. 圆角头像:
    // 头像
    Container( 
       width: 55, height: 55,
       // 圆角 (需要宽高)
       decoration: BoxDecoration(
           borderRadius: BorderRadius.circular(8.0), // 设置圆角
           image: DecorationImage(image:  AssetImage("images/HT.png"), fit: >BoxFit.fill))), // 图片填充              
    
    1. 屏幕相关属性:
    • 屏幕宽度MediaQuery.of(context).size.width;
    • 状态栏高度MediaQuery.of(context).padding.top;
    • 文件位置调整:(按模块分类文件夹

      image.png
    • const 全局常量函数声明

    import 'package:flutter/material.dart';
    
    // 微信主题色
    Color Wechat_themeColor = Color.fromRGBO(220, 220, 220, 1.0);
    
    // 屏幕宽度(必须传入MaterialApp的contrext)
    double ScreenWidth(BuildContext context) => MediaQuery.of(context).size.width;
    
    // 状态栏高度
    double StatusBarHeight(BuildContext context) => MediaQuery.of(context).padding.top;
    
    • mine我的页面代码(主要是UI布局,响应事件滚动顶部背景白色未处理):
    import 'package:flutter/material.dart';
    import 'package:wechat_demo/const.dart';
    import 'package:wechat_demo/pages/discover/discover_cell.dart';
    
    class MinePage extends StatefulWidget {
      @override
      _MinePageState createState() => _MinePageState();
    }
    
    class _MinePageState extends State<MinePage> {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
            backgroundColor: Wechat_themeColor,
            body: Stack(
              children: <Widget>[
                Container(
                  child: MediaQuery.removePadding(
                    removeTop: true,
                    context: context,
                    child: ListView(
                      children: [
                        headerView(), // 头部
                        SizedBox(height: 8),
                        DiscoverCell(imageName: "images/微信支付.png", title: "支付"),
                        SizedBox(height: 8),
                        DiscoverCell(imageName: "images/微信收藏.png", title: "收藏"),
                        sepectorLine(),
                        DiscoverCell(imageName: "images/微信相册.png", title: "相册"),
                        sepectorLine(),
                        DiscoverCell(imageName: "images/微信卡包.png", title: "卡包"),
                        sepectorLine(),
                        DiscoverCell(imageName: "images/微信表情.png", title: "表情"),
                        SizedBox(height: 8),
                        DiscoverCell(imageName: "images/微信设置.png", title: "设置")
                      ],
                    ),
                  ),
                ),
                Container(
                  margin: EdgeInsets.only(top: StatusBarHeight(context)),
                  padding: EdgeInsets.only(right: 16),
                  // 状态栏间距
                  height: 25,
                  child: Row(
                    mainAxisAlignment: MainAxisAlignment.end,
                    children: [Image(image: AssetImage("images/相机.png"))],
                  ),
                )
              ],
            ));
      }
    
      // 头部视图
      Widget headerView() {
        return Container(
            padding: EdgeInsets.only(
                top: StatusBarHeight(context) + 35,
                left: 20,
                right: 10,
                bottom: 30),
            color: Colors.white,
            child: Container(
              child: Row(
                children: <Widget>[
                  // 头像
                  Container(
                      width: 55,
                      height: 55,
                      // 圆角 (需要宽高)
                      decoration: BoxDecoration(
                          borderRadius: BorderRadius.circular(8.0), // 设置圆角
                          image: DecorationImage(  // 这是图片,fill填充
                              image: AssetImage("images/HT.png"), fit: BoxFit.fill))),
                  // 昵称等
                  Container(
                    margin: EdgeInsets.only(left: 20),
                    child: Container(
                      width: ScreenWidth(context) - 85 - 20,
                      child: Column(
                        children: [
                          Container(
                            height: 20,
                            child: Container(alignment: Alignment.centerLeft, child: Text("H", style: TextStyle(color: Colors.black, fontSize: 20, fontWeight: FontWeight.bold))),
                          ),
                          Container(
                            margin: EdgeInsets.only(top: 10),
                            height: 20,
                            child: Row(
                              mainAxisAlignment: MainAxisAlignment.spaceBetween,
                                children: [
                                  Container(
                                    child: Text("微信号: wechat_mark", style: TextStyle(color: Colors.grey,)),
                                  ),// 微信号
                                  Row(
                                      children: [
                                        Image(image: AssetImage("images/微信二维码.png"), width: 16, height: 16,),
                                        SizedBox(width: 8),
                                        Image(image: AssetImage("images/icon_right.png"), width: 14, height: 14,)
                                      ]
                                  )
                                ],
                            ),
                          )
                        ],
                      ),
                    ),
                  )
                ],
              ),
            ));
      }
    
      // 分割线
      Widget sepectorLine() {
        return Container(
            height: 1,
            child: Row(children: [
              Container(width: 40, color: Colors.white),
              Container()
            ]));
      }
    }
    

    3.【通讯录】页面

    image.png
    • 页面分析:

    页面分为导航栏滚动内容索引栏。(滚动视图索引栏使用Stack布局)

    一、 导航栏:

    1. 设置背景颜色字体颜色,隐藏分割线
    2. 添加新增朋友按钮(通过AppBaractions属性添加手势Widget

    二、滚动内容

    1. 【布局】每个数据都是一个cell视图,都包含组头(灰色字母头部)和好友信息(头像名称分割线)。组头cell数据决定是否创建。
      组头好友信息使用Column布局,好友信息内部头像名称+分割线使用Row布局,根据屏幕宽度计算宽度名称分割线使用Column布局,指定高度

    2.【数据】_headDatas记录顶部4个固定数据(新的朋友、群聊、标签、公众号),_listDatas记录其余好友数据,根据首字母进行 排序
    initState()时,使用_groupOffsets(Map结构)记录每个索引offset偏移值

    1. 【交互】 使用ScrollController滚动对象记录当前的listViewIndexBar索引栏选中索引时,通过indexBarCallBack回调函数的index参数,在_groupOffsets(Map结构)中获取到指定高度,通过_scrollController.animateTo滚动到listView指定高度。

    三、索引栏

    1. 【布局】 有索引栏选中气泡两大部件,使用Row布局。
      分为选中(背景透明、字体黑色、无气泡)和未选中(背景灰色、字体白色、展示气泡)两种状态。
    2. 【数据】INDEX_WORDS包含搜索星标A~Z 28组数据。
    3. 【交互】选中未选中会在自身布局上发生变化。也会通过indexBarCallBack函数,传递int类型的索引Index值,告知外部(外部listView会联动响应
    4. 【优化点】
      index默认值-1,避免每次进入页面时,listView滚动值index索引不一致。
      滚动时,index值未变化不会触发indexBarCallBack回调

    四、缺陷

    1. 每次进入页面,数据均刷新了,未保存状态
    2. listView的偏移值过大时(剩余内容高度不足listView满屏),不应滚动
    • 文件夹:
      新建friends文件夹,新增cell数据ModelIndexBar索引部件文件。将friends_page.dart主页面移到friends文件夹:

      image.png
    • cell文件代码(friends_cell.dart):

    import 'package:flutter/cupertino.dart';
    import 'package:flutter/material.dart';
    import 'package:wechat_demo/const.dart';
    
    class FriendCell extends StatelessWidget {
    
      final String imageAssets; // 本地图片
      final String imageUrl;  // 网络图片链接
      final String name;  // 名称
      final String groupTitle;
    
      FriendCell({this.imageAssets, this.imageUrl, @required this.name, this.groupTitle}); // 首字母
    
      @override
      Widget build(BuildContext context) {
        // 字母段写在cell上,根据内容动态展示
        return Container(
          child: Column(
            children: [
              // 字母头部
              groupTitle != null ? Container(
                height: 30,
                padding: EdgeInsets.only(left: 10),
                alignment: Alignment.centerLeft,
                child: Text(groupTitle, style: TextStyle(color: Colors.grey)),
              ) : Container(),
              // 图片和名称
              Container(
                color: Colors.white,
                height: 54,
                child: Row(
                  children: [
                    // 头像
                    Container(
                        width: 34,height: 34,
                        margin: EdgeInsets.all(10),
                        decoration: BoxDecoration(borderRadius: BorderRadius.circular(6.0),
                            image: DecorationImage(image: imageAssets != null ? AssetImage(imageAssets) : NetworkImage(imageUrl))
                        )),
                    //昵称+分割线
                    Container(
                      width: ScreenWidth(context) - 54,
                      child: Column(
                        children: [
                          Container(
                              height: 53.5,
                              alignment: Alignment.centerLeft,
                              child: Text(name, style: TextStyle(fontSize: 18),)
                          ), // 昵称
                          Container(height: 0.5, color: Wechat_themeColor), // 分割线
                        ],
                      ),
                    )
                  ],
                ),
              )
            ],
          ),
        );
      }
    }
    
    • data数据代码(friends_data.dart):
    class Friends {
      final String imageUrl;
      final String name;
      final String indexLetter;
      final String message;
      final String time;
    
      Friends({this.imageUrl, this.name, this.indexLetter, this.message, this.time});
    }
    
    List <Friends>datas = [
      Friends(
        imageUrl: 'https://randomuser.me/api/portraits/women/57.jpg',
        name: 'Lina',
        indexLetter: 'L',
        message: 'hello hank !😁',
        time: '下午 3:45',
      ),
      Friends(
        imageUrl: 'https://randomuser.me/api/portraits/women/70.jpg',
        name: '菲儿',
        indexLetter: 'F',
        message: '忙完了吗?',
        time: '下午 3:25',
      ),
      Friends(
        imageUrl: 'https://randomuser.me/api/portraits/women/60.jpg',
        name: '安莉',
        indexLetter: 'A',
        message: '我在看看,稍等。',
        time: '下午 2:45',
      ),
      Friends(
        imageUrl: 'https://randomuser.me/api/portraits/men/91.jpg',
        name: '阿贵',
        indexLetter: 'A',
        message: '我没弄明白...',
        time: '昨天',
      ),
      Friends(
        imageUrl: 'https://randomuser.me/api/portraits/women/22.jpg',
        name: '贝拉',
        indexLetter: 'B',
        message: '这个时候刷圈?',
        time: '下午 3:45',
      ),
      Friends(
        imageUrl: 'https://randomuser.me/api/portraits/women/57.jpg',
        name: 'Lina',
        indexLetter: 'L',
        message: 'hello hank !😁',
        time: '下午 3:45',
      ),
      Friends(
        imageUrl: 'https://randomuser.me/api/portraits/women/18.jpg',
        name: 'Nancy',
        indexLetter: 'N',
        message: '麻烦通过一下😁',
        time: '下午 4:05',
      ),
      Friends(
        imageUrl: 'https://randomuser.me/api/portraits/men/47.jpg',
        name: '扣扣',
        indexLetter: 'K',
        message: '你好啊😁',
        time: '下午 2:45',
      ),
      Friends(
        imageUrl: 'https://randomuser.me/api/portraits/men/3.jpg',
        name: 'Jack',
        indexLetter: 'J',
        message: '好久不见 ',
        time: '下午 4:15',
      ),
      Friends(
        imageUrl: 'https://randomuser.me/api/portraits/women/5.jpg',
        name: 'Emma',
        indexLetter: 'E',
        message: '在忙什么呢 ',
        time: '下午 3:55',
      ),
      Friends(
        imageUrl: 'https://randomuser.me/api/portraits/women/44.jpg',
        name: 'Abby',
        indexLetter: 'A',
        message: ' 最近还好吗😁',
        time: '下午 1:45',
      ),
      Friends(
        imageUrl: 'https://randomuser.me/api/portraits/men/65.jpg',
        name: 'Betty',
        indexLetter: 'B',
        message: '什么时候有空呢😁',
        time: '上午 8:45',
      ),
      Friends(
        imageUrl: 'https://randomuser.me/api/portraits/men/83.jpg',
        name: 'Tony',
        indexLetter: 'T',
        message: '一起出去玩吧😁',
        time: '上午 7:15',
      ),
      Friends(
        imageUrl: 'https://randomuser.me/api/portraits/men/36.jpg',
        name: 'Jerry',
        indexLetter: 'J',
        message: 'How are you?😁',
        time: '上午 9:10',
      ),
      Friends(
        imageUrl: 'https://randomuser.me/api/portraits/men/66.jpg',
        name: 'Colin',
        indexLetter: 'C',
        message: '谢谢你',
        time: '下午 3:40',
      ),
      Friends(
        imageUrl: 'https://randomuser.me/api/portraits/women/58.jpg',
        name: 'Haha',
        indexLetter: 'H',
        message: '最近去健身了么😁',
        time: '下午 5:40',
      ),
      Friends(
        imageUrl: 'https://randomuser.me/api/portraits/women/60.jpg',
        name: 'Ketty',
        indexLetter: 'K',
        message: '你喜欢哪个明星呀😁',
        time: '下午 3:45',
      ),
      Friends(
        imageUrl: 'https://randomuser.me/api/portraits/women/57.jpg',
        name: 'Lina',
        indexLetter: 'L',
        message: 'hello hank !😁',
        time: '下午 3:45',
      ),
      Friends(
        imageUrl: 'https://randomuser.me/api/portraits/women/57.jpg',
        name: 'Lina',
        indexLetter: 'L',
        message: 'hello hank !😁',
        time: '下午 3:45',
      )
    ];
    
    • 索引栏文件代码(index_bar.dart):
    import 'package:flutter/material.dart';
    
    import '../../const.dart';
    
    class IndexBar extends StatefulWidget {
    
      // indexBar高度
      final double indexBarHeight;
    
      // 函数传递给外界(回调)
      final void Function(int index) indexBarCallBack;
    
      const IndexBar({Key key, this.indexBarHeight = 480, this.indexBarCallBack}) : super(key: key);
    
      @override
      _IndexBarState createState() => _IndexBarState();
    }
    
    class _IndexBarState extends State<IndexBar> {
    
      // 总高度
      final double indexBarHeight = 400;
      // 当前选中的Index索引
      int _currentIndex = -1;
      // 背景颜色
      Color _bgColor = Color.fromRGBO(1, 1, 1, 0);
      // 文字颜色
      Color _indexColor = Colors.black;
    
      // 垂直拖拽(按下)
      void onVerticalDragDown(DragDownDetails details) {
        if (getCurrentIndex(context, details.globalPosition) != _currentIndex) {
          _currentIndex = getCurrentIndex(context, details.globalPosition);
          widget.indexBarCallBack(_currentIndex);
        }
        setIndexBarColor(true);
        setState(() {});
      }
      // 垂直拖拽(拖拽中)
      void onVerticalDragUpdate(DragUpdateDetails details) {
        // 传递参数(delegate)
        if (getCurrentIndex(context, details.globalPosition) != _currentIndex) {
          _currentIndex = getCurrentIndex(context, details.globalPosition);
          widget.indexBarCallBack(_currentIndex);
          setState(() {});
        }
      }
      // 垂直拖拽(拖拽结束)
      void onVerticalDragEnd(DragEndDetails details) {
        _currentIndex = -1; // 每次回归-1
        setIndexBarColor(false);
        setState(() {});
      }
    
      @override
      Widget build(BuildContext context) {
        return Container(
          height: widget.indexBarHeight,
          child: Row(
            // mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              _currentIndex != -1 ? Container(
                // 控制气泡位置
                alignment: Alignment(0, getBubble()),
                height: widget.indexBarHeight,
                width: 100,
                child: Stack(
                  // 对齐(x: 中心左移20%, y: 居中)
                    alignment: Alignment(-0.2,0),
                    children: [
                      Image(image: AssetImage("images/气泡.png"), width: 60),
                      Text(INDEX_WORDS[_currentIndex] , style: TextStyle(color: Colors.white,fontSize: 35))
                    ]
                ),
              ) : Container(),
              Container(
                width: 30,
                color: _bgColor,
                child: GestureDetector(
                  child: Column(children: getIndexBarItems()),
                  onVerticalDragDown: onVerticalDragDown,
                  onVerticalDragUpdate: onVerticalDragUpdate,
                  onVerticalDragEnd: onVerticalDragEnd,
                ),
              )
            ],
          ),
        );
      }
    
      // 索引元素
      List<Widget> getIndexBarItems() {
        List<Widget> list = [];
        for (int i = 0; i < INDEX_WORDS.length; i++) {
          list.add(Expanded(
              child: Text(INDEX_WORDS[i], style: TextStyle(color: _indexColor))));
        }
        return list;
      }
    
      // 设置IndexBar颜色
      void setIndexBarColor(bool isSelected) {
        _bgColor =
            isSelected ? Color.fromRGBO(1, 1, 1, 0.5) : Color.fromRGBO(0, 0, 0, 0);
        _indexColor = isSelected ? Colors.white : Colors.black;
      }
    
      // 获取当前选中的Index
      int getCurrentIndex(BuildContext context, Offset offset) {
        // 相对坐标
        // print(offset.dy);
    
        // 绝对坐标
        RenderBox box = context.findRenderObject();
        final moveY = box.globalToLocal(offset).dy;
        // ~/ 整除
        final index = moveY ~/ (widget.indexBarHeight / INDEX_WORDS.length);
        return index.clamp(0, INDEX_WORDS.length - 1); // 设置区间边界
      }
    
      // 获取气泡的垂直位置
      double getBubble() {
        if (_currentIndex < 1 ) { return -1.1; }
        return 2.2 / (INDEX_WORDS.length - 1) * _currentIndex - 1.1;
      }
    }
    
    const INDEX_WORDS = [
      '🔍',
      '☆',
      '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'
    ];
    
    • 好友主页面代码friends_page.dart:
    import 'dart:async';
    import 'dart:ffi';
    
    import 'package:flutter/material.dart';
    import 'package:wechat_demo/const.dart';
    import 'package:wechat_demo/pages/discover/discover_cell.dart';
    import 'package:wechat_demo/pages/discover/discover_child_page.dart';
    import 'package:wechat_demo/pages/friends/index_bar.dart';
    import 'friends_cell.dart';
    import 'friends_data.dart';
    
    class FriendsPage extends StatefulWidget {
      @override
      _FriendsPageState createState() => _FriendsPageState();
    }
    
    class _FriendsPageState extends State<FriendsPage> {
      // 滚动控制器对象(初始化后,ListView使用,其他地方通过对象调用方法)
      ScrollController _scrollController = ScrollController();
    
      // 数据
      final List<Friends> _listDatas = [];
    
      // 索引对应偏移值
      final Map _groupOffsets = {
        INDEX_WORDS[0]: 0.0,
        INDEX_WORDS[1]: 0.0,
      };
    
      @override
      void initState() {
        super.initState();
    
        // 多创建一倍数据(使用..是强制返回自身,可链式操作)
        _listDatas..addAll(datas)..addAll(datas);
        // 排序
        _listDatas.sort((Friends a, Friends b) {
          return a.indexLetter.compareTo(b.indexLetter);
        });
    
        // 索引对应偏移值
        var offset = _headDatas.length * 54.0;
        for (int i = 0; i < _listDatas.length; i++) {
          // 首位
          if (i == 0) {
            _groupOffsets.addAll({_listDatas[i].indexLetter: offset});
            offset += 84;
          }
          // 字母相同
          else if (_listDatas[i].indexLetter == _listDatas[i - 1].indexLetter) {
            offset += 54;
          }
          // 字母不同
          else {
            _groupOffsets.addAll({_listDatas[i].indexLetter: offset});
            offset += 84;
          }
        }
      }
    
      final List<Friends> _headDatas = [
        Friends(imageUrl: "images/新的朋友.png", name: "新的朋友"),
        Friends(imageUrl: "images/群聊.png", name: "群聊"),
        Friends(imageUrl: "images/标签.png", name: "标签"),
        Friends(imageUrl: "images/公众号.png", name: "公众号"),
      ];
    
      // 点击新增朋友
      void tapAddFriends() {
        // 跳转新页面
        Navigator.of(context).push(MaterialPageRoute(
            builder: (BuildContext context) => DiscoverChildPage(title: "新增朋友")));
      }
    
      // 滚动到GroupOffset位置
      void moveToGrounpOffset(int index) {
        if (_groupOffsets[INDEX_WORDS[index]] != null) {
          _scrollController.animateTo(_groupOffsets[INDEX_WORDS[index]],
              duration: Duration(milliseconds: 300), curve: Curves.easeIn);
        }
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            backgroundColor: Wechat_themeColor,
            centerTitle: true,
            // 安卓的导航栏标题未居中,可以设置居中
            title: Text("通讯录"),
            elevation: 0.0,
            // 去除分割线
            // 导航栏按钮
            actions: <Widget>[
              GestureDetector(
                child: Container(
                  margin: EdgeInsets.only(right: 10),
                  width: 20,
                  height: 20,
                  child: Image(image: AssetImage("images/icon_friends_add.png")),
                ),
                onTap: tapAddFriends,
              )
            ],
          ),
          body: Container(
            color: Wechat_themeColor,
            child: Stack(
              children: [
                ListView.builder(
                    controller: _scrollController,
                    itemCount: _headDatas.length + _listDatas.length,
                    itemBuilder: cellForRow),
                Positioned(
                    right: 0,
                    // 正确top应该 (屏幕高度 - 导航栏高度 - 底部Bar高度 - 480) / 2
                    top: (ScreenHeight(context) - 480) / 4 ,
                    child: IndexBar(
                      indexBarHeight: 480, // 外部可指定indexBar高度
                      indexBarCallBack: moveToGrounpOffset,
                    )),
              ],
            ),
          ),
        );
      }
    
      Widget cellForRow(BuildContext context, int index) {
        if (index < _headDatas.length) {
          return FriendCell(
              imageAssets: _headDatas[index].imageUrl,
              name: _headDatas[index].name);
        }
        // 记录是否展示字母头部(当前首字母与上一个不同,就展示)
        final bool _haveGroup = index > _headDatas.length
            ? _listDatas[index - _headDatas.length].indexLetter !=
                _listDatas[index - _headDatas.length - 1].indexLetter
            : true;
        return FriendCell(
            imageUrl: _listDatas[index - _headDatas.length].imageUrl,
            name: _listDatas[index - _headDatas.length].name,
            groupTitle: _haveGroup
                ? _listDatas[index - _headDatas.length].indexLetter
                : null);
      }
    }
    
    friends.gif
    • 建议阅读时,代码粘贴到Andriod Studio应用中,[ command + option + -] 折叠所有代码观看

    本系列为基础入门篇一步步入门,适合初学者练手。主要是UI布局基础思想的熟悉。一次性优化点处理完,对初学者不太友好

    相关文章

      网友评论

        本文标题:Flutter入门五:【我的】+【通讯录】

        本文链接:https://www.haomeiwen.com/subject/ruxztltx.html