- 2007-12-01 1:35
- android
Androidのドキュメントに準備されているチュートリアルのエクササイズである「Notepad」を元にAndroidアプリケーション作成の手順を追ってみます。お手元にNotepadv3Solutionというプロジェクト($ANDROID_SDK/docs/intro/codelab/NotepadCodeLab/Notepadv3Solution)を開いておくと分かりやすいと思います。
Androidのドキュメントでアプリケーション開発のチュートリアル用に準備されているプログラムです。
外観はこのようになっています。
メニューからノートを追加・削除できます。
ノートはタイトルとボディーからなっています。
アプリケーションの動きはメニューでノートをどんどん追加していき、追加されたノートのタイトルはメイン画面にリスト表示されます。
そして、そのリストのタイトルを選択すると、ノートパッドが開いて編集でき、メニューで「Delete Note」を選択するとノートバッドを削除できるアプリケーションです。
では、早速作っていきます。
まずは入り口となるメインです。AndroidのアプリケーションはActivityクラスから生成されます。そのため、作成するアプリケーションはActivityクラスを継承する必要があります。
NotepadではActivityクラスのサブクラスであるListActivityを継承しています。
public class Notepadv3 extends ListActivity { @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.notes_list); dbHelper = new DBHelper(this); fillData(); } }
アプリケーションが呼び出されると必ずonCreateがコールされますので、ここが実質メインメソッドとなります。この関数では必ずsuperクラスのonCreateをはじめにコールしておく必要があります。これはルールです。
setContentViewではこのアプリケーションのレイアウトを設定しています。
R.layout.notes_listというのは、レイアウトを設定するxmlファイル(今回はres/layout/notes_list.xml)から自動で生成されるint値です。
正確にはres/layout/notes_list.xmlファイルを自動で読み込みR.javaが生成され、そこにlayout.notes_listというstatic変数が定義されます。
res/以下にxmlファイルを作ると、自動R.javaが生成されます。R.javaはリソースの位置を値として持つクラスを定義したファイルです。
xmlの文法を間違うとR.javaは生成されませんので注意が必要です。
次に、
dbHelper = new DBHelper(this);
これは、ノートパッドに入力した内容をSQLiteに保存しておくため、そのDBアクセッサーです。
これについての詳細は述べませんが、SQLクエリーを文字列でSQLiteのDBに打ち込み、格納しているデータを取り出したり削除したりをしています。参考のため、最後にコメント付きのソースを載せておきます。
最後の、
fillData();
これは登録されているノートパッドをメイン画面にリスト表示する関数で、自分で定義します。
定義は以下の通りです。
private void fillData() { // データベースにの要素を取り出し格納するためのリスト List<String> items = new ArrayList<String>(); // データベースから全ての要素を取り出す // rowクラスはデータベースの1ラベルのデータを格納できるクラスである rows = dbHelper.fetchAllRows(); for (Row row : rows) { items.add(row.title); } // ArrayAdapterにアイテムリストをセットする ArrayAdapter<String> notes = new ArrayAdapter<String>(this, R.layout.notes_row, items); // ListActivityにアイテムリストをセットする setListAdapter(notes); }
これで入り口が完成しました。
次は、各入力のイベント処理を書いていきます。
まずはメインのイベントを見ていきます。各ボタンやアイテムが選択されたときのイベントは以下の様になっています。
メインとなるイベントは3つです。
- onCteateOptionsMenu
- onMenuItemSelected
- onListItemClick
それでは、それぞれのイベント処理を書いていきます。
メニューボタンが押されたときに発生するイベントです。
メニューボタンからノートパッドの作成・削除を行うためのメニューアイテムを表示します。
@Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); menu.add(0, INSERT_ID, R.string.menu_insert); menu.add(0, DELETE_ID, R.string.menu_delete); return true; }
とても簡単です。
menuに要素を追加していくだけです。要素と言ってもINSERT_IDとDELETE_IDというint値だけです。
これはNotepadv3クラスのクラス変数として定義しておきます。
ここでもR.javaが使われています。R.string.menu_insert(delete)はres/values/string.xmlにそれぞれの値が定義されています。
システムの固定文字列が外部でこのように定義されていると、プログラムを変更する事なく文字や値を変更できるのが便利です。
これは最近のプログラミングの流れのようですね。
メニューが選択されたときのイベントです。
つまりノートパッドを作成する、もしくは、削除する処理をここに書きます。
@Override public boolean onMenuItemSelected(int featureId, Item item) { super.onMenuItemSelected(featureId, item); switch(item.getId()) { case INSERT_ID: createNote(); break; case DELETE_ID: dbHelper.deleteRow(rows.get(getSelection()).rowId); fillData(); break; } return true; }
onCreateOptionsMenuで登録しておいたIDに応じて処理を振り分けています。
INSERT_IDなら「Add Note」なのでノートバッドを作成します(createNote)。
DELETE_IDなら「Delete Note」なのでノートパッドを削除します(deleteRow)。
deleteRowはDBHelperで定義していますので詳しくは付録のソースコードを読んで下さい。
createNoteメソッドは独自で定義しています。それは以下のようになっています。
private void createNote() { Intent i = new Intent(this, NoteEdit.class); startSubActivity(i, ACTIVITY_CREATE); }
startSubActivityで別のActivityを呼び出しています。
第一引数のIntentですが、これはAndroidで非常に大切な役割になります。
Intentはこういう画面遷移が行われる際に、データの受け渡しをするオブジェクトです。
今回は受け渡す値が無いので、呼び出すActivityのクラスを設定したオブジェクトだけが渡されます。
受け渡す値がある場合は次に説明します。
メインの画面に表示されたアイテムがクリックされたときのイベント処理を書きます。
処理内容は、クリックされたタイトルを持つノートパッドの編集画面を表示する事です。
@Override protected void onListItemClick(ListView l, View v, int position, long id) { super.onListItemClick(l, v, position, id); Intent i = new Intent(this, NoteEdit.class); i.putExtra(KEY_ROW_ID, rows.get(position).rowId); startSubActivity(i, ACTIVITY_EDIT); }
startSubActivityで別のActivityを起動します。
先ほどと同様にIntentで値を受け渡します。今回は選択された要素が何であるかを受け渡したいので、
i.putExtra(KEY_ROW_ID, rows.get(position).rowId);
という形でIntentに値を持たせてデータを引き渡します。ソースを読んだ訳ではありませんが、連想配列を想像していて間違いは無いでしょう。
さて、これでメインとなる画面のイベント処理が終わりました。
ここまで説明が長かったですが、やっている事はとても簡単です。
纏めると、
- アプリケーションの入り口を作る(onCreate)
- イベント処理を書く
- onCteateOptionsMenu
- onMenuItemSelected
- onListItemClick
これだけです。
では、次にメイン画面から呼び出されたサブ画面を作っていきます。
サブ画面とは、上でも紹介したノートパッド画面の事です。
画面は全てActivityクラスを継承してできていますので、ノートパッド画面もActivityクラスを継承します。
今回はリストを表示する訳ではないので、単純にActivityを継承します。
もちろんActivityが作成されるとonCreateメソッドがコールされますので、それも合わせて作ります。
public class NoteEdit extends Activity { @Override protected void onCreate(Bundle icicle) { super.onCreate(icicle); dbHelper = new DBHelper(this);//DBアクセス用 setContentView(R.layout.note_edit);//レイアウト設定 titleText = (EditText) findViewById(R.id.title);//項目名「Title」 bodyText = (EditText) findViewById(R.id.body);//項目名「Body」 Button confirmButton = (Button) findViewById(R.id.confirm);//編集終了ボタンを作成 // 今回はicicleはnull rowId = icicle != null ? icicle.getLong(Notepadv3.KEY_ROW_ID) : null; // IntentのputExtraで値がセットされている場合はその値を取得、 // 値が存在しない場合は編集ではなく「Add Note」メニューから新規に作成された if (rowId == null) { Bundle extras = getIntent().getExtras(); rowId = extras != null ? extras.getLong(Notepadv3.KEY_ROW_ID) : null; } populateFields(); confirmButton.setOnClickListener(new View.OnClickListener() { public void onClick(View arg0) { setResult(RESULT_OK); finish(); } }); }
このNoteEditというActivityが呼び出されるのは、2通りありました。
onMenuItemSelectedで「Add Note」でノートバッドが新規に作成される場合と、
onListItemClickでノートパッドを編集するときです。
前者はIntentでデータを受け渡していませんでしたが、後者はputExtraでデータを受け渡していました。
この違いから、onCreateの中で処理を分けています。
すなわち、データが受け渡されていない場合は、ノートパッドを新規作成ですので、DBに値が入っていません。その逆に、データが受け渡されている場合は、ノートパッドは既存ですのでDBからその値を取り出す必要があります。その処理がこれです。
if (rowId == null) { Bundle extras = getIntent().getExtras(); rowId = extras != null ? extras.getLong(Notepadv3.KEY_ROW_ID) : null; }
DBから値を取り出す処理はここでは行わず、DBの要素のIDだけを取得しています。
DBの値を取り出し編集エリアに埋めるのはpopulationFieldsメソッドです。
private void populateFields() { if (rowId != null) { DBHelper.Row row = dbHelper.fetchRow(rowId); if (row.rowId > -1) { titleText.setText(row.title); bodyText.setText(row.body); } } }
rowIdがnullの場合はIntentで値を受け渡さない場合、つまりノートパッドを新規作成する場合ですので、何もしません。
逆にrowIdがnullでない場合は編集ですのでDBからIDをキーにして要素を取り出し、各編集エリアに値を設定します。
最後にボタンのクリックイベント処理を記述します。
confirmButton.setOnClickListener(new View.OnClickListener() { public void onClick(View arg0) { setResult(RESULT_OK); finish(); } });
setResultでは終了コードをセットしておきます。正常終了ですのでRESULT_OKです。
そしてfinishメソッドでこのActivityを終わり、呼び出し元に戻ります。
さて、ここまででメイン画面からサブ画面への遷移を持つアプリケーションの作成が完了しました。
しかし、まだ1つやり残した事があります。それは、編集や新規作成したノートパッドの内容をDBに保存していません。
DBへの保存のタイミングはこのプログラムでは3つあります。
- ボタンがクリックされたタイミング(onClick)
- サブ画面からメイン画面へ戻るタイミング(onPause)
- メイン画面に戻ってきたタイミング(onActivityResult)
onClickは上のボタンがクリックされたときです。onPauseはActivityが終了するときにコールされるイベントのうちの1つです。onActivityResultはサブ画面からメイン画面に戻ってきた際に呼び出されるメインActivity側でコールされるメソッドです。正常動作時にはどの場合でも問題ないと思います。
しかし、アプリケーションはいつ止まるか分かりません。止まったときにでも大切なデータは保存したいでしょうから、ここではonPauseというメソッド内でDB保存を行います。
@Override protected void onPause() { super.onPause(); saveState(); dbHelper.close(); dbHelper = null; }
saveStateメソッドでDB保存しています。
このついでにDBアクセッサも閉じて削除しています。
private void saveState() { String title = titleText.getText().toString(); String body = bodyText.getText().toString(); if (rowId == null) { dbHelper.createRow(title, body); } else { dbHelper.updateRow(rowId, title, body); } }
さて、これにてアプリケーションが完成しました。
後半も長々と説明して複雑に思えますが、やっている事は簡単で、纏めますと、
- サブ画面が呼び出された場合に応じてDBアクセスする
- ノートバッドの編集が終了したらDBに保存する
たったこれだけです。
長かったですが簡単でしたね。
今回は2つのActivityを行き来するため、データの受け渡しが発生しました。
それにはIntentという仕組みを使うことで実現しました。
この仕組みはAndroidに非常に大切な部分ですので、今後も応用できるはずです。
これにてAndroidのアプリケーション開発のおおきな流れを理解する事ができました。
あとは他に用意されているAPIを色々触ってみると、面白い物が見つかるかもしれませんね。
間違い等がありましたら、本エントリのコメントにお願いします。
(スパムが大量に流れてきますので承認形式となっています。)
DBHelper.javaのソースをコメント付きで載せておきます。SQLの分かる人なら簡単に読めると思います。
package com.google.android.demo.notepad3; import java.io.FileNotFoundException; import java.util.ArrayList; import java.util.List; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.SQLException; import android.database.sqlite.SQLiteDatabase; import android.util.Log; public class DBHelper { // DB内の要素を格納するクラス // DBにはbody, rowId, titleが格納されている。 class Row extends Object { public String body; public long rowId; public String title; } private static final String DATABASE_CREATE = "create table todo (rowid integer primary key autoincrement, " + "title text not null, body text not null);"; private static final String DATABASE_NAME = "data"; private static final String DATABASE_TABLE = "todo"; private static final int DATABASE_VERSION = 1; private SQLiteDatabase db; // アプリケーションが持つDBを取得する。 // 取得に失敗する(DBが存在しない)場合は新たにDBテーブルを作成する。 public DBHelper(Context ctx) { try { db = ctx.openDatabase(DATABASE_NAME, null); } catch (FileNotFoundException e) { try { db = ctx.createDatabase(DATABASE_NAME, DATABASE_VERSION, 0, null); db.execSQL(DATABASE_CREATE); } catch (FileNotFoundException e1) { db = null; } } } // DBを閉じる public void close() { db.close(); } // titleとbodyの値をDBに格納する public void createRow(String title, String body) { ContentValues initialValues = new ContentValues(); initialValues.put("title", title); initialValues.put("body", body); db.insert(DATABASE_TABLE, null, initialValues); } // 番号rowIdのラベルをDBから削除する public void deleteRow(long rowId) { db.delete(DATABASE_TABLE, "rowid=" + rowId, null); } // 全てのラベルの要素を取り出す public List<Row> fetchAllRows() { ArrayList<Row> ret = new ArrayList<Row>(); try { Cursor c = db.query(DATABASE_TABLE, new String[] { "rowid", "title", "body"}, null, null, null, null, null); int numRows = c.count(); c.first(); for (int i = 0; i < numRows; ++i) { Row row = new Row(); row.rowId = c.getLong(0); row.title = c.getString(1); row.body = c.getString(2); ret.add(row); c.next(); } } catch (SQLException e) { Log.e("booga", e.toString()); } return ret; } // 番号rowIdのラベルの各要素を取り出す public Row fetchRow(long rowId) { Row row = new Row(); Cursor c = db.query(true, DATABASE_TABLE, new String[] { "rowid", "title", "body"}, "rowid=" + rowId, null, null, null, null); if (c.count() > 0) { c.first(); row.rowId = c.getLong(0); row.title = c.getString(1); row.body = c.getString(2); return row; } else { row.rowId = -1; row.body = row.title = null; } return row; } // 番号rowIdのtitleとbodyの要素をアップデートする public void updateRow(long rowId, String title, String body) { ContentValues args = new ContentValues(); args.put("title", title); args.put("body", body); db.update(DATABASE_TABLE, args, "rowid=" + rowId, null); } }
- Newer: Androidで地図を表示する方法
- Older: Gold Whirlwind — Fractal in Processing –
Comments:2
- Andro 08-11-19 (水) 10:10
-
さっそくのお返事ありがとうございます。
08-11-19 (水) 8:24 に質問した Andro です。
恥ずかしながらの初心者で、エミュレータでの話です。
実機にダウンロードはしていません。
エミュレータのメニューで、前につくったアプリの
アイコンの残骸?が残ってしまうんですよね。
ワークスペースを別にしたりしているんですが。
何かアドバイスございますでしょうか? - adamrocker 08-11-19 (水) 23:01
-
>Androさん
エミュレータでも同じですよ。
コマンドラインからアプリのuninstallが可能です。
adbというコマンドがあります。
↓ここが参考になるかもしれません。
http://android.akjava.com/cms/develop/sdk/tools/adb.htmlお試し下さい。
Trackbacks:2
- Trackback URL for this entry
- http://www.adamrocker.com/blog/167/how_to_program_android_application.html/trackback/
- Listed below are links to weblogs that reference
- Androidアプリケーション開発入門 from throw Life
- trackback from Taosoftware 08-10-09 (木) 10:23
-
android 学習方法…
Cとかjavaとか一通りの知識がある物として、最速なandroidの学習方法 1… (more…)
- pingback from Androidアプリ開発環境構築備忘録 « takyam.com 09-12-11 (金) 16:57
-
[…] http://www.adamrocker.com/blog/167/how_to_program_android_application.html […]
