前@overrideアノテーションを正しく使っているにもかかわらずエラーになるということがありました。
原因は@overrideアノテーションがJDK1.6から拡張されたことです。
それまでは@overrideはextendsしたクラスのメソッドにしか使えなかったのですが、1.6からはインターフェイスにも使用できるようになりました。
つまり古いコンパイラのまま使用しているみたいです。
JDK1.6はインストールしてあるはずなのにおかしいな。
「ウィンドウ→設定→Java→コンパイラー→コンパイラー準拠レベル」でJDKを選択することができます。
比較的新しい版を使用していたつもりだったのですが、何故か1.5に設定されていました。
ここを1.6にすることで、@overrideの新しい使用法にも対応することができるようになりました。
インターフェイスはそもそもoverrideしていないとエラーになるので@overrideアノテーションの意味はあんまり無いような気もするのですがね。
他のアノテーションはコンパイラ向けなのに対してインターフェイス用@overrideは開発者向けっぽいですね。
Androidの記事
前回の続き。
現在、一覧画面から各名刺をクリックするとShowActivityが起動し、クリックしたBizCardオブジェクトがインテントで渡されるようになっています。
ShowActivityはテンプレートを作っただけでロジックは何もしていませんでしたので、インテントから投稿内容を受け取るようにしてみましょう。
/src/com.example.bizcard/ShowActivity.java
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.show);
// Intentに入れられたBizCardオブジェクトを取得
bizCard = (BizCard)getIntent().getSerializableExtra( BizCard.TABLE_NAME);
// UI部品の取得
personNameLabel = (TextView)findViewById( R.id.personNameLabel);
companyNameLabel = (TextView)findViewById( R.id.companyNameLabel);
organizationNameLabel = (TextView)findViewById( R.id.organizationNameLabel);
positionNameLabel = (TextView)findViewById( R.id.positionNameLabel);
addressLabel = (TextView)findViewById( R.id.addressLabel);
tel1Label = (TextView)findViewById( R.id.tel1Label);
tel2Label = (TextView)findViewById( R.id.tel2Label);
mailLabel = (TextView)findViewById( R.id.mailLabel);
// 表示内容の更新
updateView();
}
private void updateView(){
//各部品にbizCardを突っ込んでるだけ
personNameLabel.setText( bizCard.getPersonName());
companyNameLabel.setText( bizCard.getCompanyName());
organizationNameLabel.setText( bizCard.getOrganizationName());
positionNameLabel.setText( bizCard.getPositionName());
tel1Label.setText( bizCard.getTel1());
tel2Label.setText( bizCard.getTel2());
mailLabel.setText( bizCard.getMail());
addressLabel.setText( bizCard.getAddress());
}
Activity.getIntent()で渡されたインテントオブジェクトを取得します。その後は、Intent.putExtra()時に使用したメソッドに対応する取得メソッドで中身を取り出すことができます。
今回はSerializableなオブジェクトBizCardを突っ込んだのでgetSerializableExtra()で取り出せます。
この取得するメソッド、取り出す型によって使い分けが必要です。
文字列だったらgetStringExtra()だったり数値であればgetIntExtra()等です。
突っ込むときにはputExtra()が大量にオーバーロードされており、個別のputStringExtra()やputIntExtra()なんかは存在しないのに、何故かgetExtra()はありません。
さらに何故かgetIntegerArrayListExtra()には特別にputStringArrayListExtra()が用意されていたりするという。
何故こんな非対称な作りになってるんでしょうか。
どうにかして取り出せれば、後は普通にBizCardオブジェクトとして扱うことができます。
これで名刺をクリックすると詳細が表示されるようになりました。
なお、電話番号やメールアドレスには自動的にリンクが張られています。
これはandroid:autoLinkによるもので、android:autoLink="phone"とだけ書いておくと、それっぽい文字列に自動的にリンクが張られ、該当のリンクをクリックしたら自動的に電話が立ち上がります。
http://developer.android.com/intl/ja/reference/android/widget/TextView.html
"web"や"email"等一部の機能だけですが、わざわざリスナーを用意したりしなくても別アプリを起動できる便利な属性です。
さて、本来なら住所部分にandroid:autoLink="map"を指定するとGoogleMapを使えるのですが、残念ながら"map"は日本語に対応していません。
ていうか" The White House 1600 Pennsylvania Avenue, NW Washington, DC 20500 "って入れても駄目だったのでなんか使い方がおかしいのかもしれませんが。
ということで任意にリンクを作成したいと思いますが、それにはSpannableStringを使用します。
元々文字列を修飾するクラスらしいのですが、SpannableString.setSpan()とTextView.setMovementMethod()でリンクを踏んだときの行き先を設定することができます。
updateView()を修正。
private void updateView(){
personNameLabel.setText( bizCard.getPersonName());
companyNameLabel.setText( bizCard.getCompanyName());
organizationNameLabel.setText( bizCard.getOrganizationName());
positionNameLabel.setText( bizCard.getPositionName());
tel1Label.setText( bizCard.getTel1());
tel2Label.setText( bizCard.getTel2());
mailLabel.setText( bizCard.getMail());
//addressLabel.setText( bizCard.getAddress());
//住所をStringとして取得
String address = bizCard.getZipCode() + " " + bizCard.getAddress();
// SpannableStringインスタンスにセット
SpannableString addressSpan = new SpannableString( address);
// スタイルとしてAddressSpanをセット
addressSpan.setSpan(new AddressSpan(), 0, address.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
// ベタテキストのかわりに、SpannableStringテキストをセット
addressLabel.setText( addressSpan);
// ClickableSpan.onClick()を呼び出すための決まり文句みたい
addressLabel.setMovementMethod( LinkMovementMethod.getInstance());
}
//住所のクリック時に呼ばれる
class AddressSpan extends ClickableSpan{
@Override
public void onClick(View widget) {
// 住所を詰めてMapActivityを起動
Intent mapIntent = new Intent( ShowActivity.this, MapActivity.class);
mapIntent.putExtra( BizCard.COLUMN_ADDRESS, bizCard.getAddress());
startActivity( mapIntent);
}
}
まず住所としてaddress文字列を取得し、SpannableStringインスタンスに投入します。次にsetSpan()で任意のスタイルをセットします。
ここの第一引数には本来UnderlineSpanとかStrikethroughSpanといったスタイルをセットするようなクラスが入りますが、中でもClickableSpanではリンクを設定することができます。
ClickableSpan.onClick()はabstractなので、AddressSpanクラスにおいて、クリックされたら住所をインテントに突っ込んでMapActivityを起動するという実装を行っています。
これでめでたしめでたしと思ったら、リンクをクリックしても何も起こりません。
実はClickableSpanには、肝心のそのクリックを感知する機能が用意されていないらしいです。
感知するには、リスナー的なものとしてTextView.setMovementMethod( LinkMovementMethod.getInstance())という決まり文句を書けばいいみたいです。
意味がわからん。
Androidの記事
http://codezine.jp/article/detail/4842?p=2
前回の続き。
現在は既に登録されている名刺を表示するだけしかできません。
新規登録アクティビティへのリンクを作成します。
まずはこれまでのテンプレートとは完全に独立した登録メニューを作成。
/res/menu/list.xml
<?xml version="1.0" encoding="utf-8" ?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <!-- 新規メニュー --> <item android:id="@+id/menu_new" android:title="@string/menu_new" android:icon="@android:drawable/ic_menu_add" /> </menu>"@string/menu_new"とついでに他のメニュー文言を登録。
/res/values/string.xml
<string name="menu_new">新規</string>
<string name="menu_save">保存</string>
<string name="menu_edit">編集</string>
<string name="menu_delete">削除</string>
メニューボタンを押したときの挙動を追加。/src/com.example.bizcard/ListActivity.java
@Override
public boolean onCreateOptionsMenu(Menu menu){
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.list, menu);
return true;
}
Activity.onCreateOptionsMenu()はメニューボタンを押したときに出てくる内容を変更するメソッドです。MenuInflater.inflate()という謎のメソッドを実行することでメニューを追加することができます。
ひとつだけだとメニュー全体にボタンが現れますが、複数回追加すると追加したぶんだけメニューが増やせます。
メニューにクリック時の遷移を追加します。
こちらは元々クリックされることが想定されているので、わざわざリスナーを追加したりせずとも簡単に監視を行うことができます。
//メニューボタンのクリックイベント
public boolean onOptionsItemSelected(MenuItem item) {
int itemId = item.getItemId();
switch (itemId) {
case R.id.menu_new:
// 編集画面へ遷移
Intent registIntent = new Intent( this, RegistActivity.class);
startActivity( registIntent);
break;
}
return true;
};
Activity.onOptionsItemSelected()でメニューボタンをクリックしたときのイベントを記述します。こちらはonItemClickと違ってMenuItemというわかりやすい引数で来ます。
クリックした部分が"@+id/menu_new"であればRegistActivityにインテントを送信しています。
今回はメニューにR.menu.listしかないのでswitch文の意味が全く無いですが、メニューに複数のボタンを追加したときでも全く同様に処理することができます。
無事に新規登録メニューができました。
サンプルと色がちがうんだが何でだろう?
Androidの記事
http://codezine.jp/article/detail/4842?p=2
前回までで名刺一覧の表示ができましたので、今回はそれぞれの名刺をクリックするとその詳細を表示させてみます。
別のアクティビティやアプリを起動するには、クリック部分にリスナーを仕込んでおき、クリックを感知して該当のアクティビティを起動する、というのが常套手段のようです。
まずリスナーをセット
/src/com.example.bizcard/ListActivity.java
public class ListActivity extends Activity implements OnItemClickListener{
public void onCreate(Bundle savedInstanceState) {
//表示
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
arrayAdapter = new ArrayAdapter<BizCard>(this, android.R.layout.simple_list_item_1);
listView = (ListView) findViewById(R.id.list);
listView.setAdapter(arrayAdapter);
//クリックリスナーをセット
listView.setOnItemClickListener(this);
}
}
OnItemClickListenerを直接実装しました。ということでOnItemClickListener.onItemClick()を実装します。
//クリックリスナー
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// 選択された要素を取得する
BizCard bizCard = (BizCard)parent.getItemAtPosition( position);
// 参照画面へ遷移する明示的インテントを生成
Intent showIntent = new Intent( this, ShowActivity.class);
// 選択されたオブジェクトをインテントに詰める
showIntent.putExtra( BizCard.TABLE_NAME, bizCard);
// アクティビティを開始する
startActivity( showIntent);
}
正直引数がなんなのかさっぱりわからないんですが。AdapterView<?>って何だ?
検索しようがないので調べるにも調べられん。
ていうかそれ以前にAdapterViewがよくわからん。
まあとりあえずAdapterView.getItemAtPosition( position)でクリックしたビューを取得できるらしいです。
第二引数viewとか第四引数idとかは何なんだ?
該当のBizCardオブジェクトを取得できたらそれをインテントに詰め込んでShowActivityを実行します。
.classってのはまた何だ。
http://www.itarchitect.jp/technology_and_programming/-/18161-5.html
> Classオブジェクトは、「Foo.class」のように「《クラス名》.class」というかたちのクラス・リテラルによって記述することができる。
> このクラス・リテラルの型は、パラメータ付きのClass型になっている。
> 例えば、「Foo.class」というクラス・リテラルであれば、Class<Foo>という型を持つ。
なるほど、まったくわからん。
クラスリテラルってなんだよ。
中身は気にしないので単に殻としてShowActivityという型だけ欲しいときに.classを使えばいいということか?
よくわかりませんがとりあえず詳細画面が表示されるようになりました。
といっても詳細画面は現在引数を使わず完全固定なのですが。
さて、今回はActivityに直接OnItemClickListenerをinplementしています。
普通はこういう作りにせず、内部クラスを作ってそちらにリスナーをセットするという形にすることが定石らしいです。
ちょっとやってみます。
こういった定石とかってどうやって知ればいいんだろう?
public void onCreate(Bundle savedInstanceState) {
//クリックリスナーをセット
listView.setOnItemClickListener(new BizCardClickListener());
}
class BizCardClickListener implements OnItemClickListener {
public void onItemClick(AdapterView<?> parent, View view, int position,long id) {
BizCard bizCard = (BizCard)parent.getItemAtPosition( position);
Intent showIntent = new Intent( ListActivity.this, ShowActivity.class);
showIntent.putExtra( BizCard.TABLE_NAME, bizCard);
startActivity( showIntent);
}
}
まあ単に内部クラスを作っただけなので実質的には何一つ変わっていません。中身のコードの意味がわからないのもそのままということです。
Androidの記事
http://codezine.jp/article/detail/4842?p=2
前回の続き。
一覧表示アクティビティはとりあえず完成しましたが、現在は同期的に動作しています。
同期的というのはつまり、onCreate()→onResume()→画面描画と順番に動作しているということで、もし名刺が大量に増えてonResume()に時間がかかるようになった場合画面表示までの時間が遅くなってしまうということです。
そこで非同期的に動作させることができるようになっています。
onResume()内の具体的処理を非同期処理に任せることで、
onCreate()→onResume()→先に画面描画
↓
非同期処理の結果を出力
と並列な処理を行うことができます。
先に画面を出すことで、全体の処理時間は変わらなくても遅いという感覚を減らすことができます。
ListActivity.onResume()を修正。
/src/com.example.bizcard/ListActivity.java
//アクティビティが前面に来たときに実行
public void onResume() {
super.onResume();
//DBの中身取得は非同期処理する
DataLoadTask task = new DataLoadTask();
task.execute();
}
DataLoadTaskを実装することにします。非同期処理タスクはAsyncTaskを継承します。
//一覧データの取得と表示を行うタスク
public class DataLoadTask extends AsyncTask<Object, Integer, List<BizCard>> {
// 処理中ダイアログ
private ProgressDialog progressDialog = null;
//バックグラウンド処理実行前に実行される
@Override
protected void onPreExecute() {
// 「データ取得中」ダイアログ表示
progressDialog = new ProgressDialog(ListActivity.this);
progressDialog.setMessage(getResources().getText(
R.string.data_loading));
progressDialog.setIndeterminate(true);
progressDialog.show();
}
//バックグラウンドで実行するメイン処理
@Override
protected List<BizCard> doInBackground(Object... params) {
// 一覧データの取得をバックグラウンドで実行
BizCardDao dao = new BizCardDao(ListActivity.this);
return dao.list();
}
//バックグラウンド実行のあとで実行
@Override
protected void onPostExecute(List<BizCard> result) {
// ダイアログをクローズ
progressDialog.dismiss();
// 表示データのクリア
arrayAdapter.clear();
// 表示データの設定
for (BizCard bizCard : result) {
arrayAdapter.add(bizCard);
}
}
}
R.string.data_loadingをセット。/res/values/strings.xml
<string name="data_loading">データを取得しています</string>
実行すると、まず普通にListActivity.onResume()までが実行されます。
そこでDataLoadTask.execute()が呼ばれることで、まずDataLoadTask.onPreExecute()が実行されます。
onPreExecute()は、次のdoInBackground()が実行される前に画面描画を行ってくれます。
ここでは単にダイアログを表示しています。
ProgressDialog.setIndeterminate(true)はプログレスバーを表示しないだそうですが、falseにしても変わりませんでした。
使用する場合、setProgress()やsetProgressStyle()などでプログレスバーをセットできます。
後DataLoadTask.doInBackground()で具体的な重い処理を記述します。
引数および返り値は任意にセットすることができ、返り値はonPostExecute()の引数として引き渡されます。
DataLoadTask.onPostExecute()は最後に実行され、後始末などの処理を行います。
今回はdoInBackground()で取得した名刺情報を画面に反映させ、ダイアログの消去を行っています。
以上で簡単に非同期処理の実装が完成しました。
てか、AsyncTaskの解説って少ないよね。
publishProgress()とかどう使えばいいのかわからんのだが。
doInBackground()はUIじゃないはずなのにprogressDialog.dismiss()とか書くと動いたりするしよくわからん。
アクティビティとデータベース操作クラスというそれぞれの部品が完成したので、これらをくっつけていきます。
まず一覧画面。
現在「新規追加」しか表示されず、しかもクリックしても動きませんが、これを動作するようにしてみます。
/src/com.example.bizcard/ListActivity.java
public class ListActivity extends Activity {
//インスタンス変数
private ListView listView;
ArrayAdapter arrayAdapter = null;
//起動時に実行
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// 配列を作成
arrayAdapter = new ArrayAdapter<bizcard>(this, android.R.layout.simple_list_item_1);
// main.xmlのListViewを取得
listView = (ListView) findViewById(R.id.list);
// 配列を突っ込む
listView.setAdapter(arrayAdapter);
}
//アクティビティが前面に来たときに実行
public void onResume() {
super.onResume();
//DBアクセスクラス
BizCardDao dao = new BizCardDao(ListActivity.this);
//DBの中身を取得
List<bizcard> result = dao.list();
//現在の値をクリア
arrayAdapter.clear();
//全データをarrayAdapterに突っ込む
for (BizCard bizCard : result) {
arrayAdapter.add(bizCard);
}
}
}
あれ?
新規登録とか書いてあったんで単なる動作選択メニューだと思ってたんだが名刺一覧だったのか。
arrayAdapter.add(bizCard)にはStringではなくBizCardを突っ込んでいるので、表示は前作ったBizCard.toString()が使用されることになります。
以上でデータベースに登録してある値を拾ってきて表示するようになりました。
初回起動時にはDatabaseOpenHelper.onCreate()が作動してデフォルトの4件のデータを登録してくれます。
めでたしめでたし。
エミュレータで動作中のデータベースの状態をEclipseから見ることはできないの?
外部閲覧方法なら前飛ばしたところに書いてあります。
コマンドプロンプトから
> adb shell
# cd /data/data/com.example.bizcard/databases/
# sqlite3 BIZ_CARD
> select * from biz_card;
で閲覧できますが、見事に文字化けしています。
AndroidはUTF-8ですが、コマンドプロンプトはSJISなうえに文字コード変更が面倒なんですよね。
右クリックから「プロパティ」→「フォント」→「MSゴシック」を選択、コマンドプロンプトから"chcp 65001"でUTF-8になり、↑も文字化け無く見ることができます。
ただコマンドプロンプトの使い勝手が悪いので常用したいとは思えません。
また、「ウィンドウ」→「ビューの表示」→「その他」→「Android」→「ファイル・エクスプローラー」→「data/data/com.example.bizcard/databases/BIZ_CARD」→右上の「Pull a file from the device」ボタンとすることでDBの状態をファイルとして出力することができます。
いやあちがうんだ、もっとこうDBツリービューを覗けばOK、みたいなかんじで現状を直接確認とかできないのか?
Androidの記事
全然どうでもいいんだがDAOって響きなんだかマヌケなかんじがしません?だお。
/src/com.example.bizcart/db/BizCardDao.java
public class BizCardDao {
//インスタンス変数
private DatabaseOpenHelper helper = null;
//コンストラクタ
public BizCardDao(Context context) {
helper = new DatabaseOpenHelper(context);
}
//レコードを保存する
public BizCard save(BizCard bizCard){
SQLiteDatabase db = helper.getWritableDatabase();
BizCard result = null;
try {
//保存するContentValuesオブジェクトを作成
ContentValues values = new ContentValues();
values.put( BizCard.COLUMN_PERSON_NAME, bizCard.getPersonName());
values.put( BizCard.COLUMN_COMPANY_NAME, bizCard.getCompanyName());
values.put( BizCard.COLUMN_ORGANIZATION_NAME, bizCard.getOrganizationName());
values.put( BizCard.COLUMN_POSITION_NAME, bizCard.getPositionName());
values.put( BizCard.COLUMN_ZIP_CODE, bizCard.getZipCode());
values.put( BizCard.COLUMN_ADDRESS, bizCard.getAddress());
values.put( BizCard.COLUMN_TEL1, bizCard.getTel1());
values.put( BizCard.COLUMN_TEL2, bizCard.getTel2());
values.put( BizCard.COLUMN_MAIL, bizCard.getMail());
Long rowId = bizCard.getRowid();
//保存
if( rowId == null){
rowId = db.insert( BizCard.TABLE_NAME, null, values);
}
else{
db.update( BizCard.TABLE_NAME, values, BizCard.COLUMN_ID + "=?", new String[]{ String.valueOf( rowId)});
}
//保存した中身を取得
result = load( rowId);
} finally {
db.close();
}
return result;
}
// 該当IDのレコードを取得
public BizCard load(Long rowId) {
SQLiteDatabase db = helper.getReadableDatabase();
BizCard bizCard = null;
try {
Cursor cursor = db.query( BizCard.TABLE_NAME, null, BizCard.COLUMN_ID + "=?", new String[]{ String.valueOf( rowId)}, null, null, null);
cursor.moveToFirst();
bizCard = getBizCard( cursor);
} finally {
db.close();
}
return bizCard;
}
// 全レコードを取得
public List list() {
SQLiteDatabase db = helper.getReadableDatabase();
List bizCardList;
try {
Cursor cursor = db.query( BizCard.TABLE_NAME, null, null, null, null, null, BizCard.COLUMN_ID);
bizCardList = new ArrayList ();
cursor.moveToFirst();
while( !cursor.isAfterLast()){
bizCardList.add( getBizCard( cursor));
cursor.moveToNext();
}
} finally {
db.close();
}
return bizCardList;
}
// 該当のレコードを削除
public void delete(BizCard bizCard) {
SQLiteDatabase db = helper.getWritableDatabase();
try {
db.delete( BizCard.TABLE_NAME, BizCard.COLUMN_ID + "=?", new String[]{ String.valueOf( bizCard.getRowid())});
} finally {
db.close();
}
}
// カーソルからオブジェクトへの変換を行うサブルーチン
private BizCard getBizCard( Cursor cursor){
BizCard bizCard = new BizCard();
bizCard.setRowid( cursor.getLong(0));
bizCard.setPersonName( cursor.getString(1));
bizCard.setCompanyName( cursor.getString(2));
bizCard.setOrganizationName( cursor.getString(3));
bizCard.setPositionName( cursor.getString(4));
bizCard.setZipCode( cursor.getString(5));
bizCard.setAddress( cursor.getString(6));
bizCard.setTel1( cursor.getString(7));
bizCard.setTel2( cursor.getString(8));
bizCard.setMail( cursor.getString(9));
return bizCard;
}
}
見知らぬコードが一気に増えました。順に見てみます。
save()でレコードを保存します。
SQLiteOpenHelperからSQLiteDatabaseオブジェクトを得るメソッドはgetReadableDatabase()とgetWritableDatabase()ふたつがありますが、前者は読み取り専用、後者は読み書きどちらも可能ということらしいです。
万一のことを考えて保存するメソッド以外はgetReadableDatabase()を呼んどけばいいということですかね。
ContentValuesはDBに保存する値を突っ込んでおく連想配列みたいなクラスです。
ContentValues.put(key, value)でキーと値のペアを注ぎ込んで、最後にinsert()なりupdate()なりの引数として渡します。
保存するメソッドはこんな形。
SQLiteDatabase.insert(String table, String nullColumnHack, ContentValues values)
SQLiteDatabase.update(String table, ContentValues values, String whereClause, String whereArgs[])
nullColumnHackって何だ?
updateの第三引数はwhere句、第四引数はバインドする値の配列です。
PHP的に言うとupdate($table, $values, 'hoge = ? AND fuga = ?', array( 'foo', 'bar' ) )ですかね。
保存に成功した場合は、引数のBizCardは使用せず保存したデータをload()で読み込んでいます。
そのload()は引数のIDのレコードを取得します。
SQLiteDatabase.query(String table, String columns[], String selection, String selectionArgs[], String groupBy, String having, String orderBy)
第二引数は取得するカラム名を並べるようです。
nullにすると全カラム取ってきてくれるみたい。
返り値はContentValuesでいいのに何故かCursorとかいう形で返ってくるので、BizCard形式に戻して返します。
このgetBizCard()がCursor.getString(1)とか書いてあるんですが今時カラム名じゃなくて番号かよ。こええよ。
Cursor.getColumn(BizCard.COLUMN_PERSON_NAME)とか無いの?
list()は保存されている全エントリをBizCardのListに入れて返します。
中身はload()とほぼ同じです。
while文中にあるCursor.isAfterLast()は最終行よりさらに先にいるかという意味のようです。
whileが最終行までくるくると回り続け、結果として全ての行を取得して返します。
これ、limitが設定されていないのでデータが増えていくと危険なことになりそうです。
delete()はそのまま削除の意味です。
サンプルでは引数がBizCardですがint BizCard.COLUMN_IDで十分な気が。
こんなかんじでデータベースを操作するクラスが完成しました。
Androidの記事
データベースハンドラを作成します。
class DatabaseOpenHelper extends SQLiteOpenHelper{}
この一行を書いてエラー部分をクリックするだけで下のように最低限の形ができあがります。
Eclipse便利すぎだろ。
/src/com.example.bizcart/db/DatabaseOpenHelper.java
package com.example.bizcard.db;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
class DatabaseOpenHelper extends SQLiteOpenHelper{
public DatabaseOpenHelper(Context context, String name,
CursorFactory factory, int version) {
super(context, name, factory, version);
}
@Override
public void onCreate(SQLiteDatabase sqlitedatabase) {}
@Override
public void onUpgrade(SQLiteDatabase sqlitedatabase, int i, int j) {}
}
ということで各メソッドを実装。
//BizCardのデータベースハンドラ
class DatabaseOpenHelper extends SQLiteOpenHelper{
//データベース名
private static final String DB_NAME = "BIZ_CARD";
//サンプルデータ
private String[][] datas = new String[][]{
{"光度 人", "××商事", "開発部", "部長","141-0031", "品川区西五反田2-19-3", "090-1111-1111", "03-1111-1111", "code@shoji.co.jp"},
{"高度 仁", "○○コミュニケーションズ", "開発部", "課長","141-0032", "品川区大崎1-2-1", "090-2222-2222", "03-2222-2222", "code@comunications.co.jp"},
{"光度 陣", "×○システムズ", "開発部", null,"153-0043", "目黒区東山1-2-1", "090-3333-3333", "03-3333-3333", "code@systems.co.jp"},
{"荒土 尋", "○×工務店", "開発部", null,"160-0014", "新宿区内藤町11-4", "090-4444-4444", "03-4444-4444", "code@koumuten.co.jp"}
};
//コンストラクタ
public DatabaseOpenHelper(Context context) {
super(context, DB_NAME, null, 1);
}
//データベースが存在しなかった場合、データベース作成後に実行される
@Override
public void onCreate(SQLiteDatabase sqlitedatabase) {
//トランザクション
sqlitedatabase.beginTransaction();
try{
// テーブルの生成
StringBuilder createSql = new StringBuilder();
createSql.append("create table " + BizCard.TABLE_NAME + " (");
createSql.append(BizCard.COLUMN_ID + " integer primary key autoincrement not null,");
createSql.append(BizCard.COLUMN_PERSON_NAME + " text not null,");
createSql.append(BizCard.COLUMN_COMPANY_NAME + " text,");
createSql.append(BizCard.COLUMN_ORGANIZATION_NAME + " text,");
createSql.append(BizCard.COLUMN_POSITION_NAME + " text,");
createSql.append(BizCard.COLUMN_ZIP_CODE + " text,");
createSql.append(BizCard.COLUMN_ADDRESS + " text,");
createSql.append(BizCard.COLUMN_TEL1 + " text,");
createSql.append(BizCard.COLUMN_TEL2 + " text,");
createSql.append(BizCard.COLUMN_MAIL + " text");
createSql.append(")");
sqlitedatabase.execSQL( createSql.toString());
// サンプルデータの投入
for( String[] data: datas){
ContentValues values = new ContentValues();
values.put(BizCard.COLUMN_PERSON_NAME, data[ 0]);
values.put(BizCard.COLUMN_COMPANY_NAME, data[ 1]);
values.put(BizCard.COLUMN_ORGANIZATION_NAME, data[ 2]);
values.put(BizCard.COLUMN_POSITION_NAME, data[ 3]);
values.put(BizCard.COLUMN_ZIP_CODE, data[ 4]);
values.put(BizCard.COLUMN_ADDRESS, data[ 5]);
values.put(BizCard.COLUMN_TEL1, data[ 6]);
values.put(BizCard.COLUMN_TEL2, data[ 7]);
values.put(BizCard.COLUMN_MAIL, data[ 8]);
sqlitedatabase.insert(BizCard.TABLE_NAME, null, values);
}
//トランザクション終了
sqlitedatabase.setTransactionSuccessful();
} finally {
//失敗
sqlitedatabase.endTransaction();
}
}
/*
* SQLiteOpenHelperのコンストラクタ第四引数versionがバージョンアップした場合に実行される
*/
@Override
public void onUpgrade(SQLiteDatabase sqlitedatabase, int i, int j) {}
}
初回起動時など、データベースが見つからなかった場合はonCreate()が呼ばれます。
Webアプリと違ってテーブルを予め用意しておくという運用ができないので、onCreate()ではテーブル作成等を行う必要があります。
今回はテーブルひとつだからまあいいんだが100個テーブルがあったら全部書くのかこれ?
その後現在のデータベース状態確認方法が載っていますが、まだ実行していないのでデータベースがありません。
とりあえずスルー。
Androidの記事
今回はデータベースを使用してみます。
SQLiteが使用できるようです。
まずはBizCardひとりぶんのデータを保持するクラスを作ります。
単にプロパティ名とそれに対応するDBカラム名、セッターゲッターを書いただけです。
setter/getterを一個一個作るのは馬鹿みたいなので、「ソース」→「toString()の生成」から自動で作ってもらいます。
/src/com.example.bizcard/db/BizCard.java
public class BizCard implements Serializable{
//バージョン
private static final long serialVersionUID = 1;
// テーブル名
public static final String TABLE_NAME = "biz_card";
// カラム名
public static final String COLUMN_ID = "_id";
public static final String COLUMN_PERSON_NAME = "person_name";
public static final String COLUMN_COMPANY_NAME = "company_name";
public static final String COLUMN_ORGANIZATION_NAME = "organization_name";
public static final String COLUMN_POSITION_NAME = "position_name";
public static final String COLUMN_ZIP_CODE = "zip_code";
public static final String COLUMN_ADDRESS = "address";
public static final String COLUMN_TEL1 = "tel1";
public static final String COLUMN_TEL2 = "tel2";
public static final String COLUMN_MAIL = "mail";
// プロパティ
private Long rowid = null;
private String personName = null;
private String organizationName = null;
private String companyName = null;
private String positionName = null;
private String zipCode = null;
private String address = null;
private String tel1 = null;
private String tel2 = null;
private String mail = null;
//setter/getter
public Long getRowid() {
return rowid;
}
public void setRowid(Long rowid) {
this.rowid = rowid;
}
public String getPersonName() {
return personName;
}
public void setPersonName(String personName) {
this.personName = personName;
}
public String getOrganizationName() {
return organizationName;
}
public void setOrganizationName(String organizationName) {
this.organizationName = organizationName;
}
public String getCompanyName() {
return companyName;
}
public void setCompanyName(String companyName) {
this.companyName = companyName;
}
public String getPositionName() {
return positionName;
}
public void setPositionName(String positionName) {
this.positionName = positionName;
}
public String getZipCode() {
return zipCode;
}
public void setZipCode(String zipCode) {
this.zipCode = zipCode;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getTel1() {
return tel1;
}
public void setTel1(String tel1) {
this.tel1 = tel1;
}
public String getTel2() {
return tel2;
}
public void setTel2(String tel2) {
this.tel2 = tel2;
}
public String getMail() {
return mail;
}
public void setMail(String mail) {
this.mail = mail;
}
/*
* toStringをオーバーライド
* 何故氏名+会社名だけかは謎
*/
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append( getPersonName());
if( getCompanyName() != null){
builder.append(":");
builder.append(getCompanyName());
}
return builder.toString();
}
}
SerializableはserialVersionUIDを定義しないと怒られます。
これは、BizCardのデータをエクスポートするときにバージョン情報も付記しておき、その後インポートしたときにデータ構造がバージョンアップされていたら警告する、みたいな機能です。
エクスポートインポート機能を備えたアプリなら重要かもしれません。
元のエントリでは@SuppressWarnings("serial")でエラーの発生を抑えていますが、こっちではとりあえず1で。
まあ今後バージョンアップする予定もありませんが。
前回の続き。
http://codezine.jp/article/detail/4657?p=2
マップ画面のデザインを行いましょう。
Androidエミュレータをインストールすると、Documents and Settings以下にいつのまにかGoogleMapAPI用の証明書が保存されているようです。
C:\Documents and Settings\ユーザ名\.android\debug.keystoreがあるかどうか探してみましょう。
見つかったら、その証明書からフィンガープリントを抽出します。
keytoolはJavaに付属してるみたい。
手元ではC:\pleades\jre1.6\binに見つかりました。
keytool -list -keystore "C:\Documents and Settings\ユーザ名\.android\debug.keystore"
を実行します。
パスワードは何も入れずにリターン。
:で区切られたMD5が取得できます。
このパスワードはどういう意味かよくわかんない。
次にここからMD5を入力すると、APIキーとMapViewのコードを取得できます。
http://code.google.com/intl/ja/android/add-ons/google-apis/maps-api-signup.html
以後はMapViewのコードをビューにそのまま書くだけで簡単にGoogleMapAPIを利用できるようになります。
/res/layout/map.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent" android:layout_height="fill_parent">
<!-- 地図 -->
<com.google.android.maps.MapView
android:id="@+id/map"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:apiKey="0PvsHuDZcbz3WPMOPTMG2N02xrRdJDF5pX2YsWQ" />
<!-- 地図の上に半透明のレイアウトを配置 -->
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="#99000080"
android:padding="2px" >
<!-- 地図切り替えのラジオボタン -->
<RadioGroup
android:id="@+id/mapRadioGroup"
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<!-- 通常の地図ボタン -->
<RadioButton
android:id="@+id/normalMapRadio"
android:text="@string/map"
android:textColor="#FFFFFFFF"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<!-- 衛星写真ボタン -->
<RadioButton
android:id="@+id/satelliteMapRadio"
android:text="@string/map_satellite"
android:textColor="#FFFFFFFF"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RadioGroup>
<!-- 現在地表示のトグルボタン -->
<ToggleButton
android:id="@+id/currentLocationToggle"
android:layout_gravity="right|center_vertical"
android:textOff="@string/current_location_off"
android:textOn="@string/current_location_on"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</FrameLayout>
<FrameLayout>は、部品を並べると単純に画面左上詰めで追加するという最もシンプルなレイアウトです。
今回の場合、まずMapViewで全画面にマップを表示し、その後LinearLayoutを重ねるということになります。
LinearLayoutはandroid:orientation="horizontal"が指定されているので、中にあるふたつの部品、RadioGroupとToggleButtonが左右に並んで表示されます。
背景色はandroid:background="#99000080"となっていますが、指定方法は"#aarrggbb"、"#rrggbb"です。aaは透明度。
/src/com.example.bizcard/ListActivity.java
public class MapActivity extends com.google.android.maps.MapActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.map);
}
@Override
protected boolean isRouteDisplayed() {
return false;
}
}
これまでアクティビティはすべてandroid.app.Activityクラスを継承していましたが、GoogleMapAPIを利用するアクティビティはcom.google.android.maps.MapActivityを継承する必要があります。
onCreateは全く同じですが、isRouteDisplayedという謎メソッドが追加されています。
ルート表示を行うならtrueだそうですが何のことやら、とりあえずfalseにしておきます。
最後にマニフェストを追加。
/AndroidManifest.xml
<activity android:name=".MapActivity" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> </intent-filter> </activity>
ここまでは同じですが、GoogleMapに接続するためにインターネット接続権限を付与しないといけません。
<application>の外側、<manifest>の直下に設置します。
<uses-permission android:name="android.permission.INTERNET" />
また、GoogleMapAPIを使用しているのでそれを宣言して取り込まないといけないようです。
こちらは<application>直下に記述します。
<uses-library android:name="com.google.android.maps" />
何故書くレベルが違うんだ?
どっちもアプリ単位とかアクティビティ単位じゃ駄目なのか?
実行。
無事にマップの取得に成功。
ボタンやドラッグなどの機能は何も実装していないので、現状では見るだけです。
Androidの記事