In actual business development, there is usually a round – cast chart on the home page.

How to develop a round cast in Flutter?

Understand the requirements

First of all, we need to understand the requirements of this function when developing a function. What function does a round play need to have?

  1. You can customize the height and some properties
  2. Show the picture
  3. Automatic page turning and playing
  4. Click on the event
  5. indicator
  6. Turn off auto – play when dragging manually

It is difficult to “turn off autoplay when dragging artificially”. We will talk about that later. That should be implemented one by one.

Custom height and some properties

Here is mainly to do some preliminary work, if our Banner to open source for others to use, then we must be to give the user some attributes can be customized, such as:

  1. The height of the Banner
  2. Image switching effect
  3. Click the callback of the event

Dart class named CustomBanner. The constructor is as follows:

CustomBanner(
  this._images, {
  this.height = 200.this.onTap,
  this.curve = Curves.linear,
}) : assert(_images ! =null);
Copy the code
  • _images: First, the link to the image must be there, and an assertion validation is done later
  • Height: Second, the height is user-definable and defaults to 200
  • OnTap: the callback that the user clicks is oneValueChanged<int>To call back an index
  • Curve: the effect of the image when switching. The default value is curveCurves.linear

With this initial preparation done, it’s time to start showing pictures.

Show the picture

A normal Banner is made up of a bunch of pictures, and then at a fixed period of time,

PageView is the first Widget we thought of, and PageView fits our needs,

It has the following properties:

  1. Multiple page turning
  2. A controller controls page turning
  3. Paging callback
  4. Infinite page

Let’s define a PageView:

Widget _buildPageView() {
  var length = widget._images.length;
  return Container(
    height: widget.height,
    child: PageView.builder(
      controller: _pageController,
      onPageChanged: (index) {
        if (index == 0) {
          _curIndex = length;
        }
      },
      itemBuilder: (context, index) {
        returnImage.network( widget._images[index % length], fit: BoxFit.cover, ); },),); }Copy the code

Here we define a method to generate a PageView from pageView. builder. The advantage of using this method is that you can generate an infinite number of pages without having to worry about sliding to the right edge.

So one might say what if it’s the left edge?

So if we look at the onPageChange method, we say if index == 0 then we change _curIndex to length, why length?

In itemBuilder, we return widget._images[index % length], mod length with index, which ensures that our images do not cross the array, and that the first image is the first image. This ensures that the left edge is not touched either.

Create a Container on top of your PageView to limit the height of your PageView.

Automatic page turning and playing

Now that you can display the image, it’s time to do the automatic page-turning.

In Dart, timer.periodic () is used to do periodic tasks. This method takes two parameters:

  1. Duration: How often to execute
  2. Callback: A task to perform when the time is up

With this method, we can easily write autoplay:

_timer = Timer.periodic(Duration(seconds: 3), (t) {
  _curIndex++;
  _pageController.animateToPage(
    _curIndex,
    duration: Duration(milliseconds: 300),
    curve: Curves.linear,
  );
});
Copy the code

So above we’ve defined a Controller for PageView, which we can use here,

First define the timer.periodic method, indicating that it is executed every three seconds, and then execute it in the callback task:

  1. _curIndex + + : index + 1
  2. Use of the controlleranimateToPageMethod, which is an animated jump

AnimateToPage takes three parameters:

  1. Page to jump to
  2. Jump to the page animation duration (i.e. how long it takes to turn to the page)
  3. Animation effect

Once defined, let’s look at the effect:

Click on the event

Now autoplay is ok, so basically there is only one click event left.

Click events are very simple, we can add a GestureDetector to PageView to recognize gestures,

But I don’t want to put it on PageView, why?

Because you’re going to add indicators later, and indicators should have their own click events, like clicking on the second dot to jump to the second page,

So we need to add gesture recognition to the Image:

return GestureDetector(
  onTap: () {
    Scaffold.of(context).showSnackBar(
      SnackBar(
        content: Text('Current page is${index % length}'),
        duration: Duration(milliseconds: 500),),); }, child: Image.network( widget._images[index % length], fit: BoxFit.cover, ), );Copy the code

Very simple, is to add a GestureDetector, see the effect:

Be reasonable, now a most basic Banner has been completed, can see the picture, there is a round broadcast, there are click events.

But it’s not perfect. Here’s the indicator.

indicator

In general, in rotation, there will be an indicator, like a little dot down here, or “1/3” or something like that, so we’ll just do the first kind of dot here.

As indicators, there should be the following:

  1. In front of the picture (crap, you can’t see it behind the picture either)
  2. There are several indicators for each picture
  3. Displays the current page

Display in front of the picture

This requirement is relatively simple, we use a Stack to wrap PageView and Indicator ok:

return Stack(
  alignment: Alignment.bottomCenter,
  children: <Widget>[
    _buildViewPager(),
    _buildIndicator(),
  ],
);
Copy the code

Defines a _buildIndicator() method that builds an indicator.

There are several indicators for each picture

The indicator we’re talking about here is a little dot, and it’s very simple, just use ClipOval to create a circle,

The specific code is as follows:

Widget _buildIndicator() {
  var length = widget._images.length;
  return Positioned(
    bottom: 10,
    child: Row(
      children: widget._images.map((s) {
        return Padding(
          padding: const EdgeInsets.symmetric(horizontal: 3.0),
          child: ClipOval(
            child: Container(
              width: 8,
              height: 8,
              color: Colors.grey,
            ),
          ),
        );
      }).toList(),
    ),
  );
}
Copy the code

Logic is:

  1. First, the length of the image data is obtained
  2. Stack defines Aligment asbottomCenter
  3. And then we define onePositionedTo control the distance from the bottom
  4. The child isRowAnd arrange the dots horizontally
  5. Give each dot a margin of 3
  6. The size of the dot is 8

Take a look at the results:

You can see that the dot does come out, but it doesn’t indicate which one it is.

Displays the current page

The next step is to show what page it is on, which is actually pretty easy (if you don’t do special effects),

We made the dots on the current page white, while the dots on the previous indicator were gray:

Widget _buildIndicator() {
  var length = widget._images.length;
  return Positioned(
    bottom: 10,
    child: Row(
      children: widget._images.map((s) {
        return Padding(
          padding: const EdgeInsets.symmetric(horizontal: 3.0),
          child: ClipOval(
            child: Container(
              width: 8,
              height: 8,
              color: s == widget._images[_curIndex % length]
              ? Colors.white
              : Colors.grey,
            ),
          ),
        );
      }).toList(),
    ),
  );
}
Copy the code

Check whether the current value of the Container is the same as the current index value.

If they are equal, they turn white. If they are not equal, they turn gray.

If the light is written like this, the dots will not change, so we will setState() in PageView onPageChanged callback,

Update the _curIndex value as well.

Rebuild the refresh page to see what it looks like:

At this time, the Banner can be said to be very perfect, but if we manually intervene sliding what problems will occur?

Because we just wrote 3 seconds for everything to change, so when we manually switch it, after it reaches the third second, it will continue to change pages.

Turn off auto – play when dragging manually

So, in this case, we need to turn off autoplay when we listen for someone dragging, and then turn it on when there is no human dragging.

We just added a GesutreDetector on top of the Image. Just so, we added the onPanDown parameter to pause the scheduled task.

Then resume the task when the finger leaves.

But! There are big potholes here!

  1. TimerNo pause method
  2. Because I’m using thetaPageView, there is a sliding conflict, so you can’t listen to the way the finger leaves

Here can only use the curve to save the country:

  1. Although the Timer does not pause, it does cancel the cancel() method.

  2. We can’t listen for the way the finger leaves, but we can listen for the way the finger touches

So we should write:

/// Cancel the scheduled task when clicking on the image
_cancelTimer() {
  if(_timer ! =null) {
    _timer.cancel();
    _timer = null; _initTimer(); }}/// ------------------------
return GestureDetector(
  onPanDown: (details) {
    _cancelTimer();
  },
  onTap: () {
    Scaffold.of(context).showSnackBar(
      SnackBar(
        content: Text('Current page is${index % length}'),
        duration: Duration(milliseconds: 500),),); }, child: Image.network( widget._images[index % length], fit: BoxFit.cover, ), );Copy the code

Canceltimer () cancels the _timer if it is not null, and then nulls it.

The _timer is then initialized.

Why would you do that? Cancel while initializing?

Since we don’t know when the finger is going to leave the screen, we start the timer again after the finger clicks,

This ensures that there is no scheduled task when clicking, and that the scheduled task will be restarted after a period of time.

Because the time of the scheduled task is 3 seconds, and we only need one or two seconds to swipe to view the picture, if we swipe again within this period of time, the previous task will be cancelled and the new task will be started again, thus achieving our effect.

Take a look:

That wraps up the entire Banner at this point.

conclusion

First of all, when encapsulating a Widget, you should first understand the function of the Widget and implement it according to the requirements of the function.

In addition, flexibility should be taken into account in the implementation process. Methods that can be set by the user should be exposed, while methods that cannot be exposed should be written private.

The full code has been uploaded to GitHub: github.com/wanglu1209/…