2011年10月7日金曜日

onCreateの引数の意味

onCreateの引数の意味

この記事は【必須】再起動阻止 configChangesの続きです。

onCreateメソッドの引数
ActivityにおけるonCreateメソッドには、Bundle savedInstanceStateという引数が付いています。androidの学習を始めた時から目にしていましたが、特に気にはなりませんでした。ここに来てやっと、使いたくは無いが、使うべき時がきてしまいました。

この引数の内容は、通常はnullです。
しかし、実機の端末を、縦位置から横位置へ(又は横位置から縦位置へ)変える(以下「configuration change」と呼ぶ。)と、null以外の値が代入されてきます。
このため、現在の起動が、初回の起動なのか、再起動なのかを捕捉することに使えます。

各技法の評価

AndroidManifest.xmlを書き換える技法
前回の私の記事では、再起動の回避策として、「AndroidManifest.xmlの<activity>の属性としてandroid:configChanges="orientation"を書く」という技法を紹介しました。しかし、この技法に対しては、Handling Runtime Changesにおいて次のようなコメントがあります。
This technique should be considered a last resort when you must avoid restarts due to a configuration change and is not recommended for most applications.
このように書かれてしまうと、使いづらくなります。

onCreateメソッドの引数を使う技法
onCreateメソッドのBundleを使う技法に対しては、「it is not designed to carry large objects (such as bitmaps)」という指摘はありますが、初回起動か再起動かの判別をしたいだけなので、有効な技法と言えます。
誰しもが目にするonCreateメソッドの引数として付いてきているのです。なので、androidの基本的な設計思想として、「configuration changeに基づく再起動は発生するのが当たり前であり、発生した時には、これを使え」と言っているようです。
Bundleの特性を活かして、再起動の前から、再起動の後へ、任意の値を渡すことができます。
しかし、再起動を許可してしまうため、Javaのcodeにおいて、初回起動時に行う部分と、再起動時に行う部分とを書き分けなければならなくなります。
このような書き分け作業を行ったとしても、そのコーディングのメリットは、「再起動による不都合を防止する」という程度であって、自分のアプリへの積極的なメリットはありません。

Intentによる再起動
「onCreateメソッドの引数」技法が使えるのは、configuration changeによる再起動の場合です。
Intentを受けた場合には、onCreateメソッドの引数は常にnullです。
このため、Intentによる初回起動or再起動の確認は別の手段を講じなければなりません。

私なりに考えた方法
再起動への対応を考える
configuration changeに伴う再起動への対応 その1
configuration changeに伴う再起動への対応 その2

上記知見を得るために使ったsourceを掲載しておきます。エミュレータでは有効に稼働しません。実機を使ってください。
public class OnSaveInstanceActivity extends Activity{
    final String TAG = "OSIA";
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        if(savedInstanceState==null){//初回起動
            Log.i(TAG, "null at onCreate");
            //ここに初回だけ実行させるコードを書けば良い。
        }
        else{//再起動
            Log.i(TAG, "Not null at onCreate");
        }
    }
}

2011年9月22日木曜日

画面を持たないActivityを作る

画面を持たないActivityを作る

Activityには画面があります。しかし、今回の目的は、IntentをActivityで受け取る仕掛けを作るだけなので、画面があってはならないのです。この話は、Intentにより起動されるタイミングの続編です。

Intentを受け取るだけの目的なので、onCreateの段階でfinish()すれば良いのですが、この場合、画面が一瞬黒くなります。

なので、そもそも、画面が無いActivityを作りたいのです。しかし、その方法が分かりません。

そこで、画面を透明にする技法にしました。
やり方は、アクティビティ(画面)の背景を透過にするにはに掲載されています。
誰も、私のような目的のために、この技法を使うものとは、思うめい。へっへっへ\(^o^)/
て、いうか、私の目的以外に、どんな目的で全画面を透明にするねん(-_-;)
この技法を紹介しているサイトには、何のためにってのが書いていません。技法しか書いてない。おそらく、私と同じ目的に使おうとしていたんだよねぇ、たぶん。憎いねぇ。さすがだねぇ。ジャ(蛇)の道は蛇(ヘビ)ですな。

xmlではなく、javaでコーディングをすれば、もっと幅広い何かができるものと思いまして、いろいろ試しましたが、結論としては、javaコードで透明にすることはできない、ということです。
参考に、半透明にしようと試みたが半透明にする効果の無いプログラムを掲載しておきます。(何のために?!(-_-;))
public class InvisibleActivity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        WindowManager.LayoutParams wl;
        Window w;
        w = getWindow();
        wl = w.getAttributes();
        wl.alpha = 0.5f;
        wl.format = PixelFormat.RGBA_8888;
        w.setAttributes(wl);
        ColorDrawable c;
        c = new ColorDrawable(0x88ff0000);
        w.setBackgroundDrawable(c);
     }
}
普通は、このコードだけで半透明になるはずなんだが、ならない。

2011年9月21日水曜日

Explicit IntentとImplicit Intent

Explicit IntentとImplicit Intent

IntentはExplicit IntentとImplicit Intentの2種類に分けられます。
このような分類や議論は設計上での話です。プログラマーの立場から見れば、Intentの引数に与える値が異なる、という話になります。「Explicit」といったコマンドがあるのではありません。

Explicit Intentについて
Explicit Intentは、起動させるアプリを明示するIntentです。プログラムの書き方としては、パッケージ名とクラス名とを引数にします。

これは、自分のアプリ内のActivityやServiceを起動するために使います。

また、他のアプリのパッケージとクラスを指定すれば、(指定したアプリが稼働していない状態であっても)そのアプリのそのクラスを起動できます。

自分のアプリ内のを起動する方法については、他の多くのサイトで紹介されています。Bound Servicesはその例です。
書き方の例を次に示します。この例は自分のアプリ内のLocalServiceというクラスを起動します。第一引数のthisは自分のアプリを指します。第二引数は自分のアプリ内のクラスを指します。

Intent intent = new Intent(this, LocalService.class);

自分以外のアプリ(他のアプリを)起動する書き方の例は次のとおりです。setClassNameメソッドの第一引数はパッケージ名です。第二引数はそのパッケージに含まれるクラスです。
Intent i;
i = new Intent();
i.setClassName(
"jp.Androyer.ReceiveIntent",
"jp.Androyer.ReceiveIntent.ReceiveService");
i.putExtra(Intent.EXTRA_TEXT, "Intent Send");
try{
startService(i);
}
catch(ActivityNotFoundException e){
Toast.makeText(this, "Error", Toast.LENGTH_LONG).show();
}

Implicit Intentについて
Implicit Intentは、起動させるアプリを明示せず、行いたい行動を指定するIntentです。
この用語は、アプリを設計する場面で使う用語です。アプリを利用する場面では、これを「共有」と呼んでいます。

Implicit Intentが発行されると、アンドロイドシステムは、指定された行動を行えるアプリの一覧を作成し、これを利用者に提示します。利用者は、この一覧の中から1個のアプリを選択します。
書き方は次のとおりです。
Intent i, i2;
i = new Intent();
i.setAction(Intent.ACTION_SEND);
i.setType("text/plain");
i.putExtra(Intent.EXTRA_TEXT, "Intent Send");
i2 = Intent.createChooser(i, "選択してください");
try{
startService(i2);
}
catch(ActivityNotFoundException e){
Toast.makeText(this, "Error", Toast.LENGTH_LONG).show();
}

intent-filter
Implicit Intentが行っているのは「指定した行動を行えるアプリを起動せよ」という指示だけであり、具体的にどのアプリを起動させるのかまでは指示しません。Intentの引数にはパッケージ名やクラス名が指定されていないのです。

Implicit Intentは指示を発する側の仕掛けです。この指示を受けて、起動するアプリを探し求める仕掛けが必要になります。
アンドロイドシステムは、指定された行動を行えるアプリがどれだけ端末にインストールされているのかを知っておかなければなりません。また、起動されるアプリ側でも、自分はどのような行動を行えるのかをアンドロイドシステムに通知しておかなければなりません。

自分が行える行動をアンドロイドシステムに通知する仕掛けが、intent-filterです。これはAndroidManifest.xmlの<intent-filter>に書きます。
filterとは、ろ過するという意味ですが、Intentをろ過して、取捨選択して、指示内容に合致したIntentだけを取り扱う、というような仕掛けになっています。

起動について
ここでは話を簡単にするため、一言で「起動」と表現しています。詳しくは、Intentにより起動されるタイミングをご覧ください。

用語の翻訳について
他のサイトでは、Implicitを「暗黙の」と翻訳されています。
私がこの勉強を始めた頃、「暗黙の」と訳された日本語の解説をあちこちで読みましたが意味が理解できませんでした。そのため、英語の原文を読んだのです。このような経緯から、私の記事では、原文のまま、Implicit Intentと表記しています。
Implicit Intentを「(起動するアプリを)明示しないIntent」と翻訳しても、間違ってはいないと思います。
なお、Implicitという言葉とExplicitという言葉は、反意語の関係にあります。Explicitという言葉は、「明白な」、「はっきりした」という意味があります。

2011年9月19日月曜日

むむむ!createChooser()

むむむ!createChooser()

androidのプログラミングを行っていると、変だなと思うことがある。
ただし、変だと思うのは私だけであって、私が変であるという可能性を否定することはできない。

IntentクラスにcreateChooserメソッドがある。
public static Intent createChooser (Intent target, CharSequence title)

これは、共有コマンドを実行する時に、起動できる複数のアプリの中から、お好みの1個のアプリを選択する画面を表示するメソッドである。
必須のメソッドでは無い。

startActivity()には対応しているが、startService()には対応していない。

もし、このメソッドを使わなければ、デフォルトの画面が表示されるだけである。このデフォルト画面には「常にこのアプリを起動する」オプションボタンがある。

このメソッドを使うメリットは、次の2点である。
1 第二引数の文字列を、画面のタイトルに設定できることである。
2 「常にこのアプリを起動する」オプションボタンが表示されなくなる。

次のように書く。
    Intent i, i2;
    i = new Intent();
    i.setAction(Intent.ACTION_SEND);
    i.setType("text/plain");
    i.putExtra(Intent.EXTRA_TEXT, "InfiniteIntent");
    i2 = Intent.createChooser(i, "選択してください");
    try{
        startActivity(i2);
    }
    catch(ActivityNotFoundException e){
    }

仕掛けが大袈裟な割には、タイトル文字列を変更するだけなのか?。などという疑問を持つのは果たして私だけだろうか。

選択してください
1:私も同感である。
2:設計をミスしたように見えるな
3:そんな疑問を持つのはアンタだけや!
4:このような仕掛けにすることが技術的に妥当である。
5:不満があるなら自分で拡張しろ!
6:むむむ!

2011年9月14日水曜日

Adapterとは何か

Adapterとは何か

概要
Adapterは、文字列や画像などの複数のデータと、(AdapterViewから派生した)ListView等との中間に存在し、両者の橋渡しをする役割を担います。

他のViewとの違い
Viewとして良く使うものに、ButtonやTextViewなどがあります。これらは、特に問題なく使えるでしょう。
訳が分からないのがListViewです。ListViewをxmlで書く例は次のとおりです。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content" 
    >
    <ListView
        android:id="@+id/ListView"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" 
        android:fastScrollEnabled="true"
        >
    </ListView>
</LinearLayout>

この書き方は、ButtonやTextViewと良く似た書き方です。このため、ButtonやTextViewと同じ振る舞いをするかのような錯覚を持ちます。この錯覚が、理解を困難にしているのだと思います。

ListViewは、上記のように1個しか書いていません。しかし、このListViewにAdapterを結びつけることによって、AdapterのデータがListViewに表示される仕組みになっています。この結びつける作業は、javaで書きます。
1個しか書いていないんだけど、実際には、複数のデータが表示されるのです。

詳細
Adapterは、全てのViewで使えるのではなく、AdapterViewから派生したViewに限って使えるのです。
AdapterViewは、ViewGroupから派生したクラスです。ViewGroupは複数のViewを取り扱うViewです。このことから、(1個ではなく)複数のデータを表示する場合に、Adapterを使うものだと理解できます。
AdapterViewから派生してできたクラスとして、GridView, ListView, Spinner等があります。当然、これらのViewはViewGroupでもあるのです。

参考に、ListViewの階層図を示しておきます。


java.lang.Object
   ↳android.view.View
   ↳android.view.ViewGroup
   ↳android.widget.AdapterView<T extends android.widget.Adapter>
   ↳android.widget.AbsListView
   ↳android.widget.ListView

GridView, ListView, Spinner等を使う場合にAdapterも併せて使うことになります。
ただし、取扱うデータの数が、3~4個程度であり、少なければ、ViewGroupを使わずに、Viewを1個1個単体で作っていけばよろしくて、その場合、Adapterは使えません。

表示するデータの並べ替えや表示順序などの指定も、Adapterが執り行うこととなっています。このことから、ListView等を扱うプログラマーは、Adapterに関する理解が必須になるのです。Adapterを使わねばならなくなった時に、あたふたーしないようにしましょう。

Adapterには次の種類があります。
  1. ListAdapter....1個の行に1個の列を作成します。Listを使ってデータを挿入します。
  2. SimpleAdapter....1個の行に複数の列を作成できます。Mapを使って1行単位のデータを作成します。Listを使ってデータを挿入します。
  3. SimpleCursorAdapter....取扱うデータをデータベースから取得します。

2011年9月10日土曜日

任意の子Viewの属性を一括して変更する

任意の子Viewの属性を一括して変更する

親のViewGroup(例えば、LinearLayout)の下に、様々な種類(1種類でもよいが)のView(例えば、Button、TextView、LinearLayout等)が存在しており、それらのViewの属性を一括して変更する場合に使うsourceを掲載しておきます。

その1:
次のsourceでは、LinearLayoutに含まれる全部の子Viewの縦方向のpaddingの値の調整をしています。この技法を用いたsourceをレイアウト間の間隔を動的に変えるに公開しておきました。

        int i, iCount;
        LinearLayout ll;
        
        ll = (LinearLayout)findViewById(R.id.LLParent);
        iCount = ll.getChildCount();
        
        for(i=0; i<iCount; i++){
            View v;
            v = ll.getChildAt(i);
            v.setPadding(0, Padding, 0, Padding);
        }

その2:
次のsourceでは、LinearLayoutに含まれる子Viewの内、クラス名の末尾がButtonで終わるViewに対してpaddingを行います。
クラス名の末尾がButtonで終わるViewとは、ButtonやToggleButtonです。
この技法を使えば、複数の種類のViewが混在していたとしても、例えば、その中から"Layout"だけを取り出す、ということができます。

        int i, iCount;
        LinearLayout ll;
        
        ll = (LinearLayout)findViewById(R.id.LLParent);
        iCount = ll.getChildCount();
        
        for(i=0; i<iCount; i++){
            View v;
            String s;
            v = ll.getChildAt(i);
            s = v.getClass().getName();
            if(s.endsWith("Button")==true){
                v.setPadding(0, Padding, 0, Padding);
            }
        }

その3:
次のsourceでは、LinearLayoutに含まれる子Viewの内、paddingという名前のtagが付いているViewに対して、paddingを行います。
tagは識別子ですが、同一の値のtagを持つViewが複数存在しても良いです。
この技法を使うためには、事前に、任意のViewにtagを付けておかなければなりません。

        int i, iCount;
        LinearLayout ll;
        
        ll = (LinearLayout)findViewById(R.id.LLParent);
        iCount = ll.getChildCount();
        
        for(i=0; i<iCount; i++){
            View v;
            String s = "padding";
            v = ll.getChildAt(i);
            if(s.equals(v.getTag())==true){
                v.setPadding(0, Padding, 0, Padding);
            }
        }

2011年9月8日木曜日

ScrollViewに関するメモ

ScrollViewに関するメモ

  1. 多数のViewが存在し、全てのViewが物理画面内に表示できない場合であっても、スクロールバーは自動的には表示されない。そして、物理画面に表示されていないViewは、表示されないままである。これはandroidシステムの仕様である。
  2. もし物理画面内に収まらないViewを表示させたいのであれば、プログラマがScrollViewを実装して、スクロールバーを使えるようにしなければならない。
  3. ScrollViewとは、物理画面に収まらない複数のViewを、利用者が画面をスクロールすることによって、表示できるようにするViewである。
  4. 文字列や画像のデータを一覧にして表示するのであれば、別の技法を用いることを検討した方が良い。例えば、GridView, ListView, Spinner等である。ScrollViewは、データでは無く、Viewを一覧表示させるものである。
  5. ScrollViewは縦方向のスクロールです。横方向にはHorizontalScrollViewを使う。
  6. スクロールさせたいViewを、ScrollViewの子として配置させて使う。ScrollViewが親になるわけです。
  7. 表示すべき子Viewが少量であり、物理画面内に収まる場合には、例えScrollViewを実装していたとしても、スクロールできないようになります。当たり前といえば、当たり前の仕様ですが、私はこれが気になって調べました。
  8. 子Viewが動的に大量になり、物理画面内に収まっていた状態から、収まらない状態へと変化した場合、スクロールできない状態からできる状態へと自動的に変化する。これの逆の状態へと子Viewが変化した場合も、スクロール状態は適切に変化する。
  9. ScrollViewは、FrameLayoutから派生されたクラスです。FrameLayoutでは複数のViewを子供にできますが、ScrollViewは1個の子供しか持てないようになっています。
  10. 普通にScrollViewを使うとすれば、子Viewは、きっとLinearLayoutでしょう。このLinearLayoutに、EditTextとかButtonとかのスクロール表示させたいViewを子供として指定します。
  11. ScrollViewの上(物理画面の上端)又は下(物理画面の下端)に任意のViewを表示することもできます。この場合、ScrollViewの親にRelativeLayoutを設定します。RelativeLayoutからの相対位置として、上端のView、下端のView、そして中段のScrollViewを指定します。
  12. smoothScrollByメソッドやfullScrollメソッド等のスクロールをさせるメソッドは、Viewが作成された後でなければ有効な実行ができません。参考:TextViewを任意の位置にスクロールさせる
  13. smoothScrollToメソッドは、左上端からの絶対座標を指定します。
  14. smoothScrollByメソッドは、現在の位置からの相対値を指定します。このため、Y軸の引数にマイナスの数値を指定すると、上方向にスクロールします。
  15. レイアウト間の間隔を動的に変えるにプロジェクトファイルを添付しておきました。
  16. ScrollViewの配下に(例え2段下であっても)ListViewを配置した場合、そのListViewのスクロールは機能しません。つまり、全然動かないので使い物にならない。
  17. もし、どうしてもListViewのようなViewを実装したいのであれば、LayoutInflaterを使って、同一のViewを複数表示するようにすれば良い。
  18. ScrollViewの配下にScrollViewを配置することは有効です。
  19. Android 4.4.2 (API 19)において、画面を4~5回上下にスクロールさせると、画面が動かなくなる。原因不明。対応措置無し。このため、ListViewで代替することになる。このListViewのAddHeaderViewにView群を挿入してやれば良い。ただし、何かダミーをList要素に入れてやらないと、HeaderViewは表示されない。空っぽのアダプターを作ってsetAdapter()すれば良い。Android Studio: Horizonal Scroll View Freezes While Testing On Emulator

上記知見を得るために使用したxmlを掲載しておきます。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    >
    <TextView
        android:id="@+id/TopView"
        android:layout_width="fill_parent" 
        android:layout_height="wrap_content" 
        android:text="@string/ThisIs"
        android:layout_alignParentTop="true"
        >
    </TextView>
    <TextView
        android:id="@+id/BottomView"
        android:text="@string/ThisIs"
        android:layout_width="fill_parent" 
        android:layout_height="wrap_content" 
        android:layout_alignParentBottom="true"
        >
    </TextView>
    <ScrollView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" 
        android:layout_above="@id/BottomView"
        android:layout_below="@id/TopView"
        >
        <LinearLayout
            android:id="@+id/LLParent"
            android:orientation="vertical"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content" 
            >
            <LinearLayout
                android:id="@+id/LLButtonReportSize"
                android:layout_width="wrap_content" 
                android:layout_height="wrap_content" 
                android:layout_gravity="center_vertical"
                android:tag="padding"
                >
                <Button
                    android:layout_width="wrap_content" 
                    android:layout_height="wrap_content" 
                    android:text="@string/ThisIs"
                    >
                </Button>
            </LinearLayout>
            <Button
                android:id="@+id/ButtonReportSize"
                android:layout_width="wrap_content" 
                android:layout_height="wrap_content" 
                android:text="@string/ThisIs"
                >
            </Button>
            
            <LinearLayout
                android:id="@+id/LLToggleButton"
                android:layout_width="wrap_content" 
                android:layout_height="wrap_content" 
                android:layout_gravity="center_vertical"
                android:tag="padding"
                >
                <ToggleButton
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    >
                </ToggleButton>
            </LinearLayout>
            <ToggleButton
                android:id="@+id/toggleButtonPadding"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                >
            </ToggleButton>
            
            <LinearLayout
                android:id="@+id/LLCheckBox"
                android:layout_width="wrap_content" 
                android:layout_height="wrap_content" 
                android:layout_gravity="center_vertical"
                android:tag="padding"
                >
                <CheckBox
                    android:text="@string/ThisIs"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    >
                </CheckBox>
            </LinearLayout>
            <CheckBox
                android:id="@+id/checkBox"
                android:text="@string/ThisIs"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                >
            </CheckBox>
            
            <SeekBar
                android:id="@+id/seekBar1"
                android:layout_height="wrap_content"
                android:layout_width="fill_parent"
                >
            </SeekBar>
            <RatingBar
                android:id="@+id/ratingBar1"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                >
            </RatingBar>
            <RatingBar
                android:id="@+id/ratingBar1"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                >
            </RatingBar>
        </LinearLayout>
    </ScrollView>
</RelativeLayout>