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の処理を
スキップしたいことはあると思う。そんな時は以下のようにインテントからデータを
抜き出して判定してやればいい。
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省略
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がちゃんとあるね。