Flutter Analog Android Racelight

Keywords: Mobile Android

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,
						);
					},
				),
			);
		},
	);
}

Posted by jarv on Thu, 16 Apr 2020 00:18:38 -0700