AndroidのSurfaceViewでテキストを1文字ずつ表示する

#自己紹介
 こんにちは、北海道の学生です。基本的にAndroidで少し開発の経験があるぐらいで、使えると思い込んでる言語はJavaとC#です。よろしくお願いします。
AndroidでRPGっぽいものを作ろうとした時に、SurfaceView上で1文字ずつテキストを表示する方法で少し詰まったので、忘れないように書いておきます。間違ってることも多々あるかもと思ってますがお許しください。
#SurfaceViewとは
- アニメーションとかゲームみたいに描画のパフォーマンスが必要なもの、複雑なグラフィックや常に更新されるような描画内容に最適である。
- メインスレッド以外でも描画可能だが、スレッド間での排他処理に注意が必要。
- SurfaceへのアクセスはSurfaceHolderインターフェイス経由で行う。

#コード

グラフィックス専門のクラスを作ってそこに描画命令をまとめておきます。

```java:Graphics.java

    //コンストラクタ
    public Graphics(int w, int h, SurfaceHolder holder) {
        this.holder = holder;
        this.holder.setFormat(PixelFormat.RGBA_8888);
        this.holder.setFixedSize(w, h);
        paint = new Paint();
        paint.setAntiAlias(true);
    }

    public Canvas getCanvas() {
        return canvas;
    }

    //ロック
    public void lock() {
        canvas = holder.lockCanvas();
        if (canvas == null) return;
        canvas.translate(originX, originY);
    }

    //アンロック
    public void unlock() {
        if (canvas == null) return;
        holder.unlockCanvasAndPost(canvas);
    }

    //描画原点の指定
    public void setOrigin(int x, int y) {
        originX = x;
        originY = y;
    }

    //描画原点のX座標の取得
    public int getOriginX() {
        return originX;
    }

    //描画原点のY座標の取得
    public int getOriginY() {
        return originY;
    }

    //色の指定
    public void setColor(int color) {
        paint.setColor(color);
    }

    //フォントサイズの指定
    public void setTextSize(int fontSize) {
        paint.setTextSize(fontSize);
    }

    //フォントメトリックスの取得
    public Paint.FontMetrics getFontMetrics() {
        return paint.getFontMetrics();
    }

    //文字幅の取得
    public int measureText(String string) {
        return (int)paint.measureText(string);
    }

    //塗り潰し矩形の描画
    public void fillRect(int x, int y, int w, int h) {
        if (canvas == null) return;
        paint.setStyle(Paint.Style.FILL);
        canvas.drawRect(new Rect(x, y, x+w, y+h), paint);
    }

    //ビットマップの描画
    public void drawBitmap(Bitmap bitmap, int x, int y) {
        if (canvas == null) return;
        int w = bitmap.getWidth();
        int h = bitmap.getHeight();
        Rect src = new Rect(0, 0, w, h);
        Rect dst = new Rect(x, y, x+w, y+h);
        canvas.drawBitmap(bitmap, src, dst, null);
    }

    //ビットマップの描画
    public void drawBitmap(Bitmap bitmap, Rect src, Rect dst) {
        if (canvas == null) return;
        canvas.drawBitmap(bitmap, src, dst, null);
    }

    //文字列の描画
    public void drawText(String string, int x, int y) {
        if (canvas == null) return;
        canvas.drawText(string, x, y, paint);
    }
```



そして、SurfaceViewを継承したクラスを作り、それをActivityにセットします。（ちょっと長くなります）

```java:IndexActivity.java

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(new RPGView(this, storyNum));
    }
```

```java:RPGView.java


    
    public RPGView(Activity activity, int story) {
        super(activity);
        holder = getHolder();
        holder.addCallback(this);

        //グラフィックスの生成
        g = new Graphics(dw, H, holder);
        g.setOrigin((dw - W) / 2, 0);
    }

    @Override
    public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
        thread = null;
    }

    @Override
    public void surfaceCreated(SurfaceHolder surfaceHolder) {
        thread = new Thread(this);
        thread.start();
    }

    @Override
    public void run() {
        while (thread != null) {

            //プログラムのメインループここに描画処理

            //スリープ
            key = KEY_NONE;
            sleep(200);
        }
    }
```

あとはスレッドのメインループの所に描画処理を書いていきます。（あれ、何するんだっけ）

#テキストを1文字ずつ…
SurfaceViewのスレッドで毎回Viewが更新されるので、それに合わせて文字列を一文字ずつ増やしていけばうまくいきました。

```java:RPGView.java

     //セリフ画面の描画
    private void talk(String message, Bitmap bitmap, String who) {
        int color = Color.rgb(0, 0, 0);
        if (bitmap != null) {
            g.lock();
            g.setColor(color);
            g.fillRect(0, 0, W, H);

            g.getCanvas().translate(0, 0);
            g.fillRect(0, 0, realX, realY);
            g.getCanvas().translate((dspW - W) / 2, 0);

            g.drawBitmap(bitmap, 0, 0);
            g.setColor(Color.rgb(255, 255, 255));
            g.fillRect((W - 504) / 2, H - 122, 504, 104);
            g.setColor(color);
            g.fillRect((W - 500) / 2, H - 120, 500, 100);

            //TODO 文字の改行！！！！
            g.setColor(Color.rgb(255, 255, 255));
            g.setTextSize(25);
            if (message.length() > 18) {
                String message1 = message.substring(0, 18);
                String message2 = message.substring(18);
                g.drawText(message1, (W - 500) / 2 + 25, 370 - (int) g.getFontMetrics().top);
                g.drawText(message2, (W - 500) / 2 + 10, 410 - (int) g.getFontMetrics().top);
            } else {
                g.drawText(message,
                        (W - 500) / 2 + 25, 370 - (int) g.getFontMetrics().top);
            }

            g.unlock();
        }
    }

```

これのメソッドをループから呼び出して一文字増やしたテキストを渡していけば…

```java:RPGView.java

    String text = "あいうえお";
    if (moji_count == text.length() || moji_count == 9999) {
          talk(text, background, null);
          moji_count = 9999;
    } else {
          talk(text.substring(0, moji_count), background, null);
          moji_count++;
    }     

```

うまく一文字ずついけました。短い間スリープさせることでちょうど良い速さで表示させることができます。

#参考
 - [AndroidのSurfaceViewの基礎](https://qiita.com/croquette0212/items/24dc2b6de3730e831aab)
 - http://boco.hp3200.com/game-devs/surfaceview/1.html