こんにちは。
2021年の年末はAndroid Studioをいじくっていました。完全に素人からのスタートで、触って3日くらいです。
ただ、私は一応、javaについては足し算引き算できるくらいには書けますので、
つまづく所は主にAndroidならではの要素の部分でして、つまりは、画面の表示だったり画面遷移の話だったりするわけです。
環境
android studio APIレベル32 (java)
ActivityからActivityの画面遷移
そもそもActivityで画面遷移すべきかどうか、という話があるようです。調べていると情報が錯綜する!
おそらくはActivityは「シーン」で1つだけあれば良く「その中」での切り替えはFragmentを使うというのが、”たぶん”正しそうです。
ただ私がざっと調べた所「Fragmentはあくまで画面表示パーツであって画面そのものではない」理解に行きました。画面の一部を書き換えるイメージ。で、画面の大部分を置き換えればそれは実質画面の切り替えになる、とかそういう。(そして大画面のタブレットなら両方並べても良いじゃない。ってことかな)
今回はActivity間遷移を扱います。
要約
- 行ったきりでいいなら
startActivity
を使う - 行って戻ってきたいなら
startActivityForResult
が今の所使える - 引数や戻り値はIntentに入れて渡す。文字列・数値ほか、Serializableであるクラスのインスタンスが渡せる
行ったきりでよいActivityの遷移
MainActivityからNextActivityに移動して、戻るボタンで戻るかもしれないけど、MainActivityはそれを知らなくても良いし戻り値も不要だって場合。タイトルからメイン画面への遷移とか、あるいは閉じるときは戻るボタンで良いAbout画面とか。設定画面なら共有の設定に保存して閉じれば良いとか、そういう感じのものですね。
内容自体はそのまんまチュートリアルのコレです。ただこのサンプルでは文字列しか渡していません。
MainActivity
/** 別のアクティビティに遷移する */
private void MoveGamen(){
Intent intent = new Intent(this.getApplicationContext(), NextActivity.class)
intent.putExtra("Param", paramObject);
startActivity(intent);
}
Intentを作成して、引数を格納したらstartActivity()
します。
IntentのputExtra()
で引数を格納できます。文字列や数値なら大概のものが入りますが、任意のクラスのオブジェクトを渡すのであればそれはSerializable(後述)である必要があります。
- Intentは「何かを行うという意図」を表わすものと書かれていますけど、ここでは引数などの次のActivityで必要なモノを一つに梱包したもの、的な感じですかね?
NextActibity
protected void onCreate(Bundle savedInstanceState) {
// 通常の処理がこのへんに //
ParamData paramData = (ParamData) getIntent().getSerializableExtra("Param");
}
渡した引数は、IntentからgetSerializableExtra
で取得できます。
どこででも出来そうですけどonCreate()
内で取得してフィールド変数に持っておけばよいのではないかと。
ちなみに、引数の"Param"
は文字列。MainActivityとNextActibityで同じ名称で渡しあえるなら何でも良いようです。
どこかで定義しておくほうが行儀は良いでしょう。
- ちなみに、Nextから別のActivityを呼びたいなら、同じことをすればよく、閉じたい場合は
finish
です。
行って戻ってきたい場合のActivityの遷移
MainActivityからSubActivityに遷移するが、その後Mainに戻ってくる。たとえば、一覧画面から詳細画面を表示して一覧画面に戻りたい場合ですね。「行ったきり」で詳細から一覧に戻ることもできますがそれだと「戻る」履歴に全部残りますし詳細で変更したものを一覧に戻すのが結構面倒くさい。
MainActivity①
private static final int REQUEST_SUB = 1;
/** 別のアクティビティに遷移する */
private void MoveGamen(){
Intent intent = new Intent(this.getApplicationContext(), SubActivity.class)
intent.putExtra("Param", paramObject);
startActivityForResult(intent, REQUEST_SUB);
}
Intentを作成して、引数を格納するまでは同じですが、startActivity
の代わりにstartActivityForResult
を今のところ使います。が、最近startActivityForResult
は非推奨となりました。
まだ動きますし、代替コードについてもベストプラクティスはまだ無さそう、とのことなんで旧仕様でいきます。
なお、REQUEST_SUB
というのは識別の数値です。後述。
SubActibity①
protected void onCreate(Bundle savedInstanceState) {
// 通常の処理がこのへんに //
ParamData paramData = (ParamData) getIntent().getSerializableExtra("Param");
}
Subの方の受け取り方は「行ったきり」と同じです。重点は戻り方ですね。
SubActivity②
/** 元のアクティビティに戻る */
private void BackGamen(){
Intent intent = new Intent();
intent.putExtra("Ret", resObj);
setResult(RESULT_OK, intent);
finish();
}
finish
をするとActivityが終了するわけですが、その前に呼び出し元に終了することを告げます。
Intentを作成して、戻り値を格納します。もちろん、戻り値がオブジェクトならSerializable
である必要があります。
(Intent作成時の引数が省略されているのはちょっと分かりません;)
MainActivity②
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case (REQUEST_SUB):
if (resultCode == RESULT_OK) {
Log.d("myApp","Return OK");
ResultData resObj = (ResultData) data.getSerializableExtra("Ret");
// 処理などを実装 //
} else if (resultCode == RESULT_CANCELED) {
Log.d("myApp","Return Cancel");
}
break;
default:
break;
}
}
その後、終了が告げられたMainActivityがそれに対応します。
この仕組み自体はActivityの親クラスの方で実装されているようなので、こちらはOverrideするだけです。
onActivityResult
です。
requestCode
は、最初にstartActivityForResult
した時に付けた識別の数値(ここではREQUEST_SUB
)です。この数値、SubActibityの方ではそれを弄る機会が無かった(可能かどうかは別として)ので、基本的にMainActibityが送り出した時に預けた数値がそのままで帰ってくるわけですね。よってこれを見れば「どこのActivityが終了したのか」を判別できます。
resultCode
はSubActibityの方がsetResult
した値です。送り出した後どうだったかを向こう側で回答を入れてきます。OKならIntentからデータを取り出して更新処理などを行います。
なお、RESULT_OK
、RESULT_CANCELED
はActivityの親クラスかどこかで定義されているのでMainやSubで宣言する必要はありません。同じ値で持っています。
Serializableについて
やり方自体は簡単でデータ受け渡し用のモデルクラスの宣言にimplements Serializable
を書くだけです。
public class ParamData implements Serializable {/*‥‥*/}
public class ResultData implements Serializable {/*‥‥*/}
それだけだとアレなんで説明しておきますと、implements Serializable
記述を付けると、javaはこのクラスがシリアライズ可能だと判断します。判断のために必要なので、それためのメソッド実装は必要ありません。
また、シリアライズを雑に言うと、オブジェクトをバイト文字列にする機能です。これにより実体化、保存、復元が容易になります。詳細は調べてください。
で、なぜこれが必要かと言いますと、
ActivityやFragmentの移動間については参照渡しが基本的にできない、ということで実体化して複製を渡すために必要なようです。
Activityはそれぞれが別の状態を持っており、状況に応じて生きていたり・死んでいたり・再作成されたりするので、同じオブジェクトを複数のActivityで共有するのは危ないからですね。‥‥たぶん。
(よってIntentに一回入れたオブジェクトは元のオブジェクトとは独立しています)
なお、必要性があってどうしてもグローバル変数が必要ならandroid.app.Application
に追加する形で持てるようです。
startActivityForResultは非推奨
ちなみに非推奨の件ですが、
基盤となる startActivityForResult() API と onActivityResult() API はあらゆる API レベルの Activity クラスで使用できますが、AndroidX Activity と Fragment で導入された Activity Result API を使用することを強くおすすめします。
とのことです。
Fragmentは正直まだ把握できていないのでまた何か見えたら書きます。
初心者向けの本やメンターの解説サイトを見ると、大抵が遷移することだけ、そのサンプルを作ることのみに特化した感じのことしか書かれておらず、引数や戻り値の受け渡しといった汎用的なことがあまり無かったので、調べた事を纏めておきます。
独学のため正確でない可能性があります。
(っ・x・)っ きゅ