Flutter沉浸式Banner + 滚动修改状态栏颜色

Flutter沉浸式Banner + 滚动修改状态栏颜色

PS:最近项目有个非人类需求,顶部放轮播广告,滚动切换状态栏颜色。对于Flutter小白来说,首先想到的是度娘,然并软,搜了一圈没找到类似的,轮播图资源还蛮多的,但是滚动改状态栏却找不到。。。瞬间懵逼,还是老老实实手敲轮子吧~(怕忘记了记录下)

  • 国际惯例先上图


    banner.gif

废话不多说,直接上代码:

  • 一个依赖
 flutter_statusbarcolor: any
  • 一个轮播图的实体类CarouselSlider :
library carousel_slider;

import 'dart:async';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

class CarouselSlider extends StatefulWidget {
  CarouselSlider({
    @required this.items,
    this.height,
    this.aspectRatio: 16 / 9,
    this.viewportFraction: 0.8,
    this.initialPage: 0,
    int realPage: 10000,
    this.enableInfiniteScroll: true,
    this.reverse: false,
    this.autoPlay: false,
    Duration autoPlayInterval,
    Duration autoPlayAnimationDuration,
    this.autoPlayCurve: Curves.fastOutSlowIn,
    this.pauseAutoPlayOnTouch,
    bool enlargeCenterPage,
    Function(int index) onPageChanged,
    this.scrollDirection: Axis.horizontal,
    @Deprecated('Use "autoPlayInterval" instead') this.interval,
    @Deprecated('Use "autoPlayAnimationDuration" instead') this.autoPlayDuration,
    @Deprecated('Use "enlargeCenterPage" instead') this.distortion,
    @Deprecated('Use "onPageChanged" instead') this.updateCallback,
  })  : assert(autoPlayInterval == null || interval == null,
  'use autoPlayInterval (preferable) or interval (deprecated), but not both.'),
        assert(autoPlayAnimationDuration == null || autoPlayDuration == null,
        'use autoPlayAnimationDuration (preferable) or autoPlayDuration (deprecated), but not both.'),
        assert(enlargeCenterPage == null || distortion == null,
        'use enlargeCenterPage (preferable) or distortion (deprecated), but not both.'),
        assert(onPageChanged == null || updateCallback == null,
        'use onPageChanged (preferable) or updateCallback (deprecated), but not both.'),
        this.autoPlayInterval = (autoPlayInterval ?? interval) ?? const Duration(seconds: 4),
        this.enlargeCenterPage = (enlargeCenterPage ?? distortion) ?? false,
        this.autoPlayAnimationDuration =
            (autoPlayAnimationDuration ?? autoPlayDuration) ?? const Duration(milliseconds: 800),
        this.onPageChanged = onPageChanged ?? updateCallback,
        this.realPage = enableInfiniteScroll ? realPage + initialPage : initialPage,
        this.pageController = PageController(
          viewportFraction: viewportFraction,
          initialPage: enableInfiniteScroll ? realPage + initialPage : initialPage,
        );

  /// The widgets to be shown in the carousel.
  final List items;

  /// Set carousel height and overrides any existing [aspectRatio].
  final double height;

  /// Aspect ratio is used if no height have been declared.
  ///
  /// Defaults to 16:9 aspect ratio.
  final double aspectRatio;

  /// The fraction of the viewport that each page should occupy.
  ///
  /// Defaults to 0.8, which means each page fills 80% of the carousel.
  final num viewportFraction;

  /// The initial page to show when first creating the [CarouselSlider].
  ///
  /// Defaults to 0.
  final num initialPage;

  /// The actual index of the [PageView].
  ///
  /// This value can be ignored unless you know the carousel will be scrolled
  /// backwards more then 10000 pages.
  /// Defaults to 10000 to simulate infinite backwards scrolling.
  final num realPage;

  ///Determines if carousel should loop infinitely or be limited to item length.
  ///
  ///Defaults to true, i.e. infinite loop.
  final bool enableInfiniteScroll;

  /// Reverse the order of items if set to true.
  ///
  /// Defaults to false.
  final bool reverse;

  /// Enables auto play, sliding one page at a time.
  ///
  /// Use [autoPlayInterval] to determent the frequency of slides.
  /// Defaults to false.
  final bool autoPlay;

  /// Sets Duration to determent the frequency of slides when
  ///
  /// [autoPlay] is set to true.
  /// Defaults to 4 seconds.
  final Duration autoPlayInterval;

  /// (Deprecated, use [autoPlayInterval] instead) Changed for ambiguous intent.
  /// interval did not explain what the variable was used for.
  /// Changing it to [autoPlayInterval] describes where it's used and for what.
  ///
  /// Sets Duration to determent the frequency of slides when
  /// [autoPlay] is set to true.
  /// Defaults to 4 seconds.
  @deprecated
  final Duration interval;

  /// (Deprecated, use [autoPlayAnimationDuration] instead) Changed for misleading intent.
  /// 'autoPlayDuration' implies reference to the duration of the entire auto play instance.
  /// Changed to 'autoPlayAnimationDuration' to sympathize its referring to the length of
  /// the animation between the page transitions.
  ///
  /// The animation duration between two transitioning pages while in auto playback.
  /// Defaults to 800 ms.
  @deprecated
  final Duration autoPlayDuration;

  /// The animation duration between two transitioning pages while in auto playback.
  ///
  /// Defaults to 800 ms.
  final Duration autoPlayAnimationDuration;

  /// Determines the animation curve physics.
  ///
  /// Defaults to [Curves.fastOutSlowIn].
  final Curve autoPlayCurve;

  /// Sets a timer on touch detected that pause the auto play with
  /// the given [Duration].
  ///
  /// Touch Detection is only active if [autoPlay] is true.
  final Duration pauseAutoPlayOnTouch;

  /// (Deprecated, use [enlargeCenterPage] instead) Changed for ambiguous intent.
  /// 'distortion' provided no information on how the image was distorted.
  /// [enlargeCenterPage] is self documenting, thus making it easier to understand
  /// the api.
  ///
  /// Determines if current page should be larger then the side images,
  /// creating a feeling of depth in the carousel.
  /// Defaults to false.
  @deprecated
  final bool distortion;

  /// Determines if current page should be larger then the side images,
  /// creating a feeling of depth in the carousel.
  ///
  /// Defaults to false.
  final bool enlargeCenterPage;

  /// The axis along which the page view scrolls.
  ///
  /// Defaults to [Axis.horizontal].
  final Axis scrollDirection;

  /// (Deprecated, use [onPageChanged] instead) Changed for ambiguous intent.
  /// 'updateCallback' provided no information on when the callback was called.
  /// Refactored to following the [PageView] naming convention.
  ///
  /// Called whenever the page in the center of the viewport changes.
  @deprecated
  final Function updateCallback;

  /// Called whenever the page in the center of the viewport changes.
  final Function(int index) onPageChanged;

  /// [pageController] is created using the properties passed to the constructor
  /// and can be used to control the [PageView] it is passed to.
  final PageController pageController;

  /// Animates the controlled [CarouselSlider] to the next page.
  ///
  /// The animation lasts for the given duration and follows the given curve.
  /// The returned [Future] resolves when the animation completes.
  Future nextPage({Duration duration, Curve curve}) {
  return pageController.nextPage(duration: duration, curve: curve);
  }

  /// Animates the controlled [CarouselSlider] to the previous page.
  ///
  /// The animation lasts for the given duration and follows the given curve.
  /// The returned [Future] resolves when the animation completes.
  Future previousPage({Duration duration, Curve curve}) {
  return pageController.previousPage(duration: duration, curve: curve);
  }

  /// Changes which page is displayed in the controlled [CarouselSlider].
  ///
  /// Jumps the page position from its current value to the given value,
  /// without animation, and without checking if the new value is in range.
  void jumpToPage(int page) {
  final index = _getRealIndex(pageController.page.toInt(), realPage, items.length);
  return pageController.jumpToPage(pageController.page.toInt() + page - index);
  }

  /// Animates the controlled [CarouselSlider] from the current page to the given page.
  ///
  /// The animation lasts for the given duration and follows the given curve.
  /// The returned [Future] resolves when the animation completes.
  Future animateToPage(int page, {Duration duration, Curve curve}) {
  final index = _getRealIndex(pageController.page.toInt(), realPage, items.length);
  return pageController.animateToPage(pageController.page.toInt() + page - index,
  duration: duration, curve: curve);
  }

  @override
  _CarouselSliderState createState() => _CarouselSliderState();
}

class _CarouselSliderState extends State with TickerProviderStateMixin {
  Timer timer;

  @override
  void initState() {
    super.initState();
    timer = getTimer();
  }

  Timer getTimer() {
    return Timer.periodic(widget.autoPlayInterval, (_) {
      if (widget.autoPlay) {
        widget.pageController
            .nextPage(duration: widget.autoPlayAnimationDuration, curve: widget.autoPlayCurve);
      }
    });
  }

  void pauseOnTouch() {
    timer.cancel();
    timer = Timer(widget.pauseAutoPlayOnTouch, () {
      timer = getTimer();
    });
  }

  Widget getWrapper(Widget child) {
    if (widget.height != null) {
      final Widget wrapper = Container(height: widget.height, child: child);
      return widget.autoPlay && widget.pauseAutoPlayOnTouch != null
          ? addGestureDetection(wrapper)
          : wrapper;
    } else {
      final Widget wrapper = AspectRatio(aspectRatio: widget.aspectRatio, child: child);
      return widget.autoPlay && widget.pauseAutoPlayOnTouch != null
          ? addGestureDetection(wrapper)
          : wrapper;
    }
  }

  Widget addGestureDetection(Widget child) =>
      GestureDetector(onPanDown: (_) => pauseOnTouch(), child: child);

  @override
  void dispose() {
    super.dispose();
    timer?.cancel();
  }

  @override
  Widget build(BuildContext context) {
    return getWrapper(PageView.builder(
      scrollDirection: widget.scrollDirection,
      controller: widget.pageController,
      reverse: widget.reverse,
      itemCount: widget.enableInfiniteScroll ? null : widget.items.length,
      onPageChanged: (int index) {
        int currentPage = _getRealIndex(index, widget.realPage, widget.items.length);
        if (widget.onPageChanged != null) {
          widget.onPageChanged(currentPage);
        }
      },
      itemBuilder: (BuildContext context, int i) {
        final int index =
        _getRealIndex(i + widget.initialPage, widget.realPage, widget.items.length);

        return AnimatedBuilder(
          animation: widget.pageController,
          child: widget.items[index],
          builder: (BuildContext context, child) {
            // on the first render, the pageController.page is null,
            // this is a dirty hack
            if (widget.pageController.position.minScrollExtent == null ||
                widget.pageController.position.maxScrollExtent == null) {
              Future.delayed(Duration(microseconds: 1), () {
                setState(() {});
              });
              return Container();
            }
            double value = widget.pageController.page - i;
            value = (1 - (value.abs() * 0.3)).clamp(0.0, 1.0);

            final double height =
                widget.height ?? MediaQuery.of(context).size.width * (1 / widget.aspectRatio);
            final double distortionValue =
            widget.enlargeCenterPage ? Curves.easeOut.transform(value) : 1.0;

            return Center(child: SizedBox(height: distortionValue * height, child: child));
          },
        );
      },
    ));
  }
}

/// Converts an index of a set size to the corresponding index of a collection of another size
/// as if they were circular.
///
/// Takes a [position] from collection Foo, a [base] from where Foo's index originated
/// and the [length] of a second collection Baa, for which the correlating index is sought.
///
/// For example; We have a Carousel of 10000(simulating infinity) but only 6 images.
/// We need to repeat the images to give the illusion of a never ending stream.
/// By calling _getRealIndex with position and base we get an offset.
/// This offset modulo our length, 6, will return a number between 0 and 5, which represent the image
/// to be placed in the given position.
int _getRealIndex(int position, int base, int length) {
  final int offset = position - base;
  return _remainder(offset, length);
}

/// Returns the remainder of the modulo operation [input] % [source], and adjust it for
/// negative values.
int _remainder(int input, int source) {
  final int result = input % source;
  return result < 0 ? source + result : result;
}
  • banner 加指示器
/**
   * banner图
   */
  Widget buildBanner(BuildContext context) {
    return new Stack(
      children: [
        getFullScreenCarousel(context),
        Positioned(top: 120,
          left: 0.0,
          right: 0.0,
          child: new Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: map(_images, (index, url) {
              return Container(
                width: 5.0,
                height: 5.0,
                margin: EdgeInsets.symmetric(vertical: 10.0, horizontal: 2.0),
                decoration: BoxDecoration(
                    shape: BoxShape.circle,
                    color: _current == index
                        ? Color.fromRGBO(0, 0, 255, 0.9)
                        : Color.fromRGBO(255, 255, 255, 1)
                ),
              );
            }),
          ),)
      ],
    );
  }

  /**
   * 全屏轮播图
   */
  CarouselSlider getFullScreenCarousel(BuildContext mediaContext) {
    return CarouselSlider(
      autoPlay: true,
      viewportFraction: 1.0,
      aspectRatio: MediaQuery
          .of(mediaContext)
          .size
          .aspectRatio,
      height: 150,
      onPageChanged: (int index) {
        setState(() {
          _current = index;
        });
      },
      items: _images.map((url) {
        return new Builder(
          builder: (BuildContext context) {
            return new Container(
                width: MediaQuery
                    .of(context)
                    .size
                    .width,
                child: ClipRRect(
                    borderRadius: BorderRadius.circular(0.0),
                    child: new Image.asset(
                      url,
                      fit: BoxFit.fill,
                      height: 200,
                    )
                )
            );
          },
        );
      }).toList(),
    );
  }

List map(List list, Function handler) {
    List result = [];
    for (var i = 0; i < list.length; i++) {
      result.add(handler(i, list[i]));
    }
    return result;
  }
  • 所有代码集合
import 'package:cardservice_flutter2/utils/CarouselSlider.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter_statusbarcolor/flutter_statusbarcolor.dart';

class NewHomePage extends StatefulWidget {

  var parentContext;

  NewHomePage(this.parentContext);

  @override
  _NewHomePageState createState() => _NewHomePageState();
}

class _NewHomePageState extends State {

  List _images =
  [
    'images/one_banner.png',
    'images/two_banner.png',
    'images/three_banner.png',
    'images/four_banner.png'
  ];

  int _current = 0;

ScrollController _controller = new ScrollController();
  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    _controller.addListener(() {
      if (_controller.offset > 150) {
        setState(() {
          _apla = 1;

        });
        FlutterStatusbarcolor.setStatusBarWhiteForeground(false);
      }
      else
        {
          setState(() {
            _isChange = true;
            _apla = _controller.offset / 150.0;
            print("透明度${_apla}");

          });
          FlutterStatusbarcolor.setStatusBarWhiteForeground(true);
        }
    }
    );
  }


  @override
  Widget build(BuildContext context) {
    return new Scaffold(
        appBar: PreferredSize(
            child: Offstage(
              child: AppBar(
              ),
              offstage: true,
            ),
            preferredSize: Size(MediaQuery
                .of(context)
                .size
                .width, 0)),
        body: new Container(
          padding: EdgeInsets.all(0),
          child: new ListView(
                controller: _controller,
                children: [
                  buildBanner(context),
                  ListView.builder(
                      shrinkWrap: true,
                      physics: new NeverScrollableScrollPhysics(),
                      itemCount: 50,
                      itemBuilder: (context, index) {
                        return new Text("66666");
                      })
                ],
              )
        )
    );
  }

  /**
   * banner图
   */
  Widget buildBanner(BuildContext context) {
    return new Stack(
      children: [
        getFullScreenCarousel(context),
        Positioned(top: 120,
          left: 0.0,
          right: 0.0,
          child: new Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: map(_images, (index, url) {
              return Container(
                width: 5.0,
                height: 5.0,
                margin: EdgeInsets.symmetric(vertical: 10.0, horizontal: 2.0),
                decoration: BoxDecoration(
                    shape: BoxShape.circle,
                    color: _current == index
                        ? Color.fromRGBO(0, 0, 255, 0.9)
                        : Color.fromRGBO(255, 255, 255, 1)
                ),
              );
            }),
          ),)
      ],
    );
  }

  /**
   * 全屏轮播图
   */
  CarouselSlider getFullScreenCarousel(BuildContext mediaContext) {
    return CarouselSlider(
      autoPlay: true,
      viewportFraction: 1.0,
      aspectRatio: MediaQuery
          .of(mediaContext)
          .size
          .aspectRatio,
      height: 150,
      onPageChanged: (int index) {
        setState(() {
          _current = index;
        });
      },
      items: _images.map((url) {
        return new Builder(
          builder: (BuildContext context) {
            return new Container(
                width: MediaQuery
                    .of(context)
                    .size
                    .width,
                child: ClipRRect(
                    borderRadius: BorderRadius.circular(0.0),
                    child: new Image.asset(
                      url,
                      fit: BoxFit.fill,
                      height: 200,
                    )
                )
            );
          },
        );
      }).toList(),
    );
  }


  List map(List list, Function handler) {
    List result = [];
    for (var i = 0; i < list.length; i++) {
      result.add(handler(i, list[i]));
    }
    return result;
  }

}
记录一下,也希望本文能对读者有帮助~~

你可能感兴趣的:(Flutter沉浸式Banner + 滚动修改状态栏颜色)