crab_test_package_001 1.0.0 copy "crab_test_package_001: ^1.0.0" to clipboard
crab_test_package_001: ^1.0.0 copied to clipboard

A package test case. Encapsulate the index bar from A to Z and display bubbles when switching. It is only for your own learning, not for others.

example/main.dart

import 'package:crab_test_package_001/crab_test_package_001.dart' as crab;
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        platform: TargetPlatform.iOS,
        highlightColor: Colors.transparent,
        splashColor: Colors.transparent,
        primarySwatch: Colors.grey,
      ),
      home: const FriendsPage(),
    );
  }
}

class FriendsPage extends StatefulWidget {
  const FriendsPage({Key? key}) : super(key: key);

  @override
  _FriendsPageState createState() => _FriendsPageState();
}

class _FriendsPageState extends State<FriendsPage>
    with AutomaticKeepAliveClientMixin<FriendsPage> {
  ScrollController? _controller;
  Map<String, double>? _mapGroupHeight;
  String _selectedWord = '';
  double _bubbleY = 0.0;
  bool _isBubbleHind = true;

  @override
  bool get wantKeepAlive => true;

  @override
  Widget build(BuildContext context) {
    super.build(context);

    return Scaffold(
      appBar: AppBar(
        title: const Text('通讯录'),
        backgroundColor: const Color.fromRGBO(240, 240, 240, 1.0),
        elevation: 0.0,
        centerTitle: true,
      ),
      body: Container(
        color: const Color.fromRGBO(240, 240, 240, 1.0),
        child: Stack(
          alignment: Alignment.centerRight,
          children: [
            FriendsTable(getListDataCallback: (ScrollController controller,
                Map<String, double> mapGroupHeight) {
              _controller = controller;
              _mapGroupHeight = mapGroupHeight;
            }),
            SizedBox(
              width: crab.FriendsBubble.ThisWidth + crab.FriendsWords.ThisWidth,
              child: Row(
                children: [
                  _getLeftView(),
                  _getRightView(),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget _getLeftView() {
    return crab.FriendsBubble(
        bubbleY: _bubbleY, word: _selectedWord, isBubbleHind: _isBubbleHind);
  }

  Widget _getRightView() {
    return crab.FriendsWords(
      dragSelectedCallback: (intIndex) {
        if (_controller == null || _mapGroupHeight == null) {
          return;
        }

        String strWord = crab.listWords[intIndex];

        if (_selectedWord == strWord) {
          return;
        }

        setState(() {
          _selectedWord = strWord;
          _bubbleY = 2.0 / (crab.listWords.length - 1) * intIndex - 1;
          _isBubbleHind = false;
        });

        if (_mapGroupHeight![strWord] == null) {
          return;
        }

        double dblGroupHeight = _mapGroupHeight![strWord] as double;

        if (dblGroupHeight > _controller!.position.maxScrollExtent) {
          dblGroupHeight = _controller!.position.maxScrollExtent;
        }

        _controller!.animateTo(dblGroupHeight,
            duration: const Duration(milliseconds: 100), curve: Curves.easeIn);
      },
      dragUnselectedCallback: () {
        setState(() {
          _isBubbleHind = true;
        });
      },
    );
  }
}

typedef GetListDataCallback = void Function(
    ScrollController controller, Map<String, double> mapGroupHeight);

class FriendsTable extends StatelessWidget {
  final ScrollController _controller = ScrollController();

  FriendsTable({GetListDataCallback? getListDataCallback}) {
    listFriend.sort((a, b) {
      return a.groupLetter!.compareTo(b.groupLetter!);
    });

    if (getListDataCallback != null) {
      Map<String, double> map = _getCalculateGroupHeightMap();
      getListDataCallback(_controller, map);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      child: ListView.builder(
        controller: _controller,
        itemBuilder: (BuildContext context, int index) {
          FriendsInfo friendsInfo = listFriend[index];

          bool isGroupHeader = true;
          if (index > 0) {
            FriendsInfo preInfo = listFriend[index - 1];
            isGroupHeader =
                friendsInfo.groupLetter == preInfo.groupLetter ? false : true;
          }

          bool isLine = false;
          if (index < listFriend.length - 1) {
            FriendsInfo nextInfo = listFriend[index + 1];
            isLine =
                friendsInfo.groupLetter == nextInfo.groupLetter ? true : false;
          }

          return FriendsCell(
              name: friendsInfo.name,
              networkImage: friendsInfo.imageUrl,
              groupLetter: friendsInfo.groupLetter,
              isLine: isLine,
              isGroupHeader: isGroupHeader);
        },
        itemCount: listFriend.length,
      ),
    );
  }

  Map<String, double> _getCalculateGroupHeightMap() {
    Map<String, double> map = {};

    double dblGroupHeight = 0;

    for (int intIndex = 0; intIndex < listFriend.length; intIndex++) {
      FriendsInfo friendsInfo = listFriend[intIndex];

      if (intIndex == 0) {
        map[friendsInfo.groupLetter!] = dblGroupHeight;
        dblGroupHeight += FriendsCell.GroupHeight + FriendsCell.CellHeight;
        continue;
      }

      FriendsInfo preInfo = listFriend[intIndex - 1];

      if (friendsInfo.groupLetter! == preInfo.groupLetter!) {
        dblGroupHeight += FriendsCell.CellHeight;
        continue;
      }

      map[friendsInfo.groupLetter!] = dblGroupHeight;
      dblGroupHeight += FriendsCell.GroupHeight + FriendsCell.CellHeight;
    }

    return map;
  }
}

class FriendsCell extends StatefulWidget {
  static const double CellHeight = 72;
  static const double GroupHeight = 28;

  final String? networkImage;
  final String? assetImage;
  final String name;
  final String? groupLetter;
  final bool isLine;
  final bool isGroupHeader;

  const FriendsCell({
    this.networkImage,
    this.assetImage,
    required this.name,
    this.groupLetter,
    this.isLine = false,
    this.isGroupHeader = false,
  });

  @override
  _FriendsCellState createState() => _FriendsCellState();
}

class _FriendsCellState extends State<FriendsCell> {
  Color _cellColor = Colors.white;

  @override
  Widget build(BuildContext context) {
    return _getAssignCell();
  }

  Widget _getAssignCell() {
    Widget cell = widget.isGroupHeader ? _getCellHaveGroup() : _getCell();

    if (widget.isLine) {
      return _getCellHaveLine(cell);
    }

    return cell;
  }

  Widget _getCellHaveGroup() {
    return Column(
      children: [
        Container(
          margin: const EdgeInsets.only(left: 10),
          alignment: Alignment.centerLeft,
          height: FriendsCell.GroupHeight,
          child: Text(
            widget.groupLetter!,
            style: const TextStyle(
              color: Colors.grey,
              fontSize: 14,
            ),
          ),
        ),
        _getCell(),
      ],
    );
  }

  Widget _getCellHaveLine(Widget cell) {
    return Stack(
      children: [
        cell,
        Positioned(
          bottom: 0,
          right: 0,
          left: 50,
          child: Container(
            height: 0.5,
            color: const Color.fromRGBO(240, 240, 240, 1.0),
          ),
        ),
      ],
    );
  }

  Widget _getCell() {
    return GestureDetector(
      onTap: () {
        setState(() {
          _cellColor = Colors.white;
        });
      },
      onTapDown: (TapDownDetails details) {
        setState(() {
          _cellColor = const Color.fromRGBO(220, 220, 220, 1.0);
        });
      },
      onTapCancel: () {
        setState(() {
          _cellColor = Colors.white;
        });
      },
      child: Container(
        color: _cellColor,
        height: FriendsCell.CellHeight,
        child: Row(
          children: [
            Container(
              margin: const EdgeInsets.only(left: 16, right: 16),
              height: 40,
              child: widget.networkImage == null
                  ? Image.asset(
                      widget.assetImage!,
                    )
                  : Image.network(
                      widget.networkImage!,
                    ),
            ),
            Text(
              widget.name,
              style: const TextStyle(
                color: Colors.black,
                fontSize: 20,
              ),
            )
          ],
        ),
      ),
    );
  }
}

class FriendsInfo {
  String imageUrl;
  String name;
  String? groupLetter;

  FriendsInfo({
    required this.imageUrl,
    required this.name,
    this.groupLetter,
  });
}

List<FriendsInfo> listFriend = [
  FriendsInfo(
      imageUrl: 'https://randomuser.me/api/portraits/women/27.jpg',
      name: 'Lina',
      groupLetter: 'L'),
  FriendsInfo(
      imageUrl: 'https://randomuser.me/api/portraits/women/17.jpg',
      name: '菲儿',
      groupLetter: 'F'),
  FriendsInfo(
      imageUrl: 'https://randomuser.me/api/portraits/women/16.jpg',
      name: '安莉',
      groupLetter: 'A'),
  FriendsInfo(
      imageUrl: 'https://randomuser.me/api/portraits/men/31.jpg',
      name: '阿贵',
      groupLetter: 'A'),
  FriendsInfo(
      imageUrl: 'https://randomuser.me/api/portraits/women/22.jpg',
      name: '贝拉',
      groupLetter: 'B'),
  FriendsInfo(
      imageUrl: 'https://randomuser.me/api/portraits/women/37.jpg',
      name: 'Lina',
      groupLetter: 'L'),
  FriendsInfo(
      imageUrl: 'https://randomuser.me/api/portraits/women/18.jpg',
      name: 'Nancy',
      groupLetter: 'N'),
  FriendsInfo(
      imageUrl: 'https://randomuser.me/api/portraits/men/47.jpg',
      name: '扣扣',
      groupLetter: 'K'),
  FriendsInfo(
      imageUrl: 'https://randomuser.me/api/portraits/men/3.jpg',
      name: 'Jack',
      groupLetter: 'J'),
  FriendsInfo(
      imageUrl: 'https://randomuser.me/api/portraits/women/5.jpg',
      name: 'Emma',
      groupLetter: 'E'),
  FriendsInfo(
      imageUrl: 'https://randomuser.me/api/portraits/women/24.jpg',
      name: 'Abby',
      groupLetter: 'A'),
  FriendsInfo(
      imageUrl: 'https://randomuser.me/api/portraits/men/15.jpg',
      name: 'Betty',
      groupLetter: 'B'),
  FriendsInfo(
      imageUrl: 'https://randomuser.me/api/portraits/men/13.jpg',
      name: 'Tony',
      groupLetter: 'T'),
  FriendsInfo(
      imageUrl: 'https://randomuser.me/api/portraits/men/26.jpg',
      name: 'Jerry',
      groupLetter: 'J'),
  FriendsInfo(
      imageUrl: 'https://randomuser.me/api/portraits/men/36.jpg',
      name: 'Colin',
      groupLetter: 'C'),
  FriendsInfo(
      imageUrl: 'https://randomuser.me/api/portraits/women/12.jpg',
      name: 'Haha',
      groupLetter: 'H'),
  FriendsInfo(
      imageUrl: 'https://randomuser.me/api/portraits/women/11.jpg',
      name: 'Ketty',
      groupLetter: 'K'),
  FriendsInfo(
      imageUrl: 'https://randomuser.me/api/portraits/women/13.jpg',
      name: 'SiSi',
      groupLetter: 'S'),
  FriendsInfo(
      imageUrl: 'https://randomuser.me/api/portraits/women/23.jpg',
      name: 'Zare',
      groupLetter: 'Z'),
];
0
likes
130
pub points
0%
popularity

Publisher

unverified uploader

A package test case. Encapsulate the index bar from A to Z and display bubbles when switching. It is only for your own learning, not for others.

Homepage

Documentation

API reference

License

BSD-3-Clause (LICENSE)

Dependencies

flutter

More

Packages that depend on crab_test_package_001