這篇文章給大家分享的是有關(guān)如何使用Flutter實(shí)現(xiàn)一個(gè)走馬燈布局的內(nèi)容。小編覺(jué)得挺實(shí)用的,因此分享給大家做個(gè)參考,一起跟隨小編過(guò)來(lái)看看吧。
為廊坊等地區(qū)用戶提供了全套網(wǎng)頁(yè)設(shè)計(jì)制作服務(wù),及廊坊網(wǎng)站建設(shè)行業(yè)解決方案。主營(yíng)業(yè)務(wù)為成都網(wǎng)站建設(shè)、成都做網(wǎng)站 、廊坊網(wǎng)站設(shè)計(jì),以傳統(tǒng)方式定制建設(shè)網(wǎng)站,并提供域名空間備案等一條龍服務(wù),秉承以專(zhuān)業(yè)、用心的態(tài)度為用戶提供真誠(chéng)的服務(wù)。我們深信只要達(dá)到每一位用戶的要求,就會(huì)得到認(rèn)可,從而選擇與我們長(zhǎng)期合作。這樣,我們也可以走得更遠(yuǎn)!
走馬燈是一種常見(jiàn)的效果,本文講一下如何用 PageView
在 Flutter
里實(shí)現(xiàn)一個(gè)走馬燈, 效果如下,當(dāng)前頁(yè)面的高度比其它頁(yè)面高,切換頁(yè)面的時(shí)候有一個(gè)高度變化的動(dòng)畫(huà)。實(shí)現(xiàn)這樣的效果主要用到的是 PageView.builder
部件。
開(kāi)發(fā)
創(chuàng)建首頁(yè)
首先創(chuàng)建一個(gè) IndexPage
部件,這個(gè)部件用來(lái)放 PageView
,因?yàn)樾枰褂?setState
方法更新 UI,所以它是 stateful 的。
import 'package:flutter/material.dart';class IndexPage extends StatefulWidget { @override _IndexPageState createState() => _IndexPageState();}class _IndexPageState extends State { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( elevation: 0.0, backgroundColor: Colors.white, ), body: Column( children: [], ), ); }}
然后在部件內(nèi)申明一個(gè) _pageIndex
變量用來(lái)保存當(dāng)前顯示的頁(yè)面的 index,在 initState
生命周期里面初始化一個(gè) PageController
用來(lái)配置 PageView
部件。
在 body
的 Column
里面創(chuàng)建一個(gè) PageView.builder
,使用一個(gè) SizedBox
部件指定 PageView
的高度,將 controller
設(shè)置為 _pageController
,在 onPageChanged
事件里將當(dāng)前顯示頁(yè)面的 index
值賦值給 _pageIndex
變量。
int _pageIndex = 0;PageController _pageController;@overridevoid initState() { super.initState(); _pageController = PageController( initialPage: 0, viewportFraction: 0.8, );}body: Column( children: [ 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); }, ), ), ],),
關(guān)鍵點(diǎn): 設(shè)置 PageController
的 viewportFraction
參數(shù)小于 1,這個(gè)值是用來(lái)設(shè)置每個(gè)頁(yè)面在屏幕上顯示的比例,小于 1 的話,就可以在當(dāng)前頁(yè)面同時(shí)顯示其它頁(yè)面的內(nèi)容了。
/// 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;
實(shí)現(xiàn) _buildItem
接著實(shí)現(xiàn) _buildItem
方法,這個(gè)方法就是返回 PageView.builder
里每一個(gè)頁(yè)面渲染的內(nèi)容,第一個(gè)參數(shù) activeIndex
是當(dāng)前顯示在屏幕上頁(yè)面的 index
,第二個(gè)參數(shù) index
是每一項(xiàng)自己的 index
。
使用一個(gè) Center
部件讓內(nèi)容居中顯示,然后用一個(gè) AnimatedContainer
添加頁(yè)面切換時(shí)的高度變化的動(dòng)畫(huà)效果,切換頁(yè)面的時(shí)候使用了 setState
方法改變了 _pageIndex
, Flutter
重新繪制每一項(xiàng)。關(guān)鍵點(diǎn)在于判斷當(dāng)前頁(yè)面是否為正在顯示的頁(yè)面,是的話它的高度就是 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(), ), );}
添加內(nèi)容
然后給 AnimatedContainer
添加每一項(xiàng)的內(nèi)容
child: Stack( fit: StackFit.expand, children: [ ClipRRect( borderRadius: BorderRadius.all( Radius.circular(12.0), ), child: Image.network( heroes[index].image, fit: BoxFit.cover, ), ), Align( alignment: Alignment.bottomCenter, child: Row( children: [ 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, ), ), ), ) ], ), ), ],),
實(shí)現(xiàn)指示器
然后實(shí)現(xiàn)頁(yè)面的指示器,創(chuàng)建一個(gè) PageIndicator
部件,需要傳入 pageCount
表示總頁(yè)數(shù),以及 currentIndex
表示當(dāng)前顯示的頁(yè)數(shù)索引。把所有指示器放在一個(gè) Row
部件里,判斷當(dāng)前指示器的 index
是否為正在顯示頁(yè)面的 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 _buildIndicators() { List 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
最后的最后優(yōu)化一下代碼,把部件封裝一下,讓它成為一個(gè)單獨(dú)的部件,創(chuàng)建一個(gè) Carousel
部件,對(duì)外暴露 items
和 height
兩個(gè)屬性,分別配置數(shù)據(jù)和高度。
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 { 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: [ ClipRRect( borderRadius: BorderRadius.all( Radius.circular(12.0), ), child: Image.network( items[index].image, fit: BoxFit.cover, ), ), Align( alignment: Alignment.bottomCenter, child: Row( children: [ 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: [ 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
部件里就只用實(shí)例化一個(gè) Carousel
了,同時(shí)由于 IndexPage
不用管理部件狀態(tài)了,可以將它變成 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: "/upload/otherpic66/44767.jpg", title: '寒冰射手-艾希', ), Hero( color: Color(0xFF7D6588), image: "/upload/otherpic66/44768.jpg", title: '刀鋒舞者-艾瑞莉婭', ), Hero( color: Color(0xFF4C314D), image: "/upload/otherpic66/44769.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 { 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: [ ClipRRect( borderRadius: BorderRadius.all( Radius.circular(12.0), ), child: Image.network( items[index].image, fit: BoxFit.cover, ), ), Align( alignment: Alignment.bottomCenter, child: Row( children: [ 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: [ 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 _buildIndicators() { List 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, ); }}
感謝各位的閱讀!關(guān)于“如何使用Flutter實(shí)現(xiàn)一個(gè)走馬燈布局”這篇文章就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,讓大家可以學(xué)到更多知識(shí),如果覺(jué)得文章不錯(cuò),可以把它分享出去讓更多的人看到吧!
本文題目:如何使用Flutter實(shí)現(xiàn)一個(gè)走馬燈布局
本文來(lái)源:
http://weahome.cn/article/jphsgs.html