使用Flutter實現(xiàn)一個走馬燈布局的示例代碼
走馬燈是一種常見的效果,本文講一下如何用 PageView 在 Flutter 里實現(xiàn)一個走馬燈, 效果如下,當(dāng)前頁面的高度比其它頁面高,切換頁面的時候有一個高度變化的動畫。實現(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>[],
),
);
}
}
然后在部件內(nèi)申明一個 _pageIndex 變量用來保存當(dāng)前顯示的頁面的 index,在 initState 生命周期里面初始化一個 PageController 用來配置 PageView 部件。
在 body 的 Column 里面創(chuàng)建一個 PageView.builder ,使用一個 SizedBox 部件指定 PageView 的高度,將 controller 設(shè)置為 _pageController ,在 onPageChanged 事件里將當(dāng)前顯示頁面的 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);
},
),
),
],
),
關(guān)鍵點: 設(shè)置 PageController 的 viewportFraction 參數(shù)小于 1,這個值是用來設(shè)置每個頁面在屏幕上顯示的比例,小于 1 的話,就可以在當(dāng)前頁面同時顯示其它頁面的內(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;
實現(xiàn) _buildItem
接著實現(xiàn) _buildItem 方法,這個方法就是返回 PageView.builder 里每一個頁面渲染的內(nèi)容,第一個參數(shù) activeIndex 是當(dāng)前顯示在屏幕上頁面的 index ,第二個參數(shù) index 是每一項自己的 index 。
使用一個 Center 部件讓內(nèi)容居中顯示,然后用一個 AnimatedContainer 添加頁面切換時的高度變化的動畫效果,切換頁面的時候使用了 setState 方法改變了 _pageIndex , Flutter 重新繪制每一項。關(guān)鍵點在于判斷當(dāng)前頁面是否為正在顯示的頁面,是的話它的高度就是 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 添加每一項的內(nèi)容
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 表示當(dāng)前顯示的頁數(shù)索引。把所有指示器放在一個 Row 部件里,判斷當(dāng)前指示器的 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:
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持我們。
欄 目:Android
下一篇:Android Selector 按下修改背景和文本顏色的實現(xiàn)代碼
本文標題:使用Flutter實現(xiàn)一個走馬燈布局的示例代碼
本文地址:http://www.jygsgssxh.com/a1/Android/9053.html
您可能感興趣的文章
- 01-10Android實現(xiàn)雙擊返回鍵退出應(yīng)用實現(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仿應(yīng)用列表實現(xiàn)網(wǎng)格布局
- 01-10Android實現(xiàn)底部導(dǎo)航欄的主界面


閱讀排行
本欄相關(guān)
- 01-10Android自定義View之繪制圓形頭像功能
- 01-10Android實現(xiàn)雙擊返回鍵退出應(yīng)用實現(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用法實例總結(jié)
- 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)境設(shè)置


