2010年11月11日木曜日

ImageViewにパラパラ漫画を表示

Androidでは、複数画像をパラパラ漫画のように表示するアニメーションをFrame-By-Frame Animationと呼ぶそうです。Frame-ByFrame AnimationはAnimationDrawableクラスを使用します。

アニメーションの内容をXMLファイルで用意しておき、それをAnimationDrawableクラスに読み込ませます。
以下のファイルをリソースのanimフォルダにanimation.xmlとしてプロジェクトに追加しておきます。

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false">
    <item android:drawable="@drawable/android1" android:duration="100" />
    <item android:drawable="@drawable/android2" android:duration="100" />
    <item android:drawable="@drawable/android3" android:duration="100" />
    <item android:drawable="@drawable/android4" android:duration="100" />
</animation-list>

android1〜4はアニメーションで使用する画像ファイル名です。それぞれの画像を100msecずつ表示するように指定しています。

ソースは以下のようにします。

    AnimationDrawable mAnimDrawable = null;
    ImageView imgView = null;
    ・
    ・
        // ImageViewのバックグランドにXMLファイルからアニメーションを読み込み
        imgView.setBackgroundResource(R.anim.animation);
       
        // ImageViewからAnimationDrawableを取得
        mAnimDrawable = (AnimationDrawable)imgView.getBackground();

        // アニメーション開始
        mAnim.start();

Viewを回転させたり拡大縮小させたり

Androidにもアニメーションを行わせる仕組みが予め組み込まれています。
iPhoneのアニメーションのやり方とは大きく異なります。

Androidでは1枚の画像を拡大縮小・回転・移動・透過変更させることを、Tween Animationと呼ぶそうです。 Tween AnimationはAnimationクラスを使用します。

また、複数画像をパラパラ漫画のように表示するアニメーションをFrame-By-Frame Animationと呼ぶそうです。Frame-ByFrame AnimationはAnimationDrawableクラスを使用します。

アニメーションの内容をXMLファイルで用意しておき、それをAnimationクラスに読み込ませます。
以下のファイルをリソースのanimフォルダにanimation.xmlとしてプロジェクトに追加しておきます。

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <alpha
        android:fromAlpha="0.0"
        android:toAlpha="1.0"
        android:duration="5000"
        android:repeatCount="infinite"
        android:repeatMode="restart" />
</set>

上記は透過レベルを0〜1に5秒間でじわっと変化させるアニメーションの例です。
拡大縮小・回転等の各タグの意味はDeveloperページをご覧下さい。


そしてソースを以下のようにします。

    private ImageView imgView = null;
    private Animation mAnim = null;
    ・
    ・
        imgView = (ImageView)findViewById(R.id.animView);


        // animation.xmlファイルを読み込み
        mAnim = AnimationUtils.loadAnimation(this, R.anim.animation);
        imgView.startAnimation(mAnim);

上記はXMLファイルをAnimationクラスに読み込み、ImageViewクラスに設定した画像の透過レベルをアニメーションで表示させています。

スレッド

AndroidでもiPhone同様、スレッドを使用することができます。
Androidの場合、一部のコントロールを除いて、別スレッドからメインスレッドのUIにはアクセスできないようです。(例外が発生します。)
その為、ハンドラに処理を渡してメインスレッドの方で実際の処理を行わせます。
ハンドラを使うので、android.os.Handlerをインポートしておく必要があります。

    Handler mHandler = new Handler();
    ・
    ・
        Thread th = new Thread() {
            public void run() {
                // 別スレッドの処理
                ・
                ・
                ・
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        // メインスレッドのTextViewにスレッド処理終了を表示
                        mTextView1.setText("Finished");
                    }
                });
            }
        };
        th.start(); // 別スレッドの処理開始

2010年11月8日月曜日

サウンドの停止・一時停止

サウンドの再生を停止・一時停止する場合、以下のメソッドを呼び出します。

MediaPlayerクラス
stop:停止
pause:一時停止


MediaPlayer mp = MediaPlayer.create(this, R.raw.soundFile);
mp.start(); // 再生



mp.pause(); // 一時停止



mp.stop(); // 再生停止

サウンドの早送り巻き戻し

サウンドファイルの再生中に早送りや巻き戻しを行うことができます。
MediaPlayerクラスの以下のメソッドを使用します。

getDuration:全体の再生時間をmsec単位で取得
getCurrentPosition:現在の再生位置をmsec単位で取得
toSeek:再生位置をmsec単位で指定


                MediaPlayer mp = MediaPlayer.create(this, R.raw.soundFile);
                mp.start(); // 再生
                ・
                ・
                ・



                int totalTime = mp.getDuration();   // サウンドの再生時間をmsec単位で取得
                int seekTime = mp.getCurrentPosition(); // サウンドの現在の再生位置をmsec単位で取得
                seekTime -= 2000; // 2秒巻き戻し
                if ( seekTime < 0) seekTime = 0; // 0より小さい場合は開始位置に移動
                if ( totalTime < seekTime ) seekTime = totalTime; // サウンド全体の長さより長い場合はサウンドの最後に移動
                mp.seekTo(seekTime); //再生位置に移動

2010年11月7日日曜日

任意のディレクトリ配下のサウンドを再生する

Androidでは、任意のディレクトリに格納されているサウンドファイルを再生することもできます。(読み取り権限は必要です)
android.media.MediaPlayerをimportし、以下のように記述します。


        // Play sound in directories
        mp = new MediaPlayer();
        String dst = "/data/data/任意のディレクトリ名/任意のファイル名";
        // SD Cardのサウンドを再生する場合
        // dst = "/sdcard/任意のサウンドファイル名";
        mp.setDataSource(dst);
        mp.prepare();
        mp.start();

上記では例外処理がありませんが、存在しないファイルなどを指定した場合は例外が発生しますので、「setDataSource」と「prepare」メソッドの呼び出しはtry〜catchで例外を補足する必要があります。

また、サウンドファイルは予めアンドロイドシミュレータに配置しておく必要がありますが、その為のコマンドは、ターミナルから以下のように入力します。


adb push /data/data/任意のディレクトリ名/サウンドファイル名 ローカルのサウンドファイル名

リソースのサウンドファイル再生

Androidはサウンド再生が簡単に行えます。
MediaPlayerクラスに再生したいサウンドを指定して、startメソッドで再生させるだけです。


        MediaPlayer mp = MediaPlayer.create(this, R.raw.soundFileName);
        mp.start();

上記はサウンドファイルをリソースから再生する例です。
リソースディレクトリにrawディレクトリを作成し、その中にsoundFileName.mp3等のサウンドファイルを格納しておきます。

なお、上記ではMediaPlayerの解放処理がありませんが、再生するActivity(画面)が破棄される場合はMediaPlayerのインスタンスも解放が必要です。

@Override
// Activityの破棄
protected void onDestroy() {
super.onDestroy();
mp.stop();
mp.release(); // 解放
}

私はCから来てるので解放処理は行うのが当たり前ですが、Javaでは解放を明示するのはめずらしいのでしょうか?

2010年11月2日火曜日

設定情報ファイルの保存場所

Androidでは、設定ファイルを保存すると、エミュレータ内の以下の場所に保存されます。

/data/data/パッケージ名(jp.co.・・・・)/shared_prefs/ファイル名

また、エミュレータで保存したファイルを取り出すことができます。
ターミナルを立ち上げ、以下のコマンドを入力します。

adb pull /data/data/パッケージ名(jp.co.・・・)/shared_prefs/settings.xml test.xml

上の例ではsettings.xmlというAndroidエミュレータ上のファイルを、カレントディレクトリのtest.xmlファイルとして取り出しています。adbコマンドはAndroid SDKをインストールしていないと使えません。

 iPhoneだとiPhoneシミュレータが使っているディレクトリを直接見に行けるのですが、Androidはこのようにコマンド入力がある分、少し面倒です。

SharedPreferencesクラスで簡単Save Load

Androidでは、比較的簡単に設定情報の保存が行えます。
getSharedPreferences関数でファイル名からSharedPreferencesクラスのインスタンスを生成し、編集するためにEditorメソッドでEditorインスタンスを取得後、編集、保存します。

    Button mSaveButton = null;
    Button mLoadButton = null;
    Button mRemoveButton = null;
    EditText mTextView = null;


        mTextView = (EditText)findViewById(R.id.textView);
       
        mSaveButton = (Button)findViewById(R.id.saveButton);
        mSaveButton.setOnClickListener(new OnClickListener() {
            // Saveボタン押下時処理
            public void onClick(View v) {
                // settings.xmlファイルのSharedPreferencesインスタンスを生成
                SharedPreferences sp = getSharedPreferences("settings", MODE_PRIVATE);
               
                // Editorを取得
                SharedPreferences.Editor editor = sp.edit();
               
                // EditTextから文字列取得
                SpannableStringBuilder ssb = (SpannableStringBuilder)mTextView.getText();
                String str = ssb.toString();
               
                // Editorに保存データ設定
                editor.putString("text", str);
               
                // Editorを保存
                editor.commit();
            }
        });
       
        mLoadButton = (Button)findViewById(R.id.loadButton);
        mLoadButton.setOnClickListener(new OnClickListener() {
            // Loadボタン押下時処理
            public void onClick(View v) {
                // settings.xmlファイルのSharedPreferencesインスタンスを生成
                SharedPreferences sp = getSharedPreferences("settings", MODE_PRIVATE);
               
                // 保存文字列を取得
                String str = sp.getString("text", "");
                mTextView.setText(str);
            }
        });
       
        mRemoveButton = (Button)findViewById(R.id.removeButton);
        mRemoveButton.setOnClickListener(new OnClickListener() {
            // Removeボタン押下時処理
            public void onClick(View v) {
                // settings.xmlファイルのSharedPreferencesインスタンス生成
                SharedPreferences sp = getSharedPreferences("settings", MODE_PRIVATE);
               
                // Editor取得
                SharedPreferences.Editor editor = sp.edit();
               
                // 文字列を削除
                editor.remove("text");
               
                // 削除を確定&保存
                editor.commit();
            }
        });

上の例では、Saveボタンを押して文字列を保存、Loadボタンで保存した文字列を読み出し、Removeボタンで削除を行います。

保存ファイル名はgetSharedPreferencesでインスタンス取得時に指定します。上の例ではsettingsというXMLファイルです。

SharedPreferencesのgetString / putStringメソッドで文字列の格納・取り出しが行えます。上の例では"text"と指定していますが、これはXMLファイルに保存する時の保存タグを指定しているものです。他にputIntやputBoolean等、大体の基本型は同じように扱えるようです。

最後に、commitで変更を確定しています。これが無いと実際の保存処理が行われません。

2010年11月1日月曜日

EditTextから文字列取得

EditTextコントロールはWindowsのテキストボックスのようなものです。
IOSでいうところのUITextViewと似ています。

ただし、textプロパティのような物がありませんので、文字列の取得はAndroid独特の方法です。

                // EditTextから文字列取得
                SpannableStringBuilder ssb = (SpannableStringBuilder)mTextView.getText();
                String str = ssb.toString();

独特と言っても、いったんSpannableStringBuilderに取り込むというだけです。
SpannableStringBuilderクラスは、StringBuilderのAndroid版?です。
IOSで言うとNSMutableStringでしょうか。Stringクラスが固定文字列なのに対して、SpannableStringBuilderは可変の文字列です。

EditTextのgetText()メソッドはEditableインタフェースを持つオブジェクトを返却するそうです。
これを同じくEditableな文字列を扱うクラスであるSpannableStringBuilderにキャストし、同クラスのtoStringメソッドで文字列化するという流れになります。

ProgressDialogで進行状況を表示

ProgressDialogを使うと、進行状況を表示できるようになります。WindowsのProgressBarみたいなものです。

setTitleメソッドで表示するメッセージを指定し、
setMaxで進行状況の最大値、
setButton / setButton2で表示するボタンを指定します。
setProgressで進行状況を設定します。


    private EditText mResultText = null;
    private ProgressDialog mProgressDialog;
    private int mProgress = 0;

                // ProgressDialogクラスで進行状況を表示
                mProgressDialog = new ProgressDialog(MainActivity.this);
                mProgressDialog.setTitle("Progress Dialogで進行状況表示");
                mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
                mProgressDialog.setMax(100);
                mProgressDialog.setButton("OK", new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int whichButton) {
                        mResultText.setText("Press OK at " + mProgress + " %");
                    }
                });
                mProgressDialog.setButton2("Cancel", new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int whichButton) {
                        mResultText.setText("Press Cancel at " + mProgress + " %");
                    }
                });
                mProgress = 0;
                mProgressDialog.show();
                mProgressDialog.setProgress(0);

時間のかかる処理を行わせる前に、上記で進行状況表示の初期ダイアログを表示します。
そして、進行状況を逐次表示するために、イベントハンドラを使ってメッセージを受け付けるようにします。

以下がイベントハンドラの例です。イベントハンドラも時間のかかる処理の前に用意しておく必要があります。
イベントハンドラではOSのメッセージを使用しますので、
import android.os.Message
でMessageをインポートしておきます。

    private Handler mProgressHandler = null;
        mProgressHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                if (mProgress >= 100) {
                    mProgressDialog.dismiss();
                    mResultText.setText("Finished process at " + mProgress + " %");
                } else {
                    mProgress++;
                    mProgressDialog.incrementProgressBy(1);
                    mProgressHandler.sendEmptyMessageDelayed(0, 100);
                }
            }
        };

上記では、単純に進行状況(mProgress)を1ずつ加算していき、100%になったらProgressDialogを閉じて、終了メッセージを表示するという処理になっています。
100になっていなければ、incrementProgressByメソッドでプログレスバーの表示を増加させ、ハンドラのsendEmptyMessageDelayedメソッドで100ms後に自身に0というメッセージを送らせています。

そして、実際に時間のかかる処理を走らせる時に

                mProgressHandler.sendEmptyMessage(0);

としてイベントハンドラに対し0というメッセージを送ります。

上記例では、単純にイベントハンドラが呼ばれる度に進行状況を1ずつ加算していくようにしましたが、sendEmptyMessageで実際のパーセンテージを送信するようにして、もっと正確な進行状況が表示されるようにすると、もっと良くなるかもしれません。

TimePickerDialogクラスで時刻指定

TimePickerDialogクラスを使用すると、時刻を指定することができます。
OnTimeSetListenerで時刻設定が変更された時のイベントハンドラを登録し、コンストラクタで最初に表示される時刻を指定します。
TimePickerDialogクラス使用時は以下のimportが必要です。
import android.app.TimePickerDialog;
import android.app.TimePickerDialog.OnTimeSetListener;
import android.widget.TimePicker;


        // TimePickerDialogクラスで時刻指定
        TimePickerDialog timePickerDialog = new TimePickerDialog(this, new OnTimeSetListener() {
            public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
                mResultText.setText(String.format("%02d:%02d", hourOfDay, minute));
            }
        }, 10, 10, true);
        timePickerDialog.show();

DatePickerDialogクラスで日付指定

DatePickerDialogクラスは日付を指定するダイアログです。
OnDateSetListenerクラスメソッドで日付が変更された時のイベントハンドラを指定します。また、コンストラクタで最初に指定する日付を指定します。


        // DatePickerDialogクラスで日付入力
        DatePickerDialog datePickerDialog = new DatePickerDialog(this
                , new DatePickerDialog.OnDateSetListener() {
            public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
                mResultText.setText(String.format("%04d/%02d/%02d"
                        , year, monthOfYear+1, dayOfMonth));
            }
        }, 2010, 11, 1);
        datePickerDialog.show();


また、以下のimportが必要です。
import android.app.DatePickerDialog;
import android.widget.DatePicker;

リストを表示する

AlertDialogクラスは、リストを表示することも可能です。
以下のようにsetItemsメソッドでString配列を渡すだけです。

                final String[] items = new String[]{"one", "two", "three", "four"};
                new AlertDialog.Builder(MainActivity.this)
                .setTitle("Alert Dialogでリスト表示")
                .setItems(items, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        mResultText.setText(items[which]);
                    }
                })
                .create()
                .show();

AlertDialogクラス

AlertDialogクラスは、WindowsでいうところのMessageBoxのようなものです。
IOSで言うところのUIAlertViewクラスと似た機能を持っています。

Builderメソッドでダイアログを表示するインスタンスを、setTitleでタイトルバーに表示される文字列、setMessageでダイアログ無いに表示される文字列を指定します。

setPositiveButtonでOKなどの肯定ボタンを、setNegativeButtonでCancel等の否定的ボタン、setNeutralButtonで無視する等の中間的ボタンを指定します。

                new AlertDialog.Builder(MainActivity.this)
                .setTitle("Alert Dialog OK")
                .setMessage("OK / Ignore / Cancel")
                .setPositiveButton("OK", new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        mResultText.setText("Pressed OK");
                    }
                })
                .setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        mResultText.setText("Pressed Cancel");
                    }
                })
                .setNeutralButton("Ignore", new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        mResultText.setText("Pressed Ignore");
                    }
                })
                .create()
                .show();

最後にcreateメソッドでインスタンスを生成し、showメソッドで表示しています。