フラグメントとアクティビティの通信


2014年4月15日現在、私が理解している内容です。
間違っている場合があります。


フラグメントから、アクティビティにボタンが押されたことを通知したい

フラグメントから、アクティビティにボタンが押されたことを通知するための、コールバックというしくみに、
それなりの手順が必要ということで、メモしておこうというコーナーです。

まずは、こんなサンプルを用意。
フラグメントのサンプルコードのサンプルコードに、ボタンとテキストを足したものです。
左の黄色の画面はアクティビティ。その右側は、フラグメントで表示。

フラグメント側のボタンを押して、アクティビティの画面の背景色を変えてみようという趣向です。

ボタンを押すと、右側のフラグメントの画面では、ボタンが押されたことがリスナーで受け取れますが、
アクティビティ側に情報を渡す部分のコードが書かれてないので、。
このままでは、アクティビティ側は、ボタンが押されたことを知ることが出来ません。



APIレベル4以後で動くように、サポートライブラリを使用したサンプルです。
フラグメント用XMLファイルは使用していません。
AndroidManifest.xmlは、新規作成状態から変更していません。

FragmentF1.java

package com.example.test10; import android.support.v4.app.Fragment; //これをインポートすること //import android.app.Fragment; //これをインポートするとエラーになる import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Color; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.View.OnClickListener; import android.view.ViewGroup.LayoutParams; import android.widget.Button; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; public class FragmentF1 extends Fragment{ public FragmentF1() {} private TextView text1; private TextView text2; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { //レイアウトを作る LinearLayout layout1 = new LinearLayout(getActivity()); //レイアウトは縦に並ぶ layout1.setOrientation(LinearLayout.VERTICAL); //Bundleのデータ読み込み String moji1 = getArguments().getString("data1"); //----------------------------------------- //アイコン画像を読み込み Bitmap img1 = BitmapFactory.decodeResource( getResources(),R.drawable.ic_launcher); //アイコン画像を貼り付ける画面 ImageView imgvw1 = new ImageView(getActivity()); //背景色 imgvw1.setBackgroundColor(Color.argb(255, 255, 0, 0)); //画面にアイコン画像をセット imgvw1.setImageBitmap(img1); //サイズをセット LayoutParams para1 = new LayoutParams(200,200); //画面のサイズをセット imgvw1.setLayoutParams(para1); //アイコンを描画した画面を並べる layout1.addView(imgvw1); //----------------------------------------- //テキストを貼り付ける text1 = new TextView(getActivity()); //テキストをセット text1.setText(moji1); //テキストを並べる layout1.addView(text1); //----------------------------------------- //ボタンをつくる Button button1 = new Button(getActivity()); //ボタンに文字を表示 button1.setText("Blue"); //ボタンをレイアウトに追加 layout1.addView(button1); //----------------------------------------- //ボタンをつくる Button button2 = new Button(getActivity()); //ボタンに文字を表示 button2.setText("Green"); //ボタンをレイアウトに追加 layout1.addView(button2); //----------------------------------------- //テキストを貼り付ける text2 = new TextView(getActivity()); //テキストをセット text2.setText("ボタンが押された?"); //テキストを並べる layout1.addView(text2); //----------------------------------------- //ボタンのリスナー button1.setOnClickListener( new OnClickListener() { //ボタンが押されたら何かする @Override public void onClick(View v) { //テキストをセット text1.setText("Blueが押された"); } }); //ボタンのリスナー button2.setOnClickListener( new OnClickListener() { //ボタンが押されたら何かする @Override public void onClick(View v) { //テキストをセット text1.setText("Greenが押された"); } }); //----------------------------------------- return layout1; } public void ChangeColor1(String color){ //テキストをセット text2.setText(color); } }



MainActivity.java

package com.example.test10; import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.LinearLayout; import android.os.Bundle; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Color; public class MainActivity extends FragmentActivity { private static final int LAYOUT_ID1 = 777; private ImageView view1; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //---------------------------------------- //親レイアウト1 LinearLayout L_layout1 = new LinearLayout(this); setContentView(L_layout1); //横に並ぶ L_layout1.setOrientation(LinearLayout.HORIZONTAL); //----------------------------------------- //アイコン画像をビューに読み込む Bitmap image1 = BitmapFactory.decodeResource(getResources(),R.drawable.ic_launcher); view1 = new ImageView(this); view1.setImageBitmap(image1); //サイズ LinearLayout.LayoutParams params1 = new LinearLayout.LayoutParams(200, ViewGroup.LayoutParams.MATCH_PARENT); view1.setLayoutParams(params1); //背景を黄色にする view1.setBackgroundColor(Color.argb(255, 255, 255, 0)); //アイコン画像のビューを親レイアウト1に追加 L_layout1.addView(view1); //-------------------------------------------- //子レイアウト2 LinearLayout L_layout2 = new LinearLayout(this); //子レイアウト2の背景を水色にする L_layout2.setBackgroundColor(Color.argb(255, 0, 255, 255)); //サイズ LinearLayout.LayoutParams layoutParams2 = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); //子レイアウト2を親レイアウト1に追加 L_layout1.addView(L_layout2,layoutParams2); //-------------------------------------------- //子レイアウト2に、フラグメントを設定するためのID L_layout2.setId(LAYOUT_ID1); if ( savedInstanceState == null ) { //アクティビティが生成された時に1度だけフラグメントに描画 Put_fragment1(); } } public void Put_fragment1(){ //FragmentManagerを設定 FragmentManager manager = getSupportFragmentManager(); //Transactionを設定 FragmentTransaction ft = manager.beginTransaction(); //---------------------------------- //フラグメントに貼るデータを作る FragmentF1 frag1 = new FragmentF1(); Bundle bd1 = new Bundle(); bd1.putString("data1", "フラグメントデータ"); //フラグメントにバンドルを渡す frag1.setArguments(bd1); //---------------------------------- //フラグメントに追加する ft.add(LAYOUT_ID1, frag1); ft.commit(); } }


AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.test10" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="18" /> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name="com.example.test10.MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>





アクティビティに、ボタンが押されたことを通知する

まず、FragmentF1.javaに変更を加えていきます。

class FragmentF1に、インターフェイスとして、
「onFragmentButtonClickedListener」「onFragmentButton1」を用意します。
名前は適当でいいです。

FragmentF1.java

//ボタンの押されたことを通知 public interface onFragmentButtonClickedListener { public void onFragmentButton1(int button); }


「listener」を冒頭に追加します。


public class FragmentF1 extends Fragment{ public FragmentF1() {} private TextView text1; private TextView text2; private onFragmentButtonClickedListener listener;


ボタンのリスナーのコードの中で、「listener.onFragmentButton1」を呼びます。
ボタンが押されたことがアクティビティにコールバックされます。


//ボタンのリスナー button1.setOnClickListener( new OnClickListener() { //ボタンが押されたら何かする @Override public void onClick(View v) { //テキストをセット text1.setText("Blueが押された"); listener.onFragmentButton1(1); } }); //ボタンのリスナー button2.setOnClickListener( new OnClickListener() { //ボタンが押されたら何かする @Override public void onClick(View v) { //テキストをセット text1.setText("Greenが押された"); listener.onFragmentButton1(2); } });


「onAttach」を追加して、中身を記述します。
「onAttach」は、フラグメントがアクティビティと関連付けられた時に、一度だけ呼ばれます


//Activityに関連付けされた時に一度だけ呼ばれる @Override public void onAttach(Activity activity) { super.onAttach(activity); listener = (onFragmentButtonClickedListener)activity; }


「Activity」でエラーが出るので、
「import android.app.Activity;」をインポートで追加します。


import android.app.Activity;






アクティビティ側から、ボタンが押されたことの通知を受けとる

次に、MainActivity.javaに変更を加えていきます。

1行目に、「implements onFragmentButtonClickedListener」を追加します。

MainActivity.java

public class MainActivity extends FragmentActivity implements onFragmentButtonClickedListener{


すると、エラーになるはずですので、eclipseの指示に従い、
「onFragmentButtonClickedListener」を、FragmentF1から、インポートします。


import com.example.test10.FragmentF1.onFragmentButtonClickedListener;


すると、また、エラーになるはずですので、eclipseの指示に従い、
FragmentF1.onFragmentButtonClickedListener.onFragmentButton1(int)を実装します。
eclipseのエラーメッセージから「実装されてないメソッドの追加」を選択します。
以下のメソッドが自動生成されたはずです。


@Override public void onFragmentButton1(int button) { // TODO 自動生成されたメソッド・スタブ }


ここで、フラグメントでボタンが押されたことがコールバックされます。
中身を次のように記述します。

「ChangeColor1」で、アクティビティからフラグメントに、情報を送り返しています。


@Override public void onFragmentButton1(int button) { // TODO 自動生成されたメソッド・スタブ //フラグメントマネージャーで、フラグメントを取得 FragmentManager manager = getSupportFragmentManager(); FragmentF1 frag2 = (FragmentF1)manager.findFragmentByTag("fragment_tag1"); switch(button){ case 1: //背景を青色にする view1.setBackgroundColor(Color.argb(255, 0,0, 255)); //フラグメントのテキストを書き換える frag2.ChangeColor1("青色になった"); break; case 2: //背景を緑色にする view1.setBackgroundColor(Color.argb(255, 0, 255, 0)); //フラグメントのテキストを書き換える frag2.ChangeColor1("緑色になった"); break; } }


アクティビティからフラグメントに情報を送り返すために、
フラグメントマネージャーを使っています。

フラグメントマネージャーが、フラグメントを識別できるようにタグを付けます。
「ft.add(LAYOUT_ID1, frag1);」を、「ft.add(LAYOUT_ID1, frag1,"fragment_tag1");」に書き換えます。



//フラグメントにタグ付きで追加する ft.add(LAYOUT_ID1, frag1,"fragment_tag1");




以上で、完成です。





できあがり


フラグメントのボタンを押すと、アクティビティ画面の背景色が変わるようになりました。
画面を回転させると、アクティビティは初期化されて黄色の背景に戻ります。




ハマったポイント。

アクティビティからフラグメントに情報を渡すとき、最初、FragmentManagerを使わず、普通のsetterとして、


FragmentF1 frag1 = new FragmentF1(); frag1.ChangeColor1("青色になった");

とか、記述してみましたが、いろいろと問題がありました。
まず、「frag1」をstaticにしないと落ちます。

さらに、画面回転させたときに、フラグメントが再生成されるようで、「ChangeColor1()」が利かなくなるようです。
ボタンを押しても、「青色になった」「緑色になった」というテキストメッセージの変更ができません。

「FragmentF1.java」の、TextViewを、staticにすると回避できましたが、あまりかっこよくありません。

フラグメントを破棄させない「setRetainInstance(true) 」というものを試してみました。
FragmentF1.javaのonCreateViewに追加します。
その場合、画面回転後でも、ボタンを押した時の「青色になった」「緑色になった」というメッセージの変更が有効になりました。

今回は、フラグメントをタグ付きで追加し、
タグを使って、アクセスすべきフラグメントを特定するという方法を使いました。
なんとなく最も正攻法な気がしましたので。




FragmentF1.java

package com.example.test10; import android.support.v4.app.Fragment; //これをインポートすること //import android.app.Fragment; //これをインポートするとエラーになる import android.app.Activity; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Color; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.View.OnClickListener; import android.view.ViewGroup.LayoutParams; import android.widget.Button; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; public class FragmentF1 extends Fragment{ public FragmentF1() {} private TextView text1; private TextView text2; private onFragmentButtonClickedListener listener; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { //レイアウトを作る LinearLayout layout1 = new LinearLayout(getActivity()); //レイアウトは縦に並ぶ layout1.setOrientation(LinearLayout.VERTICAL); //Bundleのデータ読み込み String moji1 = getArguments().getString("data1"); //----------------------------------------- //アイコン画像を読み込み Bitmap img1 = BitmapFactory.decodeResource( getResources(),R.drawable.ic_launcher); //アイコン画像を貼り付ける画面 ImageView imgvw1 = new ImageView(getActivity()); //背景色 imgvw1.setBackgroundColor(Color.argb(255, 255, 0, 0)); //画面にアイコン画像をセット imgvw1.setImageBitmap(img1); //サイズをセット LayoutParams para1 = new LayoutParams(200,200); //画面のサイズをセット imgvw1.setLayoutParams(para1); //アイコンを描画した画面を並べる layout1.addView(imgvw1); //----------------------------------------- //テキストを貼り付ける text1 = new TextView(getActivity()); //テキストをセット text1.setText(moji1); //テキストを並べる layout1.addView(text1); //----------------------------------------- //ボタンをつくる Button button1 = new Button(getActivity()); //ボタンに文字を表示 button1.setText("Blue"); //ボタンをレイアウトに追加 layout1.addView(button1); //----------------------------------------- //ボタンをつくる Button button2 = new Button(getActivity()); //ボタンに文字を表示 button2.setText("Green"); //ボタンをレイアウトに追加 layout1.addView(button2); //----------------------------------------- //テキストを貼り付ける text2 = new TextView(getActivity()); //テキストをセット text2.setText("ボタンが押された?"); //テキストを並べる layout1.addView(text2); //----------------------------------------- //ボタンのリスナー button1.setOnClickListener( new OnClickListener() { //ボタンが押されたら何かする @Override public void onClick(View v) { //テキストをセット text1.setText("Blueが押された"); listener.onFragmentButton1(1); } }); //ボタンのリスナー button2.setOnClickListener( new OnClickListener() { //ボタンが押されたら何かする @Override public void onClick(View v) { //テキストをセット text1.setText("Greenが押された"); listener.onFragmentButton1(2); } }); //----------------------------------------- return layout1; } public void ChangeColor1(String color){ //テキストをセット text2.setText(color); } //ボタンの押されたことを通知 public interface onFragmentButtonClickedListener { public void onFragmentButton1(int button); } //Activityに関連付けされた時に一度だけ呼ばれる @Override public void onAttach(Activity activity) { super.onAttach(activity); listener = (onFragmentButtonClickedListener)activity; } }


MainActivity.java

package com.example.test10; import com.example.test10.FragmentF1.onFragmentButtonClickedListener; import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.LinearLayout; import android.os.Bundle; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Color; public class MainActivity extends FragmentActivity implements onFragmentButtonClickedListener { private static final int LAYOUT_ID1 = 777; private ImageView view1; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //---------------------------------------- //親レイアウト1 LinearLayout L_layout1 = new LinearLayout(this); setContentView(L_layout1); //横に並ぶ L_layout1.setOrientation(LinearLayout.HORIZONTAL); //----------------------------------------- //アイコン画像をビューに読み込む Bitmap image1 = BitmapFactory.decodeResource(getResources(),R.drawable.ic_launcher); view1 = new ImageView(this); view1.setImageBitmap(image1); //サイズ LinearLayout.LayoutParams params1 = new LinearLayout.LayoutParams(200, ViewGroup.LayoutParams.MATCH_PARENT); view1.setLayoutParams(params1); //背景を黄色にする view1.setBackgroundColor(Color.argb(255, 255, 255, 0)); //アイコン画像のビューを親レイアウト1に追加 L_layout1.addView(view1); //-------------------------------------------- //子レイアウト2 LinearLayout L_layout2 = new LinearLayout(this); //子レイアウト2の背景を水色にする L_layout2.setBackgroundColor(Color.argb(255, 0, 255, 255)); //サイズ LinearLayout.LayoutParams layoutParams2 = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); //子レイアウト2を親レイアウト1に追加 L_layout1.addView(L_layout2,layoutParams2); //-------------------------------------------- //子レイアウト2に、フラグメントを設定するためのID L_layout2.setId(LAYOUT_ID1); if ( savedInstanceState == null ) { //アクティビティが生成された時に1度だけフラグメントに描画 Put_fragment1(); } } public void Put_fragment1(){ //FragmentManagerを設定 FragmentManager manager = getSupportFragmentManager(); //Transactionを設定 FragmentTransaction ft = manager.beginTransaction(); //---------------------------------- //フラグメントに貼るデータを作る FragmentF1 frag1 = new FragmentF1(); Bundle bd1 = new Bundle(); bd1.putString("data1", "フラグメントデータ"); //フラグメントにバンドルを渡す frag1.setArguments(bd1); //---------------------------------- //フラグメントに追加する //ft.add(LAYOUT_ID1, frag1); //フラグメントにタグ付きで追加する ft.add(LAYOUT_ID1, frag1,"fragment_tag1"); ft.commit(); } @Override public void onFragmentButton1(int button) { // TODO 自動生成されたメソッド・スタブ //フラグメントマネージャーで、フラグメントを取得 FragmentManager manager = getSupportFragmentManager(); FragmentF1 frag2 = (FragmentF1)manager.findFragmentByTag("fragment_tag1"); switch(button){ case 1: //背景を青色にする view1.setBackgroundColor(Color.argb(255, 0,0, 255)); //フラグメントのテキストを書き換える frag2.ChangeColor1("青色になった"); break; case 2: //背景を緑色にする view1.setBackgroundColor(Color.argb(255, 0, 255, 0)); //フラグメントのテキストを書き換える frag2.ChangeColor1("緑色になった"); break; } } }














戻る