#轮播组件
flutter_swiper: ^1.1.6
基本使用
import 'package:flutter/material.dart';
import 'package:flutter_swiper/flutter_swiper.dart';
class Loginpage extends StatefulWidget {
const Loginpage({Key key}) : super(key: key);
@override
_LoginpageState createState() => _LoginpageState();
}
class _LoginpageState extends State<Loginpage> {
List<Map> _imglist = [
{"url": "https://img-blog.csdnimg.cn/0c89c01ee6594c839e79122b724c40f9.png"},
{"url": "https://img-blog.csdnimg.cn/420fdd67f9db4ccfb5e69b5b20ec6ee9.png"},
{"url": "https://img-blog.csdnimg.cn/09c48a7e62bf4b4b938efc25b57d1900.png"},
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Swiper"),
),
body: new Swiper(
itemBuilder: (BuildContext context, int index) {
return new Image.network(
this._imglist[index]["url"],
fit: BoxFit.fill,
);
},
itemCount:this._imglist.length,
pagination: new SwiperPagination(),
control: new SwiperControl(),
),
);
}
}
封装轮播
import 'package:flutter/material.dart';
import 'package:flutter_swiper/flutter_swiper.dart';
import 'package:myshop_flutter/config/index.dart';
import 'package:myshop_flutter/model/home_model.dart';
import 'package:myshop_flutter/widgets/cached_image_widget.dart';
class HomeBannerWidget extends StatelessWidget {
//轮播图数据
List<BannerModel> bannerData ;
// List bannerData = List();
//数量
int size;
//视图高度
HomeBannerWidget(this.bannerData, this.size, this.viewHeight);
double viewHeight;
@override
Widget build(BuildContext context) {
return Container(
height: viewHeight,
width: double.infinity,
child: bannerData == null || bannerData.length == 0
//没有数据提示
? Container(
height: 400,
color: Colors.grey,
//居中显示
alignment: Alignment.center,
//没有数据文本提示
child: Text(KString.NO_DATA_TEXT),
)
//轮播组件
: Swiper(
//点击跳转
// onTap: (index) {
// NavigatorUtil.goWebView(context, bannerData[index].name, bannerData[index].link);
// },
//轮播图数量
itemCount: size,
//指定滚动方向
scrollDirection: Axis.horizontal,
//滚动方向,设置为Axis.vertical如果需要垂直滚动
loop: true,
//无限轮播模式开关
index: 0,
//初始的时候下标位置
autoplay: false,
itemBuilder: (BuildContext buildContext, int index) {
//使用缓存图片组件显示轮播图
// return CachedImageWidget(
// double.infinity, double.infinity, bannerData[index].url);
return Container(
width: double.infinity,
child: CachedImageWidget(
double.infinity, double.infinity, bannerData[index].url)
);
},
//滚动时长
duration: 40000,
//指定分页组件
pagination: SwiperPagination(
//底部居中展示
alignment: Alignment.bottomCenter,
//构建小圆点
builder: DotSwiperPaginationBuilder(
size: 8.0,
//未点击颜色
color: KColor.bannerDefaultColor,
//点击颜色
activeColor: KColor.bannerActiveColor)),
));
}
}
Widget _buildBanner() {
return Container(
child: bannerList.length == 0
? Container()
: Container(
height: 162,
child: Swiper(
itemBuilder: (BuildContext context, int index) {
return _createBanner(index);
},
pagination: SwiperPagination(
margin: EdgeInsets.only(bottom: 10),
builder: DotSwiperPaginationBuilder(
color: Colors.white30,
activeColor: Colors.white,
size: 8,
activeSize: 8,
),
),
itemCount: bannerList.length,
viewportFraction: 1,
autoplayDelay: 3000,
scale: 1,
autoplay: true,
onTap: (index) {},
),
),
);
}
_createBanner(int index) {
return GestureDetector(
onTap: () {},
child: Container(
width: double.infinity,
decoration: BoxDecoration(
image: DecorationImage(
image: NetworkImage(bannerList[index]),
fit: BoxFit.cover,
),
borderRadius: BorderRadius.circular(8),
),
),
);
}
List bannerList = [
'https://tse1-mm.cn.bing.net/th/id/R-C.65398d6ad86129f9628c0ad80da4040c?rik=C3qNS9mZOQk%2b5A&riu=http%3a%2f%2fwww.shijuepi.com%2fuploads%2fallimg%2f200918%2f1-20091Q10420.jpg&ehk=QBNuJIbVP1qo%2bwUD3YzXcvL4H5iHivOHXUnzzRw%2bWfU%3d&risl=&pid=ImgRaw&r=0',
'https://tse1-mm.cn.bing.net/th/id/R-C.65398d6ad86129f9628c0ad80da4040c?rik=C3qNS9mZOQk%2b5A&riu=http%3a%2f%2fwww.shijuepi.com%2fuploads%2fallimg%2f200918%2f1-20091Q10420.jpg&ehk=QBNuJIbVP1qo%2bwUD3YzXcvL4H5iHivOHXUnzzRw%2bWfU%3d&risl=&pid=ImgRaw&r=0',
'https://tse1-mm.cn.bing.net/th/id/R-C.65398d6ad86129f9628c0ad80da4040c?rik=C3qNS9mZOQk%2b5A&riu=http%3a%2f%2fwww.shijuepi.com%2fuploads%2fallimg%2f200918%2f1-20091Q10420.jpg&ehk=QBNuJIbVP1qo%2bwUD3YzXcvL4H5iHivOHXUnzzRw%2bWfU%3d&risl=&pid=ImgRaw&r=0,'
];
AppBar 和 SliverAppBar 都是继承至 StatefulWidget 类,都代表 Toobar,二则的区别在于 AppBar 位置的固定的应用最上面的;而 SliverAppBar 是可以跟随内容滚动的。
leading:在标题前面显示的一个控件,在首页通常显示应用的 logo;在其他界面通常显示为返回按钮。
title: Toolbar 中主要内容,通常显示为当前界面的标题文字。
actions:一个 Widget 列表,代表 Toolbar 中所显示的菜单,对于常用的菜单,通常使用 IconButton 来表示;对于不常用的菜单通常使用PopupMenuButton 来显示为三个点,点击后弹出二级菜单。
bottom:一个 AppBarBottomWidget 对象,通常是 TabBar。用来在 Toolbar 标题下面显示一个 Tab 导航栏。
elevation:纸墨设计中控件的 z 坐标顺序,默认值为 4,对于可滚动的 SliverAppBar,当 SliverAppBar 和内容同级的时候,该值为 0, 当内容滚动
SliverAppBar 变为 Toolbar 的时候,修改 elevation 的值。
flexibleSpace:一个显示在 AppBar 下方的控件,高度和 AppBar 高度一样,可以实现一些特殊的效果,该属性通常在 SliverAppBar 中使用
backgroundColor:Appbar 的颜色,默认值为 ThemeData.primaryColor。该值通常和下面的三个属性一起使用:
brightness:App bar 的亮度,有白色和黑色两种主题,默认值为 ThemeData.primaryColorBrightness
iconTheme:App bar 上图标的颜色、透明度、和尺寸信息。默认值为 ThemeData.primaryIconTheme
textTheme:App bar 上的文字样式。默认值为 ThemeData.primaryTextTheme
centerTitle: 标题是否居中显示,默认值根据不同的操作系统,显示方式不一样
CustomScrollView是使用Sliver组件创建自定义滚动效果的滚动组件。使用场景: ListView和GridView相互嵌套场景,ListView嵌套GridView时,需要给GridView指定高度,但我们希望高度随内容而变化(不指定),ListView和GridView使用同一个滚动效果。一个页面顶部是AppBar,然后是GridView,最后是ListView,这3个区域以整体来滚动,AppBar具有吸顶效果。CustomScrollView就像一个粘合剂,将多个组件粘合在一起,具统一的滚动效果。Sliver系列组件有很多,比如SliverList、SliverGrid、SliverFixedExtentList、SliverPadding、SliverAppBar等。
相互嵌套场景
在实际业务场景中经常见到这样的布局,顶部是网格布局(GridView),然后是列表布局(ListView),滚动的时候做为一个整体,此场景是无法使用GridView+ListView来实现的,而是需要使用CustomScrollView+SliverGrid+SliverList来实现,实现代码如下
CustomScrollView(
slivers: <Widget>[
SliverGrid.count(crossAxisCount: 4,children: List.generate(8, (index){
return Container(
color: Colors.primaries[index%Colors.primaries.length],
alignment: Alignment.center,
child: Text('$index',style: TextStyle(color: Colors.white,fontSize: 20),),
);
}).toList(),),
SliverList(
delegate: SliverChildBuilderDelegate((content, index) {
return Container(
height: 85,
alignment: Alignment.center,
color: Colors.primaries[index % Colors.primaries.length],
child: Text('$index',style: TextStyle(color: Colors.white,fontSize: 20),),
);
}, childCount: 25),
)
],
)
controller为滚动控制器,可以监听滚到的位置
_scrollController = ScrollController();
//监听滚动位置
_scrollController.addListener((){
print('${_scrollController.position}');
});
//滚动到指定位置
_scrollController.animateTo(20.0);
CustomScrollView(
controller: _scrollController,
...
)
顶部跳转
import 'dart:math';
import 'package:flutter/material.dart';
class Messagepage extends StatefulWidget {
const Messagepage({Key? key}) : super(key: key);
@override
_MessagepageState createState() => _MessagepageState();
}
class _MessagepageState extends State<Messagepage> {
bool isshowBtn = false;
ScrollController _Controller = ScrollController(initialScrollOffset: 100);
@override
void initState() {
super.initState();
_Controller.addListener(() {
setState(() {
isshowBtn = _Controller.offset >= 1000;
});
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: this.isshowBtn?FloatingActionButton(
onPressed: () {
_Controller.animateTo(0,
duration: Duration(seconds: 1), curve: Curves.easeIn);
},
child: Icon(Icons.arrow_upward),
):null,
body: ListView.builder(
controller: _Controller,
itemCount: 100,
itemBuilder: (BuildContext ctx, int index) {
return ListTile(
leading: Icon(Icons.people),
title: Text("联系$index"),
);
},
),
);
}
}
监听开始滑动事件
import 'dart:math';
import 'package:flutter/material.dart';
class Messagepage extends StatefulWidget {
const Messagepage({Key? key}) : super(key: key);
@override
_MessagepageState createState() => _MessagepageState();
}
class _MessagepageState extends State<Messagepage> {
bool isshowBtn = false;
ScrollController _Controller = ScrollController(initialScrollOffset: 100);
@override
void initState() {
super.initState();
_Controller.addListener(() {
setState(() {
//_Controller不能监听开始滚动和结束滚动
isshowBtn = _Controller.offset >= 1000;
});
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: this.isshowBtn
? FloatingActionButton(
onPressed: () {
_Controller.animateTo(0,
duration: Duration(seconds: 1), curve: Curves.easeIn);
},
child: Icon(Icons.arrow_upward),
)
: null,
body: NotificationListener(
//监听是否开始或者结束滚动
onNotification: (ScrollNotification notification) {
if (notification is ScrollNotification) {
print("开始滚动");
} else if (notification is ScrollUpdateNotification) {
print("正在滚动,总滚动距离${notification.metrics.maxScrollExtent},当前滚动位置:${notification.metrics.pixels}");
} else if (notification is ScrollNotification) {
print("结束滚动");
}
return true;
},
child: ListView.builder(
controller: _Controller,
itemCount: 100,
itemBuilder: (BuildContext ctx, int index) {
return ListTile(
leading: Icon(Icons.people),
title: Text("联系$index"),
);
},
),
),
);
}
}
实例
import 'dart:math';
import 'package:flutter/material.dart';
class Messagepage extends StatefulWidget {
const Messagepage({Key? key}) : super(key: key);
@override
_MessagepageState createState() => _MessagepageState();
}
class _MessagepageState extends State<Messagepage> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
//安全区域
slivers: [
SliverAppBar(
expandedHeight: 300, //扩展导航条的高度
flexibleSpace: FlexibleSpaceBar(
title: Text("helloword"),
background: Image.network("https://img-blog.csdnimg.cn/77b16c81bfea42b39feb940c694ee41a.png"),
),
pinned: true, //固定导航条
),
//不能存放普通的widget
SliverSafeArea(
sliver: SliverPadding(
padding: EdgeInsets.all(8),
sliver: SliverGrid(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 8,
mainAxisSpacing: 8,
childAspectRatio: 1.5),
delegate: SliverChildBuilderDelegate((BuildContext ctx, int int) {
return Container(
color: Color.fromARGB(255, Random().nextInt(256),
Random().nextInt(256), Random().nextInt(256)),
);
}, childCount: 10),
),
)),
SliverList(
delegate: SliverChildBuilderDelegate((BuildContext ctx, int index) {
return ListTile(
leading: Icon(Icons.people),
title: Text("aaa"),
);
}, childCount: 20),
)
],
),
);
}
}
·
ListView.builder()
import 'package:flutter/material.dart';
main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('day_2',
textDirection: TextDirection.ltr,
style: TextStyle(fontSize: 10, color: Colors.purpleAccent),
textAlign: TextAlign.center,
maxLines: 1),
),
body: HomeContent(),
),
);
}
}
class HomeContent extends StatelessWidget {
List listData = [
{
"title": 'Candy Shop',
"author": "Mohamed",
"imagesUrl":
"https://img-blog.csdnimg.cn/07bec00ef4d54b66a856ca14f222001b.png"
},
{
"title": 'Candy Shop2',
"author": "Mohamed2",
"imagesUrl":
"https://img-blog.csdnimg.cn/07bec00ef4d54b66a856ca14f222001b.png"
},
{
"title": 'Candy Shop3',
"author": "Mohamed3",
"imagesUrl":
"https://img-blog.csdnimg.cn/07bec00ef4d54b66a856ca14f222001b.png"
}
];
Widget _getListData(context,index) {
return Container(
child: Column(
children: <Widget>[
Image.network(listData[index]["imagesUrl"]),
SizedBox(height: 4),
Text(
listData[index]["title"],
textAlign: TextAlign.center,
style: TextStyle(fontSize: 20),
)
],
),
decoration:
BoxDecoration(border: Border.all(color: Colors.deepPurple, width: 2)),
);
}
@override
Widget build(BuildContext context) {
return GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2, //一行多少个组件
mainAxisSpacing: 10,
crossAxisSpacing: 10,
),
itemCount: listData.length,
itemBuilder: this._getListData,
);
}
}
ListView.count
import 'package:flutter/material.dart';
main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('day_2',
textDirection: TextDirection.ltr,
style: TextStyle(fontSize: 10, color: Colors.purpleAccent),
textAlign: TextAlign.center,
maxLines: 1),
),
body: HomeContent(),
),
);
}
}
class HomeContent extends StatelessWidget {
List<Widget> _getListData() {
List listData = [
{
"title": 'Candy Shop',
"author": "Mohamed",
"imagesUrl":
"https://img-blog.csdnimg.cn/07bec00ef4d54b66a856ca14f222001b.png"
},
{
"title": 'Candy Shop2',
"author": "Mohamed2",
"imagesUrl":
"https://img-blog.csdnimg.cn/07bec00ef4d54b66a856ca14f222001b.png"
},
{
"title": 'Candy Shop3',
"author": "Mohamed3",
"imagesUrl":
"https://img-blog.csdnimg.cn/07bec00ef4d54b66a856ca14f222001b.png"
}
];
var tempList = listData.map((value) {
return Container(
child: Column(
children: <Widget>[
Image.network(value["imagesUrl"]),
SizedBox(height: 4),
Text(value["title"],
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20
),
)
],
),
decoration: BoxDecoration(
border: Border.all(
color: Colors.deepPurple,
width: 2
)
),
);
});
return tempList.toList();
}
@override
Widget build(BuildContext context) {
return GridView.count(
padding: EdgeInsets.all(10),
crossAxisCount: 2, //一行多少个组件
mainAxisSpacing: 10,
crossAxisSpacing: 10,
// childAspectRatio: 0.7, //宽度和高度的比例是0.7
children: this._getListData());
}
}
builder通常返回Dialog组件,比如SimpleDialog和AlertDialog。
useRootNavigator参数用于确定是否将对话框推送到给定“context”最远或最接近的Navigator。默认情况下,useRootNavigator为“true”,被推送到根Navigator。如果应用程序有多个Navigator,关闭对话框需要使用
return showDialog(
context: context,
barrierDismissible: true,
//barrierDismissible可以控制点击对话框以外的区域是否隐藏对话框。
builder: (BuildContext context){
return AlertDialog(
//提示
title: Text("是否删除"),
content: Text("内容"),
actions: [
FlatButton(
child: Text("取消") ,
onPressed: (){
Navigator.pop(context);
},
),
FlatButton(
child: Text("确定"),
)
],
);
}
);
}
barrierDismissible:是否可以点击背景关闭。
barrierColor:背景颜色
transitionDuration:动画时长,
transitionBuilder是构建进出动画,默认动画是渐隐渐显,构建缩放动画
showMenu
_showMuneDialog(){
return showMenu(
context: context,
position: RelativeRect.fromLTRB(20, 100, 0, 30),
items: <PopupMenuEntry>[
PopupMenuItem(child: Text('语文')),
PopupMenuDivider(),
CheckedPopupMenuItem(
child: Text('数学'),
checked: true,
),
PopupMenuDivider(),
PopupMenuItem(child: Text('英语')),
]);
}
在一般的 App 页面,可能都需要搜索功能,常见的就是在主页上面有一个搜索栏,点击会跳转到一个搜索页面。 在一般的 App
页面,可能都需要搜索功能,常见的就是在主页上面有一个搜索栏,点击会跳转到一个搜索页面。
使用showSearch,首先需要重写一个SearchDelegate,实现其中的4个方法。
buildActions 显示在最右边的图标列表。
buildLeading 搜索栏左边的图标,一般都是返回。
buildResults 这个回调里进行搜索和并返回自己的搜索结果。
buildSuggestions 返回建议搜索结果。
showSearch(context: context, delegate: CustomSearchDelegate());
class CustomSearchDelegate extends SearchDelegate<String>{
@override
List<Widget> buildActions(BuildContext context) {
return null;
}
@override
Widget buildLeading(BuildContext context) {
return null;
}
@override
Widget buildResults(BuildContext context) {
return null;
}
@override
Widget buildSuggestions(BuildContext context) {
return null;
}
}
import 'package:flutter/material.dart';
//继承SearchDelegate
class search extends SearchDelegate{
//数据内容
final List list=[
"1","11","111"
];
final rege=[
"flutter",
"Android"
];
@override
List<Widget> buildActions(BuildContext context) {
return [
//放置按钮,点击时,将搜索框清空
IconButton(
icon:Icon(Icons.clear),
onPressed: (){
query="";
},
)
];
}
@override
Widget buildLeading(BuildContext context) {
return IconButton( //返回动态按钮
icon:AnimatedIcon(
icon:AnimatedIcons.menu_arrow,
progress: transitionAnimation,
),
onPressed: (){ //回调中调用close方法,关闭搜索页面
close(context, null);
},
);
}
//搜索页面的结果线束
@override
Widget buildResults(BuildContext context) {
return Container( //点击确认后会关闭搜索页面,并显示如下组件内容
child: Text(query),
);
}
@override
Widget buildSuggestions(BuildContext context) {
final sugList = query.isEmpty ? rege:list.where((input)=>input.startsWith(query)).toList();
return ListView.builder(
itemCount: sugList.length,
itemBuilder: (context,index)=>ListTile(
//利用富文本组件,将字符串变成可以设置各个片段样式以及事件的字符串
title: RichText(
text: TextSpan(
//截取数组中已经输入的内容的长度的字符串,并修改其样式
text: sugList[index].substring(0,query.length),
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.w900
),
children: [
TextSpan(
//截取数组中的每条字符串输入内容长度之后的字符,并修改其样式
text: sugList[index].substring(query.length),
style: TextStyle(color: Colors.grey),
)
]
),
),
),
);
}
}
文本片段,html里有个span这里有个TextSpan,作用基本相同,文字放一行,text与children任选一样填写。
TextSpan与RichText配合使用可以实现不同样式文字布局。
TextSpan({
this.text,
this.children,//是一个TextSpan的数组,也就是说TextSpan可以包括其他TextSpan
TextStyle style,
this.recognizer,//它是点击链接后的一个处理器,用于对该文本片段上用于手势进行识别处理,设定手势识别器
this.semanticsLabel,
})
RichText(text: TextSpan(
style: DefaultTextStyle.of(context).style,
children: [
TextSpan(
text: "我已接受并阅读",
style: TextStyle(
color: Colors.grey[200]
)
),
TextSpan(
text: "《用户协议》",
style: TextStyle(
color: Colors.red
)
),
TextSpan(
text: "与",
style: TextStyle(
color: Colors.black
)
),
TextSpan(
text: "《隐私协议》",
style: TextStyle(
color: Colors.red
)
)
]
))
Flutter中尺寸限制类容器组件包括ConstrainedBox、UnconstrainedBox、SizedBox、AspectRatio、FractionallySizedBox、LimitedBox、Container。这些组件可以约束子组件的尺寸
嵌套使用等于多个BoxConstraints约束中的最小值。同理嵌套约束最小值等于多个BoxConstraints约束中的最大值
ConstrainedBox(
constraints: BoxConstraints(maxHeight: 60, maxWidth: 200),
child: ConstrainedBox(
constraints: BoxConstraints(maxHeight: 100, maxWidth: 240),
child: Container(height: 300, width: 300, color: Colors.red),
),
)
ConstrainedBox(
//没有下面的最小高度的话,当只有一行文字的时候.9图片无法显示
constraints: BoxConstraints(minHeight: 50),
child: Container(
margin: EdgeInsets.fromLTRB(0, 10, 0, 0),
padding: EdgeInsets.fromLTRB(20, 10, 20, 10),
decoration: BoxDecoration(
image: DecorationImage(
centerSlice: Rect.fromLTRB(20, 20, 38, 38),
image: AssetImage(
'images/bg.png',
),
)),
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: 200),
child: Text(
"3333333354555444444444444433",
),
)),
)
Image.network(
'https://flutter.github.io/assets-for-api-docs/assets/widgets/puffin.jpg',
height: 150,
width: 150,
fit: BoxFit.cover,
frameBuilder: (
BuildContext context,
Widget child,
int frame,
bool wasSynchronouslyLoaded,
) {
if (frame == null) {
return Container(
height: 150,
width: 150,
decoration: BoxDecoration(
color: Colors.grey
),
);
}
return child;
},
)
SizedBox非常适合控制2个组件之间的空隙
aspectRatio参数是宽高比,可以直接写成分数的形式,也可以写成小数的形式,但建议写成分数的形式,可读性更高。
AspectRatio(
aspectRatio: 2 / 1,
child: Container(color: Colors.red),
)
Icon(IconData(0xe610,fontFamily: 'appIconFonts')
Spacer 是通过 Expanded 实现的,Expanded继承自Flexible。
填满剩余空间直接使用Expanded更方便。
Spacer 用于撑开 Row、Column、Flex 的子组件的空隙。
FractionallySizedBox
Container(
height: 200,
width: 200,
color: Colors.blue,
child: FractionallySizedBox(
widthFactor: .8,
heightFactor: .3,
child: Container(
color: Colors.red,
),
),
)
GestureDetector 是手势识别的组件,可以识别点击、双击、长按事件、拖动、缩放等手势
onTap(){}
onDoubleTap(){}
//按下然后抬起调用顺序:
onTapDown-> onTapUp-> onTap
//按下后移动的调用顺序:
onTapDown-> onTapCancel
当同时监听onTap和onDoubleTap事件时,只会触发一个事件,如果触发onTap事件,onTap将会延迟触发(延迟时间为系统判断是onDoubleTap事件的间隔),因为系统将会等待一段时间来判断是否为onDoubleTap事件,如果用户只监听了onTap事件则没有延迟
长按事件
长按事件(LongPress)包含长按开始、移动、抬起、结束事件,说明如下:
onLongPressStart:长按开始事件回调。
onLongPressMoveUpdate:长按移动事件回调。
onLongPressUp:长按抬起事件回调。
onLongPressEnd:长按结束事件回调。
onLongPress:长按事件回调
水平/垂直拖动事件
垂直/水平拖动事件包括按下、开始、移动更新、结束、取消事件,以垂直为例说明如下:
onVerticalDragDown:垂直拖动按下事件回调
onVerticalDragStart:垂直拖动开始事件回调
onVerticalDragUpdate:指针移动更新事件回调
onVerticalDragEnd:垂直拖动结束事件回调
onVerticalDragCancel:垂直拖动取消事件回调
缩放事件
缩放(Scale)包含缩放开始、更新、结束。说明如下:
onScaleStart:缩放开始事件回调。
onScaleUpdate:缩放更新事件回调。
onScaleEnd:缩放结束事件回调。
水波纹”超出的了圆角边框。Ink控件控制“水波纹”效果在其上面显示。
PageView
PageView 控件可以实现一个“图片轮播”的效果,PageView 不仅可以水平滑动也可以垂直滑动。
要同时滚动ListView和GridView可以使用SliverList和SliverGrid。
SliverList(
delegate: SliverChildBuilderDelegate((context, index) {
return Container(
decoration: BoxDecoration(color: Colors.primaries[index]),
height: 30,
child: Text("${index}"),
);
}, childCount: 10)),
SliverGrid(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, crossAxisSpacing: 5, mainAxisSpacing: 3),
delegate: SliverChildBuilderDelegate((BuildContext context, int index) {
return Container(
color: Colors.primaries[index % Colors.primaries.length],
);
}, childCount: 20),
)
floating
如果设置 floating 属性为 true,AppBar会在你做出下拉手势时就立即展开(即使ListView并没有到达顶部),该展开状态不显示flexibleSpace:
如果同时设置 floating 和 snap 属性为 true
,那么AppBar会在你做出下拉手势时就立即全部展开(即使ListView并没有到达顶部),该展开状态显示flexibleSpace:
如果同时设置 floating 和 snap 属性为 true
,那么AppBar会在你做出下拉手势时就立即全部展开(即使ListView并没有到达顶部),该展开状态显示flexibleSpace:
SliverAppBar(
title: Text("Sliver"),
backgroundColor: Colors.red,
leading: Icon(Icons.arrow_back),
actions: [
Icon(Icons.search),
],
expandedHeight: 200,
pinned: true,
flexibleSpace: FlexibleSpaceBar(
background: Image.network(
'https://img-blog.csdnimg.cn/831fd662f2004b89a2d4654e78d1ffbb.png',
fit: BoxFit.cover,
),
),
),
SliverPersistentHeader一般来说都是会展开/收起的(除非minExtent和maxExtent值相同),那么如果想要在滚动视图中添加一个普通的控件,那么就可以使用 SliverToBoxAdapter 来将各种视图组合在一起,放在CustomListView中。
SliverToBoxAdapter(
child: Container(
height:45,
margin: EdgeInsets.fromLTRB(20, 10, 20, 10),
decoration: BoxDecoration(
color: Colors.white,
boxShadow:[
BoxShadow(
blurRadius: 4,
spreadRadius: 1,
color: Colors.grey
)
],
borderRadius: BorderRadius.circular(30)
),
child:
TextFormField(
decoration: InputDecoration(
prefixIcon:Icon(Icons.search,color: Colors.grey,),
enabledBorder: InputBorder.none,
focusedBorder: InputBorder.none,
hintText: "Search category",
hintStyle: TextStyle(
color: Colors.grey.withOpacity(0.5)
)
),
)
),
)
SliverPersistentHeader控件当滚动到边缘时根据滚动的距离缩小高度,有点类似 SliverAppBar 的背景效果。
SliverPersistentHeader的delegate需要我们自定义,build返回显示的内容,maxExtent和minExtent表示最大和最小值,滚动的时候高度在这个范围内变化。
shouldRebuild表示是否需要更新,如果内容需要变化需要设置为true。
SliverPersistentHeader(delegate: MySliverPersistentHeaderDelegate()),
import 'package:flutter/material.dart';
class MySliverPersistentHeaderDelegate extends SliverPersistentHeaderDelegate {
@override
Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
return Container(
color: Colors.blue,
alignment: Alignment.center,
child: Text('我是一个SliverPersistentHeader',
style: TextStyle(color: Colors.white)));
}
@override
double get maxExtent => 200.0;
@override
// TODO: implement minExtent
double get minExtent => 100.0;
@override
bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) => false;
//如果内容更新设置为true
}
NestedScrollView
NestedScrollView 可以在其内部嵌套其他滚动视图的滚动视图
滚动隐藏AppBar
NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[SliverAppBar(
title: Text('Sliver'),
)];
},
body: ListView.builder(itemBuilder: (BuildContext context,int index){
return Container(
height: 80,
color: Colors.primaries[index % Colors.primaries.length],
alignment: Alignment.center,
child: Text(
'$index',
style: TextStyle(color: Colors.white, fontSize: 20),
),
);
},itemCount: 20,),
)