NativeScriptでサクッとAndroidのSurfaceViewを実装する方法

NativeScriptで、タッチした場所に●（丸）を描画するプログラムを書いてみます。
AbsoluteLayoutなどを使っても出来るとは思いますが、今回あえてNativeScript標準ではサポートされていないAndroidのSurfaceViewを使って実装してみました。Androidでのみ動きます。

NativeScriptからネイティブのAndroid APIを呼ぶサンプルとしても参考にしてもらえれば。

参考)
https://docs.nativescript.org/runtimes/android/generator/extend-class-interface

## 環境
NativeScript 3.1 + Angular 4
Android 7.1で動作確認

# 完成画面

![kei_o___STANDING_OVATION_Slack.jpg](https://qiita-image-store.s3.amazonaws.com/0/170712/b83a0926-15f4-0c34-ccff-56fe14f0f72f.jpeg)

# 事前準備

tns-platform-declarations　というTypeScriptの型定義ファイルを入れておきましょう。
IDE上でAndroidのAPIの補完やチェックが効くようになって開発が捗ります。

```npm i tns-platform-declarations --save-dev```

https://www.npmjs.com/package/tns-platform-declarations　

# HTML
まず、HTMLには ```<Placeholder>``` という部品を配置します。

```:component.html
<Placeholder (creatingView)="creatingView($event)">
```

creatingViewイベントに、ビューを作成するイベントハンドラをバインドします。


# TypeScript
コンポーネントのTypeScriptを見ていきましょう。

まず、SurfaceViewを継承したMySurfaceView クラスを実装していきます。

```TypeScript:component.ts
/**
 * カスタムSurfaceView
 */
class MySurfaceView extends android.view.SurfaceView {

  constructor(context: android.content.Context) {
    super(context);
    // to transform the Android native class to a JavaScript object
    return global.__native(this);
  }

```

コンストラクタで ```return global.__native(this);``` しているのがポイント。
new したときの戻り値として、JavaScriptのオブジェクトではなく、ネイティブのJavaのオブジェクトが得られるようになります。

```TypeScript:component.ts
  init(){
    var self = this;
    let holder = this.getHolder();

    holder.addCallback( new android.view.SurfaceHolder.Callback({

      surfaceCreated(holder: android.view.SurfaceHolder){
        self.drawCircle(100, 100);
        console.log('surface Created');
      },

      surfaceChanged(){},

      surfaceDestroyed(holder: android.view.SurfaceHolder){}

    }) );

    this.setOnTouchListener( new android.view.View.OnTouchListener({
      onTouch(v: android.view.View, ev: android.view.MotionEvent){
        if( ev.getAction() == android.view.MotionEvent.ACTION_DOWN ){
          self.drawCircle(ev.getX() , ev.getY() );
        }
        return true;
      }
    } ) );

    console.log('init');
  }

```
次に、コンストラクタで初期化後に呼ばれる init メソッドを実装します。
ここでは、SurfaceHolderのコールバックと、タッチイベントを取得する
OnTouchListenerをバインドしています。
SurfaceHolder.Callbackは、インターフェースですが、new することで
匿名関数として即インスタンス化しています。便利ですね！
surfaceCreated、surfaceChanged、surfaceDestroyedは
実装しなければいけないイベントメソッド達です。
以下のドキュメント、シンプルでわかりやすかったです。
http://qiita.com/croquette0212/items/24dc2b6de3730e831aab

```TypeScript:component.ts
  drawCircle(x: number, y: number){
    let holder = this.getHolder();
    let c = holder.lockCanvas();
    c.drawColor( new Color("#000000").android );
    let p = new android.graphics.Paint();
    p.setStyle( android.graphics.Paint.Style.FILL );
    p.setColor( new Color("#FF0000").android );
    c.drawCircle(x, y, 50, p);
    holder.unlockCanvasAndPost(c);
  }

}
```
drawCircleメソッドは、引数でもらった座標に、赤の円を書く描画メソッドになります。
以下のページを参考にしました。
http://donsuka-kk.hatenablog.com/entry/20121114/1352870204


これでMySurfaceViewの準備ができました。
ではいよいよ、画面にSurfaceViewを紐付けます。

```:component.ts
export class Component {

  creatingView(args: any) {
    args.view = new MySurfaceView(args.context);
    console.log('creatingView done');
  }
}
```
MySurfaceViewのインスタンスを args.view へ紐付ければ完了です。

ビルドして動かしてみましょう！
タッチすると、タッチしたところに赤い丸が描画されます。

# 所感: NativeScriptすごい

SurfaceViewのプログラミングは初めてでしたが、NativeScriptでSurfaceViewへの描画、タッチイベントをハンドリングする実装まで出来ました。最も時間がかかったのは、NativeScriptからJavaのSurfaceViewクラスをExtendして、TypeScriptへのブリッジインターフェースを実装する箇所で、
これは今回かなり勘所がわかったので、次からは悩まなそうです。

SurfaceViewの操作自体は巷のAndroid向け開発資料をいくつか見て、ほぼそのままTypeScriptでサクッと実装できたのが感動的でした。

Javaで書かなくてはいけないはずのAndroid APIに、TypeScriptからプロパティアクセスしたり、メソッド呼出したり、クラス継承したり出来るのは本当に楽チン。感動しました。