Introduction to the
This article describes how to use ListView in Flutter to implement Android running lights, and then extend this to scroll up and down.
Making the address
The widget has been successfully uploaded to pub.dev.
dependencies:
flutterswitcher: ^ 0.0.1
Copy the code
rendering
First, the effect picture:
Vertical mode
Level pattern
In the code
There are two main scroll modes, vertical and horizontal, so we define two constructors. Parameters include the scrolling speed (in Pixels/Second), the delay of each scroll, the curve change of the scroll, and the placeholder control when children is empty.
class Switcher {
const Switcher.vertical({
Key key,
@required this.children,
this.scrollDelta = _kScrollDelta,
this.delayedDuration = _kDelayedDuration,
this.curve = Curves.linearToEaseOut,
this.placeholder,
}) : assert(scrollDelta ! =null && scrollDelta > 0 && scrollDelta <= _kMaxScrollDelta),
assert(delayDuration ! =null),
assert(curve ! =null),
spacing = 0,
_scrollDirection = Axis.vertical,
super(key: key);
const Switcher.horizontal({
Key key,
@required this.children,
this.scrollDelta = _kScrollDelta,
this.delayedDuration = _kDelayedDuration,
this.curve = Curves.linear,
this.placeholder,
this.spacing = 10,}) :assert(scrollDelta ! =null && scrollDelta > 0 && scrollDelta <= _kMaxScrollDelta),
assert(delayDuration ! =null),
assert(curve ! =null),
assert(spacing ! =null && spacing >= 0 && spacing < double.infinity),
_scrollDirection = Axis.horizontal,
super(key: key);
}
Copy the code
Implementation approach
There are two ways to achieve this:
-
The first is to use a ListView;
-
The second is to paint yourself with CustomPaint;
We chose to use ListView so that the extension can be manually scrolled. If we use CustomPaint, it will be more difficult to implement.
Here’s how to do it:
Vertical mode
First analyze the vertical mode, if you want to achieve cycle rolling, so the number of children should be more than the original one, when the scroll to the last one, immediately jump to the first, and finally a practical here is the original first, so users don’t have any notice, this implementation applied in the front-end development a lot, For example, to implement a circular slide for PageView, let’s define childCount:
_initalizationElements() {
_childCount = 0;
if(widget.children ! =null) {
_childCount = widget.children.length;
}
if (_childCount > 0&& widget._scrollDirection == Axis.vertical) { _childCount++; }}Copy the code
When children change, we recalculate childCount,
@override
void didUpdateWidget(Switcher oldWidget) {
varchildrenChanged = (widget.children? .length ??0)! = (oldWidget.children? .length ??0);
if(widget._scrollDirection ! = oldWidget._scrollDirection || childrenChanged) { _initalizationElements(); _initializationScroll(); }super.didUpdateWidget(oldWidget);
}
Copy the code
If vertical mode is used, we will use childCount++. Next, implement 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) {
returnSizedBox( width: widget.spacing, ); },),); }); }Copy the code
The main logic for implementing vertical scrolling follows:
_animateVertical(double extent) {
if(! _controller.hasClients || widget._scrollDirection ! = Axis.vertical) {return;
}
if (_selectedIndex == _childCount - 1) {
_selectedIndex = 0;
_controller.jumpTo(0); } _timer? .cancel(); _timer = Timer(widget.delayedDuration, () { _selectedIndex++;var duration = _computeScrollDuration(extent);
_controller.animateTo(extent * _selectedIndex, duration: duration, curve: widget.curve).whenComplete(() {
_animateVertical(extent);
});
});
}
Copy the code
If the current scrolling direction is vertical or not, return the ScrollController. If the current scrolling direction is vertical or not, return the ScrollController. If the current scrolling direction is vertical or not, return the ScrollController. Time for our incoming timer interval, and then every two widgets. DelayedDuration rolling a time here call ScrollController. AnimateTo, rolling distance for each item multiplied by the height of the current index, the rolling time according to the calculated rolling speed:
Duration _computeScrollDuration(double extent) {
return Duration(milliseconds: (extent * Duration.millisecondsPerSecond / widget.scrollDelta).floor());
}
Copy the code
Here is our primary school, distance x = speed time, so we can according to the distance and speed it is concluded that the time required for, here is multiplied by the Duration. The millisecondsPerSecond reason is converted to milliseconds, because our speed is pixels per second.
When the current scroll is complete, do the next one, recursively calling _animateVertical, so we have a vertical circular scroll.
Level pattern
Next, implement horizontal mode, which is similar to vertical mode:
_animateHorizonal(double extent, bool needsMoveToTop) {
if(! _controller.hasClients || widget._scrollDirection ! = Axis.horizontal) {return; } _timer? .cancel(); _timer = Timer(widget.delayedDuration, () {if (needsMoveToTop) {
_controller.jumpTo(0);
_animateHorizonal(extent, false);
} else {
var duration = _computeScrollDuration(extent);
_controller.animateTo(extent, duration: duration, curve: widget.curve).whenComplete(() {
_animateHorizonal(extent, true); }); }}); }Copy the code
Here’s needsMoveToTop, because in horizontal mode, we’re going to pause at both ends, so we’re going to add a parameter that says, “needsMoveToTop is going to pass false for the current scroll to the head,” and “needsMoveToTop is going to pass True for the scroll to the tail,” Our next action is to scroll to the head, not to start scrolling to the entire list.
So let’s see where we start scrolling.
First we start scrolling when the page loads, and then we restart scrolling when the orientation and childCount change, so:
@override
void initState() {
super.initState();
_initalizationElements();
_initializationScroll();
}
@override
void didUpdateWidget(Switcher oldWidget) {
varchildrenChanged = (widget.children? .length ??0)! = (oldWidget.children? .length ??0);
if(widget._scrollDirection ! = oldWidget._scrollDirection || childrenChanged) { _initalizationElements(); _initializationScroll(); }super.didUpdateWidget(oldWidget);
}
Copy the code
And then the _initializationScroll method:
_initializationScroll() {
SchedulerBinding.instance.addPostFrameCallback((timeStamp) {
if(! mounted) {return;
}
varrenderBox = context? .findRenderObject()as RenderBox;
if(! _controller.hasClients || _childCount ==0 || renderBox == null| |! renderBox.hasSize) {return;
}
varposition = _controller.position; _timer? .cancel(); _timer =null;
position.moveTo(0);
_selectedIndex = 0;
if (widget._scrollDirection == Axis.vertical) {
_animateVertical(renderBox.size.height);
} else {
var maxScrollExtent = position.maxScrollExtent;
_animateHorizonal(maxScrollExtent, false); }}); }Copy the code
Here, when the page is drawn, we say, if the ScrollController is not loaded, if childCount == 0 or if the size is not evaluated, we get position, cancel the previous timer, roll the list to the head, initialize index to 0, If it’s in vertical mode, start scrolling vertically. If it’s in horizontal mode, start scrolling horizontally.
Note that in vertical scrolling, the scrolling distance is the height of each item, while in horizontal scrolling, the scrolling distance is the maximum scrolling length of the list.
So far we have implemented the Android running lights, but also added vertical scrolling, isn’t it easy?
If you have any questions, comments or suggestions, please let me know in the comments section. I will timely modify and refer to your comments and suggestions to optimize the code.