Flutter学习之布局、交互、动画,mysql的索引面试

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip204888 (备注Android)
img

正文

1.4.实现第三行

第三行就简单了,直接一个RowWidget,内部嵌套ExpandedTextIcon就Ok了,代码如下:

//第三行
Widget rowthreeWidget = Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
new Expanded(
child: Text(
“作者:EnjoyMoving”,
style: getTextStyle(Colors.grey[400], 14, true),
),
),
getPaddingfromLTRB(Text(
‘时间:2019-02-02’,
style: getTextStyle(Colors.black, 14, true),
), r :10.0),
getPaddingfromLTRB(Icon(
Icons.favorite_border,
color:Colors.grey[400],
),r:0.0)
],
);

1.5.整体

//根Widget
Widget ColumnWidget = Column(
//主轴上设置居中
mainAxisAlignment: MainAxisAlignment.center,
//交叉轴(水平方向)设置从左开始
crossAxisAlignment: CrossAxisAlignment.start,
children: [
//第一行
getPaddingfromLTRB(Text(‘Java synchronized原理总结’,
style: getTextStyle(Colors.black, 16,true),
),t:0.0),
//第二行
getPaddingfromLTRB(rowWidget,t:10.0),
//第三行
getPaddingfromLTRB(rowthreeWidget,t:10.0),

],
);
return new Scaffold(
appBar: new AppBar(
title: new Text(‘Flutter Demo’),
),
//用card裹住
body: Card(
child: Container(
//高度
height: 160.0,
//颜色
color: Colors.white,
padding: EdgeInsets.all(10.0),
child: Center(
child: ColumnWidget,
)
),
),
);

最终效果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2.布局二

直接上电影卡片布局,如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

大致把图看了一遍,大致框架是最外层是用Row,左孩子是图片,右孩子是Column,其孩子分为五行,最后一行主演还是用Row来实现,上分析图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2.1.实现右边图片

//根Widget 布局二 开始
//右边图片布局
Widget LayoutTwoLeft = Container(
//这次使用裁剪实现圆角矩形
child:ClipRRect(
//设置圆角
borderRadius: BorderRadius.circular(4.0),
child: Image.network(
‘https://img3.doubanio.com//view//photo//s_ratio_poster//public//p2545472803.webp’,
width: 100.0,
height: 150.0,
fit:BoxFit.fill,
),

),
);
//整体
Widget RowWidget = Row(
//主轴上设置居中
mainAxisAlignment: MainAxisAlignment.start,
//交叉轴(水平方向)设置从左开始
crossAxisAlignment: CrossAxisAlignment.center,
children: [
LayoutTwoLeft,
],
);

2.2.实现圆形头像

就是用自带的CircleAvatar这个Widget来实现:

//右下角圆形
CircleAvatar getCircleAvator(String image_url){
//圆形头像
return CircleAvatar(
backgroundColor: Colors.white,
backgroundImage: NetworkImage(image_url),
);
}

2.3.实现右边布局

右布局就是用一个Column来实现,一列一列往下实现即可:

//右布局
Widget LayoutTwoRightColumn = Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
//电影名称
Text(
‘流浪地球’,
style: getTextStyle(Colors.black, 20.0, true),
),

//豆瓣评分
Text(
‘豆瓣评分:7.9’,
style: getTextStyle(Colors.black54, 16.0, false),
),

//类型
Text(
‘类型:科幻、太空、灾难’,
style:getTextStyle(Colors.black54, 16.0, false),
),

//导演
Text(
‘导演:郭帆’,
style: getTextStyle(Colors.black54, 16.0, false),
),

//主演
Container(
margin: EdgeInsets.only(top:8.0),
child:Row(
children: [
Text(‘主演:’),
//以Row从左到右排列头像
Row(
children: [
Container(
margin: EdgeInsets.only(left:2.0),
child: getCircleAvator(‘https://img3.doubanio.com//view//celebrity//s_ratio_celebrity//public//p1533348792.03.webp’),
),
Container(
margin: EdgeInsets.only(left:12.0),
child: getCircleAvator(‘https://img3.doubanio.com//view//celebrity//s_ratio_celebrity//public//p1501738155.24.webp’),
),
Container(
margin: EdgeInsets.only(left:12.0),
child: getCircleAvator(‘https://img3.doubanio.com//view//celebrity//s_ratio_celebrity//public//p1540619056.43.webp’),
),

],
),
],
),
),
],
);

//布局二 右布局 用Expanded占满剩余空间
Widget LayoutTwoRightExpanded = Expanded(
child:Container(
//距离左布局10
margin:EdgeInsets.only(left:10.0),
//高度
height:150.0,
child: LayoutTwoRightColumn,
),
);

右布局用Expanded就是为了占满剩余空间。

2.4.整合

//整体
Widget RowWidget = Row(
//主轴上设置从开始方向对齐
mainAxisAlignment: MainAxisAlignment.start,
//交叉轴(水平方向)居中
crossAxisAlignment: CrossAxisAlignment.center,
children: [
LayoutTwoLeft,
LayoutTwoRightExpanded,
],
);
return new Scaffold(
appBar: new AppBar(
title: new Text(‘Flutter Demo’),
),
body: Card(
child: Container(
//alignment: Alignment(0.0, 0.0),
height: 160.0,
color: Colors.white,
padding: EdgeInsets.all(10.0),
child: Center(
// 布局一
// child: ColumnWidget,

// 布局二
child:RowWidget,
)
),
),
);

运行效果图如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

3.布局三

同样直接上需求:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

一看还是根布局直接用Column,一行一行实现就可以了,这个布局稍微简单一点,上分析图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

3.1.实现第一行

//布局三开始第一行
Widget LayoutThreeOne = Row(
children: [
Expanded(
child: Row(
children: [
Text(‘作者:’),
Text(‘HuYounger’,
style: getTextStyle(Colors.redAccent[400], 14, false),
),
],
)
),
//收藏图标
getPaddingfromLTRB(Icon(Icons.favorite,color:Colors.red),r:10.0),
//分享图标
Icon(Icons.share,color:Colors.black),
],
);

3.2.实现第三行

//布局三开始第三行
Widget LayoutThreeThree = Row(
children: [
Expanded(
child: Row(
children: [
Text(‘分类:’),
getPaddingfromLTRB(Text(‘开发环境/Android’,
style:getTextStyle(Colors.deepPurpleAccent, 14, false)),l:8.0),
],
),
),
Text(‘发布时间:2018-12-13’),
],
);

3.3.整合

//布局三整合
Widget LayoutThreeColumn = Column(
//主轴上设置居中
mainAxisAlignment: MainAxisAlignment.center,
//交叉轴(水平方向)设置从左开始
crossAxisAlignment: CrossAxisAlignment.start,
children: [
//第一行
LayoutThreeOne,
//第二行
getPaddingfromLTRB(Text(‘Android Monitor使用介绍’,
style:getTextStyle(Colors.black, 18, false),
),t:10.0),
//第三行
getPaddingfromLTRB(LayoutThreeThree,t:10.0),
],

);
return new Scaffold(
appBar: new AppBar(
title: new Text(‘Flutter Demo’),
),
body: Card(
child: Container(
//alignment: Alignment(0.0, 0.0),
height: 160.0,
color: Colors.white,
padding: EdgeInsets.all(10.0),
child: Center(
// 布局一
// child: ColumnWidget,

// 布局二
// child:RowWidget,

// 布局三
child:LayoutThreeColumn,
)
),
),
);
}

运行效果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

4.添加ListView

上面实现了基本的布局,有了item后,那必须有ListView,这里简单模拟一下实现一下:

return new Scaffold(
appBar: new AppBar(
title: new Text(‘Flutter Demo’),
),
//ListView提供一个builder属性
body: ListView.builder(
//数目
itemCount: 20,
//itemBuilder是一个匿名回调函数,有两个参数,BuildContext 和迭代器index
//和ListView的Item项类似 迭代器从0开始 每调用一次这个函数,迭代器就会加1
itemBuilder: (BuildContext context,int index){
return Column(
children: [
cardWidget,
],
);

}),

);

发现屏幕上被20条Item项填充满,这里想想,把下拉刷新和上滑加载加上,Flutter肯定会有方法的。

4.1.下拉刷新

Flutter已经提供和原生Android一样的刷新组件,叫做RefreshIndicator,是MD风格的,Flutter里面的ScrollView和子Widget都可以添加下拉刷新,只要在子``Widget的上层包裹一层RefreshIndicator`,先看看构造方法:

const RefreshIndicator({
Key key,
@required this.child,
this.displacement = 40.0,//下拉刷新的距离
@required this.onRefresh,//下拉刷新回调方法
this.color, //进度指示器前景色 默认是系统主题色
this.backgroundColor, //背景色
this.notificationPredicate = defaultScrollNotificationPredicate,
this.semanticsLabel, //小部件的标签
this.semanticsValue, //加载进度
})

包裹住ListView,并且定义下拉刷新方法:

return new Scaffold(
appBar: new AppBar(
title: new Text(‘Flutter Demo’),
),
body: RefreshIndicator(
//ListView提供一个builder属性
child: ListView.builder(
//数目
itemCount: 20,
//itemBuilder是一个匿名回调函数,有两个参数,BuildContext 和迭代器index
//和ListView的Item项类似 迭代器从0开始 每调用一次这个函数,迭代器就会加1
itemBuilder: (BuildContext context,int index){
return Column(
children: [
cardWidget,
],
);

}),
onRefresh: _onRefresh,),
);
//下拉刷新方法
Future _onRefresh() async {
//写逻辑
}

可以看到上面定义刷新方法_onRefresh,这里先不加任何逻辑。把根Widget继承StatefulWidget,因为后面涉及到状态更新:

class HomeStateful extends StatefulWidget{
@override
State createState(){
return new HomeWidget();
}

}

class HomeWidget extends State {
//列表要显示的数据
List list = new List();
//是否正在加载 刷新
bool isfresh = false;
//这个方法只会调用一次,在这个Widget被创建之后,必须调用super.initState()
@override
void initState(){
super.initState();
//初始化数据
initData();
}

//延迟3秒后刷新
Future initData() async{
await Future.delayed(Duration(seconds: 3),(){
setState(() {
//用生成器给所有元素赋初始值
list = List.generate(20, (i){
return i;
});
});
});
}
}

一开始先创建并初始化长度是20的List集合,ListView根据这个集合长度来构建对应数目的Item项,上面代码是初始化3秒后才刷新数据,并加了标记isfresh是否加载刷新,Scafford代码如下:

//ListView Item
Widget _itemColumn(BuildContext context,int index){
if(index <list.length){
return Column(
children: [
cardWidget,
],
);

}

}
return new Scaffold(
appBar: new AppBar(
title: new Text(‘Flutter Demo’),
),
body: RefreshIndicator(
//ListView提供一个builder属性
child: ListView.builder(
//集合数目
itemCount: list.length,
//itemBuilder是一个匿名回调函数,有两个参数,BuildContext 和迭代器index
//和ListView的Item项类似 迭代器从0开始 每调用一次这个函数,迭代器就会加1
itemBuilder: _itemColumn,
),
onRefresh: _onRefresh,),
);
}

下面把下拉刷新方法逻辑简单加一下,我这边只是重新将集合清空,然后重新添加8条数据,只是为了看刷新效果而儿:

//下拉刷新方法
Future _onRefresh() async {
//写逻辑 延迟3秒后执行刷新
//刷新把isfresh改为true
isfresh = true;
await Future.delayed(Duration(seconds: 3),(){
setState(() {
//数据清空再重新添加8条数据
list.clear();
list.addAll(List.generate(8, (i){
return i;
}));
});
});
}

为了看到刷新效果,当刷新的时候,因为isfresh为true,收藏图标♥️改为红色,否则是黑色:

//布局三开始第一行
Widget LayoutThreeOne = Row(
children: [
Expanded(
child: Row(
children: [
Text(‘作者:’),
Text(‘HuYounger’,
style: getTextStyle(Colors.redAccent[400], 14, false),
),
],
)
),
//收藏图标 改为以下
getPaddingfromLTRB(Icon(Icons.favorite,color:isfresh ? Colors.red : Colors.black),r:10.0),
//分享图标
Icon(Icons.share,color:Colors.black),
],
);

效果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

4.2.上拉加载

Flutter中加载更多的组件没有是提供的,那就要自己来实现,我的思路是,当监听滑到底部时,到底底部就要做加载处理。而ListViewScrollController这个属性来控制ListView的滑动事件,在initState添加监听是否到达底部,并且添加上拉加载更多方法:

class HomeWidget extends State {

//ListView控制器
ScrollController _controller = ScrollController();
//这个方法只会调用一次,在这个Widget被创建之后,必须调用super.initState()
@override
void initState(){
super.initState();
//初始化数据
initData();
//添加监听
_controller.addListener((){
//这里判断滑到底部第一个条件就可以了,加上不在刷新和不是上滑加载
if(_controller.position.pixels == _controller.position.maxScrollExtent){
//滑到底部了
_onGetMoreData();
}
});
}
}

//上拉加载更多方法 每次加8条数据
Future _onGetMoreData() async{
print(‘进入上拉加载方法’);
isfresh = false;
if(list.length <=30){
await Future.delayed(Duration(seconds: 2),(){
setState(() {
//加载数据
//这里添加8项
list.addAll(List.generate(8, (i){
return i;
}));

});
});

}
}

//State删除对象时调用Dispose,这是永久性 移除监听 清理环境
@override
void dispose(){
super.dispose();
_controller.dispose();
}

最后在ListView.builde下增加controller属性:

return new Scaffold(
appBar: new AppBar(
title: new Text(‘Flutter Demo’),
),
body: RefreshIndicator(
onRefresh: _onRefresh,
//ListView提供一个builder属性
child: ListView.builder(

itemBuilder: _itemColumn,
//控制器 上拉加载
controller: _controller,
),
),
);

上面代码已经实现下拉加载更多,但是没有任何交互,我们知道,软件当上拉加载都会有提示,那下面增加一个加载更多的提示圆圈:


//是否隐藏底部
bool isBottomShow = false;
//加载状态
String statusShow = ‘加载中…’;

//上拉加载更多方法
Future _onGetMoreData() async{
print(‘进入上拉加载方法’);
isBottomShow = false;
isfresh = false;
if(list.length <=30){
await Future.delayed(Duration(seconds: 2),(){
setState(() {
//加载数据
//这里添加8项
list.addAll(List.generate(8, (i){
return i;
}));
});
});
}else{
//假设已经没有数据了
await Future.delayed(Duration(seconds: 3),(){
setState(() {
isBottomShow = true;
});
});

}

//显示’加载更多’,显示在界面上
Widget _GetMoreDataWidget(){
return Center(
child: Padding(
padding:EdgeInsets.all(12.0),
// Offstage就是实现加载后加载提示圆圈是否消失
child:new Offstage(
// widget 根据isBottomShow这个值来决定显示还是隐藏
offstage: isBottomShow,
child:
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
//根据状态来显示什么
statusShow,
style:TextStyle(
color: Colors.grey[300],
fontSize: 16.0,
)
),
//加载圆圈
CircularProgressIndicator(
strokeWidth: 2.0,
)
],
),
)

),
);
}

可以看到,上面用了OffstageWidget里的offstage属性来控制加载提示圆圈是否显示,isBottomShow如果是true,加载圆圈就会消失,false就会显示。并且statusShow来显示加载中的状态,然后要在集合长度加一,也就是给ListView添加尾部:

return new Scaffold(
appBar: new AppBar(
title: new Text(‘Flutter Demo’),
),
body: RefreshIndicator(
onRefresh: _onRefresh,
//ListView提供一个builder属性
child: ListView.builder(
//数目 加上尾部加载更多list就要加1了
itemCount: list.length + 1,
//itemBuilder是一个匿名回调函数,有两个参数,BuildContext 和迭代器index
//和ListView的Item项类似 迭代器从0开始 每调用一次这个函数,迭代器就会加1
itemBuilder: _itemColumn,
//控制器
controller: _controller,
),
),
);

效果如下图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

4.3.ListView.separated

基本还可以,把上滑加载的提示圈加上去了,做到这里,我在想,有时候ListView并不是每一条Item养生都是一样的,哪有没有属性是设置在不同位置插入不同的Item呢?答案是有的,那就是ListView.separatedListView.separated就是在Android中adapter不同类型的itemView。用法如下:

body: new ListView.separated(
//普通项
itemBuilder: (BuildContext context, int index) {
return new Text(“text $index”);
},
//插入项
separatorBuilder: (BuildContext context, int index) {
return new Container(height: 1.0, color: Colors.red);
},
//数目
itemCount: 40),

自己例子实现一下:

//ListView item 布局二
Widget cardWidget_two = Card(
child: Container(
//alignment: Alignment(0.0, 0.0),
height: 160.0,
color: Colors.white,
padding: EdgeInsets.all(10.0),
child: Center(
// 布局一
child: ColumnWidget,
)
),
);

return new Scaffold(
appBar: new AppBar(
title: new Text(‘Flutter Demo’),
),
body: RefreshIndicator(
onRefresh: _onRefresh,
//ListView提供一个builder属性
child: ListView.separated(
itemBuilder: (BuildContext context,int index){
return _itemColumn(context,index);

},
separatorBuilder: (BuildContext context,int index){
return Column(
children: [
cardWidget_two
],
);
},
itemCount: list.length + 1,
controller: _controller,
),

把一开始实现的布局一作为item插入ListView,效果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

发现上面的代码是两个不同类型item项交互插入在ListView中,下面试一下每隔3项才插一条试试看:

return new Scaffold(
appBar: new AppBar(
title: new Text(‘Flutter Demo’),
),
body: RefreshIndicator(
onRefresh: _onRefresh,
//ListView提供一个builder属性
child: ListView.separated(
itemBuilder: (BuildContext context,int index){
return _itemColumn(context,index);

},
separatorBuilder: (BuildContext context,int index){
return Column(
children: [
(index + 1) % 3 == 0 ? cardWidget_two : Container()
//cardWidget_two
],
);
},
itemCount: list.length + 1,
controller: _controller,
),
);

效果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

三、交互

1.自带交互的控件

Flutter中,自带如点击事件的控件有RaisedButtonIconButtonOutlineButtonCheckboxSnackBarSwitch等,如下面给OutlineButton添加点击事件:

body:Center(
child: OutlineButton(
child: Text(‘点击我’),
onPressed: (){
Fluttertoast.showToast(
msg: ‘你点击了FlatButton’,
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
timeInSecForIos: 1,
);
}),
),

上面代码就可以捕捉OutlineButton的点击事件。

2.不自带交互的控件

很多控件不像RaisedButtonOutlineButton等已经对presses(taps)或手势做出了响应。那么如果要监听这些控件的手势就需要用另一个控件GestureDetector,那看看源码GestureDetector支持哪些手势:

GestureDetector({
Key key,
this.child,
this.onTapDown,//按下,每次和屏幕交互都会调用
this.onTapUp,//抬起,停止触摸时调用
this.onTap,//点击,短暂触摸屏幕时调用
this.onTapCancel,//取消 触发了onTapDown,但没有完成onTap
this.onDoubleTap,//双击,短时间内触摸屏幕两次
this.onLongPress,//长按,触摸时间超过500ms触发
this.onLongPressUp,//长按松开
this.onVerticalDragDown,//触摸点开始和屏幕交互,同时竖直拖动按下
this.onVerticalDragStart,//触摸点开始在竖直方向拖动开始
this.onVerticalDragUpdate,//触摸点每次位置改变时,竖直拖动更新
this.onVerticalDragEnd,//竖直拖动结束
this.onVerticalDragCancel,//竖直拖动取消
this.onHorizontalDragDown,//触摸点开始跟屏幕交互,并水平拖动
this.onHorizontalDragStart,//水平拖动开始,触摸点开始在水平方向移动
this.onHorizontalDragUpdate,//水平拖动更新,触摸点更新
this.onHorizontalDragEnd,//水平拖动结束触发
this.onHorizontalDragCancel,//水平拖动取消 onHorizontalDragDown没有成功触发
//onPan可以取代onVerticalDrag或者onHorizontalDrag,三者不能并存
this.onPanDown,//触摸点开始跟屏幕交互时触发
this.onPanStart,//触摸点开始移动时触发
this.onPanUpdate,//屏幕上的触摸点位置每次改变时,都会触发这个回调
this.onPanEnd,//pan操作完成时触发
this.onPanCancel,//pan操作取消
//onScale可以取代onVerticalDrag或者onHorizontalDrag,三者不能并存,不能与onPan并存
this.onScaleStart,//触摸点开始跟屏幕交互时触发,同时会建立一个焦点为1.0
this.onScaleUpdate,//跟屏幕交互时触发,同时会标示一个新的焦点
this.onScaleEnd,//触摸点不再跟屏幕交互,标示这个scale手势完成
this.behavior,
this.excludeFromSemantics = false
})

这里注意:onVerticalXXX/onHorizontalXXXonPanXXX不能同时设置,如果同时需要水平、竖直方向的移动,设置onPanXXX。直接上例子:

2.1.onTapXXX

child: GestureDetector(
child: Container(
width: 300.0,
height: 300.0,
color:Colors.red,
),
onTapDown: (d){
print(“onTapDown”);
},
onTapUp: (d){
print(“onTapUp”);
},
onTap:(){
print(“onTap”);
},
onTapCancel: (){
print(“onTaoCancel”);
},
)

点了一下,并且抬起,结果是:

I/flutter (16304): onTapDown
I/flutter (16304): onTapUp
I/flutter (16304): onTap
先触发onTapDown 然后onTapUp 继续onTap

2.2.onLongXXX

//手势测试
Widget gestureTest = GestureDetector(
child: Container(
width: 300.0,
height: 300.0,
color:Colors.red,
),
onDoubleTap: (){
print(“双击onDoubleTap”);
},
onLongPress: (){
print(“长按onLongPress”);
},
onLongPressUp: (){
print(“长按抬起onLongPressUP”);
},

);

实际结果:

I/flutter (16304): 长按onLongPress
I/flutter (16304): 长按抬起onLongPressUP
I/flutter (16304): 双击onDoubleTap

2.3.onVerticalXXX

//手势测试
Widget gestureTest = GestureDetector(
child: Container(
width: 300.0,
height: 300.0,
color:Colors.red,
),
onVerticalDragDown: (){
print(“竖直方向拖动按下onVerticalDragDown:”+
.globalPosition.toString());
},
onVerticalDragStart: (){
print(“竖直方向拖动开始onVerticalDragStart”+
.globalPosition.toString());
},
onVerticalDragUpdate: (){
print(“竖直方向拖动更新onVerticalDragUpdate”+
.globalPosition.toString());
},
onVerticalDragCancel: (){
print(“竖直方向拖动取消onVerticalDragCancel”);
},
onVerticalDragEnd: (_){
print(“竖直方向拖动结束onVerticalDragEnd”);
},

);

输出结果:

I/flutter (16304): 竖直方向拖动按下onVerticalDragDown:Offset(191.7, 289.3)
I/flutter (16304): 竖直方向拖动开始onVerticalDragStartOffset(191.7, 289.3)
I/flutter (16304): 竖直方向拖动更新onVerticalDragUpdateOffset(191.7, 289.3)
I/flutter (16304): 竖直方向拖动更新onVerticalDragUpdateOffset(191.7, 289.3)
I/flutter (16304): 竖直方向拖动更新onVerticalDragUpdateOffset(191.7, 289.3)
I/flutter (16304): 竖直方向拖动更新onVerticalDragUpdateOffset(191.7, 289.3)
I/flutter (16304): 竖直方向拖动更新onVerticalDragUpdateOffset(191.7, 289.3)
I/flutter (16304): 竖直方向拖动更新onVerticalDragUpdateOffset(191.3, 290.0)
I/flutter (16304): 竖直方向拖动更新onVerticalDragUpdateOffset(191.3, 291.3)
I/flutter (16304): 竖直方向拖动结束onVerticalDragEnd

2.4.onPanXXX

//手势测试
Widget gestureTest = GestureDetector(
child: Container(
width: 300.0,
height: 300.0,
color:Colors.red,
),
onPanDown: (){
print(“onPanDown”);
},
onPanStart: (
){
print(“onPanStart”);
},
onPanUpdate: (){
print(“onPanUpdate”);
},
onPanCancel: (){
print(“onPanCancel”);
},
onPanEnd: (
){
print(“onPanEnd”);
},

);

无论竖直拖动还是横向拖动还是一起来,结果如下:

I/flutter (16304): onPanDown
I/flutter (16304): onPanStart
I/flutter (16304): onPanUpdate
I/flutter (16304): onPanUpdate
I/flutter (16304): onPanEnd

2.5.onScaleXXX

//手势测试
Widget gestureTest = GestureDetector(
child: Container(
width: 300.0,
height: 300.0,
color:Colors.red,
),
onScaleStart: (){
print(“onScaleStart”);
},
onScaleUpdate: (
){
print(“onScaleUpdate”);
},
onScaleEnd: (_){
print(“onScaleEnd”);

);

无论点击、竖直拖动、水平拖动,结果如下:

I/flutter (16304): onScaleStart
I/flutter (16304): onScaleUpdate
I/flutter (16304): onScaleUpdate
I/flutter (16304): onScaleUpdate
I/flutter (16304): onScaleUpdate
I/flutter (16304): onScaleUpdate
I/flutter (16304): onScaleUpdate
I/flutter (16304): onScaleUpdate
I/flutter (16304): onScaleEnd

3.原始指针事件

除了GestureDetector能够监听触摸事件外,Pointer代表用户与设备屏幕交互的原始数据,也就是也能监听手势:

  1. PointerDownEvent:指针接触到屏幕的特定位置
  2. PointerMoveEvent:指针从屏幕上的一个位置移动到另一个位置
  3. PointMoveEvent:指针停止接触屏幕
  4. PointUpEvent:指针停止接触屏幕
  5. PointerCancelEvent:指针的输入事件不再针对此应用

上代码:

//Pointer
Widget TestContainer = Listener(
child:Container(
width: 300.0,
height: 300.0,
color:Colors.red,
),
onPointerDown: (event){
print(“onPointerDown”);
},
onPointerUp: (event){
print(“onPointerUp”);
},
onPointerMove: (event){
print(“onPointerMove”);
},
onPointerCancel: (event){
print(“onPointerCancel”);
},

);

在屏幕上点击,或者移动:

I/flutter (16304): onPointerDown
I/flutter (16304): onPointerMovee
I/flutter (16304): onPointerMove
I/flutter (16304): onPointerMoves
I/flutter (16304): onPointerMove
I/flutter (16304): onPointerUp

发现也是可以监听手势的。

4.路由(页面)跳转

Android原生中,页面跳转是通过startActvity()来跳转不同页面,而在Flutter就不一样。Flutter中,跳转页面有两种方式:静态路由方式和动态路由方式。在Flutter管理多个页面有两个核心概念和类:RouteNavigator。一个route是一个屏幕或者页面的抽象,Navigator是管理routeWidgetNavigator可以通过route入栈和出栈来实现页面之间的跳转。

4.1.静态路由
4.1.1.配置路由

在原页面配置路由跳转,就是在MaterialApp里设置每个route对应的页面,注意:一个app只能有一个材料设计(MaterialApp),不然返回上一个页面会黑屏。代码如下:

//入口页面
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
//静态路由方式 配置初始路由
initialRoute: ‘/’,
routes: {
//默认走这个条件/
‘/’:(context){
return HomeStateful();
},
//新页面路由
‘/mainnewroute’:(context){
return new newRoute();
}
},
//主题色
theme: ThemeData(
//设置为红色
primarySwatch: Colors.red),
//配置了初始路由,下面就不需要了
//home: HomeStateful(),
);
}
}

因为配置了初始路由,所以home:HomeStateful就不用配置了。

4.1.2.点击跳转

//如果新页面不在同一个类中,记得把它导入
import ‘mainnewroute.dart’;
class HomeStateful extends StatefulWidget{
@override
State createState(){
return new HomeWidget();
}

}

class HomeWidget extends State {
@override
Widget build(BuildContext context) {

//Pointer
Widget TestContainer = Listener(
child:Container(
width: 300.0,
height: 300.0,
color:Colors.red,
child: RaisedButton(
child: Text(‘点击我’),
onPressed: (){
//页面跳转方法
Navigator.of(context).pushNamed(‘/mainnewroute’);
}),
),
);
return new Scaffold(
appBar: new AppBar(
title: new Text(‘Flutter Demo’),
),
body:Center(
child: TestContainer,
),
);
}
}

RaisedButton配置了点击方法,上面用了Navigator.of(context).pushNamed('/mainnewroute'),执行到这句,路由会找routes有没有配置/mainnewroute,有的话,就会根据配置跳到新的页面。

4.1.3.配置新页面

新页面,我在lib下建立一个新的文件(页面)mainfourday.dart,很简单:

import ‘package:flutter/material.dart’;
class newRoute extends StatelessWidget{

@override
Widget build(BuildContext context){
return HomeWidget();
//注意:不需要MaterialApp
// return MaterialApp(
// theme: ThemeData(
// //设置为hongse
// primarySwatch: Colors.red),
// home: HomeWidget(),
// );

}
}

class HomeWidget extends StatelessWidget{

@override
Widget build(BuildContext context){
return Scaffold(
appBar: AppBar(
title: Text(‘new Route’),
),
body: Center(
child:RaisedButton(
child: Text(‘返回’),
onPressed: (){
//这是关闭页面
Navigator.pop(context);
}),
// child: Text(‘这是新的页面’),
),
);
}
}

最终效果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

4.2.动态路由

下面说一下跳转页面的第二种方式,动态路由方式:

child: RaisedButton(
child: Text(‘点击我’),
onPressed: (){
//Navigator.of(context).pushNamed(‘/mainnewroute’);
//动态路由
Navigator.push(
context,
MaterialPageRoute(builder: (newPage){
return new newRoute();
}),
);
}),

效果和上面是一样的。

4.3.页面传递数据

两种方式都是传递参数的,直接上动态路由传递数据代码:

Navigator.push(
context,
MaterialPageRoute(builder: (newPage){
return new newRoute(“这是一份数据到新页面”);
}),
);

在新页面改为如下:

import ‘package:flutter/material.dart’;
class newRoute extends StatelessWidget{
//接收上一个页面传递的数据
String str;
//构造函数
newRoute(this.str);

@override
Widget build(BuildContext context){
return HomeWidget(str);
}
}

class HomeWidget extends StatelessWidget{
String newDate;
HomeWidget(this.newDate);

@override
Widget build(BuildContext context){
return Scaffold(
appBar: AppBar(
title: Text(‘new Route’),
),
body: Center(
child:RaisedButton(
//显示上一个页面所传递的数据
child: Text(newDate),
onPressed: (){
Navigator.pop(context);
}),
// child: Text(‘这是新的页面’),
),
);
}
}

静态路由方式传递参数,也就是在newRoute()加上所要传递的参数就可以了

//新页面路由
‘/mainnewroute’:(context){
return new newRoute(“sdsd”);
}

4.4.页面返回数据

传递数据给新页面可以了,那么怎样将新页面数据返回上一个页面呢?也是很简单,在返回方法pop加上所要返回的数据即可:

body: Center(
child:RaisedButton(
//显示上一个页面所传递的数据
child: Text(newDate),
onPressed: (){
Navigator.pop(context,“这是新页面返回的数据”);

总结

算法知识点繁多,企业考察的题目千变万化,面对越来越近的“金九银十”,我给大家准备好了一套比较完善的学习方法,希望能帮助大家在有限的时间里尽可能系统快速的恶补算法,通过高效的学习来提高大家面试中算法模块的通过率。

这一套学习资料既有文字档也有视频,里面不仅仅有关键知识点的整理,还有案例的算法相关部分的讲解,可以帮助大家更好更全面的进行学习,二者搭配起来学习效果会更好。

部分资料展示:




有了这套学习资料,坚持刷题一周,你就会发现自己的算法知识体系有明显的完善,离大厂Offer的距离更加近。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注Android)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

idget{
String newDate;
HomeWidget(this.newDate);

@override
Widget build(BuildContext context){
return Scaffold(
appBar: AppBar(
title: Text(‘new Route’),
),
body: Center(
child:RaisedButton(
//显示上一个页面所传递的数据
child: Text(newDate),
onPressed: (){
Navigator.pop(context);
}),
// child: Text(‘这是新的页面’),
),
);
}
}

静态路由方式传递参数,也就是在newRoute()加上所要传递的参数就可以了

//新页面路由
‘/mainnewroute’:(context){
return new newRoute(“sdsd”);
}

4.4.页面返回数据

传递数据给新页面可以了,那么怎样将新页面数据返回上一个页面呢?也是很简单,在返回方法pop加上所要返回的数据即可:

body: Center(
child:RaisedButton(
//显示上一个页面所传递的数据
child: Text(newDate),
onPressed: (){
Navigator.pop(context,“这是新页面返回的数据”);

总结

算法知识点繁多,企业考察的题目千变万化,面对越来越近的“金九银十”,我给大家准备好了一套比较完善的学习方法,希望能帮助大家在有限的时间里尽可能系统快速的恶补算法,通过高效的学习来提高大家面试中算法模块的通过率。

这一套学习资料既有文字档也有视频,里面不仅仅有关键知识点的整理,还有案例的算法相关部分的讲解,可以帮助大家更好更全面的进行学习,二者搭配起来学习效果会更好。

部分资料展示:

[外链图片转存中…(img-5M2XlXv4-1713472214581)]
[外链图片转存中…(img-4T1gx6pE-1713472214581)]
[外链图片转存中…(img-WqORKFpx-1713472214581)]
[外链图片转存中…(img-S4CF17Yo-1713472214582)]

有了这套学习资料,坚持刷题一周,你就会发现自己的算法知识体系有明显的完善,离大厂Offer的距离更加近。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注Android)
[外链图片转存中…(img-vcDR3w6d-1713472214582)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 27
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值