プログラム解説:Androidで“カメ型ロボット”を作る

目覚ましアプリのフローチャート

  • メイン処理(アプリ起動)
    • appflow1.png
  • イベント処理(目覚ましセット)
    • appflow2.png

milk_btn_pagetop.png

アプリの全体構成

アプリのプログラムは下記の6つのクラスで構成される。

KameRoboAlarm
Activityを継承するメインになるクラス。
アラームの設定と解除、タッチパネルイベントの受け取り、robocontライブラリとのインターフェースとなる。(下記参照)
ClockImage
短針のみのアナログ時計の表示を行うクラス。(下記参照)
Sound
サウンドを管理するクラス。
WakeupImage
タイマー起動し、カメロボをコントロール中に表示される背景を表示するクラス。
Updatable
(下記参照)
UpdateHandle
NDK上のMobRoboライブラリを呼び出す為のインターフェースクラス。(下記参照)



アナログ時計処理(ClockImage)

マウスで操作可能な短針のみのアナログ時計を実装する。
背景画像、文字盤画像、短針画像、セットボタン、キャンセルボタンを表示して、起動時間は画面上の短針の位置でセットできるようにする。
画面上の文字盤、セットボタン、キャンセルボタンの処理もこのクラスで行う

5種類の画像を準備して、/res/drawableに置く。

  1. 背景画像(kame_haikei.jpg)
  2. 文字盤(kame_panel.png)
  3. 短針(kame_hand.png)
  4. セットボタン(kame_set_1.png, kame_set_2.png)
  5. キャンセルボタン(kame_cancel_1.png, kame_cancel_2.png)

画像ファイルを読み込む。

    bg = BitmapFactory.decodeResource(getResources(), R.drawable.kame_haikei); // 背景
    clock = BitmapFactory.decodeResource(getResources(), R.drawable.kame_panel); // 文字盤
    hand = BitmapFactory.decodeResource(getResources(), R.drawable.kame_hand); // 針
    setBtn1 = BitmapFactory.decodeResource(getResources(), R.drawable.kame_set_1); // セットボタン
    setBtn2 = BitmapFactory.decodeResource(getResources(), R.drawable.kame_set_2); // セットボタン
    cancelBtn1 = BitmapFactory.decodeResource(getResources(), R.drawable.kame_cancel_1); // キャンセルボタン
    cancelBtn2 = BitmapFactory.decodeResource(getResources(), R.drawable.kame_cancel_2); // キャンセルボタン

各画像のサイズも取得しておく。

    bgW = bg.getWidth();          // 背景の画像サイズ
    bgH = bg.getHeight();

    clockW = clock.getWidth();       // 文字盤の画像サイズ
    clockH = clock.getHeight();

    setBtnW = setBtn1.getWidth();      // セットボタンの画像サイズ
    setBtnH = setBtn1.getHeight();

    cancelBtnW = cancelBtn1.getWidth();   // キャンセルボタンの画像サイズ
    cancelBtnH = cancelBtn1.getHeight();

描画処理
背景画像と文字盤を画面中央に描画する。

      int  cx = width / 2;
      int  cy = height / 2;
      int x = cx - bgW / 2;        // 背景の中心は画面の真ん中にする
      int y = cy - bgH / 2;
      canvas.drawBitmap(bg, x, y, paint); // 背景を描画する

      x = cx - clockW / 2;        // 文字盤の中心は画面の真ん中にする
      y = cy - clockH / 2;
      canvas.drawBitmap(clock, x, y, paint); // 文字盤を描画する

短針は設定時刻に合わせて回転し、描画します。

      float h = hou % 12;
      float m = min;
      float a = (360f / 12f * h) + (360f / 12f / 60f * m) - 90; // 針の角度を計算する。(キャラが横向きなので90度補正する)

      Matrix  matrix = new Matrix();
      matrix.postRotate(a);         // 針用の回転マトリックス

      int ang = (int)(a - 45 + 360) % 360;  // 左上
      x = (int)(Math.sin(ang * Math.PI / 180) * handL); // 針の左上の位置を計算する
      y = (int)(Math.cos(ang * Math.PI / 180) * handL);
      matrix.postTranslate(cx + x, cy - y); // マトリックスに対して移動を設定する
      canvas.drawBitmap(hand, matrix, paint); // 針を描画する

セットボタンとキャンセルボタンを描画します。

      setBtnX = cx - clockW / 2;         // 文字盤の左側に合わせる
      setBtnY = cancelBtnY = cy + clockH / 2;   // 文字盤の下
      cancelBtnX = cx + clockW / 2 - cancelBtnW; // 文字盤の右側に合わせる

      canvas.drawBitmap(setBtn1, setBtnX, setBtnY, paint);     // セットボタンを描画
      canvas.drawBitmap(cancelBtn1, cancelBtnX, cancelBtnY, paint); // キャンセルボタンを描画

タッチ位置の判断。表示した各画像の表示位置と画像サイズに合わせ、何の操作をしてるのか判断する。文字盤がタッチされている場合、時間に変換し、画面を再描画します。

  /**
   * タッチ位置から時間に変換し、画面再描画をリクエストする。
   * @param  tx  タッチ位置X
   * @param  ty  タッチ位置Y
   * @retun 0=文字盤 1=セット 2=キヤンセル -1=それ以外
   */
  public int setTouchPos(float tx, float ty){
    if (0 != width && 0 != height){
      tx -= (Alarm.windowWidth - width);   // タッチ位置と表示位置のズレを補正
      ty -= (Alarm.windowHeight - height);

      float centerX = width / 2;       // 画面中心
      float centerY = height / 2;

      float x = centerX - tx;
      float y = centerY - ty;
      float w = clockW / 2;

      if ((w*w) >= (x*x + y*y)){       // 文字盤の中
        double rad = Math.atan2(-x, y);
        int ang = ((int)(rad * 180 / Math.PI) + 360) % 360;
        int h = ang / 30;
        int m = (ang - h*30) * 2;
        setTime(h, m);           // 時間をセット
        return 0;              // 文字盤
      }
      else{                  // 文字盤以外
        if (setBtnX <= tx && (setBtnX+setBtnW) >= tx
          && setBtnY <= ty && (setBtnY+setBtnH) >= ty){
          return 1;            // セットボタン
        }
        if (cancelBtnX <= tx && (cancelBtnX+cancelBtnW) >= tx
          && cancelBtnY <= ty && (cancelBtnY+cancelBtnH) >= ty){
          return 2;            // キャンセルボタン
        }
      }
    }
    return -1;                 // その他
  }

milk_btn_pagetop.png

メイン処理 (KameRoboAlarm)

アプリ起動後最初に呼ばれるクラスです。
ClockImageクラスを呼び出し、現在時刻を表示します。

  public void onCreate(Bundle savedInstanceState) {
    .
   (中略)
    .
      mClockImage = new ClockImage(this);   // 時計UI
      setContentView(mClockImage);      // 表示を時計UIに移す
      mClockImage.setTime(mCalendar.get(Calendar.HOUR_OF_DAY), mCalendar.get(Calendar.MINUTE));

タッチイベントを検出し、ClockImageクラスにタッチ位置を渡す。

  public boolean onTouchEvent(MotionEvent event){
    int  act = event.getAction();
    if (act == MotionEvent.ACTION_DOWN     // タッチした
      || act == MotionEvent.ACTION_MOVE){   // 移動した
      if (mode){
        float touchX = event.getX();
        float touchY = event.getY();
        int r = mClockImage.setTouchPos(touchX, touchY);

セットボタンが押された時は、時計表示部の設定時間を取り出し、再起動時間をセットし、アプリを終了します。

          Calendar mCalendar = new GregorianCalendar(); // 現在時刻を取得
          Date trialTime = new Date();
          mCalendar.setTime(trialTime);
          int sh = mClockImage.getHours() % 12;
          int sm = mClockImage.getMinutes();
          int ch = mCalendar.get(Calendar.HOUR_OF_DAY) % 12;
          int cm = mCalendar.get(Calendar.MINUTE);
          int h = ((sh+24) - ch) % 12;
          int m = sm - cm;
          if (0 > m){
            m += 60;
            h--;
            if (0 > h) h += 12;
          }
          if (0 != h || 0 != m){
            String  awt = "";
            if (0 == h){
              awt = m+"分後";
            }
            else{
              awt = h+"時間"+m+"分後";
            }
            mCalendar.add(Calendar.HOUR, h);
            mCalendar.add(Calendar.MINUTE, m);
            String wt = ""+mCalendar.get(Calendar.HOUR_OF_DAY)+"時"+ mCalendar.get(Calendar.MINUTE)+"分";
            Toast.makeText(this, awt+"の"+wt+"に起動します。", Toast.LENGTH_LONG).show();
          }
          long tm = mCalendar.getTimeInMillis();
          setTime(tm);
          setAlarm(tm);
          finish();

AlarmManagerに対して、アラームの設定を行う。

  private void setAlarm(long time) {
    mManager.set(AlarmManager.RTC_WAKEUP, time, pendingIntent());
    mode = false;
    setMode(mode);
  }

アラームの設定時刻に発生するインテントの作成

  private PendingIntent pendingIntent() {
    Intent i = new Intent(getApplicationContext(), KameRoboAlarm.class);
    PendingIntent pi = PendingIntent.getActivity(this, 0, i, 0);
    return pi;
  }

milk_btn_pagetop.png

MobRoboLibインターフェース(Updatable, UpdateHandle)

NDK上のMobRoboライブラリを呼び出す為のインターフェースクラス

package名
com.robo;
class名
Updatable
update() のダミー
class名
UpdateHandle
親:Handler
  • UpdateHandler(Updatable updatable);
    • インストラクタ(親の設定)
  • void handleMessage(Message msg);
    • イベント受信 上記 Updateble::update() を呼ぶ。
  • void sleep(long sleepTime);
    • 次の呼び出し時間設定
  • void kill();
    • メッセージの削除