2012年2月19日日曜日

AndroidのSpinner内のテキストってselector効かなくね?


上記のように条件によってSpinnerを選択不可にしたいパターンはよくあると思う。
ついでにSpinner内のテキストの色も選択 / 選択不可に応じて色を変えたい場合もあるだろう。
通常の例に沿って下記のようなセレクタとスタイルを用意してテキストカラー変更を試みてみた。
color/spinner_text.xml
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <selector xmlns:android="http://schemas.android.com/apk/res/android" >  
  3.     <item android:state_enabled="false" android:color="@android:color/darker_gray" />  
  4.     <item android:color="@android:color/black" />  
  5. </selector>  
values/styles.xml
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <resources>  
  3.     <style name="Theme" parent="@android:style/Theme">  
  4.         <item name="android:spinnerItemStyle">@style/SpinnerItem</item>  
  5.     </style>  
  6.   
  7.     <style name="SpinnerItem" parent="@android:style/Widget.TextView.SpinnerItem">  
  8.         <item name="android:textColor">@color/spinner_text</item>  
  9.     </style>  
  10. </resources>  

だけど、なぜか選択不可状態の色が反映されず...
ということで代替案で対応させてみたのが下記。
SpinnerActivity.java
  1. public class SpinnerActivity extends Activity implements  
  2.         OnCheckedChangeListener {  
  3.   
  4.     private Spinner mSpinner;  
  5.     private RadioGroup mRadioGroup;  
  6.   
  7.     @Override  
  8.     public void onCreate(Bundle savedInstanceState) {  
  9.         super.onCreate(savedInstanceState);  
  10.         setContentView(R.layout.main);  
  11.   
  12.         mSpinner = (Spinner) findViewById(R.id.spinner);  
  13.         String[] brands =  
  14.                 new String[] { "LARK""Seven Stars""MILD SEVEN""etc" };  
  15.         ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,  
  16.                 android.R.layout.simple_spinner_item, brands);  
  17.         adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);  
  18.         mSpinner.setAdapter(adapter);  
  19.   
  20.         mRadioGroup = (RadioGroup) findViewById(R.id.group);  
  21.         mRadioGroup.setOnCheckedChangeListener(this);  
  22.     }  
  23.   
  24.     @Override  
  25.     public void onCheckedChanged(RadioGroup group, int checkedId) {  
  26.         RadioButton radioButton = (RadioButton) findViewById(checkedId);  
  27.         if (radioButton.isChecked()) {  
  28.             spinnerControl(checkedId);  
  29.         }  
  30.     }  
  31.   
  32.     /** 
  33.      * Spinnerの選択状態を制御する 
  34.      * @param checkedId 
  35.      */  
  36.     private void spinnerControl(int checkedId) {  
  37.   
  38.         int color = Color.BLACK;  
  39.         switch (checkedId) {  
  40.         case R.id.yes:  
  41.             mSpinner.setEnabled(true);  
  42.             break;  
  43.   
  44.         case R.id.no:  
  45.             mSpinner.setEnabled(false);  
  46.             color = Color.GRAY;  
  47.             break;  
  48.         }  
  49.         // SpinnerからTextViewを取り出してテキストカラーを設定  
  50.         TextView textView = (TextView) mSpinner.getChildAt(0);  
  51.         textView.setTextColor(color);  
  52.     }  
  53.   
  54.     @Override  
  55.     public void onWindowFocusChanged(boolean hasFocus) {  
  56.         super.onWindowFocusChanged(hasFocus);  
  57.   
  58.         int checkedId = mRadioGroup.getCheckedRadioButtonId();  
  59.         spinnerControl(checkedId);  
  60.     }  
  61. }  

はい完成。ポイントは50行目〜51行目。
誰かセレクタで出来た人いたらやり方おせ〜て〜。

2012年2月18日土曜日

パッケージが更新された(バージョンが上がった)時のBroadcastについて

パッケージが更新された場合、PACKAGE_REMOVED→PACKAGE_ADDED→PACKAGE_REPLACED
の順番でBroadcastが投げられる。
個人的には違和感ありまくりなんだけど、内部的には一旦削除してから追加していて
このような挙動になっているんだと思う。(確かめてはないけど...)

しかし、パッケージ更新時のみPACKAGE_REMOVEDやPACKAGE_ADDEDの処理を
スキップしたいことはあると思う。そんな時は以下のようにインテントからデータを
抜き出して判定してやればいい。
  1. public class PackageMonitor extends BroadcastReceiver {  
  2.   
  3.     @Override  
  4.     public void onReceive(Context context, Intent intent) {  
  5.   
  6.         String action = intent.getAction();  
  7.   
  8.         // パッケージ更新の場合はスキップ  
  9.         if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {  
  10.             return;  
  11.         }  
  12.   
  13.         if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {  
  14.             // パッケージが追加された時にしたい処理  
  15.         } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {  
  16.             // パッケージが削除された時にしたい処理  
  17.         }  
  18.     }  
  19.   
  20. }  

2012年2月10日金曜日

RenamingDelegatingContextを使ってみた

昨年(2011年)の8月に開催されたAndroidテスト祭りで@ussy00さんの発表を聞いてRenamingDelegatingContextの存在を
知ってから結構経ってしまったけど、ようやく仕事でDBを使う機会が出来たので使ってみた。

なにが出来るの?


RenamingDelegatingContextを使うことでテスト用のプレフィックスのついたSQLiteファイルが用意され、
毎回クリーンなデータベース環境が手に入る。

使ってみる


一応テスト対象のクラス等も紹介しておく。

Employee.java(DTO的な何か。必要に応じてシリアライズ可能に。)
※ ゲッター/セッター、import省略
  1. public class Employee {  
  2.   
  3.     public static class EmployeeColumns {  
  4.   
  5.         public static final String ID = "_id"// 社員ID  
  6.   
  7.         public static final String NAME = "name"// 社員名  
  8.   
  9.         public static final String DEPARTMENT = "department"// 部署  
  10.     }  
  11.   
  12.     public static final String TABLE_NAME = "employee";  
  13.   
  14.     private int id;  
  15.     private String name;  
  16.     private String department;  
  17. }  

テーブル構成も簡単にしてみた。
DatabaseHelper.java(ヘルパー)
※ import省略
  1. public class DatabaseHelper extends SQLiteOpenHelper {  
  2.   
  3.     private static final String DATABASE_NAME = "company.db";  
  4.     private static final int DATABASE_VERSION = 1;  
  5.   
  6.     public DatabaseHelper(Context context) {  
  7.         super(context, DATABASE_NAME, null, DATABASE_VERSION);  
  8.     }  
  9.   
  10.     @Override  
  11.     public void onCreate(SQLiteDatabase db) {  
  12.         db.execSQL("CREATE TABLE " + Employee.TABLE_NAME + "(" +  
  13.                 Employee.EmployeeColumns.ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +  
  14.                 Employee.EmployeeColumns.NAME + " TEXT NOT NULL," +  
  15.                 Employee.EmployeeColumns.DEPARTMENT + " TEXT NOT NULL" +  
  16.                 ")");  
  17.     }  
  18.   
  19.     @Override  
  20.     public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {  
  21.     }  
  22. }  

EmployeeDao.java(データベースアクセスクラス。今回のテスト対象クラス。)
※ import省略
  1. public class EmployeeDao {  
  2.   
  3.     private DatabaseHelper mHelper;  
  4.   
  5.     public EmployeeDao(Context context) {  
  6.         mHelper = new DatabaseHelper(context);  
  7.     }  
  8.   
  9.     /** 
  10.      * 部署を条件に社員一覧を取得する。 
  11.      * 順不同 
  12.      * @param department 
  13.      * @return 社員一覧 0件の場合は空のリスト 
  14.      */  
  15.     public List<Employee> getEmployeeByDepartment(String department) {  
  16.         List<Employee> employees = new ArrayList<Employee>();  
  17.         SQLiteDatabase db = mHelper.getReadableDatabase();  
  18.   
  19.         try {  
  20.             Cursor c = db.query(Employee.TABLE_NAME,  
  21.                     new String[]{ EmployeeColumns.ID,  
  22.                             EmployeeColumns.NAME,  
  23.                             EmployeeColumns.DEPARTMENT },  
  24.                     EmployeeColumns.DEPARTMENT + " = ?",  
  25.                     new String[]{ department }, nullnullnull);  
  26.             c.moveToFirst();  
  27.             while (!c.isAfterLast()) {  
  28.                 Employee employee = new Employee();  
  29.                 employee.setId(c.getInt(c.getColumnIndex(EmployeeColumns.ID)));  
  30.                 employee.setName(c.getString(c.getColumnIndex(EmployeeColumns.NAME)));  
  31.                 employee.setDepartment(c.getString(c.getColumnIndex(EmployeeColumns.DEPARTMENT)));  
  32.                 employees.add(employee);  
  33.                 c.moveToNext();  
  34.             }  
  35.             c.close();  
  36.         } finally {  
  37.             db.close();  
  38.         }  
  39.         return employees;  
  40.     }  
  41. }  

そして今回のポイントとなるテストクラス。
EmployeeDaoTest.java
※ import省略
  1. public class EmployeeDaoTest extends AndroidTestCase {  
  2.   
  3.     private static final String TEST_PREFIX = "test_";  
  4.     private DatabaseHelper mHelper;  
  5.     private RenamingDelegatingContext mContext;  
  6.   
  7.     @Override  
  8.     protected void setUp() throws Exception {  
  9.         super.setUp();  
  10.         mContext = new RenamingDelegatingContext(getContext(), TEST_PREFIX);  
  11.         // テストメソッド毎に空のテスト用DBを用意  
  12.         mHelper = new DatabaseHelper(mContext);  
  13.     }  
  14.   
  15.     @Override  
  16.     protected void tearDown() throws Exception {  
  17.         super.tearDown();  
  18.         mHelper.close();  
  19.     }  
  20.   
  21.     /** 
  22.      * getEmployeeByDepartmentのテスト 
  23.      */  
  24.     public void testGetEmployeeByDepartment() {  
  25.         // RenamingDelegatingContextを渡してテスト用DBを使用する  
  26.         EmployeeDao dao = new EmployeeDao(mContext);  
  27.         List<Employee> employees = dao.getEmployeeByDepartment("人事部");  
  28.         assertNotNull(employees);  
  29.         // これ以降のテストは省略  
  30.     }  
  31. }  

ソースを見ればわかると思うけど手順は簡単で
  • Context と文字列を渡してRenamingDelegatingContextを生成
  • 生成したRenamingDelegatingContextを渡してヘルパーを生成
以上。

これを実行すると。。。
テスト用のDBがちゃんとあるね。