使用Flutter实现一个走马灯布局的示例代码

走马灯是一种常见的效果,本文讲一下如何用 PageViewFlutter 里实现一个走马灯, 效果如下,当前页面的高度比其它页面高,切换页面的时候有一个高度变化的动画。实现这样的效果主要用到的是 PageView.builder 部件。

开发

创建首页

首先创建一个 IndexPage 部件,这个部件用来放 PageView ,因为需要使用 setState 方法更新 UI,所以它是 stateful 的。

import 'package:flutter/material.dart';

class IndexPage extends StatefulWidget {
 @override
 _IndexPageState createState() => _IndexPageState();
}

class _IndexPageState extends State<IndexPage> {
 @override
 Widget build(BuildContext context) {
 return Scaffold(
 appBar: AppBar(
 elevation: 0.0,
 backgroundColor: Colors.white,
 ),
 body: Column(
 children: <Widget>[],
 ),
 );
 }
}

然后在部件内申明一个 _pageIndex 变量用来保存当前显示的页面的 index,在 initState 生命周期里面初始化一个 PageController 用来配置 PageView 部件。

bodyColumn 里面创建一个 PageView.builder ,使用一个 SizedBox 部件指定 PageView 的高度,将 controller 设置为 _pageController ,在 onPageChanged 事件里将当前显示页面的 index 值赋值给 _pageIndex 变量。

int _pageIndex = 0;
PageController _pageController;

@override
void initState() {
 super.initState();
 _pageController = PageController(
 initialPage: 0,
 viewportFraction: 0.8,
 );
}

body: Column(
 children: <Widget>[
 SizedBox(
 height: 580.0,
 child: PageView.builder(
 itemCount: 3,
 pageSnapping: true,
 controller: _pageController,
 onPageChanged: (int index) {
  setState(() {
  _pageIndex = index;
  });
 },
 itemBuilder: (BuildContext ctx, int index) {
  return _buildItem(_pageIndex, index);
 },
 ),
 ),
 ],
),

关键点: 设置 PageControllerviewportFraction 参数小于 1,这个值是用来设置每个页面在屏幕上显示的比例,小于 1 的话,就可以在当前页面同时显示其它页面的内容了。

/// The fraction of the viewport that each page should occupy.
/// Defaults to 1.0, which means each page fills the viewport in the scrolling direction.
final double viewportFraction;

实现 _buildItem

接着实现 _buildItem 方法,这个方法就是返回 PageView.builder 里每一个页面渲染的内容,第一个参数 activeIndex 是当前显示在屏幕上页面的 index ,第二个参数 index 是每一项自己的 index

使用一个 Center 部件让内容居中显示,然后用一个 AnimatedContainer 添加页面切换时的高度变化的动画效果,切换页面的时候使用了 setState 方法改变了 _pageIndexFlutter 重新绘制每一项。关键点在于判断当前页面是否为正在显示的页面,是的话它的高度就是 500 不是的话就是 450。

_buildItem(activeIndex, index) {
 return Center(
 child: AnimatedContainer(
 curve: Curves.easeInOut,
 duration: Duration(milliseconds: 300),
 height: activeIndex == index ? 500.0 : 450.0,
 margin: EdgeInsets.symmetric(vertical: 20.0, horizontal: 10.0),
 decoration: BoxDecoration(
 color: heroes[index].color,
 borderRadius: BorderRadius.all(Radius.circular(12.0)),
 ),
 child: Stack(),
 ),
 );
}

添加内容

然后给 AnimatedContainer 添加每一项的内容

child: Stack(
 fit: StackFit.expand,
 children: <Widget>[
 ClipRRect(
 borderRadius: BorderRadius.all(
 Radius.circular(12.0),
 ),
 child: Image.network(
 heroes[index].image,
 fit: BoxFit.cover,
 ),
 ),
 Align(
 alignment: Alignment.bottomCenter,
 child: Row(
 children: <Widget>[
  Expanded(
  child: Container(
  padding: EdgeInsets.all(12.0),
  decoration: BoxDecoration(
  color: Colors.black26,
  borderRadius: BorderRadius.only(
   bottomRight: Radius.circular(12.0),
   bottomLeft: Radius.circular(12.0),
  ),
  ),
  child: Text(
  heroes[index].title,
  textAlign: TextAlign.center,
  style: TextStyle(
   fontSize: 20.0,
   fontWeight: FontWeight.bold,
   color: Colors.white,
  ),
  ),
  ),
  )
 ],
 ),
 ),
 ],
),

实现指示器

然后实现页面的指示器,创建一个 PageIndicator 部件,需要传入 pageCount 表示总页数,以及 currentIndex 表示当前显示的页数索引。把所有指示器放在一个 Row 部件里,判断当前指示器的 index 是否为正在显示页面的 index ,是的话显示较深的颜色。

class PageIndicator extends StatelessWidget {
 final int pageCount;
 final int currentIndex;

 const PageIndicator(this.currentIndex, this.pageCount);

 Widget _indicator(bool isActive) {
 return Container(
 width: 6.0,
 height: 6.0,
 margin: EdgeInsets.symmetric(horizontal: 3.0),
 decoration: BoxDecoration(
 color: isActive ? Color(0xff666a84) : Color(0xffb9bcca),
 shape: BoxShape.circle,
 boxShadow: [
  BoxShadow(
  color: Colors.black12,
  offset: Offset(0.0, 3.0),
  blurRadius: 3.0,
  ),
 ],
 ),
 );
 }

 List<Widget> _buildIndicators() {
 List<Widget> indicators = [];
 for (int i = 0; i < pageCount; i++) {
 indicators.add(i == currentIndex ? _indicator(true) : _indicator(false));
 }
 return indicators;
 }

 @override
 Widget build(BuildContext context) {
 return Row(
 mainAxisAlignment: MainAxisAlignment.center,
 children: _buildIndicators(),
 );
 }
}

添加 PageIndicatorSizedBox 下面

封装 Carousel

最后的最后优化一下代码,把部件封装一下,让它成为一个单独的部件,创建一个 Carousel 部件,对外暴露 itemsheight 两个属性,分别配置数据和高度。

class Carousel extends StatefulWidget {
 final List items;
 final double height;

 const Carousel({
 @required this.items,
 @required this.height,
 });

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

class _CarouselState extends State<Carousel> {
 int _pageIndex = 0;
 PageController _pageController;

 Widget _buildItem(activeIndex, index) {
 final items = widget.items;

 return Center(
 child: AnimatedContainer(
 curve: Curves.easeInOut,
 duration: Duration(milliseconds: 300),
 height: activeIndex == index ? 500.0 : 450.0,
 margin: EdgeInsets.symmetric(vertical: 20.0, horizontal: 10.0),
 decoration: BoxDecoration(
  color: items[index].color,
  borderRadius: BorderRadius.all(Radius.circular(12.0)),
 ),
 child: Stack(
  fit: StackFit.expand,
  children: <Widget>[
  ClipRRect(
  borderRadius: BorderRadius.all(
  Radius.circular(12.0),
  ),
  child: Image.network(
  items[index].image,
  fit: BoxFit.cover,
  ),
  ),
  Align(
  alignment: Alignment.bottomCenter,
  child: Row(
  children: <Widget>[
   Expanded(
   child: Container(
   padding: EdgeInsets.all(12.0),
   decoration: BoxDecoration(
   color: Colors.black26,
   borderRadius: BorderRadius.only(
    bottomRight: Radius.circular(12.0),
    bottomLeft: Radius.circular(12.0),
   ),
   ),
   child: Text(
   items[index].title,
   textAlign: TextAlign.center,
   style: TextStyle(
    fontSize: 20.0,
    fontWeight: FontWeight.bold,
    color: Colors.white,
   ),
   ),
   ),
   )
  ],
  ),
  ),
  ],
 ),
 ),
 );
 }

 @override
 void initState() {
 super.initState();
 _pageController = PageController(
 initialPage: 0,
 viewportFraction: 0.8,
 );
 }

 @override
 Widget build(BuildContext context) {
 return Column(
 children: <Widget>[
 Container(
  height: widget.height,
  child: PageView.builder(
  pageSnapping: true,
  itemCount: heroes.length,
  controller: _pageController,
  onPageChanged: (int index) {
  setState(() {
  _pageIndex = index;
  });
  },
  itemBuilder: (BuildContext ctx, int index) {
  return _buildItem(_pageIndex, index);
  },
  ),
 ),
 PageIndicator(_pageIndex, widget.items.length),
 ],
 );
 }
}

之后在 IndexPage 部件里就只用实例化一个 Carousel 了,同时由于 IndexPage 不用管理部件状态了,可以将它变成 StatelessWidget

完整代码

import 'package:flutter/material.dart';

class Hero {
 final Color color;
 final String image;
 final String title;

 Hero({
 @required this.color,
 @required this.image,
 @required this.title,
 });
}

List heroes = [
 Hero(
 color: Color(0xFF86F3FB),
 image: "https://game.gtimg.cn/images/lol/act/img/skin/big22009.jpg",
 title: '寒冰射手-艾希',
 ),
 Hero(
 color: Color(0xFF7D6588),
 image: "https://game.gtimg.cn/images/lol/act/img/skin/big39006.jpg",
 title: '刀锋舞者-艾瑞莉娅',
 ),
 Hero(
 color: Color(0xFF4C314D),
 image: "https://game.gtimg.cn/images/lol/act/img/skin/big103015.jpg",
 title: '九尾妖狐-阿狸',
 ),
];

class Carousel extends StatefulWidget {
 final List items;
 final double height;

 const Carousel({
 @required this.items,
 @required this.height,
 });

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

class _CarouselState extends State<Carousel> {
 int _pageIndex = 0;
 PageController _pageController;

 Widget _buildItem(activeIndex, index) {
 final items = widget.items;

 return Center(
 child: AnimatedContainer(
 curve: Curves.easeInOut,
 duration: Duration(milliseconds: 300),
 height: activeIndex == index ? 500.0 : 450.0,
 margin: EdgeInsets.symmetric(vertical: 20.0, horizontal: 10.0),
 decoration: BoxDecoration(
  color: items[index].color,
  borderRadius: BorderRadius.all(Radius.circular(12.0)),
 ),
 child: Stack(
  fit: StackFit.expand,
  children: <Widget>[
  ClipRRect(
  borderRadius: BorderRadius.all(
  Radius.circular(12.0),
  ),
  child: Image.network(
  items[index].image,
  fit: BoxFit.cover,
  ),
  ),
  Align(
  alignment: Alignment.bottomCenter,
  child: Row(
  children: <Widget>[
   Expanded(
   child: Container(
   padding: EdgeInsets.all(12.0),
   decoration: BoxDecoration(
   color: Colors.black26,
   borderRadius: BorderRadius.only(
    bottomRight: Radius.circular(12.0),
    bottomLeft: Radius.circular(12.0),
   ),
   ),
   child: Text(
   items[index].title,
   textAlign: TextAlign.center,
   style: TextStyle(
    fontSize: 20.0,
    fontWeight: FontWeight.bold,
    color: Colors.white,
   ),
   ),
   ),
   )
  ],
  ),
  ),
  ],
 ),
 ),
 );
 }

 @override
 void initState() {
 super.initState();
 _pageController = PageController(
 initialPage: 0,
 viewportFraction: 0.8,
 );
 }

 @override
 Widget build(BuildContext context) {
 return Column(
 children: <Widget>[
 Container(
  height: widget.height,
  child: PageView.builder(
  pageSnapping: true,
  itemCount: heroes.length,
  controller: _pageController,
  onPageChanged: (int index) {
  setState(() {
  _pageIndex = index;
  });
  },
  itemBuilder: (BuildContext ctx, int index) {
  return _buildItem(_pageIndex, index);
  },
  ),
 ),
 PageIndicator(_pageIndex, widget.items.length),
 ],
 );
 }
}

class PageIndicator extends StatelessWidget {
 final int currentIndex;
 final int pageCount;

 const PageIndicator(this.currentIndex, this.pageCount);

 Widget _indicator(bool isActive) {
 return Container(
 width: 6.0,
 height: 6.0,
 margin: EdgeInsets.symmetric(horizontal: 3.0),
 decoration: BoxDecoration(
 color: isActive ? Color(0xff666a84) : Color(0xffb9bcca),
 shape: BoxShape.circle,
 boxShadow: [
  BoxShadow(
  color: Colors.black12,
  offset: Offset(0.0, 3.0),
  blurRadius: 3.0,
  ),
 ],
 ),
 );
 }

 List<Widget> _buildIndicators() {
 List<Widget> indicators = [];
 for (int i = 0; i < pageCount; i++) {
 indicators.add(i == currentIndex ? _indicator(true) : _indicator(false));
 }
 return indicators;
 }

 @override
 Widget build(BuildContext context) {
 return Row(
 mainAxisAlignment: MainAxisAlignment.center,
 children: _buildIndicators(),
 );
 }
}

class IndexPage extends StatelessWidget {
 @override
 Widget build(BuildContext context) {
 return Scaffold(
 appBar: AppBar(
 elevation: 0.0,
 backgroundColor: Colors.white,
 ),
 body: Carousel(
 height: 540,
 items: heroes,
 ),
 backgroundColor: Colors.white,
 );
 }
}

至此,整个布局就完成了! :sunglasses:

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • 详解Flutter TabLayout 布局用法

    Flutter是谷歌的移动UI框架,可以快速在iOS和Android上构建高质量的原生用户界面. Flutter可以与现有的代码一起工作.在全世界,Flutter正在被越来越多的开发者和组织使用,并且Flutter是完全免费.开源的. 本文重点给大家介绍Flutter TabLayout 布局用法,具体内容如下所示: 先上图 顶部TabBar 使用 flutter create xxxx 创建一个项目 打开项目文件夹,在 lib 目录里创建三个 dart 文件,内容分别如下 First.dart

  • Flutter布局模型之层叠定位

    Stack即层叠布局控件,能够将子控件层叠排列. Stack控件的每一个子控件都是定位或不定位,定位的子控件是被Positioned控件包裹的.Stack控件本身包含所有不定位的子控件,其根据alignment定位(默认为左上角).然后根据定位的子控件的top.right.bottom和left属性将它们放置在Stack控件上. import 'package:flutter/material.dart'; class LayoutDemo extends StatelessWidget { @

  • Flutter学习之构建、布局及绘制三部曲

    前言 学习Fullter也有些时间了,写过不少demo,对一些常用的widget使用也比较熟练,但是总觉得对Flutter的框架没有一个大致的了解,碰到有些细节的地方又没有文档可以查询,例如在写UI时总不知道为什么container添加了child就变小了:widget中key的作用,虽然官方有解释但是凭空来讲的话有点难理解.所以觉得深入一点的了解Flutter框架还是很有必要的. 构建 初次构建 flutter的入口main方法直接调用了runApp(Widget app)方法,app参数就是

  • Flutter常用的布局和事件示例详解

    Flutter 项目中常用的布局详情,及封装和使用,快速开发项目. 以及手势事件和滚动事件的使用 Scaffold 导航栏的实现,有些路由页可能会有抽屉菜单(Drawer)以及底部Tab导航菜单等 const Scaffold({ Key key, this.appBar,//标题栏 this.body,//内容 this.floatingActionButton,//悬浮按钮 this.persistentFooterButtons,//底部持久化现实按钮 this.drawer,//侧滑菜单

  • 使用Flutter实现一个走马灯布局的示例代码

    走马灯是一种常见的效果,本文讲一下如何用 PageView 在 Flutter 里实现一个走马灯, 效果如下,当前页面的高度比其它页面高,切换页面的时候有一个高度变化的动画.实现这样的效果主要用到的是 PageView.builder 部件. 开发 创建首页 首先创建一个 IndexPage 部件,这个部件用来放 PageView ,因为需要使用 setState 方法更新 UI,所以它是 stateful 的. import 'package:flutter/material.dart'; c

  • Flutter实现自定义筛选框的示例代码

    目录 一.首先自定义筛选框的按钮视图,布局很简单,一个listView就可以搞定. 二.定义筛选数据展示列表视图. 一.首先自定义筛选框的按钮视图,布局很简单,一个listView就可以搞定. 1.在数据model中添加了一个selectedModel属性,用来记录当前已选择的筛选项(目前仅支持单选). 2.当按钮数量小于3个时,按钮最大宽度为屏幕宽度的1/3:小于屏幕宽度时,则为屏幕宽度/按钮数量. 具体代码如下: var text = model.selectedFilterModel !=

  • 用ES6的class模仿Vue写一个双向绑定的示例代码

    本文介绍了用ES6的class模仿Vue写一个双向绑定的示例代码,分享给大家,具体如下: 最终效果如下: 构造器(constructor) 构造一个TinyVue对象,包含基本的el,data,methods class TinyVue{ constructor({el, data, methods}){ this.$data = data this.$el = document.querySelector(el) this.$methods = methods // 初始化 this._com

  • 使用Vue3实现一个Upload组件的示例代码

    通用上传组件开发 开发上传组件前我们需要了解: FormData上传文件所需API dragOver文件拖拽到区域时触发 dragLeave文件离开拖动区域 drop文件移动到有效目标时 首先实现一个最基本的上传流程: 基本上传流程,点击按钮选择,完成上传 代码如下: <template> <div class="app-container"> <!--使用change事件--> <input type="file" @ch

  • 基于Python编写一个点名器的示例代码

    目录 前言 主界面 添加姓名 查看花名册 使用指南 名字转动功能 完整代码 前言 想起小学的时候老师想点名找小伙伴回答问题的时候,老师竟斥巨资买了个点名器.今日无聊便敲了敲小时候老师斥巨资买的点名器. 本人姓白,就取名小白点名器啦,嘿嘿 代码包含:添加姓名.查看花名册.使用指南.随机抽取名字的功能(完整源码在最后) 主界面 定义主界面.使用“w+”模式创建test.txt文件(我添加了个背景图片,若不需要可省略) #打开时预加载储存在test.txt文件中的花名册 namelist = [] w

  • Python快速实现一个线程池的示例代码

    目录 楔子 Future 对象 提交函数自动创建 Future 对象 future.set_result 到底干了什么事情 提交多个函数 使用 map 来提交多个函数 按照顺序等待执行 取消一个函数的执行 函数执行时出现异常 等待所有函数执行完毕 小结 楔子 当有多个 IO 密集型的任务要被处理时,我们自然而然会想到多线程.但如果任务非常多,我们不可能每一个任务都启动一个线程去处理,这个时候最好的办法就是实现一个线程池,至于池子里面的线程数量可以根据业务场景进行设置. 比如我们实现一个有 10

  • Flutter实现资源下载断点续传的示例代码

    目录 协议梳理 实现步骤 写在最后 协议梳理 一般情况下,下载的功能模块,至少需要提供如下基础功能:资源下载.取消当前下载.资源是否下载成功.资源文件的大小.清除缓存文件.而断点续传主要体现在取消当前下载后,再次下载时能在之前已下载的基础上继续下载.这个能极大程度的减少我们服务器的带宽损耗,而且还能为用户减少流量,避免重复下载,提高用户体验. 前置条件:资源必须支持断点续传.如何确定可否支持?看看你的服务器是否支持Range请求即可. 实现步骤 1.定好协议.我们用的http库是dio:通过校验

  • Java实现手写一个线程池的示例代码

    目录 概述 线程池框架设计 代码实现 阻塞队列的实现 线程池消费端实现 获取任务超时设计 拒绝策略设计 概述 线程池技术想必大家都不陌生把,相信在平时的工作中没有少用,而且这也是面试频率非常高的一个知识点,那么大家知道它的实现原理和细节吗?如果直接去看jdk源码的话,可能有一定的难度,那么我们可以先通过手写一个简单的线程池框架,去掌握线程池的基本原理后,再去看jdk的线程池源码就会相对容易,而且不容易忘记. 线程池框架设计 我们都知道,线程资源的创建和销毁并不是没有代价的,甚至开销是非常高的.同

  • QT实现制作一个ListView列表的示例代码

    目录 1.概述 2.代码示例 1.自定义QListWidget 2.自定义QListWidgetItem 3.使用 3.图片演示 1.概述 案例:使用Qt制作一个ListView.点击ListView的Item可以用于测试OpenCV的各种效果 自定义一个:MainListView继承QListWidget .MainListViewItem继承QListWidgetItem 2.代码示例 1.自定义QListWidget mainlistview.h class MainListView :

  • DUCC配置平台实现一个动态化线程池示例代码

    目录 1.背景 2.代码实现 3.动态线程池应用 4.小结 作者:京东零售 张宾 1.背景 在后台开发中,会经常用到线程池技术,对于线程池核心参数的配置很大程度上依靠经验.然而,由于系统运行过程中存在的不确定性,我们很难一劳永逸地规划一个合理的线程池参数.在对线程池配置参数进行调整时,一般需要对服务进行重启,这样修改的成本就会偏高.一种解决办法就是,将线程池的配置放到配置平台侧,系统运行期间开发人员根据系统运行情况对核心参数进行动态配置. 本文以公司DUCC配置平台作为服务配置中心,以修改线程池

随机推荐