网页开发人员招聘,唐山做网站优化公司,顺风顺水的公司名字,有没有一种网站做拍卖厂的1.原始指针事件处理
一次完整的事件分为三个阶段#xff1a;手指按下、手指移动、和手指抬起#xff0c;而更高级别的手势#xff08;如点击、双击、拖动等#xff09;都是基于这些原始事件的。
Listener 组件
Flutter中可以使用Listener来监听原始触摸事件
Listener({…1.原始指针事件处理
一次完整的事件分为三个阶段手指按下、手指移动、和手指抬起而更高级别的手势如点击、双击、拖动等都是基于这些原始事件的。
Listener 组件
Flutter中可以使用Listener来监听原始触摸事件
Listener({Key key,this.onPointerDown, //手指按下回调this.onPointerMove, //手指移动回调this.onPointerUp,//手指抬起回调this.onPointerCancel,//触摸事件取消回调this.behavior HitTestBehavior.deferToChild, //先忽略此参数后面小节会专门介绍Widget child
})示例手指在一个容器上移动时查看手指相对于容器的位置
class _PointerMoveIndicatorState extends StatePointerMoveIndicator {PointerEvent? _event;overrideWidget build(BuildContext context) {return Listener(child: Container(alignment: Alignment.center,color: Colors.blue,width: 300.0,height: 150.0,child: Text(${_event?.localPosition ?? },style: TextStyle(color: Colors.white),),),onPointerDown: (PointerDownEvent event) setState(() _event event),onPointerMove: (PointerMoveEvent event) setState(() _event event),onPointerUp: (PointerUpEvent event) setState(() _event event),);}
}PointerEvent常用属性
position它是指针相对于当对于全局坐标的偏移。localPosition: 它是指针相对于当对于本身布局坐标的偏移。delta两次指针移动事件PointerMoveEvent的距离。pressure按压力度如果手机屏幕支持压力传感器(如iPhone的3D Touch)此属性会更有意义如果手机不支持则始终为1。orientation指针移动方向是一个角度值。
忽略指针事件
不想让某个子树响应PointerEvent的话我们可以使用IgnorePointer和AbsorbPointerAbsorbPointer本身是可以接收指针事件的(但其子树不行)而IgnorePointer不可以
Listener(child: AbsorbPointer(child: Listener(child: Container(color: Colors.red,width: 200.0,height: 100.0,),onPointerDown: (event)print(in),),),onPointerDown: (event)print(up),
)点击Container时由于它在AbsorbPointer的子树上所以不会响应指针事件所以日志不会输出in 但AbsorbPointer本身是可以接收指针事件的所以会输出up。
如果将AbsorbPointer换成IgnorePointer那么两个都不会输出。
2.手势识别
GestureDetector和GestureRecognizer
点击、双击、长按
通过GestureDetector对Container进行手势识别触发相应事件后在Container上显示事件名
class _GestureTestState extends StateGestureTest {String _operation No Gesture detected!; //保存事件名overrideWidget build(BuildContext context) {return Center(child: GestureDetector(child: Container(alignment: Alignment.center,color: Colors.blue,width: 200.0,height: 100.0,child: Text(_operation,style: TextStyle(color: Colors.white),),),onTap: () updateText(Tap), //点击onDoubleTap: () updateText(DoubleTap), //双击onLongPress: () updateText(LongPress), //长按),);}void updateText(String text) {//更新显示的事件名setState(() {_operation text;});}
}拖动、滑动
拖动圆形字母A的示例
class _Drag extends StatefulWidget {override_DragState createState() _DragState();
}class _DragState extends State_Drag with SingleTickerProviderStateMixin {double _top 0.0; //距顶部的偏移double _left 0.0;//距左边的偏移overrideWidget build(BuildContext context) {return Stack(children: Widget[Positioned(top: _top,left: _left,child: GestureDetector(child: CircleAvatar(child: Text(A)),//手指按下时会触发此回调onPanDown: (DragDownDetails e) {//打印手指按下的位置(相对于屏幕)print(用户手指按下${e.globalPosition});},//手指滑动时会触发此回调onPanUpdate: (DragUpdateDetails e) {//用户手指滑动时更新偏移重新构建setState(() {_left e.delta.dx;_top e.delta.dy;});},onPanEnd: (DragEndDetails e){//打印滑动结束时在x、y轴上的速度print(e.velocity);},),)],);}
}I/flutter ( 8513): 用户手指按下Offset(26.3, 101.8)
I/flutter ( 8513): Velocity(235.5, 125.8)DragDownDetails.globalPosition当用户按下时此属性为用户按下的位置相对于屏幕而非父组件原点(左上角)的偏移。DragUpdateDetails.delta当用户在屏幕上滑动时会触发多次Update事件delta指一次Update事件的滑动的偏移量。DragEndDetails.velocity该属性代表用户抬起手指时的滑动速度(包含x、y两个轴的
单一方向拖动
GestureDetector可以只识别特定方向的手势事件我们将上面的例子改为只能沿垂直方向拖动
class _DragVertical extends StatefulWidget {override_DragVerticalState createState() _DragVerticalState();
}class _DragVerticalState extends State_DragVertical {double _top 0.0;overrideWidget build(BuildContext context) {return Stack(children: Widget[Positioned(top: _top,child: GestureDetector(child: CircleAvatar(child: Text(A)),//垂直方向拖动事件onVerticalDragUpdate: (DragUpdateDetails details) {setState(() {_top details.delta.dy;});},),)],);}
}缩放
class _Scale extends StatefulWidget {const _Scale({Key? key}) : super(key: key);override_ScaleState createState() _ScaleState();
}class _ScaleState extends State_Scale {double _width 200.0; //通过修改图片宽度来达到缩放效果overrideWidget build(BuildContext context) {return Center(child: GestureDetector(//指定宽度高度自适应child: Image.asset(./images/sea.png, width: _width),onScaleUpdate: (ScaleUpdateDetails details) {setState(() {//缩放倍数在0.8到10倍之间_width200*details.scale.clamp(.8, 10.0);});},),);}
}GestureDetector内部是使用一个或多个GestureRecognizer来识别各种手势的
而GestureRecognizer的作用就是通过Listener来将原始指针事件转换为语义手势
GestureDetector直接可以接收一个子widget
示例给一段富文本RichText的不同部分分别添加点击事件处理器但是TextSpan并不是一个widget这时我们不能用GestureDetector但TextSpan有一个recognizer属性它可以接收一个GestureRecognizer。
import package:flutter/gestures.dart;class _GestureRecognizer extends StatefulWidget {const _GestureRecognizer({Key? key}) : super(key: key);override_GestureRecognizerState createState() _GestureRecognizerState();
}class _GestureRecognizerState extends State_GestureRecognizer {TapGestureRecognizer _tapGestureRecognizer TapGestureRecognizer();bool _toggle false; //变色开关overridevoid dispose() {//用到GestureRecognizer的话一定要调用其dispose方法释放资源_tapGestureRecognizer.dispose();super.dispose();}overrideWidget build(BuildContext context) {return Center(child: Text.rich(TextSpan(children: [TextSpan(text: 你好世界),TextSpan(text: 点我变色,style: TextStyle(fontSize: 30.0,color: _toggle ? Colors.blue : Colors.red,),recognizer: _tapGestureRecognizer..onTap () {setState(() {_toggle !_toggle;});},),TextSpan(text: 你好世界),],),),);}
}3.Flutter事件机制
事件分发过程很简单即遍历HitTestResult调用每一个节点的 handleEvent 方法只需要重写 handleEvent 方法就可以处理事件了。
1.命中测试当手指按下时触发 PointerDownEvent 事件按照深度优先遍历当前渲染render object树对每一个渲染对象进行“命中测试”hit test如果命中测试通过则该渲染对象会被添加到一个 HitTestResult 列表当中。 2.事件分发命中测试完毕后会遍历 HitTestResult 列表调用每一个渲染对象的事件处理方法handleEvent来处理 PointerDownEvent 事件该过程称为“事件分发”event dispatch。随后当手指移动时便会分发 PointerMoveEvent 事件。 3.事件清理当手指抬 PointerUpEvent 起或事件取消时PointerCancelEvent会先对相应的事件进行分发分发完毕后会清空 HitTestResult 列表。
4.手势原理与手势冲突
手势发生变化时只需要在 pointerRouter中取出 GestureRecognizer 的 handleEvent 方法进行手势识别即可。一个手势只有一个手势识别器生效
1.每一个手势识别器GestureRecognizer都是一个“竞争者”GestureArenaMember当发生指针事件时他们都要在“竞技场”去竞争本次事件的处理权默认情况最终只有一个“竞争者”会胜出(win)。 2.GestureRecognizer 的 handleEvent 中会识别手势如果手势发生了某个手势竞争者可以宣布自己是否胜出一旦有一个竞争者胜出竞技场管理者GestureArenaManager就会通知其他竞争者失败。 3.胜出者的 acceptGesture 会被调用其余的 rejectGesture 将会被调用。
如果对一个组件同时监听水平和垂直方向的拖动手势当我们斜着拖动时哪个方向的拖动手势回调会被触发实际上取决于第一次移动时两个轴上的位移分量哪个轴的大哪个轴在本次滑动事件竞争中就胜出
解决手势冲突
解决手势冲突的方法有两种
1.使用 Listener。这相当于跳出了手势识别那套规则。 2.自定义手势手势识别器 Recognizer
通过 Listener 解决手势冲突的原因是竞争只是针对手势的而 Listener 是监听原始指针事件原始指针事件并非语义话的手势所以根本不会走手势竞争的逻辑所以也就不会相互影响
Listener( // 将 GestureDetector 换位 Listener 即可onPointerUp: (x) print(2),child: Container(width: 200,height: 200,color: Colors.red,alignment: Alignment.center,child: GestureDetector(onTap: () print(1),child: Container(width: 50,height: 50,color: Colors.grey,),),),
);代码很简单只需将 GestureDetector 换位 Listener 即可可以两个都换也可以只换一个。可以看见通过Listener直接识别原始指针事件来解决冲突的方法很简单因此当遇到手势冲突时我们应该优先考虑 Listener 。
定义手势识别器的方式比较麻烦原理是当确定手势竞争胜出者时会调用胜出者的acceptGesture 方法表示“宣布成功”然后会调用其他手势识别其的rejectGesture 方法表示“宣布失败”。
既然如此我们可以自定义手势识别器Recognizer然后去重写它的rejectGesture 方法在里面调用acceptGesture 方法这就相当于它失败是强制将它也变成竞争的成功者了这样它的回调也就会执行。
5.事件总线
一个需要登录的 App 中页面会关注用户登录或注销事件来进行一些状态更新。这时候一个事件总线便会非常有用
//订阅者回调签名
typedef void EventCallback(arg);class EventBus {//私有构造函数EventBus._internal();//保存单例static EventBus _singleton EventBus._internal();//工厂构造函数factory EventBus() _singleton;//保存事件订阅者队列key:事件名(id)value: 对应事件的订阅者队列final _emap MapObject, ListEventCallback?();//添加订阅者void on(eventName, EventCallback f) {_emap[eventName] ?? EventCallback[];_emap[eventName]!.add(f);}//移除订阅者void off(eventName, [EventCallback? f]) {var list _emap[eventName];if (eventName null || list null) return;if (f null) {_emap[eventName] null;} else {list.remove(f);}}//触发事件事件触发后该事件所有订阅者会被调用void emit(eventName, [arg]) {var list _emap[eventName];if (list null) return;int len list.length - 1;//反向遍历防止订阅者在回调中移除自身带来的下标错位for (var i len; i -1; --i) {list[i](arg);}}
}//定义一个top-level全局变量页面引入该文件后可以直接使用bus
var bus EventBus();使用
//页面A中
...//监听登录事件
bus.on(login, (arg) {// do something
});//登录页B中
...
//登录成功后触发登录事件页面A中订阅者会被调用
bus.emit(login, userInfo);Dart中实现单例模式的标准做法就是使用static变量工厂构造函数的方式这样就可以保证EventBus()始终返回都是同一个实例
事件总线通常用于组件之间状态共享但关于组件之间状态共享也有一些专门的包如redux、mobx以及前面介绍过的Provider。对于一些简单的应用事件总线是足以满足业务需求的
6. 通知 Notification
通知Notification是Flutter中一个重要的机制在widget树中每一个节点都可以分发通知通知会沿着当前节点向上传递所有父节点都可以通过NotificationListener来监听通知。Flutter中将这种由子向父的传递通知的机制称为通知冒泡
监听可滚动组件滚动通知的例子
NotificationListener(onNotification: (notification){switch (notification.runtimeType){case ScrollStartNotification: print(开始滚动); break;case ScrollUpdateNotification: print(正在滚动); break;case ScrollEndNotification: print(滚动停止); break;case OverscrollNotification: print(滚动到边界); break;}},child: ListView.builder(itemCount: 100,itemBuilder: (context, index) {return ListTile(title: Text($index),);}),
);