FragmentをActivityからコードで設定してHello Worldを表示させてみましたが、ActivityからFragment画面への切り替えをしてみましょう。
Android Studio 3.1.2
Android 8.1.0
ActivityからFragmentに画面遷移
Fragmentが現実的に使われるケースを想定して、
任意のタイミングで起動
パラメータを渡してインスタンス生成
Activityに戻る
という観点を考えてみます。
任意のタイミングで起動
これはButtonを使って任意のタイミングでFragmentを起動できます。
ActivityにボタンタップでFragmentのインスタンス生成を行います。これは前回のコードで記述したFragmentの張り付け部分を使います。
FragmentはActivityと共に重要な機能を果たしていますが、いまいちわかりにくところがあります。最初に簡単なHello World...
Activity:
Button button01 = findViewById ( R . id . button ) ;
button01 . setOnClickListener ( new View . OnClickListener ( ) {
@Override
public void onClick ( View view ) {
FragmentManager fragmentManager = getSupportFragmentManager ( ) ;
FragmentTransaction fragmentTransaction = fragmentManager . beginTransaction ( ) ;
fragmentTransaction . replace ( R . id . container , new TestFragment ( ) ) ;
fragmentTransaction . commit ( ) ;
}
} ) ;
パラメータを渡してインスタンス生成
Fragmentの場合コンストラクタの引数で渡したり、setterから取り出したりすると問題があります。Googleのdocumentには以下のようにあります。
All subclasses of Fragment must include a public no-argument constructor. The framework will often re-instantiate a fragment class when needed, in particular during state restore, and needs to be able to find this constructor to instantiate it. If the no-argument constructor is not available, a runtime exception will occur in some cases during state restore.
Ref: Fragment
要約すると、
Fragmentは引数のないコンストラクタが必要で、フレームワークは必要に応じてFragmentを再インスタンス化することが多い。引数のないコンストラクタが利用できなと例外が発生することがある。
ということで、引数を前提としてインスタンスを生成させていると、ステムからFragmentの再生成されるときにクラッシュするかもしれないわけです。
解決方法としては、
静的な newInstance() を使ってフラグメントをインスタンス化する
これがベストプラクティスと言われています。
Activity:
× fragmentTransaction . replace ( R . id . container , new TestFragment ( "Fragment" ) ) ;
〇 fragmentTransaction . replace ( R . id . container , TestFragment . newInstance ( "Fragment" ) ) ;
newでインスタンス生成して、Bundleに設定したいパラメータをsetArguments()で追加します。
Fragment:
public static TestFragment newInstance ( String str ) {
// Fragemnt01 インスタンス生成
TestFragment fragment = new TestFragment ( ) ;
// Bundle にパラメータを設定
Bundle barg = new Bundle ( ) ;
barg . putString ( "Message" , str ) ;
fragment . setArguments ( barg ) ;
return fragment ;
}
Bundleから getArguments() で取り出す
Bundle args = getArguments ( ) ;
String str = args . getString ( "Message" ) ;
Activityに戻る
ButtonでFragmentが張り付けを行い、Activityに戻りたい場合にバックスタックを使います。
fragmentTransaction . addToBackStack ( null ) ;
これをトランザクションに設定すると、トランザクションの開始時のFragment状態がスタックに積まれまれて、戻るボタンで前の状態に戻ることが可能になります。
サンプルコード
ActivityにFragmentに画面遷移、”Fragment”の文字列をTextViewに表示させる。(実際はactvity_mainにあるcontainerにFragmentを張り付け)
BackボタンでFragmentからActivityに戻る
まとめてみます。
MainActivity.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import android . support . v4 . app . FragmentManager ;
import android . support . v4 . app . FragmentTransaction ;
import android . support . v7 . app . AppCompatActivity ;
import android . os . Bundle ;
import android . view . View ;
import android . widget . Button ;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate ( Bundle savedInstanceState ) {
super . onCreate ( savedInstanceState ) ;
setContentView ( R . layout . activity_main ) ;
Button button01 = findViewById ( R . id . button ) ;
if ( savedInstanceState == null ) {
button01 . setOnClickListener ( new View . OnClickListener ( ) {
@Override
public void onClick ( View view ) {
FragmentManager fragmentManager = getSupportFragmentManager ( ) ;
FragmentTransaction fragmentTransaction = fragmentManager . beginTransaction ( ) ;
// BackStackを設定
fragmentTransaction . addToBackStack ( null ) ;
// パラメータを設定
fragmentTransaction . replace ( R . id . container ,
TestFragment . newInstance ( "Fragment" ) ) ;
fragmentTransaction . commit ( ) ;
}
} ) ;
}
}
}
TestFragment.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import android . os . Bundle ;
import android . support . annotation . NonNull ;
import android . support . v4 . app . Fragment ;
import android . view . LayoutInflater ;
import android . view . View ;
import android . view . ViewGroup ;
import android . widget . TextView ;
public class TestFragment extends Fragment {
public static TestFragment newInstance ( String str ) {
// Fragemnt01 インスタンス生成
TestFragment fragment = new TestFragment ( ) ;
// Bundle にパラメータを設定
Bundle barg = new Bundle ( ) ;
barg . putString ( "Message" , str ) ;
fragment . setArguments ( barg ) ;
return fragment ;
}
// FragmentのViewを生成して返す
@Override
public View onCreateView ( @NonNull LayoutInflater inflater ,
ViewGroup container ,
Bundle savedInstanceState ) {
return inflater . inflate ( R . layout . fragment_main ,
container , false ) ;
}
@Override
public void onViewCreated ( @NonNull View view , Bundle savedInstanceState ) {
super . onViewCreated ( view , savedInstanceState ) ;
Bundle args = getArguments ( ) ;
if ( args != null ) {
String str = args . getString ( "Message" ) ;
TextView textView = view . findViewById ( R . id . text_fragment ) ;
textView . setText ( str ) ;
}
}
}
onViewCreated() はonCreateView()のViewを作成の直後に呼ばれます。ですからonCreateView()に入れ込んでいる記述も多く見かけます。
activity_main.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<? xml version = "1.0" encoding = "utf-8" ?>
< android . support . constraint . ConstraintLayout
xmlns : android = "http://schemas.android.com/apk/res/android"
xmlns : app = "http://schemas.android.com/apk/res-auto"
xmlns : tools = "http://schemas.android.com/tools"
android : layout_width = "match_parent"
android : layout_height = "match_parent"
android : background = "#dfe"
tools : context = ".MainActivity" >
< TextView
android : text = "@string/activity"
android : layout_width = "wrap_content"
android : layout_height = "wrap_content"
android : textSize = "30sp"
app : layout_constraintBottom_toBottomOf = "parent"
app : layout_constraintEnd_toEndOf = "parent"
app : layout_constraintStart_toStartOf = "parent"
app : layout_constraintTop_toTopOf = "parent" / >
< LinearLayout
android : orientation = "vertical"
android : layout_width = "match_parent"
android : layout_height = "match_parent" >
< Button
android : id = "@+id/button"
android : text = "@string/button"
android : layout_gravity = "center_horizontal"
android : layout_width = "wrap_content"
android : layout_height = "wrap_content" / >
< FrameLayout
android : id = "@+id/container"
android : layout_width = "match_parent"
android : layout_height = "match_parent" / >
< / LinearLayout >
< / android . support . constraint . ConstraintLayout >
fragment_main.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<? xml version = "1.0" encoding = "utf-8" ?>
< android . support . constraint . ConstraintLayout
xmlns : android = "http://schemas.android.com/apk/res/android"
xmlns : app = "http://schemas.android.com/apk/res-auto"
android : background = "#fde"
android : layout_width = "match_parent"
android : layout_height = "match_parent" >
< TextView
android : id = "@+id/text_fragment"
android : layout_width = "wrap_content"
android : layout_height = "wrap_content"
android : textSize = "30sp"
app : layout_constraintBottom_toBottomOf = "parent"
app : layout_constraintEnd_toEndOf = "parent"
app : layout_constraintStart_toStartOf = "parent"
app : layout_constraintTop_toTopOf = "parent" / >
< / android . support . constraint . ConstraintLayout >
strings.xml
< resources >
. . .
< string name = "button" > Button < / string >
< string name = "activity" > Activity < / string >
< / resources >
サンプル動画
Fragmentはactivity_mainのcontainerに張り付けているためButtonが残ってしまいますが、containerを画面一杯にするようにレイアウトすればこれを避けることができます。
関連ページ:
References:フラグメント | Android Developers FragmentTransaction