flutter 自定义仿web风格Pagination 分页组件

废话不多说,下面直入主题!!!

直接上效果:

 直接上代码:

enum PagerItemTypes { prev, next, ellipsis, number }

typedef PagerClickCallback = void Function(
    int totalPages, int currentPageIndex)?;

class PagerIndicatorItem extends StatefulWidget {
  final PagerItemTypes type;
  final int? index;
  final bool isFocused;

  const PagerIndicatorItem({
    super.key,
    required this.type,
    this.index,
    this.isFocused = false,
  });

  @override
  State<PagerIndicatorItem> createState() => _PagerIndicatorItemState();
}

class _PagerIndicatorItemState extends State<PagerIndicatorItem> {
  @override
  Widget build(BuildContext context) {
    String itemName = "";
    switch (widget.type) {
      case PagerItemTypes.prev:
        itemName = "<";
        break;
      case PagerItemTypes.next:
        itemName = ">";
        break;
      case PagerItemTypes.ellipsis:
        itemName = "...";
        break;
      case PagerItemTypes.number:
        itemName = (widget.index! + 1).toString();
        break;
    }
    return MouseRegion(
      cursor: widget.index == null
          ? SystemMouseCursors.basic
          : SystemMouseCursors.click,
      child: Container(
        height: 30,
        padding: EdgeInsets.symmetric(
            horizontal: widget.type == PagerItemTypes.ellipsis ? 0 : 10),
        decoration: BoxDecoration(
          border: widget.type == PagerItemTypes.ellipsis
              ? null
              : Border.all(
                  color:
                      widget.isFocused ? HLColors.c4C77FF : Colors.transparent),
          borderRadius: const BorderRadius.all(Radius.circular(2)),
          // color: widget.isFocused ? const Color(0xff5078F0) : null,
        ),
        child: Center(
          child: Text(
            itemName,
            style: TextStyle(
              fontSize: 14,
              color: widget.index == null
                  ? (widget.type == PagerItemTypes.ellipsis
                      ? HLColors.c000000o44
                      : HLColors.c000000o20)
                  : widget.isFocused
                      ? HLColors.c4C77FF
                      : HLColors.c000000o60,
            ),
          ),
        ),
      ),
    );
  }
}

class TablePagerIndicator extends StatefulWidget {
  final int totalCount;
  final int pageEach;
  final PagerClickCallback callback;

  const TablePagerIndicator({
    super.key,
    required this.totalCount,
    required this.pageEach,
    this.callback,
  });

  @override
  State<TablePagerIndicator> createState() => _TablePagerIndicatorState();
}

class _TablePagerIndicatorState extends State<TablePagerIndicator> {
  // 当两端同时出现省略(...)时中间展示条数
  int pagesBetweenEllipsesCount = 5;
  late int sideDiff;

  // 总页数
  late int totalPages;
  int? lastTotalPages;
  int? lastCurrentPageIndex;

  // 每页的数据数量
  late int pageEach;

  // 当前所在页
  int currentPageIndex = 0;

  TextEditingController gotoControl = TextEditingController();

  @override
  void initState() {
    super.initState();
    pageEach = widget.pageEach;
    initData();
  }

  initData() {
    totalPages = (widget.totalCount / pageEach).ceil();
    sideDiff = (pagesBetweenEllipsesCount / 2).floor();
    currentPageIndex = 0;
  }

  executeCallback() {
    if (widget.callback != null) {
      if ((lastTotalPages == null || lastCurrentPageIndex == null) ||
          (lastTotalPages != totalPages ||
              lastCurrentPageIndex != currentPageIndex)) {
        lastTotalPages = totalPages;
        lastCurrentPageIndex = currentPageIndex;
        widget.callback!(totalPages, currentPageIndex);
      }
    }
  }

  Widget pageItem(PagerIndicatorItem item) {
    return GestureDetector(
      onTapUp: (e) {
        if (item.index != null) {
          setState(() {
            currentPageIndex = item.index!;
          });
        }
        executeCallback();
      },
      child: item,
    );
  }

  List<Widget> generatePager() {
    List<Widget> pageItems = [];
    // prev添加上一页按钮(<)
    pageItems.add(pageItem(PagerIndicatorItem(
      type: PagerItemTypes.prev,
      index: currentPageIndex > 0 ? currentPageIndex - 1 : null,
    )));
    // 添加第一页(首页)
    pageItems.add(pageItem(PagerIndicatorItem(
      type: PagerItemTypes.number,
      index: 0,
      isFocused: currentPageIndex == 0,
    )));
    // 添加数字number list
    List<int> indexesBetweenEllipses = [];
    bool isReachStart = false;
    bool isReachEnd = false;
    int index = max(1, currentPageIndex - sideDiff);
    // 居中模式
    for (; index <= min(currentPageIndex + sideDiff, totalPages - 2); index++) {
      if (index == 1) {
        isReachStart = true;
      }
      if (index == totalPages - 2) {
        isReachEnd = true;
      }
      indexesBetweenEllipses.add(index);
    }
    // 补缺
    int lackDiff = pagesBetweenEllipsesCount - indexesBetweenEllipses.length;
    if (lackDiff > 0) {
      if (isReachStart) {
        for (int i = 0; i < lackDiff; i++) {
          if (index < totalPages - 1) {
            if (index == totalPages - 2) {
              isReachEnd = true;
            }
            indexesBetweenEllipses.add(index++);
          }
        }
      }
      if (isReachEnd) {
        var indexStart = indexesBetweenEllipses.first;
        for (int i = 0; i < lackDiff; i++) {
          if (indexStart > 1) {
            if (indexStart == 2) {
              isReachStart = true;
            }
            indexesBetweenEllipses.insert(0, --indexStart);
          }
        }
      }
    }
    for (var i = 0; i < indexesBetweenEllipses.length; i++) {
      int index = indexesBetweenEllipses[i];
      // 添加数字页码ui
      pageItems.add(pageItem(PagerIndicatorItem(
        type: PagerItemTypes.number,
        index: index,
        isFocused: currentPageIndex == index,
      )));
    }
    // 尾页
    if (totalPages > 1) {
      pageItems.add(pageItem(PagerIndicatorItem(
        type: PagerItemTypes.number,
        index: totalPages - 1,
        isFocused: currentPageIndex == totalPages - 1,
      )));
    }
    // next
    pageItems.add(pageItem(PagerIndicatorItem(
      type: PagerItemTypes.next,
      index: currentPageIndex < totalPages - 1 ? currentPageIndex + 1 : null,
    )));
    // ...
    if (!isReachStart &&
        indexesBetweenEllipses.length >= pagesBetweenEllipsesCount) {
      // 前
      pageItems.insert(
          2,
          pageItem(PagerIndicatorItem(
            type: PagerItemTypes.number,
            index: 1,
            isFocused: currentPageIndex == 1,
          )));
      if (currentPageIndex > 4) {
        // 当前选中下标大于4时才展示前(...)
        pageItems.insert(3,
            pageItem(const PagerIndicatorItem(type: PagerItemTypes.ellipsis)));
      }
    }
    if (!isReachEnd &&
        indexesBetweenEllipses.length >= pagesBetweenEllipsesCount) {
      // 后
      if (currentPageIndex < totalPages - 5) {
        // 当前选中下标小于总页码减5时才展示后(...)
        pageItems.insert(pageItems.length - 2,
            pageItem(const PagerIndicatorItem(type: PagerItemTypes.ellipsis)));
      }
      pageItems.insert(
          pageItems.length - 2,
          pageItem(PagerIndicatorItem(
            type: PagerItemTypes.number,
            index: totalPages - 2,
            isFocused: currentPageIndex == totalPages - 2,
          )));
    }
    executeCallback();
    return pageItems;
  }

  Widget text(String text) {
    return Container(
      height: 32,
      padding: const EdgeInsets.symmetric(horizontal: 6),
      child: Center(
        child: Text(
          text,
          style: const TextStyle(
              fontSize: 14,
              fontWeight: FontWeight.w400,
              fontFamily: "Microsoft Yahei",
              color: HLColors.c000000o60),
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    List<Widget> pageItems = generatePager();
    return Container(
      padding: const EdgeInsets.only(top: 20),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          text("共\t${widget.totalCount}\t条"),
          ...pageItems.map((pageItem) {
            // int index = pageItems.indexOf(pageItem);
            return Container(
              // margin: const EdgeInsets.only(left: 10),
              child: pageItem,
            );
          }).toList(),
          Row(
            mainAxisSize: MainAxisSize.min,
            children: [
              text("前往"),
              SizedBox(
                width: 52,
                child: TextBox(
                  unfocusedColor: Colors.transparent,
                  highlightColor: Colors.transparent,
                  controller: gotoControl,
                  autocorrect: false,
                  maxLines: 1,
                  style: HLTextStyles.s14c000000o60,
                  onEditingComplete: () {
                    try {
                      int goto = int.parse(gotoControl.text);
                      setState(() {
                        currentPageIndex = max(0, goto - 1);
                        currentPageIndex =
                            min(currentPageIndex, totalPages - 1);
                        gotoControl.text = (currentPageIndex + 1).toString();
                        gotoControl.selection = TextSelection.fromPosition(
                            TextPosition(offset: gotoControl.text.length));
                      });
                    } on FormatException catch (e) {
                      print("unvalid number : $e");
                    }
                  },
                ),
              ),
              text("页"),
            ],
          ),
        ],
      ),
    );
  }
}

直接使用:

Padding(
            padding: const EdgeInsets.only(left: 17, bottom: 17, right: 17),
            child: Align(
              alignment: Alignment.bottomRight,
              child: TablePagerIndicator(
                totalCount: 1000,
                pageEach: 10,
                callback: (totalPages, currentPageIndex) {
                  // todo 页码指示器点击翻页
                },
              ),
            ),
          )

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Flutter中,可以通过自定义Widget来创建一个自定义的Tab组件。首先,我们可以创建一个自定义的Tab类,继承自StatefulWidget,并实现它的build方法。 ```dart class CustomTab extends StatefulWidget { final String title; final bool isSelected; final Function onTap; CustomTab({required this.title, required this.isSelected, required this.onTap}); @override _CustomTabState createState() => _CustomTabState(); } class _CustomTabState extends State<CustomTab> { @override Widget build(BuildContext context) { return GestureDetector( onTap: widget.onTap, child: Container( color: widget.isSelected ? Colors.blue : Colors.transparent, child: Text( widget.title, style: TextStyle( fontSize: 16, color: widget.isSelected ? Colors.white : Colors.black, ), ), ), ); } } ``` 在这个自定义Tab类中,我们需要传入三个参数:title(标签的标题),isSelected(标签是否被选中),onTap(点击标签的回调方法)。 接下来,我们可以在TabBar中使用这个自定义Tab组件。 ```dart class MyHomePage extends StatefulWidget { @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin { late TabController _tabController; @override void initState() { super.initState(); _tabController = TabController(length: 3, vsync: this); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Custom Tab'), ), body: Column( children: [ TabBar( controller: _tabController, tabs: [ CustomTab( title: 'Tab 1', isSelected: _tabController.index == 0, onTap: () { _tabController.animateTo(0); }, ), CustomTab( title: 'Tab 2', isSelected: _tabController.index == 1, onTap: () { _tabController.animateTo(1); }, ), CustomTab( title: 'Tab 3', isSelected: _tabController.index == 2, onTap: () { _tabController.animateTo(2); }, ), ], ), Expanded( child: TabBarView( controller: _tabController, children: [ Center(child: Text('Content 1')), Center(child: Text('Content 2')), Center(child: Text('Content 3')), ], ), ), ], ), ); } } ``` 在这个例子中,我们使用TabBar和TabBarView来显示标签和对应的内容。自定义的Tab组件被作为TabBar的child组件传入。TabBar接收一个TabController来管理标签的切换。每个自定义Tab组件通过传入isSelected参数来判断自身是否被选中,并通过onTap回调方法来触发点击事件并切换标签。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值