2010年11月25日木曜日

Android Spinner(スピナー)を使用する その5(SimpleCursorAdapter)

今回はSimpleCursorAdapterを使用してみました。
SimpleCursorAdapterはxmlファイルで定義された複数のビューに対して、CursorオブジェクトのColumnをマッピングする簡単なアダプターです。



1,データベースにデータを作成します。
Android SQLiteデータベースを作成するで作成したDatabaseHelper.javaを使用し、以下のようなテーブルを作成しました。

CREATE TABLE Categories
(
CategoryID integer NOT NULL,
CategoryName text NOT NULL,
Description text,
primary key(CategoryID)
)

あとはInsert文で適当にデータを追加します。
Android データベースにデータを書き込む(Insert、Update、Delete)


2,アクティビティのレイアウトにスピナーを追加します。
res/layout/main.xml
<?xml version="1.0" encoding="utf-8"?>

<LinearLayout 
 android:id="@+id/LinearLayout01" 
 android:layout_width="fill_parent" 
 android:layout_height="fill_parent" 
 xmlns:android="http://schemas.android.com/apk/res/android" 
 android:orientation="vertical">
    
<Spinner 
 android:id="@+id/Spinner01" 
 android:layout_width="wrap_content" 
 android:layout_height="wrap_content"> 
</Spinner>

</LinearLayout>

3,スピナーのレイアウトファイルを作成します。
res/layout/spinner.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 
 xmlns:android="http://schemas.android.com/apk/res/android" 
 android:id="@+id/LinearLayout01" 
 android:layout_width="fill_parent" 
 android:layout_height="fill_parent" 
 android:orientation="vertical">
 
<TextView 
 android:id="@+id/text" 
 style="?android:attr/spinnerItemStyle"
 android:singleLine="true"    
    android:layout_width="fill_parent"    
    android:layout_height="wrap_content"    
    android:textSize="14sp"  
    android:ellipsize="marquee" > 
</TextView>

</LinearLayout>
注意点は
・android:idは3で作成するレイアウトのidと同じにすること。
・規定のスタイルを使用する場合は style="?android:attr/spinnerItemStyle"とすること。

4,スピナーのドロップダウンリストのレイアウトファイルを作成します。
res/layout/spinner_dropdown.xml
<?xml version="1.0" encoding="utf-8"?>

<LinearLayout
 xmlns:android="http://schemas.android.com/apk/res/android" 
 android:id="@+id/LinearLayout01" 
 android:layout_width="fill_parent" 
 android:layout_height="fill_parent" 
 android:orientation="vertical"> 

 <TextView 
  android:id="@+id/text" 
  style="?android:attr/spinnerDropDownItemStyle"
  android:layout_width="wrap_content" 
  android:layout_height="25sp"
  android:textSize="14sp">   
 </TextView>

 <TextView 
  android:id="@+id/description" 
  style="?android:attr/spinnerDropDownItemStyle"
  android:layout_width="wrap_content" 
  android:layout_height="20sp"
  android:textSize="10sp">
 </TextView>

</LinearLayout>
注意点は
・android:idは2で作成するレイアウトのidと同じにすること。
・規定のスタイルを使用する場合はstyle="?android:attr/spinnerDropDownItemStyle"とすること。

5,アクティビティクラス
SimpleCursorAdapterクラスを使用してスピナーの設定を行います。

SimpleCursorAdapterクラスのコンストラクタ
SimpleCursorAdapter(Context context, int layout, Cursor c, String[] from, int[] to) 
第1引数contextにはContextオブジェクトを指定します。

第2引数layoutはスピナーに使用するレイアウトファイルのIDを指定します。
今回は3で作成したレイアウトファイルを指定します。

第3引数cにはドロップダウンリストに表示するアイテムを指定します。
ドロップダウンリストに表示するアイテムは、データベースから取得したデータのCursorオブジェクトです。
Cursorオブジェクトに「_id」フィールドが含まれていないとエラーになります。

第4引数fromは第3引数で指定したCursorオブジェクトから、表示したいカラム名をString配列で指定します。

第5引数toは第4引数fromで指定したカラムの値をを表示するレイアウトファイル内のViewのIdをint配列で指定します。

またSimpleCursorAdapter#setDropDownViewResource()メソッドでドロップダウンリスト部分に使用するレイアウトファイルのIDを指定します。
既定のレイアウトを使用する場合は、アンドロイドで定義されているandroid.R.layout.simple_spinner_dropdown_itemを指定します。
今回は4で作成したレイアウトファイルを指定します。

あとは作成したAdapterオブジェクトを、Spinner#.setAdapter()メソッドでスピナーに設定します。


データベースのデータを取得するにはAndroid データベースのデータを読み込む(Select)を参考にしてください。

またSimpleCursorAdapterのコンストラクタ引数に指定するCursorオブジェクトには「_id」フィールドが含まれていないとエラーになります。データベースのテーブルに「_id」フィールドを用意するか、SELECT文で「_id」フィールドを追加してください。

今回はSELECT文で「_id」フィールドを追加しました。
package my.study.android;

import android.app.Activity;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.SimpleCursorAdapter;
import android.widget.Spinner;
import android.widget.Toast;
import android.widget.AdapterView.OnItemSelectedListener;

public class MainActivty extends Activity {
 
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        //データベースよりデータを取得します
        DatabaseHelper dbHelper = new DatabaseHelper(this);      
        SQLiteDatabase db = dbHelper.getReadableDatabase();  
        String sql = "SELECT '0' AS _id, CategoryID, CategoryName, Description " +
                     "FROM Categories";
        Cursor cursor = db.rawQuery(sql,null);
        //Adapterを作成します。
        String[] from = {"CategoryName","Description"};
        int[] to = {R.id.text,R.id.description};
        SimpleCursorAdapter adapter = 
            new SimpleCursorAdapter(this,R.layout.spinner,cursor,from,to);
        //ドロップダウンリストのレイアウトを設定します。   
        adapter.setDropDownViewResource(R.layout.spinner_dropdown);   
        //スピナーにadapterを設定します。   
        Spinner spinner  = (Spinner)this.findViewById(R.id.Spinner01);   
        spinner.setAdapter(adapter);
        //スピナーのアイテムが選択された時に呼び出されるコールバックリスナーを登録します   
        spinner.setOnItemSelectedListener(new OnItemSelectedListener() {
            public void onItemSelected(AdapterView parent, View view, int position, long id) {
                Spinner spinner = (Spinner) parent;   
                Cursor cursor = (Cursor)spinner.getSelectedItem();
                int categoryId = cursor.getInt(cursor.getColumnIndex("CategoryID"));
                Toast.makeText(MainActivty.this,
                    Integer.valueOf(categoryId).toString(),
                    Toast.LENGTH_LONG).show(); 
            }
            public void onNothingSelected(AdapterView parent) {    
            }   
        });   
    }
}


上記の方法で表示したスピナーのドロップダウンリストには、選択しているアイテムをチェック状態で表示するラジオボタンが表示されません。これは4で作成したドロップダウンリストのレイアウトファイルの最上位オブジェクトであるLinearLayoutがCheckableインターフェースを実装していないためだそうです。
ドロップダウンリストにラジオボタンを表示するには前回の記事Android Spinner(スピナー)を使用する その4(SimpleAdapter)を参考にしてください。


【関連項目】
Android Spinner(スピナー)を使用する その1
Android Spinner(スピナー)を使用する その2(ArrayAdapter)
Android Spinner(スピナー)を使用する その3(UIカスタマイズ)
Android Spinner(スピナー)を使用する その4(SimpleAdapter)
Android Spinner(スピナー)を使用する その5(SimpleCursorAdapter)

2010年11月19日金曜日

Android Spinner(スピナー)を使用する その4(SimpleAdapter)

今回はSimpleAdapterを使用してみました。
SimpleAdapterはxmlファイルで定義された複数のビューに対して、データをマッピングする簡単なアダプターです。


1,アクティビティのレイアウトにスピナーを追加します。
res/layout/main.xml
<?xml version="1.0" encoding="utf-8"?>

<LinearLayout 
 android:id="@+id/LinearLayout01" 
 android:layout_width="fill_parent" 
 android:layout_height="fill_parent" 
 xmlns:android="http://schemas.android.com/apk/res/android" 
 android:orientation="vertical">
    
<Spinner 
 android:id="@+id/Spinner01" 
 android:layout_width="wrap_content" 
 android:layout_height="wrap_content" 
</Spinner>

</LinearLayout>

2,スピナーのレイアウトファイルを作成します。
res/layout/spinner.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 
 xmlns:android="http://schemas.android.com/apk/res/android" 
 android:id="@+id/LinearLayout01" 
 android:layout_width="fill_parent" 
 android:layout_height="fill_parent" 
 android:orientation="vertical">
 
<TextView 
 android:id="@+id/text" 
 style="?android:attr/spinnerItemStyle"
 android:singleLine="true"    
    android:layout_width="fill_parent"    
    android:layout_height="wrap_content"    
    android:textSize="14sp"  
    android:ellipsize="marquee" > 
</TextView>
</LinearLayout>
注意点は
・android:idは3で作成するレイアウトのidと同じにすること。
・規定のスタイルを使用する場合は style="?android:attr/spinnerItemStyle"とすること。


3,スピナーのドロップダウンリストのレイアウトファイルを作成します。
res/layout/spinner_dropdown.xml
<?xml version="1.0" encoding="utf-8"?>

<LinearLayout 
 xmlns:android="http://schemas.android.com/apk/res/android" 
 android:id="@+id/LinearLayout01" 
 android:layout_width="fill_parent" 
 android:layout_height="fill_parent" 
 android:orientation="vertical"> 
<TextView 
 xmlns:android="http://schemas.android.com/apk/res/android" 
 android:id="@+id/text" 
 style="?android:attr/spinnerDropDownItemStyle"
 android:singleLine="true" 
 android:layout_width="fill_parent" 
 android:textSize="14sp"
 android:ellipsize="marquee" 
       android:layout_height="25sp">
</TextView> 
<TextView 
 android:id="@+id/description" 
 style="?android:attr/spinnerDropDownItemStyle"
 android:singleLine="true" 
 android:layout_width="fill_parent"
 android:layout_height="fill_parent"
 android:textSize="10sp"
 android:ellipsize="marquee">
</TextView>

</LinearLayout>
注意点は
・android:idは2で作成するレイアウトのidと同じにすること。
・規定のスタイルを使用する場合はstyle="?android:attr/spinnerDropDownItemStyle"とすること。

4,アクティビティクラス
SimpleAdapterクラスを使用してスピナーの設定を行います。

SimpleAdapterクラスのコンストラクタ
SimpleAdapter(Context context, List<? extends Map<String, ?>> data, int resource, String[] from, int[] to) 
第1引数contextにはContextオブジェクトを指定します。

第2引数dataにはドロップダウンリストに表示するアイテムを指定します。
ドロップダウンリストに表示するアイテムは、Mapインターフェースを格納したListインターフェースになっています。

第3引数resourceはスピナーに使用するレイアウトファイルのIDを指定します。
今回は2で作成したレイアウトファイルを指定します。

第4引数fromは第2引数dataで指定したListの要素であるMapから、表示したい要素のキーをString配列で指定します。

第5引数toは第4引数fromで指定したキーの値をを表示するレイアウトファイル内のViewのIdをint配列で指定します。


またSimpleAdapter#setDropDownViewResource()メソッドでドロップダウンリスト部分に使用するレイアウトファイルのIDを指定します。
既定のレイアウトを使用する場合は、アンドロイドで定義されているandroid.R.layout.simple_spinner_dropdown_itemを指定します。
今回は3で作成したレイアウトファイルを指定します。

あとは作成したAdapterオブジェクトを、Spinner#.setAdapter()メソッドでスピナーに設定します。

MainActivty.java
package my.study.android2;

import java.util.ArrayList;
import java.util.HashMap;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.SimpleAdapter;
import android.widget.Spinner;
import android.widget.Toast;
import android.widget.AdapterView.OnItemSelectedListener;

public class MainActivity extends Activity {
 
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
  
        //ドロップダウンデータの作成
        ArrayList<HashMap<String, Object>> items = new ArrayList<HashMap<String, Object>>();
        HashMap<String, Object> map = new HashMap<String, Object>();
        //item1
        map = new HashMap<String, Object>();
        map.put("value",10);
        map.put("text","item1");
        map.put("description","description1");
        items.add(map); 
        //item2
        map = new HashMap<String, Object>();
        map.put("value",20);
        map.put("text","item2");
        map.put("description","description2");
        items.add(map);
        //item3
        map = new HashMap<String, Object>();
        map.put("value",30);
        map.put("text","item3");
        map.put("description","description3");
        items.add(map);
  
        //SimpleAdapterオブジェクトの作成
        SimpleAdapter adapter = new SimpleAdapter(this,items,R.layout.spinner,
                new String[]{"text","description"},
                new int[]{R.id.text,R.id.description});
 
        //ドロップダウンリストのレイアウトを設定します。
        adapter.setDropDownViewResource(R.layout.spinner_dropdown);
        //スピナーにadapterを設定します。
        Spinner spinner  = (Spinner)this.findViewById(R.id.Spinner01);
        spinner.setAdapter(adapter);
        //スピナーのアイテムが選択された時に呼び出されるコールバックリスナーを登録します
        spinner.setOnItemSelectedListener(new OnItemSelectedListener() {
            public void onItemSelected(AdapterView<?> parent, View arg1, int position, long id) {
                Spinner spinner = (Spinner) parent;
                HashMap<String, Object> item = (HashMap<String, Object>) spinner.getSelectedItem();  
                Toast.makeText(MainActivity.this, item.get("value").toString(), Toast.LENGTH_LONG).show();
            }
            public void onNothingSelected(AdapterView<?> parent) {   
            }
        });
    }
}

ドロップダウンリストにラジオボタンを表示する

上記の方法で表示したスピナーのドロップダウンリストには、選択しているアイテムをチェック状態で表示するラジオボタンが表示されません。これは3で作成したドロップダウンリストのレイアウトファイルの最上位オブジェクトであるLinearLayoutがCheckableインターフェースを実装していないためだそうです。
こちらで詳しく解説されています。
だらだらとだらだら AndroidのAdapterViewで、カスタムビューを使っていてもCheckedTextViewなどのチェックをしたい(Spinnerのdropdown等)

次はドロップダウンリストにラジオボタンを表示したいと思います。

1,まずはCheckableインターフェースを実装したLinearLayoutを作成します。
src/my.study.android.widgets/CheckableLinearLayout.jar
package my.study.android.widgets;

import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.Checkable;
import android.widget.CheckedTextView;
import android.widget.LinearLayout;

public class CheckableLinearLayout extends LinearLayout implements Checkable {
 
    private CheckedTextView mCheckbox;
 
    public CheckableLinearLayout(Context context) {
        super(context);
    }
    public CheckableLinearLayout(Context context, AttributeSet attrs) {        
        super(context, attrs); 
    }
    @Override    
    protected void onFinishInflate() {
        super.onFinishInflate();
        // CheckedTextViewを探します。
        int childCount = getChildCount();
        for (int i = 0; i < childCount; ++i) {
            View v = getChildAt(i);
            if (v instanceof CheckedTextView) {
                mCheckbox = (CheckedTextView)v;   
            }  
        }         
    }
    public boolean isChecked() {
        return mCheckbox != null ? mCheckbox.isChecked() : false;   
    }
    public void setChecked(boolean checked) {
        if (mCheckbox != null) {
            mCheckbox.setChecked(checked);     
        }      
    }

    public void toggle() {
        if (mCheckbox != null) {
            mCheckbox.toggle();     
        }      
    }   
}

2,スピナーのドロップダウンリストのレイアウトファイルを作成します。
res/layout/spinner_dropdown.xml
<?xml version="1.0" encoding="utf-8"?>
<my.study.android.widgets.CheckableLinearLayout 
 xmlns:android="http://schemas.android.com/apk/res/android" 
 android:id="@+id/LinearLayout01" 
 android:layout_width="fill_parent" 
 android:layout_height="fill_parent" 
 android:orientation="horizontal"> 
 
<LinearLayout 
 android:id="@+id/LinearLayout02" 
 android:layout_height="wrap_content" 
 android:orientation="vertical" 
 android:layout_width="wrap_content"
 android:layout_weight="1">

 <TextView 
  android:text="@+id/TextView02" 
  android:id="@+id/text" 
  style="?android:attr/spinnerDropDownItemStyle"
  android:layout_width="wrap_content" 
  android:layout_height="25sp"
  android:textSize="14sp">   
 </TextView>

 <TextView 
  android:text="@+id/TextView01" 
  android:id="@+id/description" 
  style="?android:attr/spinnerDropDownItemStyle"
  android:layout_width="wrap_content" 
  android:layout_height="20sp"
  android:textSize="10sp">
 </TextView>
</LinearLayout>

<CheckedTextView 
 android:id="@+id/CheckedTextView01" 
 style="?android:attr/spinnerDropDownItemStyle"
 android:layout_width="wrap_content" 
 android:layout_height="wrap_content">
</CheckedTextView>

</my.study.android.widgets.CheckableLinearLayout>

MainActivity.java、Spinner.xmlの変更はありません。


【関連項目】
Android Spinner(スピナー)を使用する その1
Android Spinner(スピナー)を使用する その2(ArrayAdapter)
Android Spinner(スピナー)を使用する その3(UIカスタマイズ)
Android Spinner(スピナー)を使用する その4(SimpleAdapter)
Android Spinner(スピナー)を使用する その5(SimpleCursorAdapter)

2010年11月17日水曜日

Android Spinner(スピナー)を使用する その3(UIカスタマイズ)

今回はスピナーのレイアウトを変更したいと思います。

複数行を表示できるようにする


1,アクティビティのレイアウトにスピナーを追加します。
res/layout/main.xml
<?xml version="1.0" encoding="utf-8"?>

<LinearLayout 
 android:id="@+id/LinearLayout01" 
 android:layout_width="fill_parent" 
 android:layout_height="fill_parent" 
 xmlns:android="http://schemas.android.com/apk/res/android" 
 android:orientation="vertical">
    
<Spinner 
 android:id="@+id/Spinner01" 
 android:layout_width="wrap_content" 
 android:layout_height="wrap_content" 
</Spinner>

</LinearLayout>

2,スピナーのレイアウトファイルを作成します。
res/layout/spinner.xml
<?xml version="1.0" encoding="utf-8"?>
<TextView  
    xmlns:android="http://schemas.android.com/apk/res/android"  
    android:id="@android:id/text2" 
    style="?android:attr/spinnerItemStyle" 
    android:singleLine="false" 
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    android:textSize="12sp"
    android:ellipsize="marquee" >
</TextView>
改行を表示できるようにするため「android:singleLine="false" 」を指定します。
その他いくつか注意点があります。
・android:idは3で作成するレイアウトのidと同じにすること。
・android:idは"@android:id/text1"または"@android:id/text2"にすること。
(android.R.idで定義されているのidがtext1またはtext2であるため)
・規定のスタイルを使用する場合は style="?android:attr/spinnerItemStyle"とすること。


3,スピナーのドロップダウンリストのレイアウトファイルを作成します。
res/layout/spinner_dropdown.xml
<?xml version="1.0" encoding="utf-8"?>
<CheckedTextView  
    xmlns:android="http://schemas.android.com/apk/res/android"  
    android:id="@android:id/text1" 
    style="?android:attr/spinnerDropDownItemStyle"
    android:layout_height="?android:attr/listPreferredItemHeight" 
    android:singleLine="false" 
    android:layout_width="fill_parent"
 android:textSize="13sp"
    android:ellipsize="marquee" >
</CheckedTextView>
改行を表示できるようにするため「android:singleLine="false" 」を指定します。
注意点は
・android:idは2で作成するレイアウトのidと同じにすること。
・android:idは"@android:id/text1"または"@android:id/text2"にすること。
(android.R.idで定義されているのidがtext1またはtext2であるため)
・規定のスタイルを使用する場合はstyle="?android:attr/spinnerDropDownItemStyle"とすること。

4,アクティビティクラス
ArrayAdapterクラスを使用してスピナーの設定を行います。

ArrayAdapterクラスのコンストラクタは以下のようなものがあります。
ArrayAdapter(Context context, int textViewResourceId) 
ArrayAdapter(Context context, int textViewResourceId, T[] objects) 
ArrayAdapter(Context context, int textViewResourceId, List objects)
第1引数contextにはContextオブジェクトを指定します。

第2引数textViewResourceIdにはスピナーに使用するレイアウトファイルのIDを指定します。
既定のレイアウトを使用する場合は、アンドロイドで定義されているandroid.R.layout.simple_spinner_itemを指定します。
今回は2で作成したレイアウトファイルを指定します。

第3引数objectはドロップダウンリストに表示するアイテムを指定します。
ドロップダウンリストに表示するアイテムはObject型の配列かList インターフェイス実装クラスになっています。
それぞれの要素がCharSequence インターフェイスを実装したクラスである場合は、指定した値をそのまま表示しますが
そうでなければ要素のtoString()の結果を出力します。


またArrayAdapter#setDropDownViewResource()メソッドでドロップダウンリスト部分に使用するレイアウトファイルのIDを指定します。
既定のレイアウトを使用する場合は、アンドロイドで定義されているandroid.R.layout.simple_spinner_dropdown_itemを指定します。
今回は3で作成したレイアウトファイルを指定します。

あとは作成したAdapterオブジェクトを、Spinner#.setAdapter()メソッドでスピナーに設定します。

MainActivty.java
package my.study.android;

import android.app.Activity;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.Spinner;

public class MainActivty extends Activity {
 
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
   
        //ArrayAdapterを作成
        ArrayAdapter adapter = 
    new ArrayAdapter(this,R.layout.spinner);
        //ドロップダウンリストのレイアウトを設定      
        adapter.setDropDownViewResource(R.layout.spinner_dropdown);
        //ドロップダウンアイテムの追加
        adapter.add("item1\nDescription1");
        adapter.add("item2\nDescription2");
        adapter.add("item3\nDescription3");
        //スピナーにadapterを設定
        Spinner spinner = (Spinner)this.findViewById(R.id.Spinner01);
        spinner.setAdapter(adapter);
  
    }
}

ドロップダウンのアイコンを変更する


1,画像ファイルを配置します。
解像度に合わせてres/drawable-hdpi, res/drawable-mdpi, res/drawable-ldpiに、選択している時の画像と選択していないときの画像を配置します。
今回は選択された時の画像としてstar_big_on.png、選択していない時の画像としてstart_big_off.pngを追加しました。

star_big_on.png

star_big_off.png

2,画像のSelectorを作成します。
res/drawable-hdpi, res/drawable-mdpi, res/drawable-ldpiのそれぞれに、状態に応じて画像を選択するselectorファイルを作成します。
res/drawable-xdpi/spinner_selector.xml
<?xml version="1.0" encoding="utf-8"?>
<selector
  xmlns:android="http://schemas.android.com/apk/res/android">
 <item
  android:state_checked="true"
  android:drawable="@drawable/star_big_on" />
 <item 
  android:state_checked="false"       
  android:drawable="@drawable/star_big_off" />
</selector>

3,スタイルファイルを作成します。
先ほど作成したselectorファイルを指定するスタイルファイルを作成します。
res/values/styles.xml
<?xml version="1.0" encoding="UTF-8"?>
<resources >
    <style name="SpinnerDropDownIcon" parent="android:Widget.DropDownItem.Spinner">
        <item name="android:checkMark">@drawable/spinner_selector</item>
    </style>
</resources>

4,スピナーのドロップダウンリストのレイアウトファイルを作成します。
res/layout/spinner_dropdown.xml
<?xml version="1.0" encoding="utf-8"?>
<CheckedTextView  
    xmlns:android="http://schemas.android.com/apk/res/android"  
    android:id="@android:id/text1" 
    style="@style/SpinnerDropDownIcon"
    android:layout_height="?android:attr/listPreferredItemHeight" 
    android:singleLine="false" 
    android:layout_width="fill_parent"
 android:textSize="13sp"
    android:ellipsize="marquee" >
</CheckedTextView>
styleには3で作成したスタイルファイルを指定します。

5.スピナーのレイアウトファイルを作成します。
res/layout/spinner.xml
<?xml version="1.0" encoding="utf-8"?>
<TextView  
    xmlns:android="http://schemas.android.com/apk/res/android"  
    android:id="@android:id/text2" 
    style="?android:attr/spinnerItemStyle" 
    android:singleLine="false" 
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    android:textSize="12sp"
    android:ellipsize="marquee" >
</TextView>

6.アクティビティクラス
MainActivty.java
package my.study.android;

import android.app.Activity;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.Spinner;

public class MainActivty extends Activity {
 
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
   
        //ArrayAdapterを作成
        ArrayAdapter adapter = 
    new ArrayAdapter(this,R.layout.spinner);
        //ドロップダウンリストのレイアウトを設定      
        adapter.setDropDownViewResource(R.layout.spinner_dropdown);
        //ドロップダウンアイテムの追加
        adapter.add("item1\nDescription1");
        adapter.add("item2\nDescription2");
        adapter.add("item3\nDescription3");
        //スピナーにadapterを設定
        Spinner spinner = (Spinner)this.findViewById(R.id.Spinner01);
        spinner.setAdapter(adapter);
  
    }
}

【関連項目】
Android Spinner(スピナー)を使用する その1
Android Spinner(スピナー)を使用する その2(ArrayAdapter)
Android Spinner(スピナー)を使用する その3(UIカスタマイズ)
Android Spinner(スピナー)を使用する その4(SimpleAdapter)
Android Spinner(スピナー)を使用する その5(SimpleCursorAdapter)

2010年11月16日火曜日

Android Spinner(スピナー)を使用する その2(ArrayAdapter)

前回Android Spinner(スピナー)を使用する その1
はスピナーのドロップダウンリストに表示する各アイテムをリソースファイルで定義しましたが、今回はArrayAdapterクラスを利用してコードから指定したいと思います。


1,アクティビティのレイアウトにスピナーを追加します。
res/layout/main.xml
<?xml version="1.0" encoding="utf-8"?>

<LinearLayout 
 android:id="@+id/LinearLayout01" 
 android:layout_width="fill_parent" 
 android:layout_height="fill_parent" 
 xmlns:android="http://schemas.android.com/apk/res/android" 
 android:orientation="vertical">
    
<Spinner 
 android:id="@+id/Spinner01" 
 android:layout_width="wrap_content" 
 android:layout_height="wrap_content" 
</Spinner>

</LinearLayout>

2,スピナーのドロップダウンリストに表示するプロンプトを定義します。
res/values/strings.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="SpinnerPrompt">選択してください。</string>
        :
</resources>

3,アクティビティクラス
ArrayAdapterクラスを使用してスピナーの設定を行います。

ArrayAdapterクラスのコンストラクタは以下のようなものがあります。
ArrayAdapter(Context context, int textViewResourceId) 
ArrayAdapter(Context context, int textViewResourceId, T[] objects) 
ArrayAdapter(Context context, int textViewResourceId, List objects)
第1引数contextにはContextオブジェクトを指定します。

第2引数textViewResourceIdにはスピナーに使用するレイアウトファイルのIDを指定します。
今回はtextViewResourceIdには、アンドロイドで定義されているandroid.R.layout.simple_spinner_itemを指定します。

第3引数objectはドロップダウンリストに表示するアイテムを指定します。
ドロップダウンリストに表示するアイテムはObject型の配列かList インターフェイス実装クラスになっています。
それぞれの要素がCharSequence インターフェイスを実装したクラスである場合は、指定した値をそのまま表示しますが
そうでなければ要素のtoString()の結果を出力します。


またArrayAdapter#setDropDownViewResource()メソッドでドロップダウンリスト部分に使用するレイアウトファイルのIDを指定します。
今回は、アンドロイドで定義されているandroid.R.layout.simple_spinner_dropdown_itemを指定します。

あとは作成したAdapterオブジェクトを、Spinner#.setAdapter()メソッドでスピナーに設定します。

MainActivty.java
package my.study.android;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
import android.widget.Toast;
import android.widget.AdapterView.OnItemSelectedListener;


public class MainActivty extends Activity {
 
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
  
        // ArrayAdapter を作成  
        String items[] = {"items1","items2","items3"};
        ArrayAdapter<String> adapter
                = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, items);
        //または
        //ArrayAdapter<String> adapter
        //        = new ArrayAdapter<string>(this, android.R.layout.simple_spinner_item)
        //adapter.add("item1");
        //adapter.add("item2");
        //adapter.add("item3");
        
        // ドロップダウンリストのレイアウトを設定   
        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);  
         
        //spinnerにAdapterを設定
        Spinner spinner  = (Spinner)this.findViewById(R.id.Spinner01);
        spinner.setAdapter(adapter);
        // Spinner に表示させるプロンプトを設定   
        spinner.setPromptId(R.string.SpinnerPrompt );
  
        // スピナーのアイテムが選択された時に呼び出されるコールバックリスナーを登録します
        spinner.setOnItemSelectedListener(new OnItemSelectedListener(){
            //Spinnerのドロップダウンアイテムが選択された時
            public void onItemSelected(AdapterView parent, View viw, int arg2, long arg3) {
                Spinner spinner = (Spinner)parent;
                String item = (String)spinner.getSelectedItem();
                Toast.makeText(MainActivty.this, item, Toast.LENGTH_LONG).show();
            }
            //Spinnerのドロップダウンアイテムが選択されなかった時
            public void onNothingSelected(AdapterView parent) {
            }});
    }
}

【関連項目】
Android Spinner(スピナー)を使用する その1
Android Spinner(スピナー)を使用する その2(ArrayAdapter)
Android Spinner(スピナー)を使用する その3(UIカスタマイズ)
Android Spinner(スピナー)を使用する その4(SimpleAdapter)
Android Spinner(スピナー)を使用する その5(SimpleCursorAdapter)

Android Spinner(スピナー)を使用する その1

Spinnerはクリックするとリストが表示され、そこから項目を選択することができる、Windowsのコンボボックスのようなものです。

まずは基本的な使い方から。

1,スピナーのドロップダウンアイテムに設定する文字列の配列を定義します。
res/values/arrays.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>

<string-array name="SpinnerItems">
 <item>Item1</item>
 <item>Item2</item>
 <item>Item3</item>
</string-array>

</resources>

2,アクティビティのレイアウトにスピナーを追加します。
res/layout/main.xml
<?xml version="1.0" encoding="utf-8"?>

<LinearLayout 
 android:id="@+id/LinearLayout01" 
 android:layout_width="fill_parent" 
 android:layout_height="fill_parent" 
 xmlns:android="http://schemas.android.com/apk/res/android" 
 android:orientation="vertical">
    
<Spinner 
 android:id="@+id/Spinner01" 
 android:layout_width="wrap_content" 
 android:layout_height="wrap_content" 
 android:entries="@array/SpinnerItems" 
 android:prompt="@string/SpinnerPrompt">
</Spinner>

</LinearLayout>
entriesプロパティで1で作成した文字列配列を指定します。
SpinnerPromptプロパティでドロップダウンに表示するプロンプトを指定します。
ここではres/values/strings.xmlに指定した値を使用しています。
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="SpinnerPrompt">選択してください。</string>
        :
</resources>

3,アクティビティクラスでスピナーのアイテムが選択された時に呼び出されるコールバックリスナーを登録します。
package my.study.android;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Spinner;
import android.widget.Toast;
import android.widget.AdapterView.OnItemSelectedListener;

public class MainActivty extends Activity {
 
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        // スピナーのアイテムが選択された時に呼び出されるコールバックリスナーを登録します        
        Spinner spinner = (Spinner)this.findViewById(R.id.Spinner01);
        spinner.setOnItemSelectedListener(new OnItemSelectedListener(){
            //Spinnerのドロップダウンアイテムが選択された時
            public void onItemSelected(AdapterView parent, View viw, int arg2, long arg3) {
                Spinner spinner = (Spinner)parent;
                String item = (String)spinner.getSelectedItem();
                Toast.makeText(MainActivty.this, item, Toast.LENGTH_LONG).show();
            }
            //Spinnerのドロップダウンアイテムが選択されなかった時
            public void onNothingSelected(AdapterView parent) {
            }});
    }
}

【関連項目】
Android Spinner(スピナー)を使用する その1
Android Spinner(スピナー)を使用する その2(ArrayAdapter)
Android Spinner(スピナー)を使用する その3(UIカスタマイズ)
Android Spinner(スピナー)を使用する その4(SimpleAdapter)
Android Spinner(スピナー)を使用する その5(SimpleCursorAdapter)

2010年10月22日金曜日

Android SQLite いろいろメモ

SQLiteのデータ型

INTEGER符号付整数
REAL浮動小数点
TEXTテキスト
BLOBバイナリデータ

システム日付を取得する

CURRENT_TIMESTAMP、CURRENT_DATE、CURRENT_TIMEはUTC(協定世界時)になる。
ローカルタイムゾーンの現在時刻はdatetime('now', 'localtime')で取得する。
insert into PFNAVIKNR (
    Cd,
    Name,
    Age,
    UpdDate
    ) values (1, 'yan', 24, datetime('now', 'localtime'))

テーブルを作成する

CREATE TABLE MyTable
(
    Cd          integer   NOT NULL,
    SeqNo       integer   NOT NULL,
    UpdDate     text,
    primary key(Cd,SeqNo)
)

テーブルを削除する

DROP TABLE MyTable

2010年10月19日火曜日

.NET ファイル読み込み時の注意点

以下のコードは
ファイルの存在チェックを行い、
ファイルが存在する場合はファイルの内容を読み込みます。
Dim path As String = "D:\test.csv"  '※D:\test.csvは存在するものとします。
'ファイルが存在するかどうか
Dim isExists As Boolean = System.IO.File.Exists(path)
Console.WriteLine("isExists:" & isExists)
'ファイル読み込み
If isExists = True Then
    Dim line() As String = System.IO.File.ReadAllLines(path)
End If

ファイルパスの「途中」に改行コードを追加すると、System.IO.File.Exists()メソッドはfalseを返します。
なので、ファイル読み込みは行われません。
Dim path As String = "D:\" & Environment.NewLine & "test.csv"


ファイルパスの「末尾」に改行コードを追加すると System.IO.File.Exists()メソッドはtrueを返します。
なので、ファイル読み込みが行われるのですが、System.IO.File.ReadAllLines()メソッドは例外「ArgumentExceptionp:パスに無効な文字が含まれます。」をスローします。
Dim path As String = "D:\test.csv" & Environment.NewLine


結論
ファイル存在チェックとは別にファイルに不正な文字が含まれているかチェックする必要がある。
Dim path As String = "D:\test.csv" & Environment.NewLine
'パスに無効な文字が含まれていないか
Dim isInvalidChars As Boolean = (path.IndexOfAny(System.IO.Path.GetInvalidPathChars) <> -1)
Console.WriteLine("isInvalidChars:" & isInvalidChars)
'ファイルが存在するかどうか
Dim isExists As Boolean = System.IO.File.Exists(path)
Console.WriteLine("isExists:" & isExists)
'ファイル読み込み
If isExists = True AndAlso isInvalidChars = True Then
    Dim line() As String = System.IO.File.ReadAllLines(path)
End If

2010年10月14日木曜日

Android Buttonの表示/非表示

Buttonの表示/非表示を設定するにはview#setVisibility(int visibility)メソッドを使用します。
引数には可視状態を表す定数を指定します。
view.VISIBLE・・・表示
view.INVISIBLE・・・非表示(非表示にしたスペースは詰めない)
view.GONE・・・非表示(非表示にしたスペースを詰める)

下記の画像はButton01を INVISIBLEとGONEで非表示にしたものです。

View.INVISIBLE

View.GONE

Button03をクリックするとButton01の可視状態を切り替えます。
package my.study.android;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class MainActivty extends Activity {
 
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        Button btn3 = (Button)this.findViewById(R.id.Button03);
        btn3.setOnClickListener(new OnClickListener(){
            public void onClick(View v) {
                Button btn1 = (Button)MainActivty.this.findViewById(R.id.Button01);
                if (btn1.getVisibility() != View.VISIBLE) {
                    btn1.setVisibility(View.VISIBLE);
                } else {
                    btn1.setVisibility(View.INVISIBLE);
                }    
            }});
    }
}

レイアウト.xmlで可視状態を設定する場合は「android:visibility」で指定します。
<Button 
 android:id="@+id/Button01" 
 android:layout_width="wrap_content" 
 android:layout_height="wrap_content" 
 android:text="Button01"
 android:visibility="gone">
</Button>

OeacleLite GroupBy句とOrderBy句は一緒に使えない

OracleLite10gで、GroupBy句とOrderBy句を一緒に使用するとソートされません。

以下のようなsqlはOrderBy句が無視されます。
SELECT hoge1, hoge2, hoge3
FROM table
GROUP BY hoge1, hoge2, hoge3
ORDER BY hoge3

この問題を回避するために、GroupBy句を副表にします。
SELECT hoge1, hoge2, hoge3
FROM (SELECT hoge1, hoge2, hoge3 FROM table GROUP BY hoge1, hoge2, hoge3)
ORDER BY hoge3

2010年10月7日木曜日

Android ボタンに画像とテキストを表示する



ImageButtonではなくButtonを使用します。

レイアウト.xml
<?xml version="1.0" encoding="utf-8"?>

<LinearLayout 
 android:id="@+id/LinearLayout01" 
 android:layout_width="fill_parent" 
 android:layout_height="fill_parent" 
 xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical">
    <Button 
  android:id="@+id/Button01" 
  android:layout_width="wrap_content" 
  android:layout_height="wrap_content" 
  android:drawableTop="@drawable/icon" 
  android:text="Button01">
 </Button>
 <Button 
  android:id="@+id/Button02" 
  android:layout_width="wrap_content" 
  android:layout_height="wrap_content" 
  android:drawableLeft="@drawable/icon" 
  android:text="Button02">
 </Button>
 <Button 
  android:id="@+id/Button03" 
  android:layout_width="wrap_content" 
  android:layout_height="wrap_content" 
  android:drawableRight="@drawable/icon" 
  android:text="Button03">
 </Button>
 <Button 
  android:id="@+id/Button04" 
  android:layout_width="wrap_content" 
  android:layout_height="wrap_content" 
  android:drawableBottom="@drawable/icon" 
  android:text="Button04">
 </Button>

</LinearLayout>

Android タブ画面を表示する

Tab画面を表示するには2つの方法があります。
1,TabActivityクラスを継承する方法
2,Activityクラスを継承する方法



TabActivityクラスを継承する方法

まずレイアウトを作成します。

レイアウトにはいくつか注意点があります。
1,TabHostのidは「@android:id/tabhost」にする。
2,TabHostにはTabWidgetを配置し、そのidは「@android:id/tabs」にする
3,TabHostにはFrameLayoutを配置し、そのidは「@android:id/tabcontent」にする
4,FrameLayout(tabcontent)はpaddingTopを65px~70pxに設定する。paddingTopを設定しないとタブとタブ内容が重なってしまいます。
res/layout/main1.xml
<?xml version="1.0" encoding="utf-8"?>
<TabHost 
 android:id="@android:id/tabhost" 
 android:layout_width="fill_parent" 
 android:layout_height="fill_parent" 
 xmlns:android="http://schemas.android.com/apk/res/android">
 
 <TabWidget 
  android:id="@android:id/tabs" 
  android:layout_width="wrap_content" 
  android:layout_height="wrap_content">
 </TabWidget>
 <FrameLayout 
  android:id="@android:id/tabcontent" 
  android:layout_height="fill_parent" 
  android:layout_width="fill_parent" 
  android:paddingTop="65px">
  <LinearLayout 
   android:layout_width="wrap_content" 
   android:layout_height="wrap_content" 
   android:id="@+id/content1">
   <CheckBox 
    android:text="@+id/CheckBox01" 
    android:id="@+id/CheckBox01" 
    android:layout_width="wrap_content" 
    android:layout_height="wrap_content">
   </CheckBox>
  </LinearLayout>
  <LinearLayout 
   android:layout_width="wrap_content" 
   android:layout_height="wrap_content" 
   android:id="@+id/content2">
   <RadioButton 
    android:text="@+id/RadioButton01" 
    android:id="@+id/RadioButton01" 
    android:layout_width="wrap_content" 
    android:layout_height="wrap_content">
   </RadioButton>
  </LinearLayout>
 </FrameLayout>
</TabHost>

MainActivityはTabActivityクラスを継承して作成します。
onCreate()メソッドで、getTabHost()メソッドでTabHostオブジェクトを取得し、Tabを追加します。

MainActivity.java
package my.study.android;

import android.app.TabActivity;
import android.os.Bundle;
import android.widget.TabHost;
import android.widget.TabHost.TabSpec;

public class MainActivty extends TabActivity {
    
    private static String[] sTabId = {"Tab1","Tab2"};

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        // TabHostオブジェクト取得
        TabHost tabs = getTabHost();

        // Tab1 設定
        TabSpec tab1 = tabs.newTabSpec(sTabId[0]);
        tab1.setIndicator("タブページ1");   
        tab1.setContent(R.id.content1); 
        tabs.addTab(tab1);    
        
        // Tab2 設定
        TabSpec tab2 = tabs.newTabSpec(sTabId[1]);
        tab2.setIndicator("タブページ2");   
        tab2.setContent(R.id.content2); 
        tabs.addTab(tab2);    
        
        // 初期表示設定
        tabs.setCurrentTab(0);
    }
}


Activityクラスを継承する作成する

まずレイアウトを作成します。「TabActivityクラスを継承する方法」と同じものを使用します。
レイアウトの注意点ですが、下記のうち1は必須ではありません。
1,TabHostのidは「@android:id/tabhost」にする。
2,TabHostにはTabWidgetを配置し、そのidは「@android:id/tabs」にする
3,TabHostにはFrameLayoutを配置し、そのidは「@android:id/tabcontent」にする
4,FrameLayout(tabcontent)はpaddingTopを65px~70pxに設定する。paddingTopを設定しないとタブとタブ内容が重なってしまいます。

MainActivityはActivityクラスを継承して作成します。
onCreate()メソッドで、findViewById()メソッドでTabHostオブジェクトを取得します。
Tabを追加する前に、tabhost#setup()メソッドでTabHostを初期化し、Tabを追加します。

MainActivity.java
package my.study.android;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TabHost;
import android.widget.TabHost.TabSpec;

public class MainActivty extends Activity {
    
    private static String[] sTabId = {"Tab1","Tab2"};

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        // TabHostオブジェクト取得
        //TabHost tabs = getTabHost();
        TabHost tabs = (TabHost)findViewById(android.R.id.tabhost);    
        tabs.setup();
        // Tab1 設定
        TabSpec tab1 = tabs.newTabSpec(sTabId[0]);
        tab1.setIndicator("タブページ1");   
        tab1.setContent(R.id.content1); 
        tabs.addTab(tab1);    
        // Tab2 設定
        TabSpec tab2 = tabs.newTabSpec(sTabId[1]);
        tab2.setIndicator("タブページ2");   
        tab2.setContent(R.id.content2); 
        tabs.addTab(tab2);    
        // 初期表示設定
        tabs.setCurrentTab(0);
    }
}

2010年10月6日水曜日

2010年10月5日火曜日

Android キャッチされなかった例外を処理する

Androidアプリでキャッチされなかった例外を処理する方法が下記のサイトに詳しく紹介されています。

throw Life - Androidアプリのバグ報告システムを考える
冬通りに消え行く制服ガールは、夢物語にリアルを求めない。 - Android でアプリケーションが強制終了したとき、エラーレポートを送るようにする

上記のリンクを参考に、例外のスタックトレースをメールで送信できるようにしてみます。

ボタンを押すと例外が発生し、アプリは強制終了します。

もう一度アプリを起動すると、バグレポートをメール送信するか確認メッセージを表示します。
Postボタンをタップすると、メールを送信します。



catchしなかった例外を補足するためにThread.UncaughtExceptionHandleを実装したCsUncaughtExceptionHandlerを作成します。
uncaughtException()メソッドで例外のスタックトレースをファイルに書き込みます。
SendBugReport()メソッドでファイルを読み込んでメールを送信します。
package my.study.android;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.PrintWriter;
import java.lang.Thread.UncaughtExceptionHandler;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;

public class CsUncaughtExceptionHandler implements java.lang.Thread.UncaughtExceptionHandler {
    private static Context sContext = null;
    private static final String BUG_FILE = "BugReport";
    private static final UncaughtExceptionHandler sDefaultHandler 
             = Thread.getDefaultUncaughtExceptionHandler();
    
    /**
     * コンストラクタ
     * @param context
     */
    public CsUncaughtExceptionHandler(Context context){
        sContext = context;
    }

    /**
     * キャッチされない例外によって指定されたスレッドが終了したときに呼び出されます
     * 例外スタックトレースの内容をファイルに出力します
     */
    public void uncaughtException(Thread thread, Throwable ex) { 
        PrintWriter pw = null;   
        try {
            pw = new PrintWriter(sContext.openFileOutput(BUG_FILE, Context.MODE_WORLD_READABLE));
            ex.printStackTrace(pw); 
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (pw != null) pw.close();      
        }
        sDefaultHandler.uncaughtException(thread, ex);
    }
    
    /**
     * バグレポートの内容をメールで送信します。
     * @param activity
     */
    public static void SendBugReport(final Activity activity) {
        //バグレポートがなければ以降の処理を行いません。
        final File bugfile = activity.getFileStreamPath(BUG_FILE);
        if (!bugfile.exists()) {
            return;
        }    
        //AlertDialogを表示します。
        AlertDialog.Builder alert = new AlertDialog.Builder(activity);
        alert.setTitle("ERROR");
        alert.setMessage("予期しないエラーが発生しました。開発元にエラーを送信してください。");
        alert.setPositiveButton("Post", new DialogInterface.OnClickListener(){
            @Override
            public void onClick(DialogInterface dialog, int which) {
                SendMail(activity,bugfile);
            }});
        alert.setNegativeButton("Cancel", null);
        alert.show();
    }
    
    /**
     * バグレポートの内容をメールで送信します。
     * @param activity
     * @param bugfile
     */
    private static void SendMail(final Activity activity,File bugfile){
        //バグレポートの内容を読み込みます。
        StringBuilder sb = new StringBuilder();
        try {
            BufferedReader br = new BufferedReader(new FileReader(bugfile));
            String str;      
            while((str = br.readLine()) != null){      
                sb.append(str +"\n");     
            }       
        } catch (Exception e) {
            e.printStackTrace();
        }
        //メールで送信します。
        Intent intent = new Intent();
        intent.setAction(Intent.ACTION_SENDTO);
        intent.setData(Uri.parse("mailto:" + "xxx@xxxx.xx.xx"));
        intent.putExtra(Intent.EXTRA_SUBJECT, "【BugReport】" + R.string.app_name );
        intent.putExtra(Intent.EXTRA_TEXT, sb.toString());
        activity.startActivity(intent);
        //バグレポートを削除します。
        bugfile.delete();
    }
    
}

スレッドでキャッチされなかった例外を捕捉するために、Thread.setDefaultUncaughtExceptionHandler()メソッドにCsUncaughtExceptionHandlerオブジェクトを設定します。
アプリで1度だけハンドラを設定すればいいので、一番最初に表示されるMainActivityのonCreate()メソッドで設定します。
CsUncaughtExceptionHandlerのコンストラクタの引数には、メモリリークが発生しないようgetApplicationContext()メソッドで取得したcontextを渡すようにします。
※アプリケーションで共通に使用するcontextに、現在のActivityを指定するとメモリが解放されない事があります。
こちらで詳しく書かれています。textdrop-Androidでのメモリリーク回避

MainActivityにはボタンを1つ配置し、タップするとArithmeticExceptionを発生します。

onStart()メソッドでは、CsUncaughtExceptionHandler.SendBugReport()メソッドを呼び出して、バグレポートがあればメールを送信するようにします。

package my.study.android;

import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class MainActivity extends Activity {

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //レイアウトを読み込みます
        this.setContentView(R.layout.main);
       
        //アプリケーションで共通に利用するオブジェクトには、メモリリークが発生しないようにthisではなく
        //Context.getApplicationContext()を使用します。
        Context context = this.getApplicationContext(); 
        //キャッチされない例外により、スレッドが突然終了したときや、
        //このスレッドに対してほかにハンドラが定義されていないときに
        //呼び出されるデフォルトのハンドラを設定します。
        Thread.setDefaultUncaughtExceptionHandler(new CsUncaughtExceptionHandler(context));

        //ボタンにOnClickListenerを設定します。
        Button btn = (Button)this.findViewById(R.id.btnThrowEx);
        btn.setOnClickListener(new OnClickListener(){
            @Override
            public void onClick(View arg0) {
                int index = 5 / 0;  //ArithmeticExceptionを発生させる
            }});
    }

    @Override
    protected void onStart() {
        super.onStart();
        CsUncaughtExceptionHandler.SendBugReport(this);
    }
}

Android AlertDialogを表示する

基本的な使い方

タイトルとメッセージを表示するだけの基本的なAlertDialogです。
「戻る」ボタンで元の画面に戻ります。

package my.study.android;

import android.app.Activity;
import android.app.AlertDialog;
import android.os.Bundle;

public class MainActivity extends Activity {
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
              
        AlertDialog.Builder alert = new AlertDialog.Builder(this);
        alert.setTitle("Title");
        alert.setMessage("Message");
        alert.show();
    }  
}

OKダイアログ

次にOKボタンを表示し、ダイアログを閉じられるようにします。
OKボタンをクリックしたときに画面を閉じるだけの場合、setPositiveButton()メソッドの第2引数をnullにします。

package my.study.android;

import android.app.Activity;
import android.app.AlertDialog;
import android.os.Bundle;

public class MainActivity extends Activity {
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
              
        AlertDialog.Builder alert = new AlertDialog.Builder(this);
        alert.setTitle("Title");
        alert.setMessage("Message");
        alert.setPositiveButton("OK", null);
        alert.show();
    }  
}


Yes/Noダイアログ

Yesボタンを押したときの処理はsetPositiveButton()メソッドの第2引数にDialogInterface.OnClickListenerを設定して、処理を記述します。
Noボタンを押したときの処理はsetNegativeButton()メソッドの第2引数にDialogInterface.OnClickListenerを設定して、処理を記述します。

package my.study.android;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.widget.Toast;

public class MainActivity extends Activity {
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
              
        AlertDialog.Builder alert = new AlertDialog.Builder(this);
        alert.setTitle("Title");
        alert.setMessage("Message");
        alert.setPositiveButton("Yes", new DialogInterface.OnClickListener(){
            public void onClick(DialogInterface dialog, int which) {
                //Yesボタンが押された時の処理
                Toast.makeText(MainActivity.this, "Yes Clicked!", Toast.LENGTH_LONG).show();
            }});
        alert.setNegativeButton("No", new DialogInterface.OnClickListener(){
            public void onClick(DialogInterface dialog, int which) {
                //Noボタンが押された時の処理
                Toast.makeText(MainActivity.this, "No Clicked!", Toast.LENGTH_LONG).show();
            }});
        alert.show();
    }  
}

Yes/No/Cancelダイアログ

先ほどのYes/Noダイアログでは、setNegativeButton()メソッドにNoボタンを設定しましたが、
Yesボタン、Noボタン、Cancelボタンの順にボタンを配置したい場合は、
YesボタンはsetPositiveButton()、NoボタンはsetNeutralButton()、CancelボタンはsetNegativeButton()に処理を記述します。
setPositiveButton()メソッドで設定したボタンは一番左、setNeutralButton()メソッドで設定したボタンは中央、setNegativeButton()メソッドで設定したボタンは一番右に配置されます。

package my.study.android;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.widget.Toast;

public class MainActivity extends Activity {
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
              
        AlertDialog.Builder alert = new AlertDialog.Builder(this);
        alert.setTitle("Title");
        alert.setMessage("Message");
        alert.setPositiveButton("Yes", new DialogInterface.OnClickListener(){
            public void onClick(DialogInterface dialog, int which) {
                //Yesボタンが押された時の処理
                Toast.makeText(MainActivity.this, "Yes Clicked!", Toast.LENGTH_LONG).show();
            }});
            alert.setNeutralButton("No", new DialogInterface.OnClickListener(){
            public void onClick(DialogInterface dialog, int which) {
                //Noボタンが押された時の処理
                Toast.makeText(MainActivity.this, "No Clicked!", Toast.LENGTH_LONG).show();
            }});
        alert.setNegativeButton("Cancel", new DialogInterface.OnClickListener(){
            public void onClick(DialogInterface dialog, int which) {
                //Cancelボタンが押された時の処理
    Toast.makeText(MainActivity.this, "Cancel Clicked!", Toast.LENGTH_LONG).show();
   }});
     alert.show();
    }  
}

ダイアログにリストを表示する

setItems()メソッドを使って、ダイアログにリストを表示させる事ができます。

package my.study.android;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.widget.Toast;

public class MainActivity extends Activity {
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
              
        final String[] items = {"item1","item2","item3"};
        AlertDialog.Builder alert = new AlertDialog.Builder(this);
        alert.setTitle("Title");
        alert.setItems(items, new DialogInterface.OnClickListener(){
            @Override
            public void onClick(DialogInterface dialog, int idx) {
                // リストアイテムを選択したときの処理
                Toast.makeText(MainActivity.this, String.format("%s Selected", items[idx]), Toast.LENGTH_LONG).show();
            }});
        alert.show();
    }  
}

ダイアログにラジオボタン付きのリストを表示する

setSingleChoiceItems()メソッドを使って、ダイアログにラジオボタン付きのリストを表示できます。
setSingleChoiceItems()メソッドの第1引数にはリストに表示する項目の配列、第2引数には選択するラジオボタンの初期値を指定します。初期値に-1を選択すると何も選択されません。

package my.study.android;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.widget.Toast;

public class MainActivity extends Activity {
    
    private int mCheckedItem = -1;
 
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
               
        final String[] items = {"item1","item2","item3"};
        AlertDialog.Builder alert = new AlertDialog.Builder(this);
        alert.setTitle("Title");
        alert.setSingleChoiceItems(items,mCheckedItem,new DialogInterface.OnClickListener(){
            @Override
            public void onClick(DialogInterface dialog, int which) {
                Toast.makeText(MainActivity.this, String.format("%s Selected", items[which]), Toast.LENGTH_LONG).show();
                mCheckedItem = which;
            }});
        alert.setPositiveButton("OK", new  DialogInterface.OnClickListener(){
            @Override
            public void onClick(DialogInterface dialog, int which) {
                Toast.makeText(MainActivity.this, String.format("%s Selected", items[mCheckedItem]), Toast.LENGTH_LONG).show();
            }});
        alert.show();
    }  
}

ダイアログにチェックボックス付きのリストを表示する

setMultiChoiceItems()メソッドを使って、複数選択できるチェックボックス付きのリストを表示することができます。
setMultiChoiceItems()メソッドの第1引数にはリストに表示する項目の配列、第2引数には選択するチェックボックスの初期値を論理値配列で指定します。

package my.study.android;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.widget.Toast;

public class MainActivity extends Activity {
    
    private boolean[] mCheckedItems = {true,false,true};
 
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
               
        final String[] items = {"item1","item2","item3"};
        AlertDialog.Builder alert = new AlertDialog.Builder(this);
        alert.setTitle("Title");
        alert.setMultiChoiceItems(items, mCheckedItems, new DialogInterface.OnMultiChoiceClickListener(){
            @Override
            public void onClick(DialogInterface dialog, int which, boolean isChecked) {
                mCheckedItems[which] = isChecked; 
            }});
        alert.setPositiveButton("OK", new  DialogInterface.OnClickListener(){
            @Override
            public void onClick(DialogInterface dialog, int idx) {
                String str =  null;
                for (int i = 0; i < mCheckedItems.length; i++){
                    if (mCheckedItems[i] == true){
                        str += items[i];
                    }
                }
                if (str == null){
                    str = "No Selected";
                }
                Toast.makeText(MainActivity.this, str, Toast.LENGTH_LONG).show();
            }});
        alert.show();
    }  
}

独自のレイアウトを表示する

AlertDialogに独自のレイアウトを表示することもできます。


まずAlertDialogに表示するレイアウトを作成します。
EditTextを1つ配置したレイアウトを作成しました。
res/layout/alert.xml
<?xml version="1.0" encoding="utf-8"?>

<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
  
 <EditText android:id="@+id/EditText01" 
 android:layout_height="wrap_content" 
 android:layout_width="fill_parent">
 </EditText>

</LinearLayout>
LayoutInflaterクラスを使用して、レイアウトからViewを作成し、AlertDialog#setView()メソッドでViewを設定します。
MainActivity.java
package my.study.android;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;

public class MainActivity extends Activity { 
  
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        AlertDialog.Builder alert = new AlertDialog.Builder(this);
        alert.setTitle("Title");
        //LayoutInflaterクラスを使用して、レイアウトからViewを作成します。
        LayoutInflater inflater = LayoutInflater.from(this);
        final View viw = inflater.inflate(R.layout.alert,null);  
        //AlertDialogにビューを設定します。
        alert.setView(viw);   
        alert.setPositiveButton("OK", new  DialogInterface.OnClickListener(){
            @Override
            public void onClick(DialogInterface dialog, int idx) {
                EditText txt = (EditText)viw.findViewById(R.id.EditText01);
                Toast.makeText(MainActivity.this,txt.getText(),Toast.LENGTH_LONG).show();
            }});
        alert.show();
    }  
}

2010年9月30日木曜日

Android SQLiteデータベースを作成する(SQL文をリソースファイルに定義する)

Android SQLiteデータベースを作成する では、onCreate()メソッドで実行するSQL文をハードコーディングしていました。
1~2つのテーブルならアリかもしれませんが、テーブル数が多いのでsql文をリソースファイルに定義したいと思います。


assets\sql\createフォルダにCREATE TABLE文を定義したsqlを配置します。
assets\sql\dropフォルダにDROP TABLE文を定義したsqlを配置します。


assets\sql\create\categories.sql
初期データを追加するInsert文も定義しています。sql文の区切りは「/」にしました。
CREATE TABLE Categories
(
 CategoryID   integer PRIMARY KEY,
 CategoryName  text NOT NULL,
 Description   text
) 
/
INSERT INTO Categories
(CategoryID,CategoryName,Description)
values
(1,"飲料","")
/
INSERT INTO Categories
(CategoryID,CategoryName,Description)
values
(2,"香辛料","")
/
INSERT INTO Categories
(CategoryID,CategoryName,Description)
values
(3,"加工食品","")
assets\sql\create\products.sql
CREATE TABLE Products
(
 ProductsID   integer PRIMARY KEY,
 ProductsName  text NOT NULL,
 CategoryID   integer NOT NULL
) 
assets\sql\drop\categories.sql
DROP TABLE Categories
assets\sql\drop\products.sql
DROP TABLE Products

MainActivity.java
package my.study.android;  

import android.app.Activity;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
  
public class MainActivity extends Activity {   
      
    @Override  
    protected void onCreate(Bundle savedInstanceState) {   
        super.onCreate(savedInstanceState);   
        setContentView(R.layout.main);  
        
        //データベースヘルパーのインスタンスを作成する(まだデータベースはできない)   
        DatabaseHelper dbHelper = new DatabaseHelper(this);   
        //データベースオブジェクトを取得する(データベースにアクセスすると作成される。)   
        SQLiteDatabase db = dbHelper.getWritableDatabase();    
        //データベースを閉じる   
        db.close();       
    }   
}

DatabaseHelper.java
package my.study.android;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

import android.content.Context;
import android.content.res.AssetManager;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class DatabaseHelper extends SQLiteOpenHelper {
 
    /* データベース名 */
    private final static String DB_NAME = "androidstudydb";
    /* データベースのバージョン */
    private final static int DB_VER = 2;    
    /* コンテキスト */
    private Context mContext; 
    
    /**
     * コンストラクタ
     */
    public DatabaseHelper(Context context) {
        super(context, DB_NAME, null, DB_VER);
        mContext = context;
    }

    /**
     * データベースが作成された時に呼ばれます。
     * assets/sql/create内に定義されているsqlを実行します。
     */
    @Override
    public void onCreate(SQLiteDatabase db) {     
        try {
            execSql(db,"sql/create");
        } catch (IOException e) {
            e.printStackTrace();
        }    
    }

    /**
     * データベースをバージョンアップした時に呼ばれます。
     * assets/sql/drop内に定義されているsqlを実行します。
     * その後onCreate()メソッドを呼び出します。
     */
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {   
        try {
            execSql(db,"sql/drop");
        } catch (IOException e) {
            e.printStackTrace();
        }
        onCreate(db);
    }
    
    /**
     * 引数に指定したassetsフォルダ内のsqlを実行します。
     * @param db データベース
     * @param assetsDir assetsフォルダ内のフォルダのパス
     * @throws IOException
     */
    private void execSql(SQLiteDatabase db,String assetsDir) throws IOException {
        AssetManager as = mContext.getResources().getAssets();    
        try {
            String files[] = as.list(assetsDir);
            for (int i = 0; i < files.length; i++) {    
                String str = readFile(as.open(assetsDir + "/" + files[i]));
                for (String sql: str.split("/")){
                    db.execSQL(sql);
                } 
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    /**
     * ファイルから文字列を読み込みます。
     * @param is
     * @return ファイルの文字列
     * @throws IOException
     */
    private String readFile(InputStream is) throws IOException{
        BufferedReader br = null;
        try {
            br = new BufferedReader(new InputStreamReader(is,"SJIS"));

            StringBuilder sb = new StringBuilder();    
            String str;      
            while((str = br.readLine()) != null){      
                sb.append(str +"\n");     
            }    
            return sb.toString();
        } finally {
            if (br != null) br.close();
        }
    }

}



【関連項目】
Android SQLiteデータベースを作成する
Android SQLiteのGUIツール
Android データベースにデータを書き込む(Insert、Update、Delete)
Android データベースのデータを読み込む(Select)
Android SQLiteデータベースを作成する(SQL文をリソースファイルに定義する)
Android ファイル読み込み(res/rawフォルダ、assetsフォルダ)

2010年9月24日金曜日

Android apkに書き込み可能なファイルを含める

apkに任意のファイルを含めてインストールし、プログラムから書き込み可能に出来ないかと調べてみました。

やりたい事は、apkにテキストファイルを含めて、インストール時にfilesフォルダに配置したかったのですが、
日本Androidの会 apkにファイルを含めるによると、直接filesフォルダに展開することはムリっぽいです。
かわりにres/rawにテキストファイルを含めて、初回起動時にfilesフォルダにコピーする方法が紹介されていました。


res/raw/test.txtをfilesフォルダにコピーします。
初回起動時はfiles/test.txtが存在しない場合としました。
package my.study.android;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;

import android.app.Activity;
import android.content.res.Resources;
import android.os.Bundle;

public class MainActivity extends Activity {

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //レイアウトの読み込み
        this.setContentView(R.layout.main);
        //filesフォルダにtest.txtが存在しなければ、res/raw/test.txtをコピーする。
        File file = this.getFileStreamPath("test.txt");
        if ( file.exists() == false ) {
            Resources res = this.getResources();
            try {
                fileCopy(res.openRawResource(R.raw.test),openFileOutput(file.getName(),MODE_PRIVATE));
            } catch (IOException e) {
                e.printStackTrace();
            }   
        } 
    }
 
    private void fileCopy(InputStream is , OutputStream os )throws IOException{
        BufferedReader br = null;
        BufferedWriter bw = null;
        try {
            br = new BufferedReader(new InputStreamReader(is));    
            bw = new BufferedWriter(new OutputStreamWriter(os));   

            String str;      
            while((str = br.readLine()) != null){      
                bw.append(str +"\n");     
            } 
            bw.flush();   
        } finally {   
            if (br != null) br.close();
            if (bw != null)bw.close();       
        }  
    }

}

Android コーディング規約

Androidのコーティング規約です。
コントリビュータのためのAndroidコードスタイルガイドライン 日本語訳

一部抜粋です。
1.コメント/Javadoc: コメント/Javadocを書いてください。そこでは標準のスタイルを使ってください。
2.簡潔なメソッド: 巨大なメソッドを書いてはいけません。
3.フィールド: フィールドはファイルの先頭か、フィールドを使うメソッドの直前に書いてください。
4.ローカル変数: ローカル変数のスコープは限定するようにしてください。
5.インポート: android関連; サードパーティ製(アルファベット順); java(x) という順序でインポートしてください。
6.インデント: インデントにはスペース4つを使ってください。タブを使ってはいけません。
7.行の長さ: 1行は100カラムまでにしてください。
8.フィールド名: 非パブリック、非スタティックフィールドは m で始めてください。
9.中括弧: 開き中括弧で行を始めてはいけません。
10.アノテーション: 標準のアノテーションを使ってください。
11.頭字語(アクロニム)は単語: 名前では頭字語を単語として扱ってください。例えば、XmlHttpRequest、getUrl() など。
12.TODOのスタイル: 「TODO: こんな風に書いてください」
13.一貫性: まわりをよく見てください!
14.ログ記録: ログを記録するときには注意してください。ログにもコストがかかるのです。

Android ファイルの存在チェック

Androidではファイル名(パスを含まない)だけで、ファイルの書き込み、読み込みができます。
Android ファイルの入出力(filesフォルダ)

ファイルの存在チェックもパスはいらないだろうと思っていたら、こちらはパスがいるのですね・・・
String filepath = this.getFilesDir().getAbsolutePath() + "/" +  "test.txt";
File file = new File(filepath);
boolean isExists = file.exists();
又は
File file = this.getFileStreamPath("test.txt");
boolean isExists = file.exists();

2010年9月23日木曜日

Android リソースの種類と定義場所

resフォルダには色々なリソースファイルを配置しますが、リソースの種類により配置場所が決まっています。

名前説明
res/anim/xxx.xmlフレームアニメーションやTweenedアニメーションを定義したXMLファイル
res/drawableイメージファイル(.png、.9.png、.jpg)
デフォルトでdrawable-hdpi、drawable-ldpi、drawable-mdpiのディレクトリが作成されており、各ディレクトリに同じ名前のイメージファイルを配置する事で解像度によって使用するイメージを自動で切り替える事ができます。
res/layout/xxx.xml画面レイアウトを定義したXMLファイル
res/menu/xxx.xmlメニュー(オプションメニュー、コンテキストメニュー、サブメニュー)を定義したXMLファイル
res/values/arrays.xml配列を定義したXMLファイル
res/values/attrs.xmlカスタムレイアウトを定義したXMLファイル
res/values/colors.xml色を定義したXMLファイル
res/values/dimens.xml寸法を定義したXMLファイル
res/values/ids.xmlIDを定義したXMLファイル
res/values/strings.xml文字列を定義したXMLファイル
res/values-en/strings.xml、res/values-fr/strings.xml、res/values-ja/strings.xmlなどと言語コードをつけることで、ロケールにより自動で文字列を切り替える事ができます。
res/values/styles.xmlスタイルを定義したXMLファイル
res/xml任意のXMLファイル
res/raw任意のファイル(音楽データなどコンパイルせずにアプリケーションに含めたいファイルなど)

Android アプリ設定画面を作成する PreferenceScreen(別アクティビティ)

Android アプリ設定画面を作成する PreferenceScreen ではPreferenceScreenをクリックすると、同じアクティビティの別画面を開きました。

今回はPreferenceScreenをクリックすると、別のアクティビティを開きます。
Intentを使って別のアクティビティを開く方法は下記を見てください。
Android 別のアクティビティを開く(戻り値を受け取らない)
Android 別のアクティビティを開く(戻り値を受け取る)

まず、PreferenceScreenをクリックしたときに、開くアクティビティを作成します。
作り方はコチラAndroid プロジェクトに新しいアクティビティを追加する

Activityを継承し、TextViewを1つ配置しただけの「SubActivity」を用意しました。
res/layout/sub.xml(SubActivityのレイアウト)
<?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:text="@+id/TextView01" 
 android:layout_width="wrap_content" 
 android:layout_height="wrap_content" 
 android:id="@+id/label">
</TextView>

</LinearLayout>
SubActivity.java
package my.study.android;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.widget.TextView;

public class SubActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.sub);  
        //受け取った値をTextViewに表示する
        TextView label = (TextView)findViewById(R.id.label);
        Intent intent = getIntent();
        Bundle extra = intent.getExtras();
        String str = null;
        if (extra != null){
            str = extra.getString("arg1") + "\n"; 
            if (extra.containsKey("arg2")){
                str += extra.getString("arg2");
            }
        }
        label.setText(str);
        //遷移元に値を返す。
        Intent result = new Intent();
        result.putExtra("result", "From SubActivity Result");
        setResult(RESULT_OK,result);   
    }

}


PreferenceActivityを継承したMainActivityから、PreferenceScreenをクリックしたときに別のアクティビティを開く方法はいくつかあります。

まずはPreferenceScreenのxml定義にintentを記述する方法です。
intentタグのtargetPackageにパッケージ名を指定し、targetClassに遷移するアクティビティを指定します。
extraタグで遷移するアクティビティに値を渡す事ができます。
res/xml/mainpref.xml(MainActivityのレイアウト)
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
  xmlns:android="http://schemas.android.com/apk/res/android">

    <PreferenceScreen
        android:key="@string/prefScreen1"
        android:summary="xmlでIntentを飛ばす"
        android:title="PreferenceScreen1" >
         <intent
          android:action="android.intent.action.MAIN"
          android:targetPackage="my.study.android"
          android:targetClass="my.study.android.SubActivity" >
      <extra android:name="arg1" android:value="prefscreen1: arg1 = from xml" />
     </intent>
    </PreferenceScreen>

</PreferenceScreen>
遷移するアクティビティに渡す値をコードから追加する場合は、PreferenceScreen#getIntent()メソッドでIntentオブジェクトを取得し
Intent#putExtra()メソッドで値を追加します。
MainActivity.java
package my.study.android;  

import android.content.Intent;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceScreen;

public class MainActivity extends PreferenceActivity {
 
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //xmlレイアウトの読み込み
        this.addPreferencesFromResource(R.xml.mainpref);
        //PreferenceScreen1
        CharSequence csScreenPref1 = getText(R.string.prefScreen1);
        PreferenceScreen screenPref1 = (PreferenceScreen)findPreference(csScreenPref1);
        Intent intent1 = screenPref1.getIntent();
        intent1.putExtra("arg2", "prefscreen1: arg2 = from code");
    }
}

次はPreferenceScreenにコードでintentを記述し設定する方法です。
res/xml/mainpref.xml(MainActivityのレイアウト)
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
  xmlns:android="http://schemas.android.com/apk/res/android">

    <PreferenceScreen 
        android:summary="コードでIntentを設定する"
        android:title="PreferenceScreen2"
        android:key="@string/prefScreen2">
    </PreferenceScreen>

</PreferenceScreen>
intentオブジェクトを作成し、PreferenceScreen#setIntent()メソッドでPreferenceScreenに設定します。
MainActivity.java
package my.study.android;  

import android.content.Intent;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceScreen;

public class MainActivity extends PreferenceActivity {
 
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //xmlレイアウトの読み込み
        this.addPreferencesFromResource(R.xml.mainpref);
  
        //PreferenceScreen2
        CharSequence csScreenPref2 = getText(R.string.prefScreen2);
        PreferenceScreen screenPref2 = (PreferenceScreen)findPreference(csScreenPref2);
        Intent intent2 = new Intent(this,SubActivity.class);
        intent2.putExtra("arg1", "prefscreen2: arg1 = from code");
        screenPref2.setIntent(intent2);
    }
 
}

この2つの方法は遷移先のアクティビティに値を渡すことができますが、遷移先のアクティビティから値を受け取ることができません。
遷移先のアクティビティから戻ってきたときはonResume()が発生します。

次はPreferenceScreenのoOnPreferenceClickListenerをハンドルし、startActivityForResultでアクティビティを開く方法です。
この方法はonActivityResult()が発生し、遷移先のアクティビティから値を受け取ることができます。
もちろんonResume()も発生します。
res/xml/mainpref.xml(MainActivityのレイアウト)
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
  xmlns:android="http://schemas.android.com/apk/res/android">

    <PreferenceScreen 
        android:summary="onClickでIActivityを開く"
        android:title="PreferenceScreen3"
        android:key="@string/prefScreen3">
    </PreferenceScreen>

</PreferenceScreen>
MainActivity.java
package my.study.android;  

import android.content.Intent;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceScreen;
import android.preference.Preference.OnPreferenceClickListener;
import android.widget.Toast;


public class MainActivity extends PreferenceActivity {
 
    private static final int SUBACTIVITY = 1; 
 
    private PreferenceScreen _screenPref3;
 
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //xmlレイアウトの読み込み
        this.addPreferencesFromResource(R.xml.mainpref);
        //PreferenceScreen3
        CharSequence csScreenPref3 = getText(R.string.prefScreen3);
        _screenPref3 = (PreferenceScreen)findPreference(csScreenPref3);
        _screenPref3.setOnPreferenceClickListener(new OnPreferenceClickListener(){
            @Override
            public boolean onPreferenceClick(Preference pref) {
                return screenPref3_onPreferenceClick(pref);
            }});
  
    }
 
    private boolean screenPref3_onPreferenceClick(Preference pref){
        Intent intent = new Intent(this,SubActivity.class);   
        intent.putExtra("arg1", "prefscreen3: arg1 = from code");   
        startActivityForResult(intent, SUBACTIVITY);   
        return true;
    }
 
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
        super.onActivityResult(requestCode, resultCode, intent);
        Toast.makeText(this, "onActivityResult", Toast.LENGTH_SHORT).show();
  
        if (requestCode == SUBACTIVITY){  
            String summary = null;
            if (resultCode == RESULT_OK){   
                Bundle extras = intent.getExtras(); 
                if (extras != null){   
                    summary = "result:" + extras.getString("result");        
                }   
            }
            _screenPref3.setSummary(summary);
        }   
    } 
}




 
MainActivity

PreferenceScree1をクリック後
PreferenceScree2をクリック後

PreferenceScree3をクリック後


MainActivityに戻る




【関連項目】
Android プリファレンスの編集
Android アプリ設定画面を作成する
Android アプリ設定画面を作成する CheckBoxPreference
Android アプリ設定画面を作成する EditTextPreference
Android アプリ設定画面を作成するRingtonePreference
Android アプリ設定画面を作成するListPreference
Android アプリ設定画面を作成する 依存関係を設定する
Android アプリ設定画面を作成する PreferenceScreen
Android アプリ設定画面を作成する PreferenceScreen(別アクティビティ)

2010年9月21日火曜日

Android アプリ設定画面を作成する PreferenceScreen

PreferenceScreenを入れ子にすると、項目を選択したときに次の設定画面が開きます。


pref.xml
PreferenceScreenに入れ子でPreferenceScreenを配置します。
さらに入れ子でCheckBoxPreferenceを配置します。

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
  xmlns:android="http://schemas.android.com/apk/res/android">

 <PreferenceScreen 
  android:summary="PreferenceScreen要約" 
  android:title="PreferenceScreenタイトル" 
  android:key="@string/pref_screen">
  
  <CheckBoxPreference 
   android:key="@string/checkbox" 
   android:summary="要約" 
   android:title="タイトル">
  </CheckBoxPreference>
  
 </PreferenceScreen>

</PreferenceScreen>

子のCheckBoxPreferenceのチェック状態を変更したとき、子のCheckBoxPreferenceの要約を変更するのはAndroid アプリ設定画面を作成する CheckBoxPreference と同じです。

子のCheckBoxPreferenceのチェック状態を変更したとき、親のPreferenceScreenの要約を変更するには、いくつか方法があります。
子画面(PreferenceScreenをクリックして開く画面)は、新しいアクティビティではないため、本体の戻るボタンで親画面に戻ってもonResume()などは発生しません。
そこでPreferenceScreenのOnPreferenceClickListenerをハンドルし、子画面のオブジェクトをgetDialog()メソッドで取得します。
取得したDialogオブジェクトのOnDismissListenerをハンドルし、子画面が破棄されたタイミングで親画面の要約を変更します。

package my.study.android;  

import android.app.Dialog;
import android.content.DialogInterface;
import android.content.DialogInterface.OnDismissListener;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceScreen;
import android.preference.Preference.OnPreferenceClickListener;
import android.widget.ListView;


public class MainActivity extends PreferenceActivity {
    
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //xmlレイアウトの読み込み
        this.addPreferencesFromResource(R.xml.pref);
        //親のPreferenceScreen
        CharSequence csScreenPrefParent = getText(R.string.prefscreen_Parent);
        PreferenceScreen screenPref = (PreferenceScreen)findPreference(csScreenPrefParent);
        //OnPreferenceClickListenerをハンドルする
        screenPref.setOnPreferenceClickListener(new OnPreferenceClickListener(){
            @Override
            public boolean onPreferenceClick(Preference preference) {
                return screenPreference_OnPreferenceClick(preference);
            }});
    }
 
    private boolean screenPreference_OnPreferenceClick(Preference preference){
        PreferenceScreen screenPref = (PreferenceScreen) preference; 
        //子画面オブジェクトを取得する
        Dialog d = screenPref.getDialog();
        //子画面のOnDismissListenerをハンドルする 
        d.setOnDismissListener(new OnDismissListener(){
            @Override
            public void onDismiss(DialogInterface dialog) {
                Dialog_OnDismiss(dialog);
            }});
        return true;
    } 
  
    private void Dialog_OnDismiss(DialogInterface dialog){
        //子のCheckPreferenceのチェック状態を取得する
        CharSequence csCheckPrefChild = getText(R.string.prefchk_child);
        CheckBoxPreference pref_checkChild = (CheckBoxPreference)findPreference(csCheckPrefChild);
        String summary = null;
        if (pref_checkChild.isChecked() == true) {
            summary = "Checked:True";
        } else {
            summary = "Checked:False";
        }
        //親のPreferenceScreenの要約を変更する
        CharSequence csScreenPrefParent = getText(R.string.prefscreen_Parent  );
        PreferenceScreen screenPref = (PreferenceScreen)findPreference(csScreenPrefParent);
        screenPref.setSummary(summary);
        //再描画
        ListView v = this.getListView();
        v.invalidateViews();
    }
 
}

【関連項目】
Android プリファレンスの編集
Android アプリ設定画面を作成する
Android アプリ設定画面を作成する CheckBoxPreference
Android アプリ設定画面を作成する EditTextPreference
Android アプリ設定画面を作成するRingtonePreference
Android アプリ設定画面を作成するListPreference
Android アプリ設定画面を作成する 依存関係を設定する
Android アプリ設定画面を作成する PreferenceScreen
Androidアプリ設定画面を作成する PreferenceScreen(別アクティビティ)