ダイアログBOXを表示するには(AndroidMode編)

◆PROCESSING 逆引きリファレンス

 カテゴリー:スマホ(AndroidMode)

ダイアログBOXを表示するには(AndroidMode編)

【概要】

PROCESSINGにAndroidMode を導入する事で、PROCESSINGで開発したプログラムをAndroid端末上で動かす事ができるようになります。

AndroidModeの導入については「PROCESSINGをAndroid端末で動かすには(4.0版)」記事を参照してください。

アプリケーションを作成していると、ときどき利用者からの入力を受け付けたくなる場面があります。

たとえばゲームなどで、「宝箱がある。開けますか?(はい)(いいえ)」のような場面を作りたい時などです。

PROCESSINGでゴリゴリとコーディングすれば、簡単な問い合わせ画面を作成する事も不可能ではありません。むしろ場合によっては、その方が良いケースもあるでしょう。

が、せっかくAndroid上で動くアプリケーションなのですから、標準的な方法で問い合わせを行う方法も知りたい所です。

Windowsのプログラム(C#やVB)でダイアログBOXを表示するのは、とても簡単です。

それに比べ、PROCESSINGやJavaでAndroid OS用のダイアログBOXを表示するのは、ちょっと大変です。Androidはスマホ用のOSであるため、たかがダイアログBOXとはいえ、いろいろな前提知識が必要となる為です。

とはいえ、基本を押さえればそんなに難しくはありませんので、初心者の方にも理解できると思います。

AndroidでダイアログBOXを表示するには、DailogFragmentを利用します。そしてPROCESSINGのプログラム(Sketch)はFragment(正確にはPFragmentから呼び出されるメソッド)です。

FragmentやDailogFragmentに不慣れな方は、下記コラムに前提となる知識をまとめておきましたので、参照してみて下さい。

ダイアログBOXの選択結果を受け取る方法は幾つか考えられますが、おおまかに言うと Main Activity を介して受け取る方法と、PFragmentで受け取る方法の2種類があります


(画像URL:illust-AC 様:casa4さん)

Main Activity を介して応答を受け取る方法(A、B)でも良いのですが、少し回りくどい感じになりますので、ここではPFragmentで応答を受け取る方法(C)を紹介したいと思います。

 

【詳細】

事前準備

DialogFragmentからPFragmentに応答を戻すためには、DialogFragmentが、応答の戻し先となるPFragmentを特定できる必要があります。

このような時 Androidでよく行うのは、ダイアログBOX生成時に setTargetFragment()で呼び出し元となるFragmentを記憶し、ダイアログBOXのボタン押下時などにgetTargetFragment()で呼び出し元を特定して、応答を返す方法です。

しかしこの方法は、PROCESSINGのAndroidModeでは正しく動作しません(PROCESSINGのAndroidModeが、残念な実装になっている為です・・・)。

かわりにMain Activity からPFragmentを生成する箇所に手を入れて、PFragmentに一意のIDを付与しておきます。

そして、ダイアログBOXのボタン押下時などに findFragmentById() する事で、呼び出し元を特定し、応答を返します。

Main ActivityでSketchとPFragmentを生成する箇所は、公式サイト の例に従っていれば、上記のようになっている筈です。

PFragmentに直接IDをつける事はできませんので、PFragmentが使う画面(View:FrameLayout)にIDを付けます。★印の箇所ですね。

公式サイト のコーディングでは getUniqueViewId() 命令でランダムなIDを付与していますが、ここを下記のようにして、私達が利用しやすいIDを付与するように変更します。

本例では sketchFrame という名前を付けています。名前はみなさんの都合が良いものに変更して下さい。

また、res¥values¥strings.xml に以下の記述を追加します。

ここで、「なぜViewにIDを付与したら、PFragmentにIDを付与した事になるんだ?」と疑問に思ったあなたは鋭いです(笑)。

実は fragment.setView() を行うと、PFragment が内部で

のような処理を行っているのです。

Viewに与えたIDを、PFragmentの実体を作成する際のIDにも流用しているのですね。よって、以降は FragmentManager # findFragmentById( R.id.sketchFrame ) で探せば、PFragmentを見つけることができるようになります。

 

ダイアログBOXを作成する

今回の例では、ダイアログBOXの作成にはDialogFragmentを継承した専用のクラス(MyDialogクラス)を用意しています。

MyDialogクラスは以下のようになっています。

Sketch側は以下のような感じです。

下記に、ポイントとなる箇所を説明します。

 

A:DialogFragmentを継承する

ダイアログBOXクラスには、DialogFragmentを継承する必要があります。

DaialogFragmentには

  • android.app.DialogFragment
  • android.support.v4.app.DialogFragment

の2つがありますが、特別な理由がない限り android.support.v4.app.DialogFragment の方を利用して下さい。

 

B:余計なステータスバーやナビゲーションバーを隠す

PROCESSINGを全画面モードで利用していると、普段はステータスバーやナビゲーションバーが表示されることはありません。しかしダイアログBOXを開くと、突然これらが表示されるようになってしまいます。

しかもダイアログBOXを閉じても、ステータスバーやナビゲーションバーが自動で隠れる事がありません・・・。

これを防ぐためには、ダイアログBOXにフォーカスを当てないようにします。

ただしフォーカスを外すとキーボード操作を受け付けなくなりますので、ダイアログBOX内でテキスト入力が行いたいような場合には、別の手段を用いる必要があります。

ダイアログBOXからフォーカスを外すには、onActivityCreated をオーバライドして、以下の記述を加えてください。

 

C:デフォルトコンストラクタを定義する

DialogFragmentもFragmentの一種ですので、必ず引数が無いデフォルトコンストラクタを定義してください。

これがないと普段は正常に動作していても、ある時(メモリが少なくなった時など)にダイアログBOXが不正終了する原因となります。

 

D:Bundleに保存し、Bundleから取り出す

上位(本例ではSketch)から渡されたパラメータで、ダイアログBOXの表示に必要なものは、必ずBundleに保存しておきます。内部変数に格納してはいけません。

そしてダイアログBOXを表示する処理(onCreateDialog)の中で、Bundleから取り出して利用します。以下のような感じです。

もちろん上位から何も受け取る必要がない場合は、Bundleに保存する必要はありません。

このあたりの理屈がわからない方は、下記コラムを参照してみてください。

 

E:戻し先を特定する

戻し先となるFragmentは、FragmentManager # findFragmentById( R.id.sketchFrame ) で見つけます。

FragmentManager は Activity の getSupportFragmentManager() で取得します。

getActivity() や findFragmentById() は、ボタン押下処理などの中に記述します。

ActivityやFragmentを、ファクトリーメソッドやデフォルトコンストラクタの中で取得して、変数に覚えるような作りにしてはいけません。

どうしてもボタン押下処理の中で記述したくない場合は、onActivityCreated をオーバライドして、その中に記述するのが良いでしょう。

FragmentやFragmentManagerには、それぞれ

  • android.app.Fragment
  • android.support.v4.app.Fragment
  • android.app.FragmentManager
  • android.support.v4.app.FragmentManager

の2つがありますが、特別な理由がない限り android.support.v4.app 側を利用して下さい。

 

F:複数の値を呼び出し元に伝えるには Intent を使う

上位に複数の値を戻す必要があるときは、Intent を使うと便利です。

今回の例であれば、応答の返却先となる上位の  onActivityResult メソッドは、引数にIntentを持っていますので、任意のパラメータを伝えることが可能です。

 

コラム:Fragmentに関する予備知識

DialogFragmentについて

問い合わせを行う画面を「ダイアログBOX」と呼びます。

昔は Activity.showDialog() というAndroid SDKの命令でダイアログBOXを作成しましたが、この方法はAPI13から非推奨となりました。

AndroidでダイアログBOXを作成する時は、showDialog ではなく、DialogFragment を利用します。

DialogFragmentは、その名の通りFragmentの一種です。

Androidの画面はActivityという単位で実行されますが、最近はActivityよりも応用が効くFragmentという単位で実行される事が多くなってきました。

Fragmentは「画面を持った部品」のような存在で、親となるActivityを持ち、Activityから作成されます。またFragmentからFragmentを作成する事も可能です。

表示したい画面ごとにFragmentを用意し、Fragmentを切り替えることで、画面遷移したようにみせかける事が可能なのです。

(画像URL:illust-AC 様:かみたまさん)

実はAndroid上で動作するPROCESSINGのプログラム(Sketch)も、Main Activityから呼び出される Fragment の一種(PFragment)です。

つまりダイアログBOXを表示するには、Fragment(PFragmentとSketch)からサブFragment(DialogFragment) を呼び出す形式となります(ここ重要!)。

絵にすると下図のようになります。


Fragmentは、1つのアプリケーションに幾つも作成する事ができます。各Fragmentは、Fragmentを作成する際に「TAG」とよばれる名前や一意のID(番号)を与える事で識別します。

FragmentはFragmentManagerと呼ばれるクラスで管理されます。作成したFragmentをFragmentManagerに対して、登録したり、入れ替えたり、削除するようにお願いする事で、画面に表示される仕組みとなっています。

(画像URL:illust-AC 様:natuさん、ヤマシタポテコさん)

 

Fragmentを使う際の注意

DialogFragmentもFragmentの一種ですので、Fragmentを扱う時と同じ注意事項があります。

それはダイアログBOX表示中にスマホを回転させたり、メモリが少なくなると、ActivityとFragmentが勝手に再作成される事です。

Windowsなどで動作するプログラムしか作ったことがないとピンとこないのですが、Android OSではメモリやバッテリーの節約のために、表示されなくなった画面はOSが勝手に破棄する仕組みとなっています。

またスマホを回転させたタイミングでも破棄されます。

画面が勝手に破棄されると、使う側(私達)にとっては都合が悪いので、その画面を再度表示する必要が生じた時に、これまたOSが勝手に画面を再作成して、何事もなかったかのように見せかけているのです。

(画像URL:illust-AC 様:キラーT細胞さん、K-factoryさん)

ActivityやFragmentが再作成されると、変数に覚えていた内容も初期化されてしまいます。

これの何が問題かというと、パラメータなどで渡された内容や、計算結果を変数に覚えておくと、再作成と共にその内容が忘れられてしまうという事です。

今回であればダイアログBOXに表示するタイトルや本文を、SketchからDialogFragmentにパラメータとして渡しますが、これをDialogFragmentの内部変数に覚えておくのはNGとなります。

(画像URL:illust-AC 様:キラーT細胞さん、K-factoryさん)

Androidにはこのような状況に対応するために、Bundleという仕組みがあります。

Bundleに記憶された内容は、ActivityやFragmentを再作成しても失われる事がありません。いうなれば大切なデータを金庫にしまうようなものですね(笑)。

必要なタイミングで大切な情報をBundleに保存しておき、画面が再作成されたら、ActivityやFragmentの開始段階で、Bundleに保存しておいた情報を取り出して、変数に再セットするのです。

(画像URL:illust-AC 様:キラーT細胞さん、K-factoryさん、よろづやさん)

ただし、OSが自動的にBundleから復元してくれるわけではありませんから、復元処理は私達の方で行う必要があります。

またダイアログBOXで選択された結果を、呼び出し元であるSketch(PFragment)に伝えるためには、DailogFragment側で「誰から呼ばれたのか」を特定できる必要があります。

このような時のために、AndroidにはsetTargetFragment()とgetTargetFragment()という命令が用意されています。

DialogFragment#setTargetFragment() を実行すると、ちょうど Bundle に変数を覚えるのと同じような仕組みで、戻り先となる Fragment の情報を覚えておくことができます。

そして呼び出し元に結果を伝えたい時に、DialogFragment#getTargetFragment() で戻り先の情報を取り出し、該当Fragmentの特定メソッドへ応答を返すのが一般的な手法となります。

(画像URL:illust-AC 様:よろづやさん、minokikakuさん、ナナとハチさん)

ただしこの方法は、PROCESSINGのAndroidModeでは利用できないので注意してください。

PROCESSINGで応答を戻す方法については、詳細の説明を参考にしてください。

この他にもFragmentを扱う時には、以下のような注意事項があります。

  • 引数なしのコンストラクタを用意する事
  • ファクトリーメソッドを使ってインスタンスを作成する事

少し補足しておくと、Fragmentの処理を独立したクラスで実現している場合、該当クラスのコンストラクタに引数を渡してインスタンスを作成するようなコーディングはNGとなります。

以下はNGな例です。

そうではなく、以下のようにします。

ファクトリーメソッドと呼ばれる関数を用意して、そこでインスタンスを作成します。

そしてコンストラクタは、引数無しのものを必ず用意しておきます。

この辺りについては、以下のページなどが参考となります。

 

【関連記事】

 


自力ダイアログBOX例:

Android SDK を利用せずに、PROCESSINGの命令だけでダイアログBOXっぽいものを表示する例です。

画面をタッチすると、OKとCANCELボタンをもったダイアログBOXを表示し、ボタンの入力を受け付けます。

単純なダイアログBOXを表示するなら、Android SDKの命令を使ったほうが楽ですね(汗)。

<出力サンプル>

(画像URL:photo-AC 様:acworksさん、涼介さん、 illust-AC 様:casa4さん


PROCESSING逆引きリファレンス一覧 へ戻る

本ページで利用しているアイコン画像は、下記サイト様より拝借しております。各画像の著作権は、それぞれのサイト様および作者にあります。