This paper describes how to implement Android's horse-running light in flutter, then expand it to scroll up and down, first with the effect picture:
Vertical mode
Horizontal mode
Upper Code
There are two main scrolling modes, vertical and horizontal, so we define two construction methods. The parameters are scroll speed, delay of each scroll, curve change of scroll, and placeholder controls when children is empty.
const Switcher.vertical({ Key key, @required this.children, this.scrollSpeed = _kScrollSpeed, this.delayedDuration = _kDelayedDuration, this.curve = Curves.linearToEaseOut, this.placeholder, }) : assert(scrollSpeed != null && scrollSpeed > 0 && scrollSpeed <= _kMaxScrollSpeed), assert(delayDuration != null), assert(curve != null), spacing = 0, _scrollDirection = Axis.vertical, super(key: key); const Switcher.horizontal({ Key key, @required this.children, this.scrollSpeed = _kScrollSpeed, this.delayedDuration = _kDelayedDuration, this.curve = Curves.linear, this.placeholder, this.spacing = 10, }) : assert(scrollSpeed != null && scrollSpeed > 0 && scrollSpeed <= _kMaxScrollSpeed), assert(delayDuration != null), assert(curve != null), assert(spacing != null && spacing >= 0 && spacing < double.infinity), _scrollDirection = Axis.horizontal, super(key: key);
There are two ways to achieve this:
The first is using ListView; The second is to draw by CustomPaint himself;
Here we choose to use ListView for later extensions to scroll manually, which is more cumbersome if you use CustomPaint.
Next, let's analyze how to achieve this:
First, analyze the vertical mode, if you want to achieve circular scrolling, then the number of children should be one more than the original. When you scroll to the last, jump to the first one immediately. The last one here is actually the original first, so the user will not be aware of it. This implementation is used in front-end development a lot, such as implementing a cycle of PageView.Slide, so let's define childCount here:
_initalizationElements() { _childCount = 0; if (widget.children != null) { _childCount = widget.children.length; } if (_childCount > 0 && widget._scrollDirection == Axis.vertical) { _childCount++; } }
When children changes, we recalculate the childCount.
@override void didUpdateWidget(Switcher oldWidget) { var childrenChanged = (widget.children?.length ?? 0) != (oldWidget.children?.length ?? 0); if (widget._scrollDirection != oldWidget._scrollDirection || childrenChanged) { _initalizationElements(); _initializationScroll(); } super.didUpdateWidget(oldWidget); }
So if it's vertical, let's say childCount+, build method:
@override Widget build(BuildContext context) { if (_childCount == 0) { return widget.placeholder ?? SizedBox.shrink(); } return LayoutBuilder( builder: (context, constraints) { return ConstrainedBox( constraints: constraints, child: ListView.separated( itemCount: _childCount, physics: NeverScrollableScrollPhysics(), controller: _controller, scrollDirection: widget._scrollDirection, padding: EdgeInsets.zero, itemBuilder: (context, index) { final child = widget.children[index % widget.children.length]; return Container( alignment: Alignment.centerLeft, height: constraints.constrainHeight(), child: child, ); }, separatorBuilder: (context, index) { return SizedBox( width: widget.spacing, ); }, ), ); }, ); }