<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                企業??AI智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                ## 手勢識別GestureDetector GestureDetector是一個用于手勢識別的功能性Widget,我們通過它可以來識別各種手勢,它是指針事件的語義化封裝,接下來我們詳細介紹一下各種手勢識別: ### 點擊、雙擊、長按 我們通過GestureDetector對Container進行手勢識別,觸發相應事件后,在Container上顯示事件名,為了增大點擊區域,將Container設置為200×100,代碼如下: ``` class GestureDetectorTestRoute extends StatefulWidget { @override _GestureDetectorTestRouteState createState() => new _GestureDetectorTestRouteState(); } class _GestureDetectorTestRouteState extends State<GestureDetectorTestRoute> { String _operation = "No Gesture detected!"; //保存事件名 @override Widget 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; }); } } ``` 運行效果: ![](https://box.kancloud.cn/52bd91bd7dc1414766b42a85addf00d2_320x513.png) **注意**: 當同時監聽`onTap`和`onDoubleTap`事件時,當用戶觸發tap事件時,會有200毫秒左右的延時,這是因為當用戶點擊完之后很可能會再次點擊以觸發雙擊事件,所以GestureDetector會等一斷時間來確定是否為雙擊事件。如果用戶只監聽了`onTap`(沒有監聽`onDoubleTap`)事件時,則沒有延時。 ### 拖動、滑動 一次完整的手勢過程是指用戶手指按下到抬起的整個過程,期間,用戶按下手指后可能會移動,也可能不會移動。GestureDetector對于拖動和滑動事件是沒有區分的,他們本質上是一樣的。GestureDetector會將要監聽的widget的原點(左上角)作為本次手勢的原點,當用戶在監聽的widget上按下手指時,手勢識別就會開始。下面我們看一個拖動圓形字母A的示例: ``` class _Drag extends StatefulWidget { @override _DragState createState() => new _DragState(); } class _DragState extends State<_Drag> with SingleTickerProviderStateMixin { double _top = 0.0; //距頂部的偏移 double _left = 0.0;//距左邊的偏移 @override Widget 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); }, ), ) ], ); } } ``` 運行后,就可以在任意方向拖動了: ![](https://box.kancloud.cn/975afea63b3cdb2ae9afd7369adfa767_320x140.png) 日志: ``` I/flutter ( 8513): 用戶手指按下:Offset(26.3, 101.8) I/flutter ( 8513): Velocity(235.5, 125.8) ``` 代碼解釋: - `DragDownDetails.globalPosition`:當用戶按下時,此屬性為用戶按下的位置相對于**屏幕**(而非父widget)原點(左上角)的偏移。 - `DragUpdateDetails.delta`:當用戶在屏幕上滑動時,會觸發多次Update事件,`delta`指一次Update事件的滑動的偏移量。 - `DragEndDetails.velocity`:該屬性代表用戶抬起手指時的滑動速度(包含x、y兩個軸的),示例中并沒有處理手指抬起時的速度,常見的效果是根據用戶抬起手指時的速度做一個減速動畫。 ### 單一方向拖動 在本示例中,是可以朝任意方向拖動的,但是在很多場景,我們只需要沿一個方向來拖動,如一個垂直方向的列表,GestureDetector可以只識別特定方向的手勢事件,我們將上面的例子改為只能沿垂直方向拖動: ``` class _DragVertical extends StatefulWidget { @override _DragVerticalState createState() => new _DragVerticalState(); } class _DragVerticalState extends State<_DragVertical> { double _top = 0.0; @override Widget 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; }); } ), ) ], ); } } ``` 這樣就只能在垂直方向拖動了,如果只想在水平方向滑動同理。 ### 縮放 GestureDetector可以監聽縮放事件,下面示例演示了一個簡單的圖片縮放效果: ``` class _ScaleTestRouteState extends State<_ScaleTestRoute> { double _width = 200.0; //通過修改圖片寬度來達到縮放效果 @override Widget build(BuildContext context) { return Center( child: GestureDetector( //指定寬度,高度自適應 child: Image.asset("./images/sea.png", width: _width), onScaleUpdate: (ScaleUpdateDetails details) { setState(() { //縮放倍數在0.8到10倍之間 _width=200*details.scale.clamp(.8, 10.0); }); }, ), ); } } ``` 運行效果: ![](https://box.kancloud.cn/6948ddf47af8fef9a397a55e192ccc44_300x433.png) 現在在圖片上雙指張開、收縮就可以放大、縮小圖片。本示例比較簡單,實際中我們通常還需要一些其它功能,如雙擊放大或縮小一定倍數、雙指張開離開屏幕時執行一個減速放大動畫等,我們將在后面“動畫”一章中實現一個完整的縮放Widget。 ### GestureRecognizer GestureDetector內部是使用一個或多個GestureRecognizer來識別各種手勢的,而GestureRecognizer的作用就是通過Listener來將原始指針事件轉換為語義手勢,GestureDetector直接可以接收一個子Widget。GestureRecognizer是一個抽象類,一種手勢的識別器對應一個GestureRecognizer的子類,Flutter實現了豐富的手勢識別器,我們可以直接使用。 #### 示例 假設我們要給一段富文本(RichText)的不同部分分別添加點擊事件處理器,但是TextSpan并不是一個Widget,這時我們不能用GestureDetector,但TextSpan有一個`recognizer`屬性,它可以接收一個GestureRecognizer,假設我們在點擊時給文本變色: ``` import 'package:flutter/gestures.dart'; class _GestureRecognizerTestRouteState extends State<_GestureRecognizerTestRoute> { TapGestureRecognizer _tapGestureRecognizer = new TapGestureRecognizer(); bool _toggle = false; //變色開關 @override void dispose() { //用到GestureRecognizer的話一定要調用其dispose方法釋放資源 _tapGestureRecognizer.dispose(); super.dispose(); } @override Widget 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: "你好世界"), ] ) ), ); } } ``` 運行效果: ![](https://box.kancloud.cn/ba487ffd3a0453b0c2fef8d3e111efc6_250x107.png) 注意:使用GestureRecognizer后一定要調用其`dispose()`方法來釋放資源(主要是取消內部的計時器)。 ### 手勢競爭與沖突 #### 競爭 如果在上例中我們同時監聽水平和垂直方向的拖動事件,那么我們斜著拖動時哪個方向會生效?實際上取決于第一次移動時兩個軸上的位移分量,哪個軸的大,哪個軸在本次滑動事件競爭中就勝出。實際上Flutter中的手勢識別引入了一個Arena的概念,Arena直譯為“競技場”的意思,每一個手勢識別器(GestureRecognizer)都是一個“競爭者”(GestureArenaMember),當發生滑動事件時,他們都要在“競技場”去競爭本次事件的處理權,而最終只有一個“競爭者”會勝出(win)。例如,假設有一個ListView,它的第一個子Widget也是ListView,如果現在滑動這個子ListView,父ListView會動嗎?答案是否定的,這時只有子Widget會動,因為這時子Widget會勝出而獲得滑動事件的處理權。 **示例** 我們以拖動手勢為例,同時識別水平和垂直方向的拖動手勢,當用戶按下手指時就會觸發競爭(水平方向和垂直方向),一旦某個方向“獲勝”,則直到當次拖動手勢結束都會沿著該方向移動。代碼如下: ``` import 'package:flutter/material.dart'; class BothDirectionTestRoute extends StatefulWidget { @override BothDirectionTestRouteState createState() => new BothDirectionTestRouteState(); } class BothDirectionTestRouteState extends State<BothDirectionTestRoute> { double _top = 0.0; double _left = 0.0; @override Widget build(BuildContext context) { return Stack( children: <Widget>[ Positioned( top: _top, left: _left, child: GestureDetector( child: CircleAvatar(child: Text("A")), //垂直方向拖動事件 onVerticalDragUpdate: (DragUpdateDetails details) { setState(() { _top += details.delta.dy; }); }, onHorizontalDragUpdate: (DragUpdateDetails details) { setState(() { _left += details.delta.dx; }); }, ), ) ], ); } } ``` 此示例運行后,每次拖動只會沿一個方向移動(水平或垂直),而競爭發生在手指按下后首次移動(move)時,此例中具體的“獲勝”條件是:首次移動時的位移在水平和垂直方向上的分量大的一個獲勝。 #### 手勢沖突 由于手勢競爭最終只有一個勝出者,所以,當有多個手勢識別器時,可能會產生沖突。假設有一個widget,它可以左右拖動,現在我們也想檢測在它上面手指按下和抬起的事件,代碼如下: ``` class GestureConflictTestRouteState extends State<GestureConflictTestRoute> { double _left = 0.0; @override Widget build(BuildContext context) { return Stack( children: <Widget>[ Positioned( left: _left, child: GestureDetector( child: CircleAvatar(child: Text("A")), //要拖動和點擊的widget onHorizontalDragUpdate: (DragUpdateDetails details) { setState(() { _left += details.delta.dx; }); }, onHorizontalDragEnd: (details){ print("onHorizontalDragEnd"); }, onTapDown: (details){ print("down"); }, onTapUp: (details){ print("up"); }, ), ) ], ); } } ``` 現在我們按住圓形“A”拖動然后抬起手指,控制臺日志如下: ``` I/flutter (17539): down I/flutter (17539): onHorizontalDragEnd ``` 我們發現沒有打印"up",這是因為在拖動時,剛開始按下手指時在沒有移動時,拖動手勢還沒有完整的語義,此時TapDown手勢勝出(win),此時打印"down",而拖動時,拖動手勢會勝出,當手指抬起時,`onHorizontalDragEnd` 和 `onTapUp`發生了沖突,但是因為是在拖動的語義中,所以`onHorizontalDragEnd`勝出,所以就會打印 “onHorizontalDragEnd”。如果我們的代碼邏輯中,對于手指按下和抬起是強依賴的,比如在一個輪播圖組件中,我們希望手指按下時,暫停輪播,而抬起時恢復輪播,但是由于輪播圖組件中本身可能已經處理了拖動手勢(支持手動滑動切換),甚至可能也支持了縮放手勢,這時我們如果在外部再用`onTapDown`、`onTapUp`來監聽的話是不行的。這時我們應該怎么做?其實很簡單,通過Listener監聽原始指針事件就行: ``` Positioned( top:80.0, left: _leftB, child: Listener( onPointerDown: (details) { print("down"); }, onPointerUp: (details) { //會觸發 print("up"); }, child: GestureDetector( child: CircleAvatar(child: Text("B")), onHorizontalDragUpdate: (DragUpdateDetails details) { setState(() { _leftB += details.delta.dx; }); }, onHorizontalDragEnd: (details) { print("onHorizontalDragEnd"); }, ), ), ) ``` 總結: 手勢沖突只是手勢級別的,而手勢是對原始指針的語義化的識別,所以在遇到復雜的沖突場景時,都可以通過Listener直接識別原始指針事件來解決沖突。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看