使用Flutter實現(xiàn)一個走馬燈布局的示例代碼
走馬燈是一種常見的效果,本文講一下如何用 PageView
在 Flutter
里實現(xiàn)一個走馬燈, 效果如下,當前頁面的高度比其它頁面高,切換頁面的時候有一個高度變化的動畫。實現(xiàn)這樣的效果主要用到的是 PageView.builder
部件。
開發(fā)
創(chuàng)建首頁
首先創(chuàng)建一個 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
里面創(chuàng)建一個 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
參數(shù)小于 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;
實現(xiàn) _buildItem
接著實現(xiàn) _buildItem
方法,這個方法就是返回 PageView.builder
里每一個頁面渲染的內容,第一個參數(shù) activeIndex
是當前顯示在屏幕上頁面的 index
,第二個參數(shù) 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, ), ), ), ) ], ), ), ], ),
實現(xiàn)指示器
然后實現(xiàn)頁面的指示器,創(chuàng)建一個 PageIndicator
部件,需要傳入 pageCount
表示總頁數(shù),以及 currentIndex
表示當前顯示的頁數(shù)索引。把所有指示器放在一個 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
最后的最后優(yōu)化一下代碼,把部件封裝一下,讓它成為一個單獨的部件,創(chuàng)建一個 Carousel
部件,對外暴露 items
和 height
兩個屬性,分別配置數(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<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
不用管理部件狀態(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: "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:
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持我們。
欄 目:Android
下一篇:Android Selector 按下修改背景和文本顏色的實現(xiàn)代碼
本文標題:使用Flutter實現(xiàn)一個走馬燈布局的示例代碼
本文地址:http://mengdiqiu.com.cn/a1/Android/9053.html
您可能感興趣的文章
- 01-10Android實現(xiàn)雙擊返回鍵退出應用實現(xiàn)方法詳解
- 01-10android實現(xiàn)記住用戶名和密碼以及自動登錄
- 01-10android實現(xiàn)簡單計算器功能
- 01-10Android 友盟第三方登錄與分享的實現(xiàn)代碼
- 01-10C++自定義API函數(shù)實現(xiàn)大數(shù)相乘算法
- 01-10如何給Flutter界面切換實現(xiàn)點特效
- 01-10android實現(xiàn)指紋識別功能
- 01-10Android實現(xiàn)圓形漸變加載進度條
- 01-10RecyclerView仿應用列表實現(xiàn)網(wǎng)格布局
- 01-10Android實現(xiàn)底部導航欄的主界面


閱讀排行
本欄相關
- 01-10Android自定義View之繪制圓形頭像功能
- 01-10Android實現(xiàn)雙擊返回鍵退出應用實現(xiàn)方
- 01-10android實現(xiàn)簡單計算器功能
- 01-10android實現(xiàn)記住用戶名和密碼以及自動
- 01-10C++自定義API函數(shù)實現(xiàn)大數(shù)相乘算法
- 01-10Android 友盟第三方登錄與分享的實現(xiàn)代
- 01-10android實現(xiàn)指紋識別功能
- 01-10如何給Flutter界面切換實現(xiàn)點特效
- 01-10Android實現(xiàn)圓形漸變加載進度條
- 01-10Emoji表情在Android JNI中的兼容性問題詳
隨機閱讀
- 08-05織夢dedecms什么時候用欄目交叉功能?
- 08-05DEDE織夢data目錄下的sessions文件夾有什
- 01-10C#中split用法實例總結
- 08-05dedecms(織夢)副欄目數(shù)量限制代碼修改
- 01-11Mac OSX 打開原生自帶讀寫NTFS功能(圖文
- 01-10delphi制作wav文件的方法
- 04-02jquery與jsp,用jquery
- 01-10使用C語言求解撲克牌的順子及n個骰子
- 01-11ajax實現(xiàn)頁面的局部加載
- 01-10SublimeText編譯C開發(fā)環(huán)境設置