使用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,decoration: BoxDecoration( color: items[index].color,child: Stack( fit: StackFit.expand,children: <Widget>[ ClipRRect( borderRadius: BorderRadius.all( Radius.circular(12.0),child: Image.network( items[index].image,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),child: Text( items[index].title,style: TextStyle( fontSize: 20.0,) ],); } @override void initState() { super.initState(); _pageController = PageController( initialPage: 0,); } @override Widget build(BuildContext context) { return Column( children: <Widget>[ Container( height: widget.height,child: PageView.builder( pageSnapping: true,itemCount: heroes.length,onPageChanged: (int index) { setState(() { _pageIndex = index; }); },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,); } } class PageIndicator extends StatelessWidget { final int currentIndex; final int pageCount; const PageIndicator(this.currentIndex,); } } class IndexPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( elevation: 0.0,body: Carousel( height: 540,items: heroes,); } }
至此,整個佈局就完成了! :sunglasses:
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。