使用Flutter实现一个走马灯布局的示例代码
走马灯是一种常见的效果,本文讲一下如何用 pageview
在 flutter
里实现一个走马灯, 效果如下,当前页面的高度比其它页面高,切换页面的时候有一个高度变化的动画。实现这样的效果主要用到的是 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
部件。
在 body
的 column
里面创建一个 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); }, ), ), ], ),
关键点: 设置 pagecontroller
的 viewportfraction
参数小于 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
方法改变了 _pageindex
, flutter
重新绘制每一项。关键点在于判断当前页面是否为正在显示的页面,是的话它的高度就是 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(), ); } }
添加 pageindicator
到 sizedbox
下面
封装 carousel
最后的最后优化一下代码,把部件封装一下,让它成为一个单独的部件,创建一个 carousel
部件,对外暴露 items
和 height
两个属性,分别配置数据和高度。
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:
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
上一篇: 秦国出现的将相失和,最后导致白起被赐死