データベースをアップグレードする時
SQLiteのデータベースを使用する際はSQLiteOpenHelperクラスを使います。一般的には下のようなコードになると思います。
public class DatabaseHelper extends SQLiteOpenHelper { public DatabaseHelper(Context context) { super(context, "test.db", null, 1); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL("CREATE TABLE " + test_table + " (" + BaseColumns._ID + " INTEGER PRIMARY KEY," + "name TEXT" + ");" ); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } }
使用法はコンストラクタからインスタンスを作成し、getReadableDatabaseかgetWritableDatabaseを呼び出して参照や更新のクエリを発行するSQLiteDatabaseクラスを取得することになります。
onCreateは初めてデータベースにアクセスする際に呼ばれるので、ここでデータベースの初期化を行います。onUpgradeはバージョンアップの際に呼び出されますので、ここでALTER TABLEなどのSQLを使って対応することになります。oldVersionとnewVersionが渡ってくるので、バージョンが1から2になった場合、2から3になった時などで分岐させられます。
バージョンアップの方法
上で作成したデータベースに、機能追加で新しいテーブルを追加することになった場合、以下のようにします。
- コンストラクタで呼び出す最後の引数が、新しいデータベースのバージョンになります。前回1だったので、今回は2にします。
- onCreateで行う初期化は最新バージョンのものにします。古いデータベースが作成されている場合はonCreateは実行されません。
- onUpgradeに、古いデータベースが作成されている場合のバージョンアップコードを書きます。
public class DatabaseHelper extends SQLiteOpenHelper { public DatabaseHelper(Context context) { super(context, "test.db", null, 2); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL("CREATE TABLE " + test_table + " (" + BaseColumns._ID + " INTEGER PRIMARY KEY," + "name TEXT" + ");" ); db.execSQL("CREATE TABLE " + test_table2 + " (" + BaseColumns._ID + " INTEGER PRIMARY KEY," + "name TEXT" + ");" ); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { if( oldVersion == 1 && newVersion == 2 ){ db.execSQL("CREATE TABLE " + test_table2 + " (" + BaseColumns._ID + " INTEGER PRIMARY KEY," + "name TEXT" + ");" ); } } }
これで、新しいバージョンを新規にインストールした時と、古いバージョンからバージョンアップした時のテーブル構成を同じにすることが出来ます。
内部の処理
実際にSQLiteOpenHelperクラスはどんな処理をしているのか、SDKのソースを覗いてみました。onCreateやonUpgradeは、getReadableDatabaseやgetWritableDatabaseの内部で実行されていました。その中のコードを引用します。
SQLiteOpenHelper.java
mIsInitializing = true; if (mName == null) { db = SQLiteDatabase.create(null); } else { db = mContext.openOrCreateDatabase(mName, 0, mFactory); } int version = db.getVersion(); if (version != mNewVersion) { db.beginTransaction(); try { if (version == 0) { onCreate(db); } else { onUpgrade(db, version, mNewVersion); } db.setVersion(mNewVersion); db.setTransactionSuccessful(); } finally { db.endTransaction(); } }
内部の処理では、最初にバージョン0として空のデータベースを作成しonCreateを呼び出しています。データベースが存在していてバージョンが変わっていればonUpgradeを呼び出していますね。どちらもトランザクションで囲まれているので、onCreateやonUpgradeの中でトランザクションを使用する必要は無いみたいです。
ところで、現在のデータベースのバージョンはどこに保存されているのか、SQLiteDatabase.getVersion()とsetVersion()のソースを見てみます。
SQLiteDatabase.java
public int getVersion() { SQLiteStatement prog = null; lock(); try { prog = new SQLiteStatement(this, "PRAGMA user_version;"); long version = prog.simpleQueryForLong(); return (int) version; } finally { if (prog != null) prog.close(); unlock(); } } public void setVersion(int version) { execSQL("PRAGMA user_version = " + version); }
テーブルでは無く、SQLiteのPRAGMAのuser_versionで管理していました。PRAGMAって知らなかったのですが、SQLite3での環境変数のようなものみたいです。http://www.sqlite.org/pragma.html
なので、データベースのバージョンが知りたい場合は、AndroidのSDKのshellからSQLiteでDBを開き、上記のクエリーを発行すれば良さそうです。
データベースの構造を変えるのは大変ですが、こういう仕組みが用意されているのは有り難いですね。
クリップボードツール、CopiPeリリース
http://www.androlib.com/android.application.com-matabii-copipe-zAtA.aspx
Androidにコピー&ペースト用のツールをリリースしました。
よく使う単語や定型文を登録しておけば、選択するだけでクリップボードにコピーできます。
特徴
- カテゴリー付きなので、登録数が多くなっても整理して使えます。
- ステータスバーにアイコンが常駐するので、テキスト入力中にも呼び出しが簡単で、そのまま貼付けられます。
- 現在のクリップボードの内容をワンタッチで登録することが出来ます。
NetBeans上で使ってみる
NetBeansではJ2EEアプリのひな形の作成と、内蔵されているGrassFishへのデブロイも簡単に出来るので、JRuby & Sinatra環境を構築してみます。Railsと違いプロジェクトのテンプレートが用意されているわけではないので、多少は手動でごにょごにょする必要はあります。
プロジェクトの作成
新規プロジェクトの作成で、Java WebのWeb アプリケーションをひな形にして作成します。
プロジェクト名とかは適当に。そのままWebApplication1として作成しました。
デフォルトのままです。GlassFish V3を使います。
ここもデフォルトです。ここに出てるフレームワークは使いません。これで完了です。
ライブラリの追加
JRubyとJRuby-Rackのjarファイルを用意します。
JRubyは、http://jruby.org/download/ からjruby-complete.jarをダウンロードしてきます。必要なのはcompleteが付いてるものです。今回は最新バージョンの、http://dist.codehaus.org/jruby/1.3.1/jruby-complete-1.3.1.jar をダウンロードしました。
Rackは、http://kenai.com/projects/jruby-rack/pages/Home#Download からjruby-rack.jarをダウンロードしてきます。今回は最新バージョンの、 http://kenai.com/downloads/jruby-rack/jruby-rack-0.9.5.jar をダウンロードしました。
用意した2つのjarファイルを、プロジェクトのライブラリとして追加します。プロジェクトプロパティのライブラリで追加しています。
gemの追加
Sinatraはgemからインストールします。JRuby-Rackは何も指定しないとWEB-INF/gemsディレクトリにgemがインストールされているとみなします。NetBeansで作成したWebApplicationにはgemsディレクトリは作られていないので、自分で作成してやる必要があります。
作成したgemsディレクトリに対してsinatraのgemをインストールします。普通にjrubyを使っても良いですが、先ほどダウンロードしてきたjruby-complete.jarを使うことも出来ます。NetBeansはgemの管理も出来るのですが、今回はコマンドラインから手動で追加しました。
java -jar WebApplication1/lib/jruby-complete-1.3.1.jar -S gem install -i WebApplication1/web/WEB-INF/gems/ sinatra --no-ri --no-rdoc
gem installに-iオプションを付けてインストールディレクトリを指定しています。他にもgemを使いたい場合は、同じように追加していけばOKです。
サンプル用のSinatraのRubyプログラムを作成
WEB-INF直下にdemo.rbとして作成しました。
require 'rubygems' require 'sinatra' get '/' do 'Hello world!' end get '/test/' do 'Hello Test' end
ここまでで、ディレクトリ構造はこんな感じになってます。
webの下にindex.jspが作成されていたのですが、邪魔なので消してます。
web.xmlの編集
最後にweb.xmlを編集します。ほとんどJRuby-Rackのサンプルをコピーしてきました。通常Rackのアプリを動かす時にruファイルを作成しますが、その内容をrackupのパラメータにて設定する感じです。
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <context-param> <param-name>rackup</param-name> <param-value> require 'rubygems' gem 'sinatra', '~> 0.9' require './demo' set :run, false set :environment, :production run Sinatra::Application </param-value> </context-param> <filter> <filter-name>RackFilter</filter-name> <filter-class>org.jruby.rack.RackFilter</filter-class> </filter> <filter-mapping> <filter-name>RackFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <listener> <listener-class>org.jruby.rack.RackServletContextListener</listener-class> </listener> </web-app>
動作チェック
これにて完了です。あとはWebサーバを起動すれば確認することが出来ます。NetBeansでプロジェクトを右クリック、実行で配備出来ます。
http://localhost:8080/WebApplication1/ にアクセスすればdemo.rbにて作成したものが表示されます。
使いどころ
J2EEで動かすのでパフォーマンスはかなり良いです。CGIと比べると圧倒的だと思います。
起動してしまえば早いのですが、起動完了まで時間がかかります。あと、ソースを修正する度にアプリケーションの再起動が必要なので、開発に使うには厳しいです。普段は素のRubyで動かしておいて、最終的にJ2EE上にデブロイするのが上手いやり方だと思います。
これだけだと手間がかかるだけで魅力的に感じませんが、Google App Engine上で動かす方法が確立されているので、それについてはかなり魅力的だと思います。
http://jugyo.org/blog/3388
NetBeans上で、RackとSinatraを使いJRubyを動かす
NetBeansを使うと、JRuby On Railsアプリケーションが嘘みたいに簡単に作れます。
http://www.netbeans.org/kb/docs/ruby/rapid-ruby-weblog.html
内蔵のGlassFishでさくっと動かせるし、Warblerを使えばwar化出来るので、デブロイするのも簡単です。
しかし簡単すぎて、Railsを使うまでもない、ちょっとしたCGIレベルのアプリケーションを作ろうとすると逆に難しく感じてしまいます。
JRubyでサーブレットを書くのは以下の記事が参考になりますが、フレームワークを使ってもう少し簡単に動かしたいところです。
http://www.okisoft.co.jp/esc/ruby/jservlet/index.html
Rackを使う事にする
RubyにはRackと呼ばれる有名なライブラリがあり、これを使うとサーバの違いを気にする事無くWebアプリを作る事が可能です。
http://route477.net/d/?date=20080716
JRuby用のJRuby-Rackという物も作られていて、同じ書式でRack用のWebアプリを作成出来ます。これもサーバの違い(JRubyの場合はTomcatやGlassFishやらのJ2EEサーバ)を気にしなくても良いのと、サーブレットへのアダプタとしても動いてくれるので、理論上はRack用に作られているRubyアプリケーションはJRubyでも簡単に動かせます。NetBeansのJRuby On Railsプロジェクトは、これを使って動くように作成されます。素のRuby On Railsも最近はRackが使われてますよね。
つまりRackで動くように書けばJRubyでも動かせます。これでサーブレットをゴリゴリ書く必要が無くなり、随分と楽をすることが可能になります。
Intentの凄さ
AndroidにはIntentと呼ばれる概念があり、これを使うことでアプリケーションをまたいでのデータのやり取りが可能になります。
例えば、端末の電話帳データを取得したり、メーラーを立ち上げて送信画面を開いたりすることが出来るのですが、Androidがデフォルトで持っているものだけでなく、サードパーティ製のアプリケーションにおいてもIntentが公開されていれば用いる事が出来るのです。
バーコードリーダーで試してみる
今時の日本の携帯には必ず付いてるバーコードリーダーですが、Androidには付いてません。マーケットにいくつか公開されていますので、その中の「Barcode Scanner」を利用させて頂きます。http://www.androlib.com/android.application.com-google-zxing-client-android-xzA.aspx
単体で使用しても、URLのバーコードを読み取るとブラウザを立ち上げたり、商品のバーコードであれば商品検索をしてみたりと十分使えるものですが、これのバーコードを読み取る機能の部分だけ別のアプリケーションから利用する使い方も出来るのです。
以下、実際に利用してみたコードです。レイアウトはButtonとTextViewだけ配置してます。
Activity
public class main extends Activity { public static final int REQUEST_CODE = 1; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Button b = (Button)findViewById(id.Button); b.setOnClickListener(new View.OnClickListener(){ public void onClick(View v) { Intent intent = new Intent("com.google.zxing.client.android.SCAN"); try{ startActivityForResult(intent, REQUEST_CODE); }catch (ActivityNotFoundException e){ Toast.makeText(main.this, "not found Barcode Scanner", Toast.LENGTH_SHORT ).show(); } } }); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if( requestCode == REQUEST_CODE && resultCode == Activity.RESULT_OK){ String contents = data.getStringExtra("SCAN_RESULT"); TextView t = (TextView)findViewById(id.Text); t.setText(contents); } } }
下のコードで、Intentを作ってバーコードリーダーを起動しています。これは「Barcode Scanner」をインストールすることで使用可能になるものなので、
Intent intent = new Intent("com.google.zxing.client.android.SCAN"); try{ startActivityForResult(intent, REQUEST_CODE); }catch (ActivityNotFoundException e){ Toast.makeText(main.this, "not found Barcode Scanner", Toast.LENGTH_SHORT ).show(); }
インストールされていないとActivityNotFoundExceptionが発生して、Toastのメッセージが表示されます。
インストールされていた場合、カメラのActivityが起動して、バーコードの読み込み体制に入ります。
読み込みに成功すると、当たり前のように元のActivityに戻ります。下のコードが起動していたActivityから結果を取得する箇所です。
protected void onActivityResult(int requestCode, int resultCode, Intent data) { if( requestCode == REQUEST_CODE && resultCode == Activity.RESULT_OK){ String contents = data.getStringExtra("SCAN_RESULT"); TextView t = (TextView)findViewById(id.Text); t.setText(contents); } }
TextVIewをバーコードの値にしています。
まるでバーコード読み込み機能を自前で実装しているかのようにスマートに連携出来ました。今回はIntentを指定する部分で「com.google.zxing.client.android.SCAN」という値を使いましたが、これで「Barcode Scanner」アプリ側で公開されている固定のIntentを呼び出しています。
Intentの指定方法はもっと冗長に「jpeg画像を開く」などとすることも可能で、その場合はjpeg画像を開くIntentを公開しているアプリが起動します。つまり、何のアプリで起動させるかまで指定しなくて良いのです。もし存在しなければエラーになりますし、複数あった場合はユーザーがその中から選ぶことになります。OSのファイル拡張子のようなものです。これは他のスマートフォンには無い、Androidならではと言える機能だと思います。是非、他のアプリから呼び出されるような物を作ってみたいですね。
ちなみに「Barcode Scanner」はオープンソースで開発されています。バーコード読み込み部分はもちろん、Intentの使い方やAndroidアプリの作り方の部分も凄く参考になります。http://code.google.com/p/zxing/
読み込んだバーコードはたまたま近くにあった「激落ちキング」です。これも凄く良いものです。
Android marketにリリース
本日、http://smart.fm/のAPIを利用した語学学習アプリ、「Tango」をリリースしました。smart.fmの学習を簡略化したものをAndroid上で行うことが出来ます。まだまだ機能が荒削りですが、得られたフィードバックを元に改良など続けていきます。
ダウンロードやスクリーンショットはこちらから。
http://www.androlib.com/android.application.com-matabii-tango-zzqF.aspx
smart.fmとは
無料で英語等の学習が出来るサイトです。Flashで学習用のアプリが作られているのですが、これによりゲーム感覚で勉強することが出来ます。また、SNSとしての概念も持っていて、利用者同士でコミュニケーションを取ったり、学習用の教材を作成して公開するなんてことも可能です。まだアカウントを持ってない方は、是非登録してみて下さい。http://smart.fm/
以下、こだわりポイント
キャッシュの利用
APIへのアクセスを必要最小限に抑え、結果をキャッシュとして保存することで、ネット環境が不安定でも快適に使用出来るようにしています。
オリジナルキーボード
spellチェックの問題も出題しているのですが、一般的な入力ボックスであるEditTextに、AndroidのデフォルトキーボードやSimejiを利用して入力してもらう方法だと、補完リストが出てしまい問題の意味が無くなってしまったり、正解チェックをする時にスペースなどの特殊文字が混ざっていると扱いが難しくなってしまいます。
そこで、入力用のViewとソフトウェアキーボードを自前で実装しています。これにより利用端末や言語の違いを吸収する事ができ、中国語のPinyinやアクセント付きのlatin文字「œ」←こんなの。もサポートする事が出来ました。
このように、画面内にソフトウェアキーボードを直接配置しています。
ListViewについて
台風の影響でJJUG CCC行けなかったです。飛行機は欠航だし・・・
さて、以前、ListViewの色の変え方という記事を書きましたが、当時はAndroidを触り始めという事もあり、読み返してみてこれは酷いと思ったので書き直してみます。今もまだまだ勉強中ですが。
http://d.hatena.ne.jp/isher/20090713
http://d.hatena.ne.jp/isher/20090714
サンプルアプリの作成
今回使うサンプルとして、ドメインと国名の一覧を出力する簡単なリストを作ってみます。
一応ソースも乗せておきます。
- Eclipseでプロジェクトの作成
- 作成されたmain.xmlのTextViewを消して、ListViewを配置します。
- list_at.xmlの作成
- Activityの編集
res/layout/main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <ListView android:id="@+id/ListView" android:layout_width="fill_parent" android:layout_height="wrap_content" /> </LinearLayout>
新しいレイアウトファイルとして、list_at.xmlを作ります。これがリストの項目を表示するためのレイアウトになります。
LinearLayoutにドメインを表示するためのTextViewと、国名を表示するためのTextViewを並べます。
res/layout/list_at.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="60dp" android:orientation="vertical" > <TextView android:id="@+id/Domain" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="2dp" android:textSize="15sp" /> <TextView android:id="@+id/Country" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_margin="2dp" android:textSize="20sp" /> </LinearLayout>
今回はSimpleAdapterを使う事にしたので、データ用のMapのListを作成して、レイアウトIDを合わせています。
mapの初期化をダラダラ書くのがダルいなーと、良い方法を探していたら下の記事を見つけたので参考にさせて頂きました。
http://d.hatena.ne.jp/dai4649/20090611
public class ListTest extends Activity { static final List<HashMap<String, String>> countries = Arrays.asList( new HashMap<String, String>() { { put("domain", "cn"); put("country","中華人民共和国"); } }, new HashMap<String, String>() { { put("domain", "de"); put("country","ドイツ連邦共和国"); } }, new HashMap<String, String>() { { put("domain", "fr"); put("country","フランス共和国"); } }, new HashMap<String, String>() { { put("domain", "il"); put("country","イスラエル国"); } }, new HashMap<String, String>() { { put("domain", "in"); put("country","インド"); } }, new HashMap<String, String>() { { put("domain", "jp"); put("country","日本国"); } }, new HashMap<String, String>() { { put("domain", "kr"); put("country","大韓民国"); } }, new HashMap<String, String>() { { put("domain", "uk"); put("country","イギリス"); } }, new HashMap<String, String>() { { put("domain", "us"); put("country","アメリカ合衆国"); } } ); @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); ListView list = (ListView)findViewById(R.id.ListView); SimpleAdapter adapter = new SimpleAdapter(this,countries, R.layout.list_at, new String[]{"domain", "country"}, new int[]{R.id.Domain, R.id.Country}); list.setAdapter(adapter); } }
ここまではAndroidのサンプルなどで良く出てくるものと同じなので、特に問題無いと思います。
レイアウトの変更
このListViewの文字色や背景色を変更したいと思います。通常は黒色の背景に白色の文字、フォーカスされている場合はオレンジ色の背景に黒色の文字になると思います。このように状態によって変化を付けたい場合、selectorと言うxmlを作成することで実装出来ます。
背景の変更
文字と背景でファイルが別になるので、背景のselectorから始めます。どのようなxmlを書けば良いのかについては、http://developer.android.com/intl/ja/reference/android/content/res/ColorStateList.htmlが参考になりますが、デフォルトのListViewでどう使われているか見てみます。Androidのソースの、frameworks/base/core/res/res/drawable/list_selector_background.xmlがそれらしい物でした。
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_window_focused="false" android:drawable="@color/transparent" /> <item android:state_focused="true" android:state_enabled="false" android:state_pressed="true" android:drawable="@drawable/list_selector_background_disabled" /> <item android:state_focused="true" android:state_enabled="false" android:drawable="@drawable/list_selector_background_disabled" /> <item android:state_focused="true" android:state_pressed="true" android:drawable="@drawable/list_selector_background_transition" /> <item android:state_focused="false" android:state_pressed="true" android:drawable="@drawable/list_selector_background_transition" /> <item android:state_focused="true" android:drawable="@drawable/list_selector_background_focus" /> </selector>
どういうステータスの時(フォーカスが当たっている時や押されている時など)に、どういう表示をするのかをitemタグに書いています。例えば一番下の
<item android:state_focused="true" android:drawable="@drawable/list_selector_background_focus" />
は、フォーカスが当たっている時にlist_selector_background_focusを使うということです。これは、list_selector_background_focus.9.pngというファイルが用意されてました。9-patchという、拡張しても崩れない画像ファイルです。http://developer.android.com/intl/ja/guide/developing/tools/draw9patch.html
ちなみにこんなファイルでした。ListViewをそのまま使うとこれになりますよね。
それでは、このファイルを参考に、オリジナルのselectorを作ってみる事にします。作成する場所についてはどこが適切なのか分からないのですが、とりあえず実験目的なのでres/xmlの下に作成しました。
res/xmlにlist_selector.xmlというファイル名で、新規xmlファイルを作成して、下のように記述しました。
res/xml/list_selector.xml
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_window_focused="false" > <color android:color="#00000000" /> </item> <item android:state_focused="true" android:state_enabled="false" android:state_pressed="true" android:drawable="@drawable/list_selector_background_disabled" /> <item android:state_focused="true" android:state_enabled="false" android:drawable="@drawable/list_selector_background_disabled" /> <item android:state_focused="true" android:state_pressed="true" android:drawable="@drawable/list_selector_background_pressed" /> <item android:state_focused="false" android:state_pressed="true" android:drawable="@drawable/list_selector_background_disabled" /> <item android:state_focused="true" android:drawable="@drawable/list_selector_background_focus" /> </selector>
res/drawableに3つの画像を作っておきます。デフォルトのオレンジ色のを反転して青色にしてみました。灰色のdisabled用の画像はそのままです。
list_selector_background_disabled.9.png
list_selector_background_focus.9.png
list_selector_background_pressed.9.png
xmlではフォーカスがある時、ない時、押された時、無効化されてる時に上の画像を背景にする設定をしています。あとは長押しされた時などのステータスもあるようですが、今回は省略しています。
一番上の
<item android:state_window_focused="false" > <color android:color="#00000000" /> </item>
だけ違ってますが、これは透明色を設定しているだけです(#00000000は透明色)。つまり、フォーカスが無い場合は親のレイアウトの背景色で表示されます。(今回の場合はLinearLayoutのbackground)
これでselectorは完成なので、レイアウトファイルのmain.xmlを編集して、今回作ったselectorを使用するようにします。
res/layout/main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="#FFC0C0" > <ListView android:id="@+id/ListView" android:layout_width="fill_parent" android:layout_height="wrap_content" android:listSelector="@xml/list_selector" android:cacheColorHint="#000000" /> </LinearLayout>
LinearLayoutにはandroid:backgroundを追加して背景色を変えています。先ほどのselectorにてListViewの背景に透明色を設定しているので、背景色は親であるLinearLayoutで設定するわけです。
ListViewにはandroid:listSelectorを追加して、先ほど作成したselectorを使用するようにしています。もう一つListViewにてandroid:cacheColorHintを設定していますが、これはスクロール時に表示される色を設定しています。詳しくはhttp://www.curious-creature.org/2008/12/22/why-is-my-list-black-an-android-optimization/
これでエミュレータを起動すると・・・
選択行が青色になりました。タッチパネルを押したり決定ボタンを押すと、濃い青色になるはずです。背景色は透明なので、LinearLayoutに設定した#FFC0C0のピンクっぽい色になっています。
文字色の変更
背景色を変更すると文字が見辛くなるので、こちらも変えてやらないといけません。方法はListViewの背景色の変更とほぼ同じで、selectorを作成します。まずはAndroidの規定のselectorを見てみます。
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_window_focused="false" android:state_enabled="false" android:color="@android:color/dim_foreground_dark_disabled"/> <item android:state_window_focused="false" android:color="@android:color/dim_foreground_dark"/> <item android:state_selected="true" android:state_enabled="false" android:color="@android:color/dim_foreground_dark_inverse_disabled"/> <item android:state_pressed="true" android:state_enabled="false" android:color="@android:color/dim_foreground_dark_inverse_disabled"/> <item android:state_selected="true" android:color="@android:color/dim_foreground_dark_inverse"/> <item android:state_pressed="true" android:color="@android:color/dim_foreground_dark_inverse"/> <item android:state_enabled="false" android:color="@android:color/dim_foreground_dark_disabled"/> <item android:color="@android:color/dim_foreground_dark"/> </selector>
背景色の設定とほとんど同じです。背景色ではdrawableを使いましたが、textColorは基本的に単色なのでcolorを使っています。色の指定はres/valuesにcolors.xmlを作成して定数値を定義するのが良くやる方法なのですが、今回は実験なので色コードをべた書きで作ってしまいます。
res/xmlにtext_color_selector.xmlというファイル名で、新規xmlファイルを作成して、下のように記述します。
res/xml/text_color_selector.xml
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_selected="true" android:state_enabled="false" android:state_pressed="true" android:color="#323232" /> <item android:state_selected="true" android:state_enabled="false" android:color="#323232" /> <item android:state_selected="true" android:state_pressed="true" android:color="#FFEEFF" /> <item android:state_selected="false" android:state_pressed="true" android:color="#FFEEFF" /> <item android:state_selected="true" android:color="#FFEEFF" /> <item android:state_selected="false" android:color="#191919" /> </selector>
背景のと少し違ってますが、考え方は同じはずです。TextViewはフォーカスを得ないので、代わりにstate_selectedを使ってます。どういう時に、どのステータスがONになるのかがドキュメントを見る限り分からないので(Viewによっても違いそう)サンプル等を参考にしたり検索したりソース読んだりで作りました。選択されていない状態の時は黒っぽい色にして、選択されている場合は白っぽい色にしています。
ここで作ったselectorを、文字を出力しているTextViewに対して設定します。TextViewが配置されているのは、リストの項目用のレイアウトファイル、list_at.xmlです。
res/layout/list_at.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="60dp" android:orientation="vertical" > <TextView android:id="@+id/Domain" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="2dp" android:textSize="15sp" android:textColor="@xml/text_color_selector" /> <TextView android:id="@+id/Country" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_margin="2dp" android:textSize="20sp" android:textColor="@xml/text_color_selector" /> </LinearLayout>
追加してるのはTextViewのtextColorです。ここに今作成したselectorを設定して、状態によって色を変更します。これでエミュレータを立ち上げると・・
文字色も状態によって変えられるものが設定できました。
TextView一つ一つに設定するのも大変なので、こういう場合は「テーマ」を作ってActivity全体に反映させるのでしょうね。Androidが用意しているテーマを調べればもっと理解が深まりそうなので、次はそこを見て行きたいと思います。