专栏首页大前端Flutter跨平台移动端开发丨自定义 Banner Widget

Flutter跨平台移动端开发丨自定义 Banner Widget

移动端开发过程中 Banner 组件非常常见,项目中用的到就封装一个,主要用到 Timer + PageView,采用定时轮播的方法实现


自定义 Banner Widget

可设置 banner 高度、图片展示时间、图片切换速度,如需其它支持可自行添加、更改

import 'package:flutter/material.dart';
import 'dart:async';
import 'BannerBean.dart';
import 'package:meta/meta.dart';

/**
 * @des banner 部件
 * @author liyongli 20190702
 * */
class BannerWidget extends StatefulWidget{

  // banner 数据实体集合
  List<BannerBean> bannerData;

  // banner 默认高度
  double bannerHeight;

  // banner 默认展示时间(毫秒)
  int bannerDuration;

  // banner 切换速度(毫秒)
  int bannerSwitch;

  // 图片加载器
  Build bannerBuild;

  // 点击事件回调接口
  OnBannerPress bannerPress;

  BannerWidget(
      {Key key,
        @required this.bannerData,
        this.bannerHeight,
        this.bannerDuration,
        this.bannerSwitch,
        this.bannerPress,
        this.bannerBuild})
      : super(key: key);

  @override
  State<StatefulWidget> createState() => BannerWidgetState();

}

/**
 * @des banner 部件 State
 * @author liyongli 20190702
 * */
const CountMax = 0x7fffffff;
typedef void OnBannerPress(int position, BannerBean entity);
typedef Widget Build(int position, BannerBean entity);
class BannerWidgetState extends State<BannerWidget>{

  // 定时器
  Timer bannerTimer;

  // 当前 banner 页下标
  int bannerIndex = 0;

  // 控制器
  PageController bannerController;

  @override
  void initState() {
    super.initState();
    double current = (CountMax / 2) - ((CountMax / 2) % widget.bannerData.length);
    bannerController = PageController(initialPage: current.toInt());
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      height: widget.bannerHeight,
      color: Colors.white,
      child: Stack(
        children: <Widget>[
          viewPages(),
          viewTips()
        ],
      ),
    );
  }

  /**
   * banner 图片组件
   * */
  Widget viewPages(){
    return PageView.builder(
      itemCount: CountMax,
      controller: bannerController,
      onPageChanged: onPageChanged,
      itemBuilder: (context, index){
        return InkWell(

          onTap: (){
            if(null != widget.bannerPress){
              widget.bannerPress(bannerIndex, widget.bannerData[bannerIndex]);
            }
          },

          child: widget.bannerBuild == null ? FadeInImage.memoryNetwork( image: widget.bannerData[index % widget.bannerData.length].bannerUrl, fit: BoxFit.fitWidth) : widget.bannerBuild(index, widget.bannerData[index % widget.bannerData.length])
        );
      },
    );
  }

  /**
   * 更新坐标与图片
   * */
  void onPageChanged(index){
    bannerIndex = index % widget.bannerData.length;
    setState(() {});
  }

  /**
   * banner 小原点组件
   * */
  Widget viewTips(){

    if(widget.bannerData.length <= 1){
      return Align();
    }

    return Align(
      alignment: Alignment.bottomCenter,
      child: Container(
        height: 32.0, 
        padding: EdgeInsets.all(5.0),
        color: Colors.black,
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: <Widget>[
            Text(widget.bannerData[bannerIndex].bannerTitle, style: TextStyle(color: Colors.white)),
            Row(children: bannerCircle(),)
          ],
        ),
      ),
    );
  }

  /**
   * 绘制小圆点并组成集合返回
   * */
  List<Widget> bannerCircle(){
    List<Widget> circleList = [];
    for(var i = 0 ; i < widget.bannerData.length ; i++){
      circleList.add(
        Container(
          margin:  EdgeInsets.all(3.0),
          width: 5.0,
          height: 5.0,
          decoration: BoxDecoration(
            shape: BoxShape.circle,
            color: bannerIndex == i ? Colors.deepOrange : Colors.white,
          ),
        )
      );
    }
    return circleList;
  }

  /**
   * 启动计时器
   * */
  void start(){
    if(null != bannerTimer && bannerTimer.isActive){
      stop();
    }

    if(null == widget.bannerData || widget.bannerData.length <= 1){
      return;
    }

    bannerTimer = Timer.periodic(Duration(milliseconds: widget.bannerDuration), (bannerTimer){
      bannerController.animateToPage(bannerController.page.toInt() + 1, duration: Duration(milliseconds: widget.bannerSwitch), curve: Curves.linear);
    });
  }

  /**
   * 停止计时器
   * */
  void stop(){
    bannerTimer?.cancel();
    bannerTimer = null;
  }

  /**
   * 释放资源
   * */
  @override
  void dispose() {
    stop();
    bannerController?.dispose();
    super.dispose();
  }
}

/**
 * @des banner 组件抽象类
 * @author liyongli 20190702
 * */
abstract class BannerBeanUtils{

  // 获取 banner 地址
  get bannerUrl;

  // 获取 banner 介绍
  get bannerTitle;

}

自定义 Banner Widget 数据实体

可设置图片加载地址、图片配文、图片跳转参数等

import 'package:delongzhixuan/utils/banner/BannerWidget.dart';

/**
 * @des banner 组件实体类
 * @author liyongli 20190702
 * */
class BannerBean extends Object with BannerBeanUtils{

  String imageUrl;
  String titleStr;
  int intentType;

  BannerBean({this.imageUrl, this.titleStr, this.intentType});

  @override
  get bannerTitle => titleStr;

  @override
  get bannerUrl => imageUrl;

}

自定义 Banner Widget 应用

本实例展示加载本地图片

import 'dart:async';
import 'dart:core';
import 'package:delongzhixuan/utils/banner/BannerBean.dart';
import 'package:delongzhixuan/utils/banner/BannerWidget.dart';
import 'package:flutter/material.dart';

/**
 * @des 首页
 * @author liyongli
 * */
class MainHome extends StatefulWidget {

  @override
  State<StatefulWidget> createState() => new _MainHomeState();

}

/**
 * @des 首页 State
 * @author liyongli
 * */
class _MainHomeState extends State<MainHome> {

  @override
  Widget build(BuildContext context) {
    return _initWidget(context);
  }

  // 跨域访问
  GlobalKey<BannerWidgetState> globalKey = new GlobalKey<BannerWidgetState>();

  // banner 数据集合
  List<BannerBean> bannerData;

  /**
   * 初始化 widget
   * */
  Widget _initWidget(BuildContext context){
    return new Scaffold(
      body: SingleChildScrollView(
          child: Column(
            mainAxisSize: MainAxisSize.max,
            children: <Widget>[

              // banner 部分
              Container(
                width: double.maxFinite,
                height: 260.0,
                color: Colors.white,
                child: BannerWidget( // 自定义 banner widget
                  key: globalKey, // 跨域访问目标
                  bannerData: _initBannerData(), // 数据集合
                  bannerDuration: 5000, // 展示时间
                  bannerSwitch: 500, // 切换耗时
                  bannerPress: _bannerPress, // 点击事件监听
                  bannerBuild: (position, BannerBean){ // 加载器
                    return Image.asset(BannerBean.bannerUrl, fit: BoxFit.fitWidth);
                  }
                ),
              ),
            ],
          ),
      )
    );
  }

  /**
   * 初始化 banner 数据
   * */
  List<BannerBean> _initBannerData(){

    List<BannerBean> bannerList = [
      new BannerBean(imageUrl:"images/main_banner01.png", titleStr: "main_banner01", intentType: 0),
      new BannerBean(imageUrl:"images/main_banner01.png", titleStr: "main_banner02", intentType: 0),
      new BannerBean(imageUrl:"images/main_banner01.png", titleStr: "main_banner03", intentType: 0),
    ];

    // 2 秒后启动轮播
    Timer timer;
    timer = new Timer(new Duration(seconds: 2), () {
      globalKey.currentState.start();
      timer.cancel();
      timer = null;
    });

    return bannerList;
  }
  
  /**
   * banner 点击事件监听
   * */
  void _bannerPress(int position, BannerBean entity){
    print(position);
    print(entity.titleStr + entity.imageUrl);
  }

}

运行结果

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 推荐 GitHub 上值得前端学习的开源实战项目

    获取真实链接请点击:https://github.com/biaochenxuying/blog/issues/32

    夜尽天明
  • 理解算法的时间复杂度[每日前端夜话0x82]

    在计算机科学中,算法分析是非常关键的部分。找到解决问题的最有效算法非常重要。可能会有许多算法能够解决问题,但这里的挑战是选择最有效的算法。现在关键是假如我们有一...

    疯狂的技术宅
  • Thymeleaf 引入公共代码页面

    前面写过,写前端页面的时候,尤其是写后台管理系统,每个页面都会遇到一些重复的代码,比如左侧导航栏,头部的信息,前面也做了一些整合,把代码放在了公共的页面,每个页...

    祈澈菇凉
  • 【自古套路得人心】最全UI图表设计技巧和套路,分分钟打造最优设计

    现今,软件应用大都选用图表设计来可视化各类软件数据,成功简化界面设计的同时,也大幅提升其用户体验。试想,如若市场上的各种监控、金融、保健以及旅游类软件应用,不再...

    奔跑的小鹿
  • 老焦专栏 | 为什么我们要成为全栈工程师?

    最近我身边的几个技术专家都在学习前端技术(VUE)的开发,为什么他们要学习前端技术呢?因为随着软件技术的发展,全栈工程师已然成为未来发展趋势,对大部分技术人而言...

    yuanyi928
  • How to run the CRM Fiori 1.0 applications in WebIDE

    I ran into some problem running the CRM My Opportunities application in WebIDE e...

    Jerry Wang
  • react 移动端项目配置 postcss-pxtorem

    yangdongnan
  • 部署明星关系图谱那些事儿(GitHub Pages)

    时光飞逝,距离发布上一篇文章 InteractiveGraph 实现酷炫关系图谱之前瞻 已经过去了近两个月,嘴上说着会马上把实战“娱乐圈明星关系图谱”的代码开源...

    古柳_DesertsX
  • 如果有人问你Python爬虫抓取技术的门道,请叫他来看这篇文章

    web是一个开放的平台,这也奠定了web从90年代初诞生直至今日将近30年来蓬勃的发展。然而,正所谓成也萧何败也萧何,开放的特性、搜索引擎以及简单易学的html...

    一墨编程学习
  • 简述Java类加载机制

    Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行验证、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是Java虚拟机的类...

    搬砖俱乐部

扫码关注云+社区

领取腾讯云代金券