溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

如何使用Flutter實現(xiàn)一個走馬燈布局

發(fā)布時間:2021-09-28 10:51:42 來源:億速云 閱讀:233 作者:小新 欄目:編程語言

這篇文章給大家分享的是有關(guān)如何使用Flutter實現(xiàn)一個走馬燈布局的內(nèi)容。小編覺得挺實用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。

走馬燈是一種常見的效果,本文講一下如何用 PageViewFlutter 里實現(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 部件。

bodyColumn 里面創(chuàng)建一個 PageView.builder ,使用一個 SizedBox 部件指定 PageView 的高度,將 controller 設(shè)置為 _pageController ,在 onPageChanged 事件里將當(dāng)前顯示頁面的 index 值賦值給 _pageIndex 變量。

int _pageIndex = 0;PageController _pageController;@overridevoid 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è)置 PageControllerviewportFraction 參數(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(), ); }}

添加 PageIndicatorSizedBox 下面

封裝 Carousel

最后的最后優(yōu)化一下代碼,把部件封裝一下,讓它成為一個單獨的部件,創(chuàng)建一個 Carousel 部件,對外暴露 itemsheight 兩個屬性,分別配置數(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://cache.yisu.com/upload/information/20201211/272/44767.jpg", title: '寒冰射手-艾希', ), Hero( color: Color(0xFF7D6588), image: "https://cache.yisu.com/upload/information/20201211/272/44768.jpg", title: '刀鋒舞者-艾瑞莉婭', ), Hero( color: Color(0xFF4C314D), image: "https://cache.yisu.com/upload/information/20201211/272/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<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, ); }}

感謝各位的閱讀!關(guān)于“如何使用Flutter實現(xiàn)一個走馬燈布局”這篇文章就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,讓大家可以學(xué)到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!

向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI