在学习flutter练手时,就打算把公司项目翻一个flutter版的,需要用到一个带指示器的PageView(如下图所示),然鹅百度了一圈大多都是类似banner的带原点的指示器且没有动画效果,所以憋了一天半终于自己手撸了一个(刚学太菜了😅)
在这里插入图片描述
话不多说上代码


import 'package:flutter/material.dart';
import 'package:flutter_shenzhou/library/utils/Utils.dart';

class PageViewWidget extends StatefulWidget {
  PageViewWidget(
      {Key key,
      @required this.childWidget,
      @required this.titles,
      this.initPage = 0,
      this.height = 36,
      this.width = 40,
      this.indicatorColor = Colors.black38,
      this.indicatorSelectedColor = Colors.red,
      this.backgroundColor = Colors.grey,
      this.scrollDirection = Axis.horizontal,
      this.indicatorWidth = 80,
      this.indicatorHeight = 2,
      this.onPageClicked})
      : super(key: key);

  //PageViewWidget的宽
  final double width;

  //PageViewWidget的高
  final double height;

  final List<Widget> childWidget;
  final List<String> titles;
  final Axis scrollDirection;

  final ValueChanged<int> onPageClicked;

  //显示的第一页
  final int initPage;

  //指示器未选中时的颜色
  final Color indicatorColor;

  //指示器选中时的颜色
  final Color indicatorSelectedColor;

  //背景色
  final Color backgroundColor;

  //指示条长度
   double indicatorWidth;

  //指示条高度
  final double indicatorHeight;

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

class _PageViewWidgetState extends State<PageViewWidget>
    with TickerProviderStateMixin {
  int selectedPage = 0;
  PageController _controller;
  AnimationController controller;
  Animation<EdgeInsets> animation;

  //根据titles平分屏幕宽度
  double itemWidth;

  void onPageChanged(int index) {
    controller =
        AnimationController(duration: Duration(milliseconds: 200), vsync: this);
    controller.addListener(() {
      setState(() {});
    });
    if (index > selectedPage) {
//      print("x1>>>" +
//          (selectedPage * itemWidth + (itemWidth - widget.indicatorWidth) / 2)
//              .toString() +
//          "x2>>>" +
//          ((selectedPage + 1) * itemWidth +
//                  (itemWidth - widget.indicatorWidth) / 2)
//              .toString());
      animation = EdgeInsetsTween(
              begin: EdgeInsets.only(
                  left: selectedPage * itemWidth +
                      (itemWidth - widget.indicatorWidth) / 2),
              end: EdgeInsets.only(
                  left: (selectedPage + (index - selectedPage)) * itemWidth +
                      (itemWidth - widget.indicatorWidth) / 2))
          .animate(controller);
    } else {
//      print("x3>>>" +
//          (selectedPage * itemWidth + (itemWidth - widget.indicatorWidth) / 2)
//              .toString() +
//          "x4>>>" +
//          ((selectedPage - 1) * itemWidth +
//                  (itemWidth - widget.indicatorWidth) / 2)
//              .toString());

      animation = EdgeInsetsTween(
              begin: EdgeInsets.only(
                  left: selectedPage * itemWidth +
                      (itemWidth - widget.indicatorWidth) / 2),
              end: EdgeInsets.only(
                  left: (selectedPage - (selectedPage - index)) * itemWidth +
                      (itemWidth - widget.indicatorWidth) / 2))
          .animate(controller);
    }
    setState(() {
      selectedPage = index;
    });

    controller.forward();
  }

  void _onPageClicked() {
    widget?.onPageClicked(selectedPage);
  }

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

  @override
  Widget build(BuildContext context) {
    itemWidth = Utils.getScreenWidth(context) / widget.titles.length;
    print("lenth>>" + ((itemWidth - widget.indicatorWidth) / 2).toString());
    _controller = PageController(
      initialPage: widget.initPage,
    );
    Widget pageView = GestureDetector(
      onTap: _onPageClicked,
      child: PageView(
        children: widget.childWidget,
        scrollDirection: widget.scrollDirection,
        onPageChanged: onPageChanged,
        controller: _controller,
      ),
    );

    Widget indicatorWidget = Container(
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          Container(
              height: 40,
              color: widget.backgroundColor,
              child: Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: widget.titles.map((f) {
                    int index = widget.titles.indexOf(f);
                    return Expanded(
                        child: Center(
                            child: GestureDetector(
                                onTap: () {
                                  onPageChanged(index);
                                  _controller.jumpToPage(index);
                                },
                                child: Text(
                                  widget.titles[index],
                                  style: TextStyle(
                                    color: index == selectedPage
                                        ? widget.indicatorSelectedColor
                                        : widget.indicatorColor,
                                  ),
                                ))));
                  }).toList())),
          Container(
            //即_getPadding()
            padding: _getPadding(),
            child: Container(
              width: _getIndicatorWidth(),
              height: widget.indicatorHeight,
              color: widget.indicatorSelectedColor,
            ),
          ),
        ],
      ),
    );

    return Container(
        width: widget.width,
        height: widget.height,
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[indicatorWidget, Expanded(child: pageView)],
        ));
  }

  EdgeInsetsGeometry _getPadding() {
    if (animation == null) {
      //边界处理padding不能小于0
      if (((itemWidth - widget.indicatorWidth) / 2) < 0) {
        return EdgeInsets.only(left: 0);
      } else {
        return EdgeInsets.only(left: (itemWidth - widget.indicatorWidth) / 2);
      }
    } else {
      //边界处理padding不能小于0
      if (animation.value.left < 0) {
        return EdgeInsets.only(left: 0);
      } else {
        return animation.value;
      }
    }
  }

  //widget.indicatorWidth不能大于itemWidth
  double _getIndicatorWidth(){
    if(widget.indicatorWidth > itemWidth){
       widget.indicatorWidth = itemWidth;
    }
    return widget.indicatorWidth;
  }

  @override
  void dispose() {
    // TODO: implement dispose
    super.dispose();
    controller.dispose();
  }
}

接下来是如何使用

import 'package:flutter/material.dart';
import 'package:flutter_shenzhou/common/Colors.dart';
import 'package:flutter_shenzhou/library/widget/PageViewWidget.dart';

class HomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(appBar:AppBar(title: Text('带指示器的Pageview')),body:PageViewWidget(
      width: double.infinity,
      height: double.infinity,
      backgroundColor: ColorsM.GRAY_1,
      titles: ["推荐商品", "推荐美食", "推荐潮牌","推荐景点"],
      childWidget: <Widget>[
        Center(
          child: Container(
            color: Colors.red,
          ),
        ),
        Center(
          child: Container(
            color: Colors.pink,
          ),
        ),
        Center(
          child: Container(
            color: Colors.yellow,
          ),
        ),Center(
          child: Container(
            color: Colors.blue,
          ),
        )
      ],
    ) );
  }
}

属性说明默认值必填项
childWidgetPageView要现实的widget*
titles指示器文字列表*
widthwidget的宽100
heightwidget的高100
initPagepageView初始显示第n页0
indicatorColor未选中title的颜色Colors.black38
indicatorSelectedColor选中title的颜色Colors.red
backgroundColor指示器北京颜色Colors.grey
scrollDirectionpageview的滑动方向(水平/竖直)Axis.horizontal
indicatorWidth指示器指示条的宽度80
indicatorHeight指示器指示条的高度2
onPageClickedpageview页面点击回调

大家可以根据自己的需求来适当改动。

*注:当titles过多时会出现换行的情况,Ui体验较差,可以将titles放入listview进行显示(如:今日头条的导航栏效果)

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐