This figure has nothing to do with the text, just to look good

Writing in the front

The last article was about how to implement a floating navigation bar with CustomPaint. It was a bit of a read and probably not something you would care about. So this article to write a common function ———— infinite wheel broadcast map.

As you can see at the end of this article, the development of this round map came from a project OF mine, because the plugins on PUB didn’t meet my needs (or didn’t fit my needs), so I decided to try to write one myself and see the final result.

The picture comes from netease cloud music, while listening to the song, the infringement will be deleted

Read the key

The implementation of Flutter is quite simple. The Flutter provides a PageView component that can do this by itself. There is just one small problem with the infinite rotation. Take your time. I’ll talk about it later.

First think about it from the front end (why the front end? Because I’m just the front end) how to do infinite rotation, what I usually do is copy the last image at the head of the array image, copy the first image at the end of the array image, and then rotate to the last image and skip to the second image, rotate to the first image and skip to the penultimate image. So, following this train of thought (inertial thinking), we first realize this infinite round seeding.

Create two new files carousel and CustomPageView. CustomPageView contains the code for the copied PageView:

Create a new StatefulWidget in Carousel:

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_samples/carousel/CustomPageView.dart';

class Carousel extends StatefulWidget {
  @override
  _State createState() => _State();
}

class _State extends State<Carousel> {
  PageController _pageController = PageController(initialPage: 1);// The index starts at 0 and is set to 1 because of additions
  int _currentIndex = 1;
  List<String> _images = [
    'images/1.png'.'images/2.png'.'images/3.png'.'images/4.png'.'images/5.png'.'images/6.png'.'images/7.png'.'images/8.png'.'images/9.png',]; Timer _timer;/ / timer
}
Copy the code

The first import is needed by Timer, and there is nothing else to say.

Next, set a timer, because what we’re doing is auto rotation:

// Set the timer
_setTimer() {
    _timer = Timer.periodic(Duration(seconds: 4), (_) {
      _pageController.animateToPage(_currentIndex + 1,
          duration: Duration(milliseconds: 400), curve: Curves.easeOut);
    });
}
Copy the code

There is a timer set by the method periodic, which is executed every 4 seconds, and what it does is slide to the next slide.

Next, work with the image array:

  @override
  Widget build(BuildContext context) {
    List addedImages = [];
    if (_images.length > 0) { addedImages .. add(_images[_images.length -1]).. addAll(_images) .. add(_images[0]);
    }
    return Scaffold(
      appBar: AppBar(
        elevation: 0.0,
        title: Text('Carousel'),
        centerTitle: true,
      ),
      body: AspectRatio(
        aspectRatio: 2.5,
        child:
      ),
    );
  }
Copy the code

Here we define an addedImages, which means an array of updated images (remember to check if _images is empty, even though we’re dead here, but think about it).

AspectRatio is an aspectRatio, and aspectRatio will automatically set the height of the child component based on the incoming aspectRatio, and the height will automatically adjust according to the screen width (see below), so if you want to adapt, take notes.

Next, write the code for the image part:

 NotificationListener(
      onNotification: (ScrollNotification notification) {
        if (notification.depth == 0 &&
            notification is ScrollStartNotification) {
          if(notification.dragDetails ! =null) { _timer.cancel(); }}else if (notification is ScrollEndNotification) {
          _timer.cancel();
          _setTimer();
        }
      },
      child: _images.length > 0
          ? CustomPageView(
              physics: BouncingScrollPhysics(),
              controller: _pageController,
              onPageChanged: (page) {
                int newIndex;
                if (page == addedImages.length - 1) {
                  newIndex = 1;
                  _pageController.jumpToPage(newIndex);
                } else if (page == 0) {
                  newIndex = addedImages.length - 2;
                  _pageController.jumpToPage(newIndex);
                } else {
                  newIndex = page;
                }
                setState(() {
                  _currentIndex = newIndex;
                });
              },
              children: addedImages
                  .map((item) => Container(
                        margin: EdgeInsets.all(10.0),
                        child: ClipRRect(
                          borderRadius: BorderRadius.circular(5.0),
                          child: Image.asset(
                            item,
                            fit: BoxFit.cover,
                          ),
                        ),
                      ))
                  .toList(),
            )
          : Container(),
    ),
Copy the code

We did two very important things in onNotification. One was to cancel the timer when the user swiped with their hand (or foot) and then reset the timer when the swiping ended.

Notification. depth indicates what level the event is at. In A Flutter, events also bubble, so the source (i.e. the level from which the event originated) is 0. If confused, refer to the web event while reading the document.

Notification. dragDetails can get the displacement of sliding, which will not be used here for the time being. We just need to confirm that the user has swiped the wheel.

We reset the current index in the onPageChanged callback of CustomPageView (the original PageView) every time the wheel switches.

Next comes the indicator section:

 Positioned(
      bottom: 15.0,
      left: 0,
      right: 0,
      child: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        crossAxisAlignment: CrossAxisAlignment.center,
        children: _images
            .asMap()
            .map((i, v) => MapEntry(
                i,
                Container(
                  width: 6.0,
                  height: 6.0,
                  margin: EdgeInsets.only(left: 2.0, right: 2.0),
                  decoration: ShapeDecoration(
                      color: _currentIndex == i + 1
                          ? Colors.red
                          : Colors.white,
                      shape: CircleBorder()),
                )))
            .values
            .toList(),
      ),
    )
Copy the code

The point is that dart doesn’t even provide an index for the List traversal method, so it’s a bit of a problem to implement the current item highlighting. There are two ways to do this: create a new method and run it through a for loop inside the method (which I don’t like); The second is the way the text is written.

The List is converted to a Map using asMap, where the key in the Map is the index and the value is the value. Then the Map method is used to retrieve the index.

Then call the timer in initState:

 @override
  void initState() {
    print(_images.asMap());
    if (_images.length > 0) {
      _setTimer();
    }
    super.initState();
  }
Copy the code

Take a look at the effect:

The eagle-eyed bobbin may have noticed the problem, which is a flicker when sliding to the first or last slide, or even a non-ideal switch if the user is swiping:

This is the little problem I mentioned above when using the old PageView for infinite rotation, the first and last page (in fact, for all images) will be swiped halfway to a new page.

In fact, the effect of infinite rotation has been achieved, but there is this small problem of disharmony, so as long as this problem is solved, infinite rotation is perfect.

So how to solve this problem? Let’s look at the source of PageView, which has this code:

 onNotification: (ScrollNotification notification) {
    if (notification.depth == 0&& widget.onPageChanged ! =null && notification is ScrollUpdateNotification) {
      final PageMetrics metrics = notification.metrics;
      final int currentPage = metrics.page.round();
      if (currentPage != _lastReportedPage) {
        _lastReportedPage = currentPage;
        widget.onPageChanged(currentPage);
      }
    }
    return false;
  }
Copy the code

The snag is in this sentence:

notification is ScrollUpdateNotification
Copy the code

This line identifies the type of notification that keeps executing the code inside if as it slides. Once the metrics.page fraction is greater than 0.5, metrics.page.round() gets the new page and the switch is performed.

So we can just change our ScrollUpdateNotification here to ScrollEndNotification, which is just doing internal judgment after the swipe, that’s it.

The viewportFraction of the PageController can also be passed a value such as 0.9 to achieve a parallax effect:

Here we have infinite rotation, and last but not least, remember to destroy the timer:

@override
voiddispose() { _timer? .cancel();super.dispose();
}
Copy the code

Adaptive effect:

The last words

The method described in this article with animation is enough to achieve most conventional rotation effects, of course, if the designer can come up with a sharper effect, you may want to study Scrollable, but that is not the focus of this article, source point here.

A practical tutorial on Flutter has been recorded for those interested