- 在大前端的开发中,必然存在各种各样与用户交互的情况,比如手指点击,滑动,长按等等;
- 在Flutter中,手势有两个不同的层次:
- 第一层:原始指针事件,描述了屏幕上由触摸板,鼠标,指示笔等触发的指针的位置和移动;
- 第二层:手势识别,这个是在原始事件上的一种封装;
指针事件Pointer- Pointer表示人机界面的原始数据,一共有四种指针事件:
PointerDownEvent :指针在特定位置与屏幕接触;PointerMoveEnent :指针从屏幕的一个位置移动到另一个位置;PointerUpEvent :指针与屏幕停止接触;PointerCancelEvent :指针因为某些特殊的情况被取消;
- Pointer事件的原理:在指针落下时,框架做了一个Hit Test操作,确定与屏幕发生接触的位置上有哪些widget以及分发给最内部的组件去响应;事件会沿着最内部的组件向组件树的根冒泡分发;不能取消或者停止事件的进一步分发;
- 基本上与iOS的事件传递与响应机制的原理一致;
- 案例代码:
import 'package:flutter/material.dart';main() => runApp(SFMyApp());class SFMyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: SFHomePage(), ); }}class SFHomePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("商品列表"), ), body: Center( child: Listener( onPointerDown: (event) { print("手指按下: $event"); print(event.position); print(event.localPosition); }, onPointerMove: (event) { print("手指移动: $event"); }, onPointerUp: (event) { print("手指抬起: $event"); }, onPointerCancel: (event) { print("取消 $event"); }, child: Container( width: 100, height: 100, color: Colors.red, ), ), ) ); }}
- 给目标组件
Container 包装一个事件监听Listener ,内部实现各种不同事件的回调方法; event.position :是以屏幕左上角为原点;event.localPosition :是以目标组件Container 的左上角为原点;
手势识别- 手势是对一系列Pointer的封装,官方建议在开发中尽可能使用手势,而不是Pointer;
手势的分类:- 点击
- OnTapDown:手指按下
- OnTapUp:手指抬起
- OnTap:点击事件完成
- OnTapCancel:事件取消
- 双击
- 长按
- 纵向拖拽
- OnVerticalDragStart:指针与屏幕产生接触并可能开始纵向移动;
- OnVerticalDragUpdate:指针与屏幕产生接触,在纵向上发生移动并保持移动;
- OnVerticalDragEnd:指针与屏幕产生接触结束;
- 横向拖拽
- OnHorizontalDragStart:指针与屏幕产生接触并可能开始横向移动;
- OnHorizontalDragUpdate:指针与屏幕产生接触,在横向上发生移动并保持移动;
- OnHorizontalDragEnd:指针与屏幕产生接触结束;
- 移动
- OnPathStart:指针与屏幕产生接触并可能开始横向移动或者纵向移动,如果设置了onHorizontalDragStart或者onVerticalDragStart,该回调会
引发崩溃 ; - OnPathUpdate:指针与屏幕产生接触,在横向或者纵向上发生移动并保持移动,如果设置了OnHorizontalDragUpdate或者OnVerticalDragUpdate,该回调会
引发崩溃 ; - OnPathEnd:指针与屏幕产生了接触,并以特定的速度移动,此后不再在屏幕上接触发生移动,如果设置了OnHorizontalDragEnd或者OnVerticalDragEnd,该回调会
引发崩溃 ;
- 案例演练:
import 'package:flutter/material.dart';main() => runApp(SFMyApp());class SFMyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: SFHomePage(), ); }}class SFHomePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("商品列表"), ), body: Center( child: GestureDetector( onTapDown: (details) { print("手指按下"); print(details.globalPosition); print(details.localPosition); }, onTapUp: (details) { print("手指抬起"); }, onTapCancel: () { print("手势取消"); }, onTap: () { print("点击"); }, onDoubleTap: () { print("双击"); }, onLongPress: () { print("长按"); }, child: Container( width: 200, height: 200, color: Colors.greenAccent, ), ), ) ); }}
- 给目标组件Container,包装一个
GestureDetector 手势管理器,实现各种手势的监听回调函数;
案例演练:组件重叠显示时的手势识别import 'package:flutter/material.dart';main() => runApp(SFMyApp());class SFMyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: SFHomePage(), ); }}class SFHomePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("商品列表"), ), body: Center( child: GestureDetector( onTapDown: (details) { print("outside click"); }, child: Container( width: 200, height: 200, color: Colors.red, alignment: Alignment.center, child: GestureDetector( onTapDown: (details) { print("inside click"); }, child: Container( width: 100, height: 100, color: Colors.green, ), ), ), ), ) ); }}
- 两个组件都加了手势监听,当点击绿色组件的时候,红色组件偶尔也会出现监听回调,为了解决这个问题,改变组件的布局层级,利用Stack组件,代码如下:
import 'package:flutter/material.dart';main() => runApp(SFMyApp());class SFMyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: SFHomePage(), ); }}class SFHomePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("商品列表"), ), body: Center( child: Stack( alignment: Alignment.center, children: [ GestureDetector( onTapDown: (details) { print("red click"); }, child: Container( width: 200, height: 200, color: Colors.red, alignment: Alignment.center, ) ), GestureDetector( onTapDown: (details) { print("greenAccent click"); }, child: Container( width: 100, height: 100, color: Colors.green, ), ) ], ) ) ); }}
import 'package:flutter/material.dart';main() => runApp(SFMyApp());class SFMyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: SFHomePage(), ); }}class SFHomePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("商品列表"), ), body: Center( child: Stack( alignment: Alignment.center, children: [ GestureDetector( onTapDown: (details) { print("red click"); }, child: Container( width: 200, height: 200, color: Colors.red, alignment: Alignment.center, ) ), IgnorePointer( child: GestureDetector( onTapDown: (details) { print("greenAccent click"); }, child: Container( width: 100, height: 100, color: Colors.green, ), ), ) ], ) ) ); }}
- 给绿色组件 最外层包裹了一个
IgnorePointer ,实现了手势事件的忽略;
跨组件事件的传递- 使用第三方库
event_bus 实现跨组件事件的传递; - 工程中引用
event_bus :
dependencies: event_bus: ^1.1.1
- 使用步骤:
1.创建全局的EventBus对象; 2.发出事件; - 监听事件的类型;
- 案例代码如下:
import 'package:event_bus/event_bus.dart';import 'package:flutter/material.dart';//1.创建全局的EventBus对象final EventBus eventBus = EventBus();class SFUserInfo { String name; int level; SFUserInfo(this.name,this.level);}main() => runApp(SFMyApp());class SFMyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: SFHomePage(), ); }}class SFHomePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("商品列表"), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ SFButton(), SFText(), ], ), ) ); }}class SFButton extends StatelessWidget { @override Widget build(BuildContext context) { return RaisedButton( child: Text("按钮"), onPressed: () { print("点击按钮"); //2.发出事件 final userInfo = SFUserInfo("liyanyan",100); eventBus.fire(userInfo); }, ); }}class SFText extends StatefulWidget { @override _SFTextState createState() => _SFTextState();}class _SFTextState extends State { String _message = "Hello World"; @override void initState() { // TODO: implement initState super.initState(); //3.监听事件的类型 eventBus.on().listen((data) { print(data.name); print(data.level); _message = "${data.name} -- ${data.level}"; }); } @override Widget build(BuildContext context) { return Text("Hello World!!!",style: TextStyle(fontSize: 25)); }}
路由管理- 路由的概念由来已久,包括
网络路由 ,后端路由 ,到现在广为流行的前端路由 ; - 无论路由的概念如何应用,它的核心是一个
路由映射表 ,例如名字detail 映射到DetailPage 页面; - 有了映射表之后,我们就可以方便的根据名字来完成路由的转发,即页面跳转;
- 在Flutter中,路由管理主要有两个类
Route 与Navigator
Route- Route:一个页面要想被路由统一管理,必须包装为一个Route,Route是一个抽象类,所以只能实例化其子类;
MaterialPageRoute 是使用最广泛的路由,其在不同的平台会有不同的表现:- 在Android平台,打开一个页面从屏幕底部滑到屏幕顶部;
- 在iOS平台,打开一个页面从屏幕右侧滑到屏幕左侧;
- 在iOS平台还可以使用
CupertinoPageRoute
Navigator- Navigator:管理所有的Route的widget,通过一个栈进行管理的;
- 在开发中我们不需要手动去创建一个Navigator,因为在开发中使用的MaterialApp,CupertinoApp,WidgetsApp它们默认是有插入Navigator的,所以,我们在需要的时候,只需要直接使用即可;
路由的基本使用- 需求:首页与详情页两个页面,当首页->详情页 携带数据过去,当详情页 -> 首页 传递数据给首页,代码如下:
- 首页代码:
import 'package:flutter/cupertino.dart';import 'package:flutter/material.dart';import 'package:learn_flutter_01/day02/SFDetail.dart';main() => runApp(SFMyApp());class SFMyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: SFHomePage(), ); }}class SFHomePage extends StatefulWidget { @override _SFHomePageState createState() => _SFHomePageState();}class _SFHomePageState extends State { String homeMessage = "default message"; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("首页"), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(homeMessage,style: TextStyle(fontSize: 20)), RaisedButton( child: Text("跳转到详情页面"), onPressed: () { goToDetail(context); }, ) ], ) ) ); } void goToDetail(BuildContext ctx) { Future result = Navigator.of(ctx).push(MaterialPageRoute( builder: (ctx) { return SFDetail("liyanyan"); } )); result.then((value) { print(value); setState(() { homeMessage = value; }); }); }}
import 'package:flutter/cupertino.dart';import 'package:flutter/material.dart';class SFDetail extends StatelessWidget { final String message; SFDetail(this.message); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("详情页"), leading: IconButton( icon: Icon(Icons.backspace), onPressed: () => backHomePage(context), ), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(message,style: TextStyle(fontSize: 20)), RaisedButton( child: Text("返回首页"), onPressed: () => backHomePage(context), ) ], ) ), ); } void backHomePage(BuildContext ctx) { Navigator.of(ctx).pop("xie shen fang"); }}
- 首页 -> 详情页 创建
MaterialPageRoute 路由
void goToDetail(BuildContext ctx) { Future result = Navigator.of(ctx).push(MaterialPageRoute( builder: (ctx) { return SFDetail("liyanyan"); } )); result.then((value) { print(value); setState(() { homeMessage = value; }); }); }
void backHomePage(BuildContext ctx) { Navigator.of(ctx).pop("xie shen fang"); }
- 在详情页
去除了系统提供的返回按钮 ,使用自己的按钮; - 在详情页,使用系统的提供返回的按钮,但是我们要重写点击事件的逻辑,可以使用
WillPopScope ,如下所示:
import 'package:flutter/cupertino.dart';import 'package:flutter/material.dart';class SFDetail extends StatelessWidget { final String message; SFDetail(this.message); @override Widget build(BuildContext context) { return WillPopScope( onWillPop: () { //当返回为true时,可以自动返回; //当返回为false时,不能自动返回首页,需要我们手写代码进行返回; backHomePage(context); return Future.value(false); }, child: Scaffold( appBar: AppBar( title: Text("详情页"), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(message,style: TextStyle(fontSize: 20)), RaisedButton( child: Text("返回首页"), onPressed: () => backHomePage(context), ) ], ) ), ), ); } void backHomePage(BuildContext ctx) { Navigator.of(ctx).pop("xie shen fang"); }}
onWillPop 参数,传入函数,其函数的返回值为Future.value(bool)
当bool = true时,可以自动返回; 当bool = false时,不能自动返回首页,需要我们手写代码进行返回; 上述采用的简单的路由跳转,下面介绍路由表映射的跳转,再新建一个SFAbout 页面:
import 'package:flutter/cupertino.dart';import 'package:flutter/material.dart';class SFAbout extends StatelessWidget { static const String routeName = "SFAbout"; @override Widget build(BuildContext context) { final message = ModalRoute.of(context).settings.arguments as String; return Scaffold( appBar: AppBar( title: Text("关于页面"), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(message), RaisedButton( child: Text("返回首页"), onPressed: () { }, ) ], ), ), ); }}
import 'package:flutter/cupertino.dart';import 'package:flutter/material.dart';import 'package:learn_flutter_01/day02/SFAbout.dart';import 'package:learn_flutter_01/day02/SFDetail.dart';main() => runApp(SFMyApp());class SFMyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( routes: { SFHomePage.routeName : (ctx) => SFHomePage(), SFAbout.routeName : (ctx) => SFAbout() }, initialRoute: SFHomePage.routeName ); }}class SFHomePage extends StatefulWidget { static const String routeName = "/"; @override _SFHomePageState createState() => _SFHomePageState();}class _SFHomePageState extends State { String homeMessage = "default message"; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("首页"), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(homeMessage,style: TextStyle(fontSize: 20)), RaisedButton( child: Text("跳转到详情页面"), onPressed: () { goToDetail(context); }, ), RaisedButton( child: Text("跳转到关于页面"), onPressed: () { goToAbout(context); }, ) ], ) ) ); } void goToDetail(BuildContext ctx) { Future result = Navigator.of(ctx).push(MaterialPageRoute( builder: (ctx) { return SFDetail("liyanyan"); } )); result.then((value) { print(value); setState(() { homeMessage = value; }); }); } void goToAbout(BuildContext ctx) { Future result = Navigator.of(context).pushNamed(SFAbout.routeName,arguments: "liyanyan"); result.then((value) { print(value); }); }}
- 在
MaterialApp 组件中,传入routes 与initialRoute 两个参数,其中routes 表示路由表,initialRoute 表示App的首页路由为SFHomePage(); - 在首页 跳转 SFAbout页面时,执行:
Navigator.of(context).pushNamed(SFAbout.routeName,arguments: "liyanyan") 其中arguments为携带的数据; - 在SFAbout页面接收到首页的数据:
final message = ModalRoute.of(context).settings.arguments as String SFDetail 页面的构造函数,定义了一个入参,那么在MaterialApp 的routes ,写路由映射时,不能传参的,这时就引入了onGenerateRoute ,再次生成一个新的路由,可传参的,实现如下:
import 'package:flutter/cupertino.dart';import 'package:flutter/material.dart';import 'package:learn_flutter_01/day02/SFAbout.dart';import 'package:learn_flutter_01/day02/SFDetail.dart';main() => runApp(SFMyApp());class SFMyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( routes: { SFHomePage.routeName : (ctx) => SFHomePage(), SFAbout.routeName : (ctx) => SFAbout() }, initialRoute: SFHomePage.routeName, onGenerateRoute: (setttings) { if (setttings.name == SFDetail.routeName) { return MaterialPageRoute( builder: (ctx) { return SFDetail(setttings.arguments); } ); } return null; } ); }}class SFHomePage extends StatefulWidget { static const String routeName = "/"; @override _SFHomePageState createState() => _SFHomePageState();}class _SFHomePageState extends State { String homeMessage = "default message"; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("首页"), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(homeMessage,style: TextStyle(fontSize: 20)), RaisedButton( child: Text("跳转到详情页面"), onPressed: () { goToDetail(context); }, ), RaisedButton( child: Text("跳转到关于页面"), onPressed: () { goToAbout(context); }, ), RaisedButton( child: Text("跳转到详情页面"), onPressed: () { goToDetail2(context); }, ) ], ) ) ); } void goToDetail(BuildContext ctx) { Future result = Navigator.of(ctx).push(MaterialPageRoute( builder: (ctx) { return SFDetail("liyanyan"); } )); result.then((value) { print(value); setState(() { homeMessage = value; }); }); } void goToAbout(BuildContext ctx) { Future result = Navigator.of(context).pushNamed(SFAbout.routeName,arguments: "liyanyan"); result.then((value) { print(value); }); } void goToDetail2(BuildContext ctx) { Navigator.of(context).pushNamed(SFDetail.routeName,arguments: "123"); }}
- 如果跳转的页面 在路由映射表中 没有注册,就会跳转报错,一般我们定义一个错误页面
SFUnKnownPage ,代码如下:
import 'package:flutter/cupertino.dart';import 'package:flutter/material.dart';class SFUnKnownPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("错误页面"), ), body: Center( child: Text("错误页面"), ), ); }}
- 然后在
MaterialApp 中,新增参数onUnknownRoute ,传入错误页面的路由,如下所示:
import 'package:flutter/cupertino.dart';import 'package:flutter/material.dart';import 'package:learn_flutter_01/day02/SFAbout.dart';import 'package:learn_flutter_01/day02/SFDetail.dart';import 'package:learn_flutter_01/day02/SFUnknownPage.dart';main() => runApp(SFMyApp());class SFMyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( routes: { SFHomePage.routeName : (ctx) => SFHomePage(), SFAbout.routeName : (ctx) => SFAbout() }, initialRoute: SFHomePage.routeName, onGenerateRoute: (setttings) { if (setttings.name == SFDetail.routeName) { return MaterialPageRoute( builder: (ctx) { return SFDetail(setttings.arguments); } ); } return null; }, onUnknownRoute: (settings) { return MaterialPageRoute( builder: (ctx) { return SFUnKnownPage(); } ); }, ); }}class SFHomePage extends StatefulWidget { static const String routeName = "/"; @override _SFHomePageState createState() => _SFHomePageState();}class _SFHomePageState extends State { String homeMessage = "default message"; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("首页"), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(homeMessage,style: TextStyle(fontSize: 20)), RaisedButton( child: Text("跳转到详情页面"), onPressed: () { goToDetail(context); }, ), RaisedButton( child: Text("跳转到关于页面"), onPressed: () { goToAbout(context); }, ), RaisedButton( child: Text("跳转到详情页面"), onPressed: () { goToDetail2(context); }, ), RaisedButton( child: Text("跳转到设置页面"), onPressed: () { goToAbou2(context); }, ) ], ) ) ); } void goToDetail(BuildContext ctx) { Future result = Navigator.of(ctx).push(MaterialPageRoute( builder: (ctx) { return SFDetail("liyanyan"); } )); result.then((value) { print(value); setState(() { homeMessage = value; }); }); } void goToAbout(BuildContext ctx) { Future result = Navigator.of(context).pushNamed(SFAbout.routeName,arguments: "liyanyan"); result.then((value) { print(value); }); } void goToDetail2(BuildContext ctx) { Navigator.of(context).pushNamed(SFDetail.routeName,arguments: "123"); } void goToAbou2(BuildContext ctx) { ///setting为定义的页面 跳转页面会直接报错 Navigator.of(context).pushNamed("/settings"); }}
import 'package:flutter/material.dart';import 'package:learn_flutter_01/day02/SFAbout.dart';import 'package:learn_flutter_01/day02/SFDetail.dart';import 'package:learn_flutter_01/main.dart';import '../SFUnknownPage.dart';class SFRouter { static final Map routers = { SFHomePage.routeName : (ctx) => SFHomePage(), SFAbout.routeName : (ctx) => SFAbout(), }; static final String initialRoute = SFHomePage.routeName; static final RouteFactory generateRoute = (settings) { if (settings.name == SFDetail.routeName) { return MaterialPageRoute( builder: (ctx) { return SFDetail(settings.arguments); } ); } return null; }; static final RouteFactory unknownRoute = (settings) { return MaterialPageRoute( builder: (ctx) { return SFUnKnownPage(); } ); };}
import 'package:flutter/cupertino.dart';import 'package:flutter/material.dart';import 'package:learn_flutter_01/day02/SFAbout.dart';import 'package:learn_flutter_01/day02/SFDetail.dart';import 'package:learn_flutter_01/day02/SFUnknownPage.dart';import 'package:learn_flutter_01/day02/router/SFRouter.dart';main() => runApp(SFMyApp());class SFMyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( routes: SFRouter.routers, initialRoute: SFRouter.initialRoute, onGenerateRoute: SFRouter.generateRoute, onUnknownRoute: SFRouter.unknownRoute, ); }}class SFHomePage extends StatefulWidget { static const String routeName = "/"; @override _SFHomePageState createState() => _SFHomePageState();}class _SFHomePageState extends State { String homeMessage = "default message"; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("首页"), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(homeMessage,style: TextStyle(fontSize: 20)), RaisedButton( child: Text("跳转到详情页面"), onPressed: () { goToDetail(context); }, ), RaisedButton( child: Text("跳转到关于页面"), onPressed: () { goToAbout(context); }, ), RaisedButton( child: Text("跳转到详情页面"), onPressed: () { goToDetail2(context); }, ), RaisedButton( child: Text("跳转到设置页面"), onPressed: () { goToAbou2(context); }, ) ], ) ) ); } void goToDetail(BuildContext ctx) { Future result = Navigator.of(ctx).push(MaterialPageRoute( builder: (ctx) { return SFDetail("liyanyan"); } )); result.then((value) { print(value); setState(() { homeMessage = value; }); }); } void goToAbout(BuildContext ctx) { Future result = Navigator.of(context).pushNamed(SFAbout.routeName,arguments: "liyanyan"); result.then((value) { print(value); }); } void goToDetail2(BuildContext ctx) { Navigator.of(context).pushNamed(SFDetail.routeName,arguments: "123"); } void goToAbou2(BuildContext ctx) { ///setting为定义的页面 跳转页面会直接报错 Navigator.of(context).pushNamed("/settings"); }}
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |