前言
在移动应用开发中,滚动组件是提升用户体验的重要元素。Flutter提供了一系列强大的滚动组件,使开发者能够轻松构建流畅且交互丰富的用户界面。本文将深入探讨Flutter中的多种滚动组件,包括SliverAppBar、PageView、NestedScrollView、ListView、GridView和SingleChildScrollView。
SliverAppBar
body: NestedScrollView( // sliver 家庭组件 headerSliverBuilder: (BuildContext context ,bool innerBoxIsScrolled){ return [ // buildSliverAppBar() SliverAppBar(title: Text('this is title'), centerTitle: true,), ]; }, body: buildBodyWidget(), ),
pageView
简单使用
import 'package:flutter/material.dart'; class ViewTest extends StatefulWidget { const ViewTest({Key? key}) : super(key: key); @override State<ViewTest> createState() => _ViewTestState(); } class _ViewTestState extends State<ViewTest> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("view test"), ), body: PageView( scrollDirection: Axis.vertical, // 改变 滑动 方向 children: [ Center( child: Text( "this is first", style: Theme.of(context).textTheme.headlineLarge, ), ), Center( child: Text( "this is second", style: Theme.of(context).textTheme.headlineLarge, ), ), Center( child: Text( "this is third", style: Theme.of(context).textTheme.headlineLarge, ), ), ], ), ); } }
PageView.custom SliverChildBuilderDelegate fit: BoxFit.fill,
import 'package:flutter/material.dart'; class test extends StatefulWidget{ const test({Key? key}) : super(key: key); @override State<StatefulWidget> createState() =>_test(); } class _test extends State<test> { PageController pageController = new PageController(); double currentPage = 0; List<String> imgList=[ "imgs/happy.webp", "imgs/laying.jpg", "imgs/noodle.jpg", "imgs/sleep.webp", ]; void initState(){ super.initState(); pageController = new PageController( initialPage: 0, keepPage: true, ); pageController.addListener(() { setState(() { currentPage = pageController.page!; }); }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('滑动视图'), ), body: buildPageView(), ); } buildPageView() { return Container( height: 200, width: 400, child: PageView.custom( controller: pageController, childrenDelegate: SliverChildBuilderDelegate((BuildContext context,int index){ if(index==currentPage.floor()){ return Transform( alignment: Alignment.center, transform: Matrix4.identity()..rotateX(currentPage-index) ..rotateY(0.98), child: buildItem(index) ); }else if(index == currentPage.floor()+1){ return Transform( alignment: Alignment.center, transform:Matrix4.identity() ..rotateX(currentPage-index) ..rotateY(0.9), child: buildItem(index), ); }else{ print("currentPage is $index"); return buildItem(index); } }, childCount: imgList.length ), ), ); } buildItem(int index){ print("index $index"); return Container( child: Image.asset( "${imgList[index]}", fit: BoxFit.fill, ), ); } } void main(){ runApp(const MaterialApp( home:test(), )); }
pageview.builder
class _ViewTestState extends State<ViewTest> { @override Widget build(BuildContext context) { return PageView.builder( scrollDirection: Axis.vertical, itemCount: 10, itemBuilder: (context,index){ return Center( child: Text( "this is ${index+1} page", style: Theme.of(context).textTheme.headlineLarge, ), ); }); } }
实现 无限 下拉
思路 使用 for 循环
import 'package:flutter/material.dart'; class ViewTest extends StatefulWidget { const ViewTest({Key? key}) : super(key: key); @override State<ViewTest> createState() => _ViewTestState(); } class _ViewTestState extends State<ViewTest> { List<Widget> list = []; void initState() { super.initState(); for (var i = 0; i < 10; i++) { list.add( Center( child: Text( "this is ${i + 1} page", style: TextStyle(fontSize: 40), ), ), ); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('hello,flutter'), ), body: PageView( onPageChanged: (index) { if (index + 2 == list.length) { for (var i = 0; i < 10; i++) { setState(() { list.add( Center( child: Text( "this is ${i + 1} page", style: TextStyle(fontSize: 40), ), ), ); }); } } }, scrollDirection: Axis.vertical, children: list), ); } }
NestedScorllVie
NestedScorllView 继承 于 Custom ScrollView
import 'package:flutter/material.dart'; class test extends StatefulWidget{ const test({Key? key}) : super(key: key); @override State<StatefulWidget> createState() =>_test(); } class _test extends State<test> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title:Text('test nestScorllView')), body: NestedScrollView( // sliver 家庭组件 headerSliverBuilder: (BuildContext context ,bool innerBoxIsScrolled){ return [ SliverAppBar(title: Text('this is title'), centerTitle: true,), ]; }, body: Container( alignment: Alignment.center, color: Colors.grey, height: 1600, child: Text('this is test data'), ), ), ); } } void main(){ runApp(MaterialApp( home:test(), )); }
配合listview
import 'package:flutter/material.dart'; class test extends StatefulWidget{ const test({Key? key}) : super(key: key); @override State<StatefulWidget> createState() =>_test(); } class _test extends State<test> with SingleTickerProviderStateMixin{ late TabController tabController; @override void initState(){ super.initState(); //初始化控制器 tabController=TabController(length: 3, vsync: this); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title:Text('test nestScorllView')), body: NestedScrollView( // sliver 家庭组件 headerSliverBuilder: (BuildContext context ,bool innerBoxIsScrolled){ return [ // buildSliverAppBar() SliverAppBar(title: Text('this is title'), centerTitle: true,), ]; }, body: buildBodyWidget(), ), ); } buildBodyWidget(){ return ListView.builder(itemBuilder: (BuildContext contest ,int index){ return Container( color: Colors.grey[200], height: 100, child: Container( margin: EdgeInsets.only(left: 8,right: 8,top: 4,bottom: 4), padding: EdgeInsets.all(8), color: Colors.white, child: Text('this is test data'), ), ); }, // 注意为这个 在listview .builder 前面 itemCount:200, ); } } void main(){ runApp(MaterialApp( home:test(), )); }
CustomScrollView
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title:Text('test nestScorllView')), body:CustomScrollView( controller: scrollController, slivers: [ SliverAppBar( title: Text('title'), centerTitle: true, ) ], ) ); }
SliverGrid buildSliverGrid(){ return new SliverGrid( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( mainAxisSpacing: 10.0, crossAxisSpacing: 10.0, childAspectRatio: 3.0, crossAxisCount: 2), delegate: SliverChildBuilderDelegate( (BuildContext context,int index){ return Container( alignment: Alignment.center, color: Colors.grey[100*(index%9)], child: Text('grid item $index'), ); }, childCount: 20, ), ); }
ListView
简单使用
Widget buildListView(){ return ListView( // scorll direction axis .vertical horizontal scrollDirection: Axis.vertical, // 默认为false 为true 时 滚动到底部 reverse: false, // 回弹 physics: BouncingScrollPhysics(), children: [ Text('this is first'), Text('this is second'), Text('this is third'), Text('this is fourth'), Text('this is fifth'), ], ); }
Widget buildListView2(){ return ListView.builder(itemBuilder: (BuildContext contest ,int index) { return Container( color: Colors.grey[200], height: 100, child: Container( margin: EdgeInsets.only(left: 8, right: 8, top: 4, bottom: 4), padding: EdgeInsets.all(8), color: Colors.cyan[100*(index % 9)], child: Text('this is test data'), ), ); } ); }
separator
return ListView.separated( itemCount: 100, itemBuilder: (BuildContext contest, int index) { return Container( color: Colors.grey[200], height: 100, child: Container( margin: EdgeInsets.only(left: 8, right: 8, top: 4, bottom: 4), padding: EdgeInsets.all(8), color: Colors.cyan[100 * (index % 9)], child: Text( 'this is test data ${index}', style: TextStyle(decoration: TextDecoration.none), ), ), ); }, separatorBuilder: (BuildContext context, int index) { return Divider( color: Colors.red, indent: 30, endIndent: 30, thickness: 10, ); }, );
懒加载的方式
Widget buildListView3() { return ListView.custom( childrenDelegate: SliverChildBuilderDelegate((BuildContext context,int index){ return new Container( height: 40, color: Colors.cyan[100* (index % 9)], ); }, childCount:20, ) ); }
使用图片加文字
简单使用文字和图片
return ListView( padding: EdgeInsets.fromLTRB(0,10,0,0), children: <Widget>[ // title ListTile( leading: Icon(Icons.home), title: Text('i am a home'), trailing: Icon(Icons.home), ), // 加一个下划线 Divider(), ListTile( leading: Icon(Icons.assignment), title: Text('i am a assignment'), subtitle:Text('i am a assignment2') , ), Divider(), ListTile( leading: Icon(Icons.payment,color: Colors.red,), title: Text('i am a payment'), ), Divider(), ListTile( leading: Icon(Icons.add_box), title: Text('i am a add_box'), ), Divider(), ], );
一张图片加一段文字
return ListView( children: [ Image.asset('./assets/imgs/laying.jpg'), Container( padding: EdgeInsets.fromLTRB(0,10,0,0), child: Text('i am a photo', textAlign: TextAlign.center, style: TextStyle(fontSize: 22),), ), Image.asset('./assets/imgs/laying.jpg'), Container( padding: EdgeInsets.fromLTRB(0,10,0,0), child: Text('i am a photo', textAlign: TextAlign.center, style: TextStyle(fontSize: 22),), ) ], );
水平列表
scorllDirect:Axis.horizontal
Widget build(BuildContext context) { return SizedBox( height: 100, child: ListView( scrollDirection: Axis.horizontal, // 水平列表 高度自适应 padding: const EdgeInsets.all(10), children: <Widget>[ Container( // height:120, width: 100, // 宽度 设置没用, 自适应 child: Column( children: [ SizedBox( height:80, width: 80, child: Image.asset("./assets/imgs/laying.jpg"), ), Text('this is a sentence') ], ), ), Container( width: 100, // 宽度 设置没用, 自适应 decoration: const BoxDecoration(color: Colors.blue), ), ], ) ); 容器里面套容器, 将 默认的 竖 改为横 horizontal
GridView
Widget buildGridView1(){ return GridView(gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( // cross axis 每行的个数 crossAxisCount: 2, // 空间 mainAxisSpacing: 10, crossAxisSpacing: 10, // 长度比例 childAspectRatio: 1.4 ), children: [ buildListViewItem(1), buildListViewItem(2), buildListViewItem(3), buildListViewItem(4), buildListViewItem(5), buildListViewItem(6), ], ); } Widget buildListViewItem(int index){ return new Container( height: 84, alignment: Alignment.center, color: Colors.grey[100*(index % 9)], child: new Text('grid item $index'), ); }
Widget buildGridView() { return GridView( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( // cross axis 每行的个数 crossAxisCount: 3, // 空间 mainAxisSpacing: 10, crossAxisSpacing: 10, // 长度比例 childAspectRatio: 1.4), children: List.generate(50, (index) { return Container( color: Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256), Random().nextInt(256)), ); })); }
Widget buildGridView2() { return GridView( gridDelegate: // 给长度 最长 的 为 220 大概 值 SliverGridDelegateWithMaxCrossAxisExtent( crossAxisSpacing: 8, mainAxisSpacing: 8, childAspectRatio: 1.6, maxCrossAxisExtent: 220), children: List.generate(50, (index) { return Container( color: Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256), Random().nextInt(256)), ); }), ); }
Widget buildGridView2() { return GridView.builder( gridDelegate: // 给长度 最长 的 为 220 大概 值 SliverGridDelegateWithMaxCrossAxisExtent( crossAxisSpacing: 8, mainAxisSpacing: 8, childAspectRatio: 1.6, maxCrossAxisExtent: 220), itemBuilder: (BuildContext context, int index) { return Container( color: Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256), Random().nextInt(256)), ); }, ); }
下拉刷新更多
return Scaffold( backgroundColor: Colors.white, appBar: AppBar( title: Text('this is title'), ), body: RefreshIndicator( color: Colors.blue, // 下拉停止距离 displacement: 44.0, backgroundColor: Colors.grey[200], onRefresh: ()async{ await Future.delayed(Duration(milliseconds: 3000)); return Future.value(true); }, child: buildGridView(), ), );
page swiper
先 定义 一个 图片 组件
所给 的 值 为 自己 定义 的值 可以 传参
import 'package:flutter/material.dart'; // 单个组件 后面 方便 使用 class ImagePage extends StatelessWidget { final double width; final double height; final String src; const ImagePage({ super.key,this.width=double.infinity,this.height=200,required this.src}); @override Widget build(BuildContext context) { // 定义 组件 设置 属性 在 组件 里面 return SizedBox( width: width, height: height, child: Image.asset(src,fit:BoxFit.cover), // child: Text("hello,world"), ); } }
定义一个 list 存放 图片
List<Widget> list = []; @override void initState() { super.initState(); list = [ ImagePage( width: double.infinity, height: 200, src: "imgs/1.png", ), ImagePage( width: double.infinity, height: 200, src: "imgs/laying.jpg", ), ImagePage( width: double.infinity, height: 200, src: "imgs/noodle.jpg", ), ]; }
使用
body: SizedBox( height: 400, width: 400, child:PageView( children: list, ) ),
可以 无限 循环
body: SizedBox( height: 400, width: 400, child:PageView.builder( itemCount: 1000, itemBuilder: (context,index){ //0 % 3 = 0 1 %3 = 1 2 % 3 = 2 3 % 3 = 1 return list[index % list.length]; }, ) ),
用 stack 在 图片 下面 加 3 个 小点
Positioned( // right 加 left 都 为 0 就会 占满 整行 // 之后 便可 居中 right: 0, left: 0, bottom: 2, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: List.generate(3, (index) { return Container( margin: EdgeInsets.all(5), width: 10, height: 10, decoration: BoxDecoration( color: _currentIndex==index?Colors.blue:Colors.grey, // borderRadius: BorderRadius.circular(5) shape: BoxShape.circle ), ); }).toList(), ), )
定义一个 变量 存储 此时的 位置 int _currentIndex=0; color: _currentIndex==index?Colors.blue:Colors.grey, 当前 的 为 蓝色
全部 代码
SingleChildScrollView
body: Center( child: SingleChildScrollView( padding: EdgeInsets.all(20), // 滑倒边界有回弹效果 physics: BouncingScrollPhysics(), controller: _scrollController, child: Container( color: Colors.grey, height: 1000, child: Align( alignment: FractionalOffset.bottomCenter, child: Text( 'Hello, Flutter!', style: TextStyle( fontSize: 20, color: Colors.white, ), ), )), ), ),
监听距离
void initState() { // TODO: implement initState super.initState(); _scrollController.addListener(() { double offsetValue=_scrollController.offset; double max= _scrollController.position.maxScrollExtent; if(offsetValue<=0){ print("已经在顶部啦"); }else if(offsetValue>=max){ print("已经到底部啦"); }else{ print("the offsetValue is: $offsetValue max: $max"); } }); }
滑动到指定位置 高度
void scrollOffset(double offset) { _scrollController.animateTo(offset, duration: Duration(milliseconds: 200), curve: Curves.ease); }
void scrollToTop() { scrollOffset(0.0); } void scrollToBottom() { double maxScroll = _scrollController.position.maxScrollExtent; scrollOffset(maxScroll); }
使用
children: [ ElevatedButton( onPressed: () { scrollToBottom(); }, child: Text("scrollToBottom"), ), SizedBox(height:900), ElevatedButton( onPressed: () { scrollToTop(); }, child: Text("scrollToTop"), ), ],