android.widget.ListAdapter#getView ↑ android.widget.AbsListView#obtainView ↑ android.widget.ListView#makeAndAddView ↑ android.widget.ListView#fillDown ↑ android.widget.ListView#fillGap ↑ android.widget.AbsListView#trackMotionScroll ↑ android.widget.AbsListView#scrollIfNeeded ↑ android.widget.AbsListView#onTouchEventまだ続いてるけどここまでわかればOKでしょ。 ちなみにこれ4.2.2の挙動ね。
2013年8月11日日曜日
ListAdapterのgetViewが呼び出される階層を調べてみた
ListViewを継承して独自のカスタムViewを作ろうとしてた時に必要になったので、
ListAdapterのgetViewが呼び出される階層を調べてみた。
2013年5月25日土曜日
Android StudioでRecent Projectのリストをクリアする
Android Studioでいろいろ触りまくっているとRecent Projectがいっぱいになってすごい邪魔になる。IntelliJだとRecent Projectをクリアするメニューがあるんだけど、Android Studioだと見つからなかった。なので、無理やりRecent Projectをクリアする方法をメモ。基本的にはIntelliJで無理やりやる方法と一緒。ちなみにMacね。
以下のファイルを開く
んで以下のあたりのrecentPathsのlistの中身を消す。
以下が修正後。
以下のファイルを開く
/Users/<username>/Library/Preferences/AndroidStudioPreview/options/other.xml
んで以下のあたりのrecentPathsのlistの中身を消す。
<component name="RecentProjectsManager"> <option name="recentPaths"> <list> <option value="$USER_HOME$/AndroidStudioProjects/SampleUseLibrary" /> </list> </option> <option name="names"> <map> <entry key="$USER_HOME$/AndroidStudioProjects/SampleUseLibrary" value="" /> <entry key="$USER_HOME$/dev/github/ActionBarSherlock/actionbarsherlock-samples/fragments" value="" /> </map> </option> <option name="lastPath" value="$USER_HOME$/AndroidStudioProjects/SampleUseLibrary" /> </component>
以下が修正後。
<component name="RecentProjectsManager"> <option name="recentPaths"> <list> </list> </option> <option name="names"> <map> <entry key="$USER_HOME$/AndroidStudioProjects/SampleUseLibrary" value="" /> <entry key="$USER_HOME$/dev/github/ActionBarSherlock/actionbarsherlock-samples/fragments" value="" /> </map> </option> <option name="lastPath" value="$USER_HOME$/AndroidStudioProjects/SampleUseLibrary" /> </component>
2013年1月3日木曜日
Androidで「Unknown error merging manifest」
AndroidでライブラリプロジェクトのAndroidManifest.xmlをメインプロジェクトに自動でマージさせるにはproject.propertiesにmanifestmerger.enabled=trueを加えるだけで良い。
しかし、メインプロジェクトのandroid:targetSdkVersionがライブラリプロジェクトより低いと以下のようなエラーメッセージが表示される。
Unknown error merging manifestもっとわかりやすいメッセージを表示してほしいよね。
2012年5月18日金曜日
さくらVPSのUbuntu10.04(64bit)にgit + maven + Jenkinsな環境構築をしてAndroidのCIが出来るまでのメモ
Androidに限らずgit + Jenkins + mavenな環境はよく使うと思うけど、初回のビルドはだいたいこける。
環境が整備出来ていないという理由でね。
ということでAndroidプロジェクトがCI出来るまでにやったことをメモしておく。
さくらVPSにカスタムOSのUbuntu 10.04をインストール+セキュリティ設定などをした時のメモ
を参考にインストール・設定を行う。
(ここではsshのport変更はやってない)
Jenkinsをインストールするとjenkinsユーザーが自動で作成されるけどなんとなく先に作っておいた。
openjdk-6-jdkとmavenをインストールする。
まず、32ビット版のソフトを動かすためにia32-libsをインストールする。(ハマりポイント)
jenkinsユーザーから実行出来るようにする必要があるのでjenkinsユーザーで行う。
まず、gitosisをインストールする。今までapt-getで今回はaptitudeだけど気にしないw
gitosisの初期化の際に一人目の管理者の公開鍵が必要なのでgitosisユーザーから見える箇所に配置する。
(ここでは同サーバー内のubuntuユーザーを一人目の管理者とする)
gitosis管理者ユーザーで管理情報リポジトリをクローンする。(クローン済みならpullする)
gitosis.confを修正する。(XXXXは任意ユーザー)
修正が終わったらjenkinsユーザーのssh公開鍵をkeydir配下に配置する。
(ssh公開鍵はパスフレーズなしで作成)
コミットしてプッシュしたら実際にリポジトリを作成する。
jenkinsユーザーまたはmembersに記載した任意のユーザーから以下コマンドを実行してクローン出来ることを確認する。
jenkinsユーザーがGitリポジトリと通信するためknown_hostsに追加する必要がある。(重要)
(ここではGitリポジトリとJenkinsが同サーバ)
続いてクローン時に必要になるgitconfigの設定を行う。
http://your-jenkins-server:8080にアクセスする。
Jenkinsの管理 -> プラグインの管理から以下の3つを選択してインストールする。
環境が整備出来ていないという理由でね。
ということでAndroidプロジェクトがCI出来るまでにやったことをメモしておく。
Ubuntuインストール・設定
さくらVPSにカスタムOSのUbuntu 10.04をインストール+セキュリティ設定などをした時のメモ
を参考にインストール・設定を行う。
(ここではsshのport変更はやってない)
Jenkinsインストール
Jenkinsをインストールするとjenkinsユーザーが自動で作成されるけどなんとなく先に作っておいた。
$ sudo adduser jenkinsインストールはここを参考に行う。
ビルド環境構築
openjdk-6-jdkとmavenをインストールする。
$ sudo apt-get install openjdk-6-jdkmavenインストールはここを参考に行う。
Android SDKインストール
まず、32ビット版のソフトを動かすためにia32-libsをインストールする。(ハマりポイント)
$ sudo apt-get install ia32-libs
jenkinsユーザーから実行出来るようにする必要があるのでjenkinsユーザーで行う。
$ mkdir -p /var/lib/jenkins/tools $ cd /var/lib/jenkins/tools $ wget http://dl.google.com/android/android-sdk_r18-linux.tgz // バージョンは適宜変更 $ tar zxvf android-sdk_r18-linux.tgz $ ./android-sdk-linux/tools/android update sdk -u
gitosisインストール・設定
まず、gitosisをインストールする。今までapt-getで今回はaptitudeだけど気にしないw
$ sudo aptitude install gitosis
gitosisの初期化の際に一人目の管理者の公開鍵が必要なのでgitosisユーザーから見える箇所に配置する。
(ここでは同サーバー内のubuntuユーザーを一人目の管理者とする)
$ cp -p /home/ubuntu/.ssh/id_rsa.pub /tmp/. $ sudo su - gitosis $ gitosis-init < /tmp/id_rsa.pub
gitリポジトリ作成
gitosis管理者ユーザーで管理情報リポジトリをクローンする。(クローン済みならpullする)
$ git clone gitosis@ホスト名:gitosis-admin.git
gitosis.confを修正する。(XXXXは任意ユーザー)
[gitosis] loglevel=DEBUG [group gitosis-admin] writable = gitosis-admin members = XXXX [group リポジトリ名] writable = リポジトリ名 members = XXXX jenkins
修正が終わったらjenkinsユーザーのssh公開鍵をkeydir配下に配置する。
(ssh公開鍵はパスフレーズなしで作成)
コミットしてプッシュしたら実際にリポジトリを作成する。
$ cd /srv/gitosis/repositories $ sudo mkdir リポジトリ名.git $ cd リポジトリ名.git $ sudo git init --bare --shared=true $ sudo chown -R gitosis:gitosis ./
jenkinsユーザーまたはmembersに記載した任意のユーザーから以下コマンドを実行してクローン出来ることを確認する。
$ git clone gitosis@ホスト名:リポジトリ名.git
Jenkinsセットアップ
jenkinsユーザーがGitリポジトリと通信するためknown_hostsに追加する必要がある。(重要)
(ここではGitリポジトリとJenkinsが同サーバ)
$ sudo -u jenkins ssh gitosis@localhost // yesを答える
続いてクローン時に必要になるgitconfigの設定を行う。
$ sudo su - jenkins $ git config --global user.email "jenkins@jenkins-server" $ git config --global user.name "jenkins"
http://your-jenkins-server:8080にアクセスする。
Jenkinsの管理 -> プラグインの管理から以下の3つを選択してインストールする。
- Git plugin
- Android Emulator Plugin
- Android Lint Plugin
続いてJenkinsの管理 -> システムの設定から以下を設定する。
- Android
- Android SDK root : /var/lib/jenkins/tools/android-sdk-linux
- Maven
- Name : Default(任意)
- MAVEN_HOME : /usr/local/maven
ビルドしてみる
上記で作成したリポジトリにプロジェクトを追加してプッシュする。
android-archetypesのandroid-with-testを使用してプロジェクトを作成する。
正常に作成出来たらリポジトリにプッシュする。(Jenkinsからクローン出来るように)
mvn archetype:generate \ -DarchetypeArtifactId=android-with-test \ -DarchetypeGroupId=de.akquinet.android.archetypes \ -DarchetypeVersion=1.0.8 \ -DgroupId=your.group \ -DartifactId=your-project-name \ -Dpackage=your.package
http://your-jenkins-server:8080にアクセスし新規ジョブ作成よりMaven2/3プロジェクトのビルドのジョブを作成する。
ソースコード管理システムでGitを選択し、Repository URLにgitosis@ホスト名:リポジトリ名.gitを入力する。
ソースコード管理システムでGitを選択し、Repository URLにgitosis@ホスト名:リポジトリ名.gitを入力する。
ビルドのルートPOMにリポジトリから見たルートのpom.xmlのパスを入力する。
ゴールとオプションにinstallを入力する。
ビルド環境の"Run an Android emulator during build"にチェックを入れ、エミュレーターの設定項目を入力する。
"Show emulator window"のチェックを外す。(重要)
ビルド後の処理の追加をクリックして成果物を保存を選択する。
保存するファイルにメインプロジェクトのapkを指定すればいい。
保存、ビルド実行して"Finished: SUCCESS"がコンソールに出力されればOK。
最後に成果物としてapkが存在していれば完了〜
2012年3月6日火曜日
android-maven-pluginのライフサイクル
android-maven-pluginのcomponents.xmlで定義されているライフサイクルを表にまとめてみた。
components.xml見るとprepare-packageフェイズでandroid:emmaを呼び出しているように見えるけど
android:emmaゴールなんて見つからんぞー!?
packagingがapkの場合(通常のAndroidアプリケーション)
phase | goal |
---|---|
generate-sources | android:generate-sources |
process-resources | resources:resources |
compile | compiler:compile |
process-classes | android:proguard |
process-test-resources | resources:testResources |
test-compile | compiler:testCompile |
test | surefire:test |
prepare-package | android:dex |
package | jar:jar, android:apk |
install | install:install |
pre-integration-test | android:internal-pre-integration-test |
integration-test | android:internal-integration-test |
deploy | deploy:deploy |
packagingがapklibの場合(Androidライブラリ)
phase | goal |
---|---|
generate-sources | android:generate-sources |
process-resources | resources:resources |
compile | compiler:compile |
process-classes | android:proguard |
process-test-resources | resources:testResources |
test-compile | compiler:testCompile |
test | surefire:test |
package | jar:jar, android:apklib |
install | install:install |
deploy | deploy:deploy |
components.xml見るとprepare-packageフェイズでandroid:emmaを呼び出しているように見えるけど
android:emmaゴールなんて見つからんぞー!?
2012年2月19日日曜日
AndroidのSpinner内のテキストってselector効かなくね?
上記のように条件によってSpinnerを選択不可にしたいパターンはよくあると思う。
ついでにSpinner内のテキストの色も選択 / 選択不可に応じて色を変えたい場合もあるだろう。
通常の例に沿って下記のようなセレクタとスタイルを用意してテキストカラー変更を試みてみた。
color/spinner_text.xml
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android" > <item android:state_enabled="false" android:color="@android:color/darker_gray" /> <item android:color="@android:color/black" /> </selector>values/styles.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <style name="Theme" parent="@android:style/Theme"> <item name="android:spinnerItemStyle">@style/SpinnerItem</item> </style> <style name="SpinnerItem" parent="@android:style/Widget.TextView.SpinnerItem"> <item name="android:textColor">@color/spinner_text</item> </style> </resources>
だけど、なぜか選択不可状態の色が反映されず...
ということで代替案で対応させてみたのが下記。
SpinnerActivity.java
public class SpinnerActivity extends Activity implements OnCheckedChangeListener { private Spinner mSpinner; private RadioGroup mRadioGroup; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); mSpinner = (Spinner) findViewById(R.id.spinner); String[] brands = new String[] { "LARK", "Seven Stars", "MILD SEVEN", "etc" }; ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, brands); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); mSpinner.setAdapter(adapter); mRadioGroup = (RadioGroup) findViewById(R.id.group); mRadioGroup.setOnCheckedChangeListener(this); } @Override public void onCheckedChanged(RadioGroup group, int checkedId) { RadioButton radioButton = (RadioButton) findViewById(checkedId); if (radioButton.isChecked()) { spinnerControl(checkedId); } } /** * Spinnerの選択状態を制御する * @param checkedId */ private void spinnerControl(int checkedId) { int color = Color.BLACK; switch (checkedId) { case R.id.yes: mSpinner.setEnabled(true); break; case R.id.no: mSpinner.setEnabled(false); color = Color.GRAY; break; } // SpinnerからTextViewを取り出してテキストカラーを設定 TextView textView = (TextView) mSpinner.getChildAt(0); textView.setTextColor(color); } @Override public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); int checkedId = mRadioGroup.getCheckedRadioButtonId(); spinnerControl(checkedId); } }
はい完成。ポイントは50行目〜51行目。
誰かセレクタで出来た人いたらやり方おせ〜て〜。
2012年2月18日土曜日
パッケージが更新された(バージョンが上がった)時のBroadcastについて
パッケージが更新された場合、PACKAGE_REMOVED→PACKAGE_ADDED→PACKAGE_REPLACED
の順番でBroadcastが投げられる。
個人的には違和感ありまくりなんだけど、内部的には一旦削除してから追加していて
このような挙動になっているんだと思う。(確かめてはないけど...)
しかし、パッケージ更新時のみPACKAGE_REMOVEDやPACKAGE_ADDEDの処理を
スキップしたいことはあると思う。そんな時は以下のようにインテントからデータを
抜き出して判定してやればいい。
の順番でBroadcastが投げられる。
個人的には違和感ありまくりなんだけど、内部的には一旦削除してから追加していて
このような挙動になっているんだと思う。(確かめてはないけど...)
しかし、パッケージ更新時のみPACKAGE_REMOVEDやPACKAGE_ADDEDの処理を
スキップしたいことはあると思う。そんな時は以下のようにインテントからデータを
抜き出して判定してやればいい。
public class PackageMonitor extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); // パッケージ更新の場合はスキップ if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { return; } if (Intent.ACTION_PACKAGE_ADDED.equals(action)) { // パッケージが追加された時にしたい処理 } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) { // パッケージが削除された時にしたい処理 } } }
2012年2月10日金曜日
RenamingDelegatingContextを使ってみた
昨年(2011年)の8月に開催されたAndroidテスト祭りで@ussy00さんの発表を聞いてRenamingDelegatingContextの存在を
知ってから結構経ってしまったけど、ようやく仕事でDBを使う機会が出来たので使ってみた。
RenamingDelegatingContextを使うことでテスト用のプレフィックスのついたSQLiteファイルが用意され、
毎回クリーンなデータベース環境が手に入る。
一応テスト対象のクラス等も紹介しておく。
Employee.java(DTO的な何か。必要に応じてシリアライズ可能に。)
※ ゲッター/セッター、import省略
テーブル構成も簡単にしてみた。
DatabaseHelper.java(ヘルパー)
※ import省略
EmployeeDao.java(データベースアクセスクラス。今回のテスト対象クラス。)
※ import省略
そして今回のポイントとなるテストクラス。
EmployeeDaoTest.java
※ import省略
ソースを見ればわかると思うけど手順は簡単で
これを実行すると。。。
テスト用のDBがちゃんとあるね。
知ってから結構経ってしまったけど、ようやく仕事でDBを使う機会が出来たので使ってみた。
なにが出来るの?
RenamingDelegatingContextを使うことでテスト用のプレフィックスのついたSQLiteファイルが用意され、
毎回クリーンなデータベース環境が手に入る。
使ってみる
一応テスト対象のクラス等も紹介しておく。
Employee.java(DTO的な何か。必要に応じてシリアライズ可能に。)
※ ゲッター/セッター、import省略
public class Employee { public static class EmployeeColumns { public static final String ID = "_id"; // 社員ID public static final String NAME = "name"; // 社員名 public static final String DEPARTMENT = "department"; // 部署 } public static final String TABLE_NAME = "employee"; private int id; private String name; private String department; }
テーブル構成も簡単にしてみた。
DatabaseHelper.java(ヘルパー)
※ import省略
public class DatabaseHelper extends SQLiteOpenHelper { private static final String DATABASE_NAME = "company.db"; private static final int DATABASE_VERSION = 1; public DatabaseHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL("CREATE TABLE " + Employee.TABLE_NAME + "(" + Employee.EmployeeColumns.ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + Employee.EmployeeColumns.NAME + " TEXT NOT NULL," + Employee.EmployeeColumns.DEPARTMENT + " TEXT NOT NULL" + ")"); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } }
EmployeeDao.java(データベースアクセスクラス。今回のテスト対象クラス。)
※ import省略
public class EmployeeDao { private DatabaseHelper mHelper; public EmployeeDao(Context context) { mHelper = new DatabaseHelper(context); } /** * 部署を条件に社員一覧を取得する。 * 順不同 * @param department * @return 社員一覧 0件の場合は空のリスト */ public List<Employee> getEmployeeByDepartment(String department) { List<Employee> employees = new ArrayList<Employee>(); SQLiteDatabase db = mHelper.getReadableDatabase(); try { Cursor c = db.query(Employee.TABLE_NAME, new String[]{ EmployeeColumns.ID, EmployeeColumns.NAME, EmployeeColumns.DEPARTMENT }, EmployeeColumns.DEPARTMENT + " = ?", new String[]{ department }, null, null, null); c.moveToFirst(); while (!c.isAfterLast()) { Employee employee = new Employee(); employee.setId(c.getInt(c.getColumnIndex(EmployeeColumns.ID))); employee.setName(c.getString(c.getColumnIndex(EmployeeColumns.NAME))); employee.setDepartment(c.getString(c.getColumnIndex(EmployeeColumns.DEPARTMENT))); employees.add(employee); c.moveToNext(); } c.close(); } finally { db.close(); } return employees; } }
そして今回のポイントとなるテストクラス。
EmployeeDaoTest.java
※ import省略
public class EmployeeDaoTest extends AndroidTestCase { private static final String TEST_PREFIX = "test_"; private DatabaseHelper mHelper; private RenamingDelegatingContext mContext; @Override protected void setUp() throws Exception { super.setUp(); mContext = new RenamingDelegatingContext(getContext(), TEST_PREFIX); // テストメソッド毎に空のテスト用DBを用意 mHelper = new DatabaseHelper(mContext); } @Override protected void tearDown() throws Exception { super.tearDown(); mHelper.close(); } /** * getEmployeeByDepartmentのテスト */ public void testGetEmployeeByDepartment() { // RenamingDelegatingContextを渡してテスト用DBを使用する EmployeeDao dao = new EmployeeDao(mContext); List<Employee> employees = dao.getEmployeeByDepartment("人事部"); assertNotNull(employees); // これ以降のテストは省略 } }
ソースを見ればわかると思うけど手順は簡単で
- Context と文字列を渡してRenamingDelegatingContextを生成
- 生成したRenamingDelegatingContextを渡してヘルパーを生成
これを実行すると。。。
テスト用のDBがちゃんとあるね。
2012年1月26日木曜日
AndroidでDBの存在確認
AndroidでDBの存在確認の仕方は他にもあるけど、
データベースの保存場所がストレージであれば「/data/data/パッケージ名/databases/DB名」
に保存されるのでファイルの存在確認するのが手っ取り早い。
データベースの保存場所がストレージであれば「/data/data/パッケージ名/databases/DB名」
に保存されるのでファイルの存在確認するのが手っ取り早い。
String DB_NAME = "test.db"; File file = new File(context.getDatabasePath(DB_NAME).getPath()); boolean dbExists = file.exists();
2012年1月11日水曜日
Androidでステータスバー(通知バー)を起動する
ステータスバーとか通知バーとかいろんな言い方あるけど、とにかくコレのこと↓
これを起動するのにちょいと手間がかかる。かかるといってもたいしたことはないけど。
まず、AndroidManifest.xmlに下記パーミッションを追加。
あとはこんな感じのソースを書いて呼べばOK。
ちなみにHideなAPI使ってるのでいきなり使えなくなるかも?
これを起動するのにちょいと手間がかかる。かかるといってもたいしたことはないけど。
まず、AndroidManifest.xmlに下記パーミッションを追加。
<uses-permission android:name="android.permission.EXPAND_STATUS_BAR" />
あとはこんな感じのソースを書いて呼べばOK。
private void showNotifications() { try { Object service = getSystemService("statusbar"); if (service != null) { Method expand = service.getClass().getMethod("expand"); expand.invoke(service); } } catch (Exception e) { } }
ちなみにHideなAPI使ってるのでいきなり使えなくなるかも?
2011年12月26日月曜日
Androidでファイルの入出力
汎用的なユーティリティー系の処理はその都度書いていては時間の無駄なので
ファイルの入出力の処理をコピペ出来るようにここに貼付けておく。
ちなみにファイルの入出力先は「/data/data/パッケージ名/files/」
FileUtils.java
ファイルの入出力の処理をコピペ出来るようにここに貼付けておく。
ちなみにファイルの入出力先は「/data/data/パッケージ名/files/」
FileUtils.java
package yourpackage; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.InputStream; import java.io.OutputStream; import android.content.Context; public class FileUtils { /** * ファイルへ文字列を書き込み * @param context * @param str ファイル出力文字列 * @param fileName ファイル名 */ public static void writeFile(Context context, String str, String fileName) { writeBinaryFile(context, str.getBytes(), fileName); } /** * ファイルへバイナリデータを書き込み * @param context * @param data バイトデータ * @param fileName ファイル名 */ public static void writeBinaryFile(Context context, byte[] data, String fileName) { OutputStream out = null; try { out = context.openFileOutput(fileName, Context.MODE_PRIVATE); out.write(data, 0, data.length); } catch (Exception e) { // 必要に応じて // throw e; } finally { try { if (out != null) out.close(); } catch (Exception e) { } } } /** * ファイルから文字列を読み込む * @param context * @param fileName ファイル名 * @return 文字列 ファイルがない場合はnull */ public static String readFile(Context context, String fileName) { String str = null; byte[] data = readBinaryFile(context, fileName); if (data != null) { str = new String(data); } return str; } /** * ファイルからバイナリデータを読み込む * @param context * @param fileName * @return バイトデータ ファイルがない場合はnull */ public static byte[] readBinaryFile(Context context, String fileName) { // ファイルの存在チェック if (!(new File(context.getFilesDir().getPath() + "/" + fileName).exists())) { return null; } int size; byte[] data = new byte[1024]; InputStream in = null; ByteArrayOutputStream out = null; try { in = context.openFileInput(fileName); out = new ByteArrayOutputStream(); while ((size = in.read(data)) != -1) { out.write(data, 0, size); } return out.toByteArray(); } catch (Exception e) { // エラーの場合もnullを返すのでここでは何もしない } finally { try { if (in != null) in.close(); if (out != null) out.close(); } catch (Exception e) { } } return null; } }
2011年11月20日日曜日
いつからかEclipse上でAndroidプロジェクトのMavenビルドが出来なくなったので…
久々にMavenを使用してAndroidアプリを作成しようと思ったらなぜかビルドが出来ない。。
ADTやらSDKを更新したタイミングで使えなくなったのだろうと思い、いろいろ思考錯誤してみた。
そしてようやくビルドが出来る環境を作成する手順が確立出来たので忘れずメモ。
ちなみに私の環境は以下の通り。
Mac OS X 10.6.8
ADT 15 & SDK15
Maven 3.0.3
Eclipse Indigo
プルダウンからIndigoのアップデートサイトを選択しCollaborationを展開する。
「Maven Integration for Eclipse」と「slf4j over logback loggind」にチェックし、-> [Next]
あとは指示通り進んでインストール完了後Eclipseを再起動。
「m2e-android」と検索すると「Android Configuration for M2E」がヒットするので
Installボタンをクリックし指示通り進んでインストール完了後Eclipseを再起動。
Workspace locationに任意の場所を指定 -> [Next] -> [Add Archetype...]
下記のような画面になるので各項目を入力 -> [OK]
Archetype Group Id: de.akquinet.android.archetypes
Archetype Artifact Id : android-quickstart
Archetype Version: 1.0.6
Repository URL: 空白
上記で追加したandroid-quickstartを選択しAndroidプロジェクトを作成する。
POMエディターで開く。
pom.xmlを開くと上記に赤くメッセージが表示されるのでその箇所をクリック。
下記のような黄色いウィンドウが表示されるので「Discover new m2e connectors」をクリック。
m2e Marketplaceなるものが開くので「Application」と「Maven」にチェックをし、
「Lifecycle Mappings」と「embedded maven runtimes」をインストールしEclipseを再起動。
ちなみにpom.xmlの全体はこんな感じ。
ADTやらSDKを更新したタイミングで使えなくなったのだろうと思い、いろいろ思考錯誤してみた。
そしてようやくビルドが出来る環境を作成する手順が確立出来たので忘れずメモ。
ちなみに私の環境は以下の通り。
Mac OS X 10.6.8
ADT 15 & SDK15
Maven 3.0.3
Eclipse Indigo
m2eのインストール
[Help] -> [Install New Software...]プルダウンからIndigoのアップデートサイトを選択しCollaborationを展開する。
「Maven Integration for Eclipse」と「slf4j over logback loggind」にチェックし、-> [Next]
あとは指示通り進んでインストール完了後Eclipseを再起動。
m2e-androidのインストール
[Help] -> [Eclipse Marketplace...]「m2e-android」と検索すると「Android Configuration for M2E」がヒットするので
Installボタンをクリックし指示通り進んでインストール完了後Eclipseを再起動。
Archetypeの追加
[New] -> [Project...] -> [Maven Project]Workspace locationに任意の場所を指定 -> [Next] -> [Add Archetype...]
下記のような画面になるので各項目を入力 -> [OK]
Archetype Group Id: de.akquinet.android.archetypes
Archetype Artifact Id : android-quickstart
Archetype Version: 1.0.6
Repository URL: 空白
上記で追加したandroid-quickstartを選択しAndroidプロジェクトを作成する。
m2e connectorのインストール
Androidプロジェクトを作成すると、下記画面のようにpom.xmlにエラーマークがつくのでPOMエディターで開く。
pom.xmlを開くと上記に赤くメッセージが表示されるのでその箇所をクリック。
下記のような黄色いウィンドウが表示されるので「Discover new m2e connectors」をクリック。
m2e Marketplaceなるものが開くので「Application」と「Maven」にチェックをし、
「Lifecycle Mappings」と「embedded maven runtimes」をインストールしEclipseを再起動。
pom.xmlの修正
<artifactId>maven-android-plugin</artifactId> <version>2.8.4</version> ↓ <artifactId>android-maven-plugin</artifactId> <version>3.0.0-alpha-14</version>以上でEclipse上でAndroidプロジェクトのMavenビルドが出来るようになるはず。
ちなみにpom.xmlの全体はこんな感じ。
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>jp.u1aryz.products.hoge</groupId> <artifactId>HogeSample</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>apk</packaging> <name>HogeSample</name> <dependencies> <dependency> <groupId>com.google.android</groupId> <artifactId>android</artifactId> <version>2.1.2</version> <scope>provided</scope> </dependency> </dependencies> <build> <finalName>${project.artifactId}</finalName> <sourceDirectory>src/main/java</sourceDirectory> <plugins> <plugin> <groupId>com.jayway.maven.plugins.android.generation2</groupId> <artifactId>android-maven-plugin</artifactId> <version>3.0.0-alpha-14</version> <configuration> <sdk> <!-- SDKのパスはsettings.xmlで定義していれば不要 --> <path>"your ANDROID_HOME"</path> <platform>7</platform> </sdk> <manifest> <debuggable>true</debuggable> </manifest> </configuration> <extensions>true</extensions> </plugin> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>2.3.2</version> <configuration> <source>1.6</source> <target>1.6</target> </configuration> </plugin> </plugins> </build> </project>
2011年11月18日金曜日
RoboGuiceでAndroidアプリのDI開発
今更ながらRoboGuiceを使ってみたので忘れずメモ。
RoboGuiceについて簡単に触れておくとGoogle GuiceベースのAndroid用のDIコンテナである。
roboguice - Project Hosting on Google Code
細かいViewの制御は抜きにして以下のようなアプリを例としてRoboGuiceを使用して作成してみる。
トップ画面より入力された名前を次の画面で文字列を付け足して表示する。
まず前エントリーを参考にMavenプロジェクトからAndroidプロジェクトを作成。
pom.xmlを追記
TopActivity
ListActivityの場合はRoboListActivity、TabActivityの場合はRoboTabActivityなどそれぞれ対応したもの
が存在する。
ソースを見るとわかる通り、@InjectView(resouceId)でViewをInjectしてくれる。
リソースを参照したい場合は@InjectResource(resourceId)を使用すればいい。
他にもこんなことが可能である。
HelloActivityを実装する前にインターフェースと実装クラスを使用して
少しDIっぽい処理を入れてみる。
RoboSampleService
RoboSampleServiceImpl
続いて上記サービスをバインド
MyApplication
アプリケーションのエントリポイントとして上記クラスを呼び出す必要があるので
AndroidManifest.xmlに以下を追加
最後にHelloActivityの実装
HelloActivity
@InjectExtra("name")でTopActivityでputExtraした値が入る。
また、RoboSampleServiceに@Injectアノテーションをつけることで上記でバインドした
RoboSampleServiceImplをInjectしてくれるのでインスタンスを生成せず使用することが出来る。
続いてDIの長所を活かしてHelloActivityのテストを書いてみる。
テストプロジェクトを作成する前にテスト対象のプロジェクトのクラスパスをエクスポート。
通常通りAndroidテストプロジェクトを作成する。
今回作成したサンプルプログラムのhelloメソッドは静的な文字列を付加するだけの
シンプルな作りであるが、実際は動的であったり、バグが混入していたり、
決まっていなかったりするので依存するクラスの振る舞いを固定化するモックを作成する。
MockServiceImpl
続いてテストコードの実装
HelloActivityTest
RoboActivityUnitTestCase<対象アクティビティ>を継承してテストコードを作成する。
RoboSampleServiceのInject要求に対してモックを返すようにバインドさせ、
setApplicationでセットすることで依存したオブジェクトの振る舞いを固定化させることが出来る。
Run As -> Android JUnit Testで...
これで一通り完了。案外使いやすいかも?
RoboGuiceについて簡単に触れておくとGoogle GuiceベースのAndroid用のDIコンテナである。
roboguice - Project Hosting on Google Code
細かいViewの制御は抜きにして以下のようなアプリを例としてRoboGuiceを使用して作成してみる。
まず前エントリーを参考にMavenプロジェクトからAndroidプロジェクトを作成。
pom.xmlを追記
<dependency> <groupId>org.roboguice</groupId> <artifactId>roboguice</artifactId> <version>1.1.2</version> </dependency>
TopActivity
public class TopActivity extends RoboActivity { @InjectResource(R.string.message) String message; @InjectView(R.id.txt_msg) TextView mTextView; @InjectView(R.id.edt_name) EditText mEditText; @InjectView(R.id.btn_decision) Button mButton; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // mTextView.setText(message); // ここでViewにアクセスするとエラー setContentView(R.layout.top); // ここでViewがInjectされる mTextView.setText(message); mButton.setOnClickListener(new OnClickListener() { public void onClick(View v) { Intent intent = new Intent(TopActivity.this, HelloActivity.class); intent.putExtra("name", mEditText.getText().toString()); startActivity(intent); } }); } }RoboActivityを継承してアクティビティを作成する。※1.1からGuiceActivityからRoboActivityに変更された
ListActivityの場合はRoboListActivity、TabActivityの場合はRoboTabActivityなどそれぞれ対応したもの
が存在する。
ソースを見るとわかる通り、@InjectView(resouceId)でViewをInjectしてくれる。
リソースを参照したい場合は@InjectResource(resourceId)を使用すればいい。
他にもこんなことが可能である。
Drawable icon = getResources().getDrawable(R.drawable.icon); ↓ @InjectResource(R.drawable.icon) Drawable icon;
LocationManager loc = (LocationManager) getSystemService(Activity.LOCATION_SERVICE); ↓ @Inject LocationManager loc;
HelloActivityを実装する前にインターフェースと実装クラスを使用して
少しDIっぽい処理を入れてみる。
RoboSampleService
public interface RoboSampleService { public String hello(String name); }
RoboSampleServiceImpl
public class RoboSampleServiceImpl implements RoboSampleService { public String hello(String name) { return "Hello " + name; } }
続いて上記サービスをバインド
MyApplication
public class MyApplication extends RoboApplication { @Override protected void addApplicationModules(List<Module> modules) { modules.add(new MyModule()); } static class MyModule extends AbstractAndroidModule { @Override protected void configure() { // RoboSampleServiceのInject要求に対してRoboSampleServiceImplを返すようバインドする bind(RoboSampleService.class).to(RoboSampleServiceImpl.class); } } }※こちらもGuiceApplicationからRoboApplicationに変更されているので注意
アプリケーションのエントリポイントとして上記クラスを呼び出す必要があるので
AndroidManifest.xmlに以下を追加
<application... android:name=".MyApplication" ...>
最後にHelloActivityの実装
HelloActivity
public class HelloActivity extends RoboActivity { @InjectExtra("name") String name; @InjectView(R.id.hello_msg) TextView textView; @Inject RoboSampleService service; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.hello); textView.setText(service.hello(name)); } }
@InjectExtra("name")でTopActivityでputExtraした値が入る。
また、RoboSampleServiceに@Injectアノテーションをつけることで上記でバインドした
RoboSampleServiceImplをInjectしてくれるのでインスタンスを生成せず使用することが出来る。
続いてDIの長所を活かしてHelloActivityのテストを書いてみる。
テストプロジェクトを作成する前にテスト対象のプロジェクトのクラスパスをエクスポート。
通常通りAndroidテストプロジェクトを作成する。
今回作成したサンプルプログラムのhelloメソッドは静的な文字列を付加するだけの
シンプルな作りであるが、実際は動的であったり、バグが混入していたり、
決まっていなかったりするので依存するクラスの振る舞いを固定化するモックを作成する。
MockServiceImpl
public class MockServiceImpl implements RoboSampleService { @Override public String hello(String name) { return "test"; } }
続いてテストコードの実装
HelloActivityTest
public class HelloActivityTest extends RoboActivityUnitTestCase<HelloActivity> { private Context mContext; public HelloActivityTest() { super(HelloActivity.class); } class MockApplication extends RoboApplication { public MockApplication(Context context) { super(); attachBaseContext(context); } @Override protected void addApplicationModules(List<Module> modules) { modules.add(new MockModule()); } } class MockModule extends AbstractAndroidModule { @Override protected void configure() { // RoboSampleServiceのInject要求に対してMockServiceImplを返すようバインドする bind(RoboSampleService.class).to(MockServiceImpl.class); } } @Override protected void setUp() throws Exception { super.setUp(); mContext = getInstrumentation().getContext(); setApplication(new MockApplication(mContext)); } @MediumTest public void testShouldBeHelloMsg() { Intent intent = new Intent(mContext, HelloActivity.class); intent.putExtra("name", "name"); HelloActivity a = startActivity(intent, null, null); assertNotNull(a); // Mockで返す文字列を"test"にしているため assertEquals(((TextView)a.findViewById(R.id.hello_msg)).getText(), "test"); } }
RoboActivityUnitTestCase<対象アクティビティ>を継承してテストコードを作成する。
RoboSampleServiceのInject要求に対してモックを返すようにバインドさせ、
setApplicationでセットすることで依存したオブジェクトの振る舞いを固定化させることが出来る。
Run As -> Android JUnit Testで...
これで一通り完了。案外使いやすいかも?
2011年9月25日日曜日
Androidでオブジェクト/XMLマッピング
TwitterクライアントなどXMLやJSONをWeb上から取得してアプリに表示することは少なくない。
しかし、XMLやJSONをパースするような単調な処理は出来るだけ無くしたいので、
Spring AndroidとSimple(xmlシリアルフレームワーク)を使用してXMLデータを取得し、
アプリに表示させてみた。
今回はイベント開催支援ツールのATNDのAPIを使用してAndroid系イベントを取得して
イベントタイトルとイベント日時をアプリにリスト表示してみる。
実際に使用するAPI→http://api.atnd.org/events/?keyword=android&format=xml
サンプルプロジェクトのダウンロードリンクは記事の最後。
まず前エントリーを参考にMavenプロジェクトからAndroidプロジェクトを作成。
pom.xmlを追記
次にXMLデータをマッピングさせるオブジェクトを作成する。
要素には@Element、属性には@Attributeをつける。
@Element、@Attributeのnameは要素名(属性名)と変数名が同じであれば省略可能。
XMLのすべての要素をオブジェクトにマッピングしない場合は@Rootアノテーションにstrict = false
をつける必要がある。
Event.java
リストの場合は@ElementListアノテーションを使う。
EventList.java
そしてXMLデータからオブジェクトにマッピングさせる処理を作成する。
HTTP通信は通常、UIスレッドとは別のスレッドで行うため、今回のポイントとなる処理も
doInBackground内に入れる。
GetAndroidEventActivity.java
最後にViewに取得したデータをセットするAdapterの作成。
特別な処理はしていないので、通常のAndroidの知識で理解出来ると思う。
EventListAdapter.java
<uses-permission android:name="android.permission.INTERNET" />を忘れなければ
下記のようにイベントを取得出来る。
日時のフォーマットが見づらいけど。。。
これで単調なパース処理とおさらば〜
時間があればJSONバージョンもやるかも!?
ダウンロードはこちらから
しかし、XMLやJSONをパースするような単調な処理は出来るだけ無くしたいので、
Spring AndroidとSimple(xmlシリアルフレームワーク)を使用してXMLデータを取得し、
アプリに表示させてみた。
今回はイベント開催支援ツールのATNDのAPIを使用してAndroid系イベントを取得して
イベントタイトルとイベント日時をアプリにリスト表示してみる。
実際に使用するAPI→http://api.atnd.org/events/?keyword=android&format=xml
サンプルプロジェクトのダウンロードリンクは記事の最後。
まず前エントリーを参考にMavenプロジェクトからAndroidプロジェクトを作成。
pom.xmlを追記
<dependency> <groupId>org.simpleframework</groupId> <artifactId>simple-xml</artifactId> <version>2.6.1</version> <exclusions> <exclusion> <artifactId>stax</artifactId> <groupId>stax</groupId> </exclusion> <exclusion> <artifactId>stax-api</artifactId> <groupId>stax</groupId> </exclusion> <exclusion> <artifactId>xpp3</artifactId> <groupId>xpp3</groupId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.android</groupId> <artifactId>spring-android-rest-template</artifactId> <version>1.0.0.M4</version> </dependency> <repositories> <repository> <id>org.springframework.maven.milestone</id> <name>Spring Maven Milestone Repository</name> <url>http://maven.springframework.org/milestone</url> <snapshots><enabled>false</enabled></snapshots> </repository> </repositories>
次にXMLデータをマッピングさせるオブジェクトを作成する。
要素には@Element、属性には@Attributeをつける。
@Element、@Attributeのnameは要素名(属性名)と変数名が同じであれば省略可能。
XMLのすべての要素をオブジェクトにマッピングしない場合は@Rootアノテーションにstrict = false
をつける必要がある。
Event.java
package jp.u1aryz.products.atndsearch; import org.simpleframework.xml.Element; import org.simpleframework.xml.Root; @Root(name = "event", strict = false) public class Event { @Element private String title; @Element(name = "started_at") private String eventDate; public void setTitle(String title) { this.title = title; } public String getTitle() { return title; } public void setEventDate(String eventDate) { this.eventDate = eventDate; } public String getEventDate() { return eventDate; } }
リストの場合は@ElementListアノテーションを使う。
EventList.java
package jp.u1aryz.products.atndsearch; import java.util.List; import org.simpleframework.xml.ElementList; import org.simpleframework.xml.Root; @Root(name = "events", strict = false) public class EventList { @ElementList private List<Event> events; public void setEvents(List<Event> events) { this.events = events; } public List<Event> getEvents() { return events; } }
そしてXMLデータからオブジェクトにマッピングさせる処理を作成する。
HTTP通信は通常、UIスレッドとは別のスレッドで行うため、今回のポイントとなる処理も
doInBackground内に入れる。
GetAndroidEventActivity.java
package jp.u1aryz.products.atndsearch; import java.util.ArrayList; import java.util.List; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.client.RestTemplate; import android.app.ListActivity; import android.app.ProgressDialog; import android.os.AsyncTask; import android.os.Bundle; import android.util.Log; public class GetAndroidEventActivity extends ListActivity { private static String TAG = GetAndroidEventActivity.class.getSimpleName(); private ProgressDialog progressDialog; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } @Override protected void onStart() { super.onStart(); // ATND APIの呼び出しを非同期で行う new GetEventTask().execute(); } private void refreshEvents(List<Event> events) { if (events != null) { EventListAdapter adapter = new EventListAdapter(this, events); setListAdapter(adapter); } } public void showProgressDialog() { if (progressDialog == null) { progressDialog = new ProgressDialog(this); progressDialog.setIndeterminate(true); } progressDialog.setMessage("Loading. Please wait..."); progressDialog.show(); } public void dismissProgressDialog() { if (progressDialog != null) { progressDialog.dismiss(); } } private class GetEventTask extends AsyncTask<Void, Void, List<Event>> { @Override protected void onPreExecute() { // プログレスバーを表示 showProgressDialog(); } @Override protected List<Event> doInBackground(Void... params) { try { // Android関連のイベントを検索するURL String url = "http://api.atnd.org/events/?keyword=android&format=xml"; // Acceptヘッダに"application/xml"をセット HttpHeaders requestHeaders = new HttpHeaders(); List<MediaType> acceptableMediaTypes = new ArrayList<MediaType>(); acceptableMediaTypes.add(MediaType.APPLICATION_XML); requestHeaders.setAccept(acceptableMediaTypes); HttpEntity<?> requestEntity = new HttpEntity<Object>(requestHeaders); RestTemplate restTemplate = new RestTemplate(); // HTTPのGETリクエストを実行(マッピングさせるクラスを指定する) ResponseEntity<EventList> responseEntity = restTemplate.exchange(url, HttpMethod.GET, requestEntity, EventList.class); // イベントリストを取得 EventList eventList = responseEntity.getBody(); return eventList.getEvents(); } catch (Exception e) { Log.e(TAG, e.getMessage(), e); } return null; } @Override protected void onPostExecute(List<Event> result) { // プログレスバーを非表示 dismissProgressDialog(); // 結果を返す refreshEvents(result); } } }
最後にViewに取得したデータをセットするAdapterの作成。
特別な処理はしていないので、通常のAndroidの知識で理解出来ると思う。
EventListAdapter.java
package jp.u1aryz.products.atndsearch; import java.util.List; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.TextView; public class EventListAdapter extends ArrayAdapter<Event> { private LayoutInflater mInflater; public EventListAdapter(Context context, List<Event> objects) { super(context, 0, objects); mInflater = LayoutInflater.from(context); } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder; if (convertView == null) { convertView = mInflater.inflate(android.R.layout.simple_list_item_2, null); holder = new ViewHolder(); holder.title = (TextView) convertView.findViewById(android.R.id.text1); holder.eventDate = (TextView) convertView.findViewById(android.R.id.text2); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } Event event = getItem(position); holder.title.setText(event.getTitle()); holder.eventDate.setText(event.getEventDate()); return convertView; } } class ViewHolder { TextView title; TextView eventDate; }
<uses-permission android:name="android.permission.INTERNET" />を忘れなければ
下記のようにイベントを取得出来る。
日時のフォーマットが見づらいけど。。。
これで単調なパース処理とおさらば〜
時間があればJSONバージョンもやるかも!?
ダウンロードはこちらから
2011年9月23日金曜日
MavenでAndroidアプリケーションの構成管理をはじめる手順
最近ではサードパーティー製のAndroidフレームワークやライブラリが増加してきており、
構成管理にMavenを使用すると便利である。
Maven初心者ながらAndroidプロジェクトの構成管理を始めてみたのでその時の手順をメモ。
2011年11月20日に更新しました。
<事前環境>
Apache Maven 3.0.3
Eclipse 3.7 + ADT v12
※環境のインストール手順については割愛
Androidへのデプロイはプロジェクト配下で下記コマンドを実行する。
構成管理にMavenを使用すると便利である。
Maven初心者ながらAndroidプロジェクトの構成管理を始めてみたのでその時の手順をメモ。
2011年11月20日に更新しました。
<事前環境>
Apache Maven 3.0.3
Eclipse 3.7 + ADT v12
※環境のインストール手順については割愛
- M2_REPOの設定
EclipseからMavenを利用するために下記コマンドを実行
mvn -Declipse.workspace=<path-to-eclipse-workspace> eclipse:add-maven-repo - Eclipseプラグインのインストール
Update siteから下記の2つのプラグインを追加
( [Help] -> [Install New Software...] -> [Add...] )
- Maven Integration for Eclipse
http://m2eclipse.sonatype.org/sites/m2e - Maven Integration for Android Development Tools
https://svn.codespot.com/a/eclipselabs.org/m2eclipse-android-integration/updates/m2eclipse-android-integration/
- Maven Integration for Eclipse
- Archetypeの追加
現在ではGitHub等に多数Archetypeが公開されているので、今回はこちらを利用する。
[New] -> [Project...] -> [Maven Project]
Workspace locationに任意の場所を指定 -> [Next] -> [Add Archetype...]
下記のような画面になるので各項目を入力 -> [OK]
Archetype Group Id: de.akquinet.android.archetypes
Archetype Artifact Id : android-quickstart
Archetype Version: 1.0.5
Repository URL: 空白
- Androidプロジェクトの作成
上記で追加したandroid-quickstartを選択 -> [Next]
下記のような画面になったら各項目に任意の値を入力 -> [Finish]
Androidへのデプロイはプロジェクト配下で下記コマンドを実行する。
mvn clean install android:deploy
2011年7月14日木曜日
AndroidのXMLで2次元配列を定義してソースから呼び出す
Androidでは文字列などのリソースをXMLで定義すると何かと便利なので、
2次元配列をXMLで定義してソースからアクセスする方法を忘れずにメモ。
XML
Java
これに多少の応用を利かせればDrawable配列などの多次元配列も可能となる。
2次元配列をXMLで定義してソースからアクセスする方法を忘れずにメモ。
XML
<array name="array_parent"> <item >@array/array_sub1</item> <item >@array/array_sub2</item> </array> <string-array name="array_sub1" > <item >data1</item> <item >data2</item> <item >data3</item> </string-array> <string-array name="array_sub2" > <item >data4</item> <item >data5</item> <item >data6</item> </string-array>
Java
TypedArray typedArray = getResources().obtainTypedArray(R.array.array_parent); // 配列の要素数(ここではarray_parentの子要素の数) int length = typedArray.length(); // 子要素の配列のリソースIDを取得(ここではarray_sub1のリソースID) int resourceId = typedArray.getResourceId(0, 0); // 配列の値を取得(ここではarray_sub1の各値) String[] array = getResources().getStringArray(resourceId);
これに多少の応用を利かせればDrawable配列などの多次元配列も可能となる。
2011年5月29日日曜日
Androidでスクリーンロック解除しなくても操作可能な画面をつくる&呼び出す
音楽アプリなどで音楽再生中にスクリーンロックがかかった場合、
再度音楽を操作するのにいちいちスクリーンロックを解除するのはかなりの手間である。
Androidマーケットで常に上位のPowerAMPはこのように操作可能になっている。
このようなアプリではユーザビリティを考慮する上でも必要な機能となる。
そこで今回はスクリーンロック解除しなくても操作可能な画面&呼び出しを
ミニマム構成で作成してみた。
サンプルプロジェクトのダウンロードリンクは記事の最後。
構成は以下の通り。
- 画面の電源ONの通知を受け取るレシーバーの登録/登録解除を行うサービス
ScreenStateService
- スクリーンロック解除画面より手前に表示させる画面
ScreenLockEnabledActivity
- "2"の画面を表示させるか否かを設定する画面
SetActivity
画面の電源が入るとACTION_SCREEN_ONが通知される。
ACTION_SCREEN_ONを受け取るには明示的にregisterReceiverする必要がある。
ScreenStateService.java
package jp.u1aryz.products.screenlockenable; import android.app.Service; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.IBinder; public class ScreenStateService extends Service { private BroadcastReceiver mScreenOnListener = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); // 画面の電源が入ったらActivityを起動 if (action.equals(Intent.ACTION_SCREEN_ON)) { Intent i = new Intent(context, ScreenLockEnabledActivity.class); i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(i); } } }; @Override public void onStart(Intent intent, int startId) { super.onStart(intent, startId); // ACTION_SCREEN_ONを受け取るBroadcastReceiverを登録 IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_SCREEN_ON); registerReceiver(mScreenOnListener, filter); } @Override public void onDestroy() { // BroadcastReceiverを登録解除 unregisterReceiver(mScreenOnListener); super.onDestroy(); } @Override public IBinder onBind(Intent intent) { return null; } }サービスが起動されたらレシーバーの登録、サービスが停止されたらレシーバーの登録解除を行う。
かなり簡略化してシンプルな作りにしている。
そして今回のポイントとなる"Lock解除画面より手前に表示させる画面"を作成する。
ScreenLockEnabledActivity.java
package jp.u1aryz.products.screenlockenable; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.view.Window; import android.view.WindowManager; import android.view.View.OnClickListener; import android.widget.Button; public class ScreenLockEnabledActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.lock); // Lock解除画面より手前に表示させる final Window win = getWindow(); win.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD); Button btnRelease = (Button) findViewById(R.id.btn_release); btnRelease.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // Activityを終了することでLock解除画面に移る finish(); } }); } }こちらもかなりミニマムであるが、このActivityに各々操作できるウィジェット(View)等を
配置するといいと思う。
続いて"上記の画面を表示させるか否かを設定する画面"を作成する。
通常はPreferenceActivityなどで実装すると良い。
SetActivity.java
package jp.u1aryz.products.screenlockenable; import android.app.Activity; import android.content.Intent; import android.content.SharedPreferences; import android.os.Bundle; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; public class SetActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // チェックボックスの値を保存するために使用 final SharedPreferences pref = getSharedPreferences("pref", MODE_PRIVATE); final Intent intent = new Intent(SetActivity.this, ScreenStateService.class); CheckBox chbEnable = (CheckBox) findViewById(R.id.chb_enable); chbEnable.setChecked(pref.getBoolean("is_lockEnable", false)); chbEnable.setOnCheckedChangeListener(new OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { // チェックされたらサービスを起動 if (isChecked) { pref.edit().putBoolean("is_lockEnable", isChecked).commit(); startService(intent); // チェックが外されたらサービスを停止 } else { pref.edit().putBoolean("is_lockEnable", isChecked).commit(); stopService(intent); } } }); } }端末の再起動のパターンやその他もろもろ対応しきれてないけどあしからず〜
ダウンロードはこちらから
2011年3月27日日曜日
ListViewにグループタイトルをつける
上記のようにアルファベットなどでグルーピングされたリストに
グループタイトルをつける方法を考えてみた。
(PreferenceCategoryのタイトルのようなものです)
まず、ListViewの1行分のレイアウトを用意する。
その際、グループタイトル+通常要素(アルバム)となるようにする。
上記のようなアルバム一覧の場合、下記のようなレイアウトを用意する。
具体的なレイアウトのXMLは下記のようになる。
list_item.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" > <!-- グループタイトル --> <TextView android:id="@+id/groupTitle" android:layout_width="fill_parent" android:layout_height="wrap_content" android:background="#280030" android:textColor="#c62b00" android:textStyle="bold" android:paddingLeft="5dp" /> <!-- アルバム名 --> <TextView android:id="@+id/albumName" android:layout_width="fill_parent" android:layout_height="wrap_content" android:textColor="#FFFFFF" android:textSize="24sp" android:paddingLeft="5dp" /> <!-- アーティスト名 --> <TextView android:id="@+id/artistName" android:layout_width="fill_parent" android:layout_height="wrap_content" android:paddingLeft="5dp" /> </LinearLayout>
続いてArrayAdapterを継承したクラスを実装する。
MyAdapter.java
package jp.u1aryz.products.grouptitle; import java.util.List; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.TextView; class BindData { String groupTitle; String albumName; String artistName; public BindData(String groupTitle, String albumName, String artistName) { this.groupTitle = groupTitle; this.albumName = albumName; this.artistName = artistName; } } class ViewHolder { TextView groupTitle; TextView albumName; TextView artistName; } public class MyAdapter extends ArrayAdapter<BindData> { private LayoutInflater inflater; public MyAdapter(Context context, List<BindData> objects) { super(context, 0, objects); this.inflater = (LayoutInflater) context .getSystemService(Context.LAYOUT_INFLATER_SERVICE); } @Override public boolean isEnabled(int position) { // BindDataのgroupTitleが!nullの場合、選択不可にする return getItem(position).groupTitle == null; } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder; if (convertView == null) { convertView = inflater.inflate(R.layout.list_item, parent, false); holder = new ViewHolder(); holder.groupTitle = (TextView) convertView.findViewById(R.id.groupTitle); holder.albumName = (TextView) convertView.findViewById(R.id.albumName); holder.artistName = (TextView) convertView.findViewById(R.id.artistName); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } BindData data = getItem(position); // グループタイトル if (!isEnabled(position)) { holder.groupTitle.setVisibility(View.VISIBLE); holder.groupTitle.setText(data.groupTitle); holder.albumName.setVisibility(View.GONE); holder.artistName.setVisibility(View.GONE); // アルバム } else { holder.groupTitle.setVisibility(View.GONE); holder.albumName.setVisibility(View.VISIBLE); holder.albumName.setText(data.albumName); holder.artistName.setVisibility(View.VISIBLE); holder.artistName.setText(data.artistName); } return convertView; } }
ここでは選択不可の項目を設定する際、BindDataのgroupTileが!nullだった場合と
ルール決めをしている。
ポイントは63行目〜75行目になり、View#setVisibility(int visibility)を使用し、
各項目を表示、非表示と切り替える。
最後にActivityから以下のようにしてadapterを設定する。
package jp.u1aryz.products.grouptitle; import java.util.ArrayList; import java.util.List; import android.app.ListActivity; import android.os.Bundle; public class MyActivity extends ListActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); setTitle("アルバム一覧"); List<BindData> list = new ArrayList<BindData>(); // ここにlistに項目を追加する処理が入る MyAdapter adapter = new MyAdapter(this, list); setListAdapter(adapter); } }
正直もっといい方法がありそうなので、より良い方法が見つかったら更新するかも。。。
2011年3月6日日曜日
ListViewのスクロール位置のあれこれ
AndroidMarketアプリやYouTubeアプリ、Gmailアプリなどでは
ListViewを最後までスクロールすると自動的に次の数件を取得し表示される。
android.widget.AbsListView.OnScrollListener#onScrollを利用すると
表示されている先頭のインデックス(firstVisibleItem)、
表示されているリストの数(visibleItemCount)、リストのトータル数(totalItemCount)
が引数として渡ってくるので、firstVisibleItem + visibleItemCount = totalItemCount
になる時、最後までスクロールされたと判定出来る。
上図で赤枠部分が画面に表示されている部分になる。
最後までスクロールしている右図でfirstVisibleItem + visibleItemCount = totalItemCount
になっていることがわかる。
下記がソースの一部。
ListView listView = (ListView) findViewById(android.R.id.list); listView.setOnScrollListener(new OnScrollListener() { @Override public void onScrollStateChanged(AbsListView view, int scrollState) { } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { // 最後までスクロールされたかどうかの判定 if (totalItemCount == firstVisibleItem + visibleItemCount) { // ここに次の数件を取得して表示する処理を書けばいい } } });
リストの追加などでListViewを更新する際、スクロール位置が毎回戻ってしまっては
ユーザービリティの悪いものになってしまう。
ListViewのスクロール位置の取得と設定は下記のようにして実現出来る。
取得
int position = listView.getFirstVisiblePosition(); int y = listView.getChildAt(0).getTop();
設定
listView.setSelectionFromTop(position, y);
インターネット上からデータを取得する際に使用する頻度が多いかも。
登録:
投稿 (Atom)