Flutter で Google Glass Enterprise Edition 2 のアプリを作る際のメモ

Google Glass Enterprise Edition 2(以下「Glass」)の実機を入手したので、サンプルアプリをいろいろ作ってみている。 Android ネイティブで作ってもいいが、先日 Flutter 2 が出たところだし、せっかくなので Flutter で試してみている。 とりあえず、Tips がいくつかあったのでメモしておく。

アプリアイコンを Glass の Launcher に表示させる

Flutter のプロジェクト作成時の初期状態では、apk をビルドしてインストールしても、Glass のホーム画面である Launcher にはアイコンが表示されない。 アイコンを表示させるためには、Project/android/app/src/main/AndroidManifest.xml に以下の記述を追加する。*1

<manifest ...>
    <application ...>
        <intent-filter>
            <!-- ここから -->
            <category android:name="com.google.android.glass.category.DIRECTORY" />
            <!-- ここまで -->
        </intent-filter>
    </application>
</manifest>

指での操作を Flutter 側で利用する

Glass にはタップ操作できる画面はないので、本体横の平面をタップやスワイプするのが標準操作となる。 Flutter では常に AndroidMainActivity それらの操作を Flutter 側でハンドリングするために、

1) Project/android/app/src/main/AndroidManifest.xml に以下の記述を追加する

<manifest ...>
    <application ...>
            <!-- ここから -->
            <meta-data
                android:name="com.google.android.glass.TouchEnabledApplication"
                android:value="true" />
            <!-- ここまで -->
    </application>
</manifest>

2) ネイティブ側の MainActivity.java でネイティブの GestureDetector を使ったハンドラを記述する。

package dev.nosu.flutter_slide_viewer.flutter_slide_viewer;

import android.os.Handler;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.GestureDetector;
import io.flutter.Log;
import io.flutter.embedding.android.FlutterActivity;

public class MainActivity extends FlutterActivity implements GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener {

    private static final String DEBUG_TAG = "Gestures";
    private GestureDetector mDetector;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mDetector = new GestureDetector(this,this);
        mDetector.setOnDoubleTapListener(this);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event){
        if (this.mDetector.onTouchEvent(event)) {
            return true;
        }
        return super.onTouchEvent(event);
    }

    @Override
    public boolean onDown(MotionEvent event) {
        Log.d(DEBUG_TAG,"onDown: " + event.toString());
        return true;
    }

    @Override
    public boolean onFling(MotionEvent event1, MotionEvent event2,
                           float velocityX, float velocityY) {
        Log.d(DEBUG_TAG, "onFling: " + event1.toString() + event2.toString());
        return true;
    }

    @Override
    public void onLongPress(MotionEvent event) {
        Log.d(DEBUG_TAG, "onLongPress: " + event.toString());
    }

    @Override
    public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX,
                            float distanceY) {
        Log.d(DEBUG_TAG, "onScroll: " + event1.toString() + event2.toString());
        return true;
    }

    @Override
    public void onShowPress(MotionEvent event) {
        Log.d(DEBUG_TAG, "onShowPress: " + event.toString());
    }

    @Override
    public boolean onSingleTapUp(MotionEvent event) {
        Log.d(DEBUG_TAG, "onSingleTapUp: " + event.toString());
        return true;
    }

    @Override
    public boolean onDoubleTap(MotionEvent event) {
        Log.d(DEBUG_TAG, "onDoubleTap: " + event.toString());
        return true;
    }

    @Override
    public boolean onDoubleTapEvent(MotionEvent event) {
        Log.d(DEBUG_TAG, "onDoubleTapEvent: " + event.toString());
        return true;
    }

    @Override
    public boolean onSingleTapConfirmed(MotionEvent event) {
        Log.d(DEBUG_TAG, "onSingleTapConfirmed: " + event.toString());
        return true;
    }
}

あとは、Flutter 側で GestureDetector を使ってあげれば良い。 下向きのスワイプをアプリ終了に割り当てるには以下のようにする。(Glass では「戻る」または「アプリ終了」操作に割り当てるのが標準になっている)

class GestureContainer extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      child: GestureDetector(
          onVerticalDragEnd: (details) {
            print("onVerticalDragEnd primaryVelocity:" + details.primaryVelocity.toString());
            if(details.primaryVelocity > 0) {
              SystemNavigator.pop();
            }
          },
          child: ChildWidget()
      ),
    );
  }
}

ほかにもいろいろポイントはありそうなので追って追記する予定。