欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

31_Flutter之从无到有撸一个轮播图组件

程序员文章站 2022-05-29 21:06:19
...

Flutter之从无到有撸一个轮播图组件

本文将基于PageView组件实现一个轮播图组件,PageView组件和android中的ViewPage类似

一.轮播图组件应该具有如下属性

  • scrollDirection:滑动或轮播方向

    • Axis.horizontal: 左右轮播
    • Axis.vertical: 上下轮播
  • children: 轮播图的各个item,类型为List

  • indicatorSize: 指示器的大小,类型为Size

  • indicatorSelectedColor: 指示器在选中状态下的颜色,类型为Color

  • indicatorUnselectedColor: 指示器在未选中状态下的颜色,类型为Color

  • indicatorRadius: 指示器的圆角半径,类型为double

  • indicatorBackgroundColor: 摆放指示器的父组件的背景色,类型为Color

  • indicatorPosition: 指示器显示的位置,类型为:

    enum {
        left,
        right,
        top,
        bottom
    }
    
  • indicatorPadding: 摆放指示器的父组件的padding,类型为EdgeInsetsGeometry

  • indicatorAlignment: 由于摆放指示器的父组件是一个Row组件,所以这里为指示器在主轴方向的对齐方式,类型为MainAxisAlignment

    • MainAxisAlignment.start: 开始位置对齐
    • MainAxisAlignment.center: 居中对齐
    • MainAxisAlignment.end: 结束位置对齐
  • duration: 轮播时长,类型为int

  • animationDuration: 轮播图的item切换动画的时长,类型为int

  • showIndicator: 是否显示指示器,类型为bool

二. scrollDirection

由于要根据PageView的选中状态来更新指示器的选中状态,所以该轮播图组件应该为一个有状态组件,即应该继承自StatefulWidget

class CarouselView extends StatefulWidget {

  final Axis scrollDirection;

  CarouselView({
     Key key,
    this.scrollDirection = Axis.horizontal,
  }):super(key:key);

  @override
  State<StatefulWidget> createState() {
    // TODO: implement createState
    return _CarouselViewState();
  }
}

class _CarouselViewState extends State<CarouselView> {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Stack(
      children: <Widget>[
        PageView(
          scrollDirection: widget.scrollDirection,
        )
      ],
    );
  }
}

三.children

class CarouselView extends StatefulWidget {

  final Axis scrollDirection;
  final List<Widget> children;

  CarouselView({
    Key key,
    this.scrollDirection = Axis.horizontal,
    this.children,
  }):super(key:key);

  @override
  State<StatefulWidget> createState() {
    // TODO: implement createState
    return _CarouselViewState();
  }
}

class _CarouselViewState extends State<CarouselView> {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Stack(
      children: <Widget>[
        PageView(
          scrollDirection: widget.scrollDirection,
          children: widget.children,
        )
      ],
    );
  }

}

为CarrouselView设置children,就可以将item显示在CarrouselView中了:

home: Scaffold(
        appBar: AppBar(
          title: Text("CarouselView"),
        ),
        body: Container(
          child: Container(
            width: double.infinity,
            height: 240,
            child: CarouselView(
              scrollDirection: Axis.horizontal,
              children: <Widget>[
                Image.network("http://pic23.nipic.com/20120727/10614465_163109621190_2.jpg", fit: BoxFit.cover),
                Image.network("http://pic27.nipic.com/20130311/11468523_154151382161_2.jpg", fit: BoxFit.cover),
                Image.network("http://pic59.nipic.com/file/20150203/2306733_113254806000_2.jpg", fit: BoxFit.cover),
              ],
            ),
          )
        ),
      )

31_Flutter之从无到有撸一个轮播图组件

四.添加指示器,并实现选中状态切换

class CarouselView extends StatefulWidget {

  final Axis scrollDirection;
  final List<Widget> children;
  final Size indicatorSize;
  final Color indicatorSelectedColor;
  final Color indicatorUnselectedColor;
  final double indicatorRadius;
  final IndicatorPosition indicatorPosition;
  final EdgeInsetsGeometry indicatorPadding;
  final MainAxisAlignment indicatorAlignment;

  _Position _position;

  CarouselView({
    Key key,
    this.scrollDirection = Axis.horizontal,
    this.children,
    this.indicatorSize,
    this.indicatorSelectedColor,
    this.indicatorUnselectedColor,
    this.indicatorRadius,
    this.indicatorPosition = IndicatorPosition.bottom,
    this.indicatorPadding,
    this.indicatorAlignment = MainAxisAlignment.start,
  }):super(key:key) {
    _position = _judgePosition();
  }

  @override
  State<StatefulWidget> createState() {
    // TODO: implement createState
    return _CarouselViewState();
  }

  /**
   * 判断指示器的显示位置
   */
  _Position _judgePosition() {
    _Position position = _Position();
    switch(indicatorPosition) {
      case IndicatorPosition.left:
        position = _Position(left: 0, right: null, top: 0, bottom: 0);
        break;
      case IndicatorPosition.right:
        position = _Position(left:null, right: 0, top: 0, bottom: 0);
        break;
      case IndicatorPosition.top:
        position = _Position(left: 0, right:0, top: 0, bottom: null);
        break;
      case IndicatorPosition.bottom:
        position = _Position(left: 0, right:0, top: null, bottom: 0);
        break;
    }
    return position;
  }
}

class _CarouselViewState extends State<CarouselView> {
  int _currentIndex = 0;

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Stack(
      children: <Widget>[
        PageView(
          scrollDirection: widget.scrollDirection,
          children: widget.children,
          onPageChanged: (index) {
            /**
             * 通知Flutter去重新渲染界面,从而改变指示器的选中状态
             */
            _changeCurrentIndex(index);
          }
        ),
        Positioned(
            left: widget._position.left,
            right: widget._position.right,
            top: widget._position.top,
            bottom: widget._position.bottom,
            child: Container(
//            color: Color(0xffff0000),
              padding: widget.indicatorPadding,
              child: Row(
                  mainAxisAlignment: widget.indicatorAlignment,
                  children: _buildIndicators()
              ),
            ),
        )
      ],
    );
  }

  /**
   * 生成指示器
   */
  List<Widget> _buildIndicators() {
    List<Widget> indicators = <Widget>[];
    for(int i=0; i<widget.children.length; i++) {
      indicators.add(
          Container(
            width: widget.indicatorSize.width,
            height: widget.indicatorSize.height,
            decoration: BoxDecoration(
                color: _currentIndex == i ? widget.indicatorSelectedColor : widget.indicatorUnselectedColor,
                borderRadius: BorderRadius.all(Radius.circular(widget.indicatorRadius))
            ),
            margin: i > 0 || i< widget.children.length - 1 ? EdgeInsets.symmetric(horizontal: 3) : i == 0 ? EdgeInsets.only(right: 3):EdgeInsets.only(left: 3),
          )
      );
    }
    return indicators;
  }

  void _changeCurrentIndex(int index) {
    setState(() {
      _currentIndex = index;
      print(_currentIndex);
    });
  }
}

class _Position {
  final double left;
  final double right;
  final double top;
  final double bottom;

  _Position({
    this.left,
    this.right,
    this.top,
    this.bottom,
  });
}

enum IndicatorPosition {
  left,
  right,
  top,
  bottom
}
home: Scaffold(
        appBar: AppBar(
          title: Text("CarouselView"),
        ),
        body: Container(
          child: Container(
            width: double.infinity,
            height: 240,
            child: CarouselView(
              scrollDirection: Axis.horizontal,
              children: <Widget>[
                Image.network("http://pic23.nipic.com/20120727/10614465_163109621190_2.jpg", fit: BoxFit.cover),
                Image.network("http://pic27.nipic.com/20130311/11468523_154151382161_2.jpg", fit: BoxFit.cover),
                Image.network("http://pic59.nipic.com/file/20150203/2306733_113254806000_2.jpg", fit: BoxFit.cover),
              ],
              indicatorSize: Size(10, 10),
              indicatorSelectedColor: Color(0xff0000ff),
              indicatorUnselectedColor: Color(0xff00ff00),
              indicatorRadius: 5,
              indicatorAlignment: MainAxisAlignment.center,
              indicatorPosition: IndicatorPosition.bottom,
              indicatorPadding: EdgeInsets.all(10.0),
            ),
          )
        ),
      )

31_Flutter之从无到有撸一个轮播图组件

五.item循环滚动实现

31_Flutter之从无到有撸一个轮播图组件

  • children = [A, B, C]

  • initialPage = 1

  • 当向左滑动到index = 0时,应重新切换到对应children的index=2

  • 当向右滑动到index = 4时,应重新切换到对应children的index=0

    class _CarouselViewState extends State<CarouselView> {
      ...
      static PageController _controller = PageController(
        initialPage: 1
      );
      static int _oldIndex = 1;
      @override
      Widget build(BuildContext context) {
        // TODO: implement build
        return Stack(
          children: <Widget>[
            PageView(
              scrollDirection: widget.scrollDirection,
              children: _buildPages(),
              controller: _controller,
              onPageChanged: (index) {
                _oldIndex = index;
                if(_oldIndex == 0) {
                  index = widget.children.length - 1;
    
                  Future.delayed(Duration(milliseconds: 500), (){
                    _controller.jumpToPage(widget.children.length);
                  });
                } else if(_oldIndex == widget.children.length + 1) {
                  index = 0;
                  Future.delayed(Duration(milliseconds: 500), (){
                    _controller.jumpToPage(1);
                  });
                } else {
                  index -= 1;
                }
                _changeCurrentIndex(index);
              },
            ),
            ...
          ],
        );
      }
      
      List<Widget> _buildPages() {
        List<Widget> pages = <Widget> [];
        int firstIndex = 0;
        int lastIndex = widget.children.length - 1;
        pages.add(widget.children[lastIndex]);
    
        for(int i=0; i<widget.children.length; i++) {
          pages.add(widget.children[i]);
        }
    
        pages.add(widget.children[firstIndex]);
        return pages;
      }
      
      ...
    }
    

    31_Flutter之从无到有撸一个轮播图组件

六.上一页和下一页实现

class CarouselView extends StatefulWidget {

  ...
  final int animationDuration;
  
  CarouselView({
    ...
    this.animationDuration = 500,
  }):super(key: key) {
    _position = _judgePosition();
  }
  
  static void prevPage() {
    _CarouselViewState._prevPage();
  }

  static void nextPage() {
    _CarouselViewState._nextPage();
  }
}

class _CarouselViewState extends State<CarouselView> {
  int _currentIndex = 0;
  static PageController _controller = PageController(
    initialPage: 1
  );
  static int _oldIndex = 1;
  static int _pageSize;
  static int _animationDuration;

  ...

  List<Widget> _buildPages() {
    List<Widget> pages = <Widget> [];
    int firstIndex = 0;
    int lastIndex = widget.children.length - 1;
    pages.add(widget.children[lastIndex]);

    for(int i=0; i<widget.children.length; i++) {
      pages.add(widget.children[i]);
    }

    pages.add(widget.children[firstIndex]);
    _pageSize = widget.children.length;
    _animationDuration = widget.animationDuration;
    return pages;
  }

  static void _prevPage() {
    int index = _oldIndex - 1;
    _controller.animateToPage(index, duration: Duration(milliseconds: _animationDuration), curve: Curves.linear);
  }

  static void _nextPage() {
    int index = _oldIndex + 1;
    _controller.animateToPage(index, duration: Duration(milliseconds: _animationDuration), curve: Curves.linear);
  }
}
home: Scaffold(
        appBar: AppBar(
          title: Text("CarouselView"),
        ),
        body: Column(
          mainAxisSize: MainAxisSize.max,
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children:<Widget>[
            Container(
              width: double.infinity,
              height: 240,
              child: CarouselView(
                scrollDirection: Axis.horizontal,
                children: <Widget>[
                  Image.network("http://pic23.nipic.com/20120727/10614465_163109621190_2.jpg", fit: BoxFit.cover),
                  Image.network("http://pic27.nipic.com/20130311/11468523_154151382161_2.jpg", fit: BoxFit.cover),
                  Image.network("http://pic59.nipic.com/file/20150203/2306733_113254806000_2.jpg", fit: BoxFit.cover),
                ],
                indicatorSize: Size(10, 10),
                indicatorSelectedColor: Color(0xff0000ff),
                indicatorUnselectedColor: Color(0xff00ff00),
                indicatorRadius: 5,
                indicatorAlignment: MainAxisAlignment.center,
                indicatorPosition: IndicatorPosition.bottom,
                indicatorPadding: EdgeInsets.all(10.0),
              ),
            ),
            RaisedButton(
              onPressed: () {
                CarouselView.prevPage();
              },
              child: Text('上一页'),
            ),
            RaisedButton(
              onPressed: () {
                CarouselView.nextPage();
              },
              child: Text('下一页'),
            ),
          ]
        ),
      )

31_Flutter之从无到有撸一个轮播图组件

七.自动轮播实现

class CarouselView extends StatefulWidget {

  ...
  final int duration;

  _Position _position;

  CarouselView({
    ...
    this.duration = 3000,
  }):super(key: key) {
    _position = _judgePosition();
  }

  @override
  State<StatefulWidget> createState() {
    // TODO: implement createState
    _start();
    return _CarouselViewState();
  }

  void _start() {
    Future.delayed(Duration(milliseconds: duration), (){
      CarouselView.nextPage();
      _start();
    });
  }

  ...
}

31_Flutter之从无到有撸一个轮播图组件