Flutter:使用手勢識別做一個360旋轉展物

kimi_he發表於2019-07-21

最近在複習Flutter的GestureDetector相關知識時,想到了以前用Android實現的展物專案,這次完整的用Flutter實現了一下,真是感覺程式碼簡潔了很多,

專案整體需求

  1. 進入app時展物自動順時針旋轉
  2. 觸控展物時停止旋轉
  3. 手勢在展物上左右觸控滑動時展物跟隨旋轉
  4. 離開觸控時記錄觸控方向然後繼續自動旋轉
  5. 可以順時針,也可以逆時針旋轉

旋轉的原理

首先我們瞭解一下怎麼讓展物旋轉起來,展物是一件在博物館展示的文物,一共拍攝了72張,角度每5度進行一次拍攝,所以總體算起來就是72乘以5,一共就是360度正好是一圈,那麼寫到程式碼裡面就是從第1張圖開始計算,然後每90毫秒替換一張,不斷累加到第72張,然後再從第一張開始,這樣就正好轉了一圈。 理解了實現思路之後,那麼,轉換成Flutter程式碼如下: 先設定第一張圖片,

Image.asset(
    'images/product00${index}.png',
    fit: BoxFit.cover,
    width: width,
    height: height,
),
複製程式碼

然後,我們通過Timer每100秒替換一下index改變圖片的值,程式碼如下:

Timer.periodic(new Duration(milliseconds: 90), (timer) {
    setState(() {});
    if (index < count) {
      index++;
      return;
    }
    // 重新回到第一張圖
    index = 1;
});
複製程式碼

記得Timer在用完之後需要cancel掉,大家對state應該有所瞭解,這裡就是通過修改 index的值得替換圖片的。

但是這樣會有一個問題,就是圖片替換太快,而每張圖片大小在40K左右,替換過程中會有白屏閃爍的問題,這是因為圖片需要載入到記憶體中然後通過Image.asset展示出來,幸好,有一個屬性可以解決這樣的問題,把gaplessPlayback設定為true即可, 修改後程式碼如下:

Image.asset(
    'images/product00${index}.png',
    fit: BoxFit.cover,
    width: width,
    height: height,
    // 該屬性防止圖片快速切換時白屏發生,在新圖出現前保持舊的圖
    gaplessPlayback: true,
    excludeFromSemantics: true,
),
複製程式碼

gaplessPlayback設定為true就能確保在新圖出現之前呈現的還是舊圖,這樣就不會出現白屏閃爍問題。

GestureDetector基本使用

接下來就是如何與手勢結合起來了,通過一個例子,我們先來了解一下GestureDetector的基本用法,程式碼如下:

import 'package:flutter/material.dart';

class GestureDetectorPage extends StatefulWidget {
  @override
  _GestureDetectorState createState() => new _GestureDetectorState();
}

class _GestureDetectorState extends State<GestureDetectorPage> {
  String _opName = "未檢測到操作";

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("GestureDetector手勢識別"),
      ),
      body: Center(
        child: GestureDetector(
          child: Container(
            alignment: Alignment.center,
            color: Colors.blue,
            width: 240.0,
            height: 120.0,
            child: Text(
              _opName,
              style: TextStyle(color: Colors.white),
            ),
          ),
          onTap: () => _showEventText("Tap"),
          onTapUp: (e) => _showEventText("TapUp"),
          onTapDown: (e) => _showEventText("TapDown"),
          onTapCancel: () => _showEventText("TapCancel"),
          onDoubleTap: () => _showEventText("DoubleTap"),
          onLongPress: () => _showEventText("LongPress"),
          onVerticalDragDown: (e) => _showEventText("onVerticalDragDown"),
          onVerticalDragStart: (e) => _showEventText("onVerticalDragStart"),
          onVerticalDragUpdate: (e) => _showEventText("onVerticalDragUpdate"),
          onVerticalDragEnd: (e) => _showEventText("onVerticalDragEnd"),
          onVerticalDragCancel: () => _showEventText("onVerticalDragCancel"),
          onHorizontalDragDown: (e) => _showEventText("onHorizontalDragDown"),
          onHorizontalDragStart: (e) => _showEventText("onHorizontalDragStart"),
          onHorizontalDragUpdate: (e) => _showEventText("onHorizontalDragUpdate"),
          onHorizontalDragEnd: (e) => _showEventText("onHorizontalDragEnd"),
          onHorizontalDragCancel: () => _showEventText("onHorizontalDragCancel"),
//          onPanDown: (e) => _showEventText("onPanDown"),
//          onPanStart: (e) => _showEventText("onPanStart"),
//          onPanUpdate: (e) => _showEventText("onPanUpdate"),
//          onPanEnd: (e) => _showEventText("onPanEnd"),
//          onScaleStart: (e) => _showEventText("onScaleStart"),
//          onScaleUpdate: (e) => _showEventText("onScaleUpdate"),
//          onScaleEnd: (e) => _showEventText("onScaleEnd"),
        ),
      ),
    );
  }

  void _showEventText(String text) {
    setState(() {
      _opName = text;
    });
    print(_opName);
  }
}
複製程式碼

通過這個例子,我們可以在螢幕上的Text區域記錄並顯示出經過GestureDetector手勢事件,以便幫助我們理解。

在本例中,我們需要用到的就是onTap,onPanStart,onPanUpdate,onPanEnd這幾個回撥方法,經過實踐並對程式碼加入手勢事件之後,程式碼如下:

GestureDetector(
  onTap: () => _cancelTimer(),
  onPanStart: (e) => _cancelTimer(),
  onPanUpdate: (e) => _onTouchImage(e),
  // 在觸屏結束之後,恢復自動旋轉
  onPanEnd: (e) => _startTimer(),
  child: Image.asset(
    'images/product00${index}.png',
    fit: BoxFit.cover,
    width: width,
    height: height,
    // 該屬性防止圖片快速切換時白屏發生,在新圖出前時保持舊的圖
    gaplessPlayback: true,
    excludeFromSemantics: true,
  ),
)
複製程式碼

其中onTap,onPanStart,onPanEnd這幾個很好理解,主要是onPanUpdate,裡面呼叫了一個自己實現的_onTouchImage(e)方法,程式碼如下:

void _onTouchImage(e) {
    setState(() {
      index -= e.delta.dx.toInt();
    });
    // 防止取到不存在的圖片報錯
    if (index < 1) index = 1;
    if (index > count) index = count;
}
複製程式碼

上面的程式碼中,在觸控螢幕的同時轉換成index值替換圖片,這樣就實現了圖片替換跟隨手勢的功能,然後,在手指離開螢幕之後還能繼續旋轉,這時候我們可以在觸控時加入方向判斷,

if (e.delta.dx < 0) {
  // 順時針
  direction = DIRECTION_CLOCKWISE;
}
if (e.delta.dx > 0) {
  // 逆時針
  direction = DIRECTION_ANTICLOCKWISE;
}
複製程式碼

這樣,一個簡單的360展物就實現了,你可以在螢幕上任意觸控控制展物,很神奇吧!看一下最終效果:

Flutter:使用手勢識別做一個360旋轉展物

github地址:github.com/heruijun/fl…

新書推薦

《Flutter從0到1構建大前端應用》

Flutter:使用手勢識別做一個360旋轉展物

從Flutter基礎開始講解,結合實際案例,讓讀者逐步掌握Flutter的核心內容,實戰專案篇又通過2個實戰專案讓讀者除了掌握Flutter相關知識之外,對node、mongo,vue做了一些介紹,可以讓更多的讀者擁抱目前最火的大前端技術。

京東購買連結:點選購買

相關文章