2013年10月31日木曜日

Fragmentを使ってみる(Activityとの連携)

今回は、FragmentとActivityのやりとりを試したいと思います(`・ω・´)シャキーン
動作的には、上部のFragmentをクリックすると、下部のFragmentのテキストを変更して、
下部のFragmentをクリックすると、上部のFragmentのテキストを変更するようにしました( ̄ー ̄)ニヤリ

各FragmentのイベントをActivityが受け取り、
そのイベントを別のFragmentに伝えるような流れになっています。
■AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.fragment6"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="8" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.example.fragment6.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>

■fragment_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#eeeeee"
    android:gravity="center"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/text_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="10dp" />

    <ImageView
        android:id="@+id/image_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/ic_launcher" />

</LinearLayout>
■activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/layout_linearlayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <LinearLayout
        android:id="@+id/layout_fragment_top"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:background="#ff0000"
        android:orientation="vertical" />
    <LinearLayout
        android:id="@+id/layout_fragment_bottom"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:background="#ff0000"
        android:orientation="vertical" />
    
</LinearLayout>

■MainFragmentTop.java

FragmentからActivityを取得するには、getActivityを利用します。
//FragmentからActivityの取得する
LinearLayout linearLayout = (LinearLayout) getActivity().findViewById(R.id.layout_linearlayout);
コールバックの設定として、Activityにインターフェイスを実装するやり方をしていますが、
別パターンのリスナーとして登録する方法は避けた方が良いそうです。(´・∀・`)ヘー

こちらのサイトにその内容が書かれていますъ(゚Д゚)グッジョブ!!
Fragment から Activity にコールバックする方法
バックグラウンドにある Fragment はメモリが足りなくなると、
システムによって破棄され、必要になったときにシステムによって再生成されることがあるため、
その際 setOnOkBtnClickListener() 部分は呼ばれないのです。

そのため、システムによって再生成された場合、
コールバックを受け取れなくなるということが起こりえます。

以前のエントリで書きましたが、レイアウトで定義した( で定義した)Fragment は
FragmentTransaction の対象にしてはいけないため、
上記のコードの MainFragment が onCreate() 内で生成されるのではなく、
レイアウトXMLファイルで定義されている場合は、問題になることは(たぶん)あまりないと思います。

ただ、いずれにしてもこの実装はさけた方が懸命です。

で、どうするかというと、Activity 自体にリスナーを実装するようにします。
(私はあまりこの書き方は好きじゃないのですが、Fragment がシステムから再生成されうるのでしょうがないです。。。)

package com.example.fragment6;

import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

public class MainFragmentTop extends Fragment {

 public final static String TAG = "MainFragmentTop";

 // コールバック
 private FragmentTopCallback mCallback;

 /*
  * コールバックのインターフェイス
  */
 public static interface FragmentTopCallback {

  public void onClick(TextView v);

 }

 /*
  * Fragmentが Activityに対して何らかのコールバックを提供する場合、
  * Activityが必要なインタフェースを備えているかどうかチェックなどを行う。
  * 
  * @see android.support.v4.app.Fragment#onAttach(android.app.Activity)
  */
 @Override
 public void onAttach(Activity activity) {
  super.onAttach(activity);
  Log.d(TAG, "FragmentTop-onAttach");

  // Activityがコールバックを実装しているかチェック
  if (activity instanceof FragmentTopCallback == false) {
   throw new ClassCastException(
     "activity が FragmentTopCallback を実装していません.");
  }
  //
  mCallback = (FragmentTopCallback) activity;
 }

 /*
  * Fragment がユーザに見える状態になるまで保持しておくべきコンポーネントの初期化。
  * 
  * @see android.support.v4.app.Fragment#onCreate(android.os.Bundle)
  */
 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  Log.d(TAG, "FragmentTop-onCreate");

  // fragment再生成抑止。Fragment を破棄させないようにする。
  setRetainInstance(true);

 }

 /*
  * Fragment が持つ View を構築する状態です。 View を持たない Fragment は、ここで null を返す
  * 
  * @see
  * android.support.v4.app.Fragment#onCreateView(android.view.LayoutInflater,
  * android.view.ViewGroup, android.os.Bundle)
  */

 @Override
 public View onCreateView(LayoutInflater inflater, ViewGroup container,
   Bundle savedInstanceState) {
  Log.d(TAG, "FragmentTop-onCreateView");
  // Bundleで保存されたデータを復元
  String title = getArguments().getString("title");

  //
  Log.d("onCreateView", title);

  // FragmentからActivityの取得する
  LinearLayout linearLayout = (LinearLayout) getActivity().findViewById(
    R.id.layout_linearlayout);
  //
  Log.d(TAG, "onCreateView" + String.valueOf(linearLayout));

  // 第1引数:レイアウトXMLファイルのリソースID、
  // 第2引数:
  // 第3引数:trueにするかfalseにするかで戻り値となるルートビュー(View)が変わる。
  // trueの場合には、第2引数で渡したViewGrou、falseの場合には第1引数で渡したリソースIDがルートビューになる
  View view = inflater.inflate(R.layout.fragment_main, container, false);
  // FragmentのTextViewの取得
  final TextView text = (TextView) view.findViewById(R.id.text_view);
  //
  text.setText(title);
  //
  text.setTag(TAG);
  // FragmentのImageViewの取得
  ImageView image = (ImageView) view.findViewById(R.id.image_view);
  //
  // image.setTag(TAG);
  //
  image.setOnClickListener(new OnClickListener() {
   @Override
   public void onClick(View v) {
    if (mCallback != null) {
     mCallback.onClick(text);
    }
   }
  });
  //
  return view;
 }
}
■MainFragmentBottom.java
基本的なコードはMainFragmentTopと殆ど同じコードになります。
package com.example.fragment6;

import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnClickListener;
import android.widget.ImageView;
import android.widget.TextView;

public class MainFragmentBottom extends Fragment {

 //
 public static String TAG = "MainFragmentBottom";

 // コールバック
 private FragmentBottomCallback mCallback;

 /*
  * コールバックのインターフェイス
  */
 public static interface FragmentBottomCallback {

  public void onClick(TextView v);

 }

 /*
  * Fragmentが Activityに対して何らかのコールバックを提供する場合、
  * Activityが必要なインタフェースを備えているかどうかチェックなどを行う。
  * 
  * @see android.support.v4.app.Fragment#onAttach(android.app.Activity)
  */
 @Override
 public void onAttach(Activity activity) {
  super.onAttach(activity);
  Log.d(TAG, "FragmentBottom-onAttach");

  // Activityがコールバックを実装しているかチェック
  if (activity instanceof FragmentBottomCallback == false) {
   throw new ClassCastException(
     "activity が FragmentBottomCallback を実装していません.");
  }

  //
  mCallback = (FragmentBottomCallback) activity;
 }

 /*
  * Fragment がユーザに見える状態になるまで保持しておくべきコンポーネントの初期化。
  * 
  * @see android.support.v4.app.Fragment#onCreate(android.os.Bundle)
  */
 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  Log.d(TAG, "FragmentBottom-onCreate");

  // fragment再生成抑止。Fragment を破棄させないようにする。
  setRetainInstance(true);

 }

 /*
  * Fragment が持つ View を構築する状態です。 View を持たない Fragment は、ここで null を返す
  * 
  * @see
  * android.support.v4.app.Fragment#onCreateView(android.view.LayoutInflater,
  * android.view.ViewGroup, android.os.Bundle)
  */

 @Override
 public View onCreateView(LayoutInflater inflater, ViewGroup container,
   Bundle savedInstanceState) {
  Log.d(TAG, "FragmentBottom-onCreateView");
  // Bundleで保存されたデータを復元
  String title = getArguments().getString("title");

  //
  Log.d("onCreateView", title);

  // 第1引数:レイアウトXMLファイルのリソースID、
  // 第2引数:
  // 第3引数:trueにするかfalseにするかで戻り値となるルートビュー(View)が変わる。
  // trueの場合には、第2引数で渡したViewGrou、falseの場合には第1引数で渡したリソースIDがルートビューになる
  View view = inflater.inflate(R.layout.fragment_main, container, false);
  // FragmentのTextViewの取得
  final TextView text = (TextView) view.findViewById(R.id.text_view);
  // テキストを挿入
  text.setText(title);
  // タグを設定する
  text.setTag(TAG);
  // FragmentのImageViewの取得
  ImageView image = (ImageView) view.findViewById(R.id.image_view);
  // 画像クリック時のイベントリスナー
  image.setOnClickListener(new OnClickListener() {
   @Override
   public void onClick(View v) {
    if (mCallback != null) {
     mCallback.onClick(text);
    }
   }
  });
  //
  return view;
 }

}
■MainActivity.java
各画面にFragmentが複数ある場合のやりとりとして、

Fragmentが2つあり、その中に各クリックイベント用のコールバックが用意されているので、
Activity側でそのインターフェイスを実装する必要があります。

実装用のメソッドが同名で、且つ引数がまったく同じインターフェイスになので、
同じコールバックのonClickが呼びだされてしまいます。
そのため、どっちのFragmentからクリックされたかを内部で判定する必要があります。
判定にはFragment側でTextViewに設定したタグを使って行っています。
package com.example.fragment6;

import com.example.fragment6.MainFragmentBottom.FragmentBottomCallback;
import com.example.fragment6.MainFragmentTop.FragmentTopCallback;

import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.util.Log;
import android.widget.TextView;

public class MainActivity extends FragmentActivity implements
  FragmentTopCallback, FragmentBottomCallback {

 public final static String TAG = "MainActivity";

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

  // Fragmentを管理するFragmentManagerを取得
  FragmentManager manager = getSupportFragmentManager();
  // 追加や削除などを1つの処理としてまとめるためのトランザクションクラスを取得
  FragmentTransaction tx = manager.beginTransaction();

  // 既にFragmentが作成されているかチェック
  if (manager.findFragmentByTag("layout_fragment_top") == null) {

   // 1つ目のfragmentを生成
   MainFragmentTop fragment_top = new MainFragmentTop();
   Bundle bundle1 = new Bundle();
   bundle1.putString("title", "キタ――(゚∀゚)――!!");
   // フラグメントに渡す値をセット
   fragment_top.setArguments(bundle1);

   // Fragment をスタックに追加する
   // メインレイアウトに対して追加先のビューのID、Fragment、Fragmentのタグ。
   // add() したときに既にバックスタックに同じタグの Fragment が存在する場合、
   // Fragment は新規作成されず、既にインスタンス化してある Fragment が再表示される。
   tx.add(R.id.layout_fragment_top, fragment_top,
     "layout_fragment_top");
  }

  if (manager.findFragmentByTag("layout_fragment_bottom") == null) {
   // 2つ目のfragmentを生成
   MainFragmentBottom fragment_bottom = new MainFragmentBottom();
   Bundle bundle2 = new Bundle();
   bundle2.putString("title", "クル━━━━(゚∀゚)━━━━??");
   // フラグメントに渡す値をセット
   fragment_bottom.setArguments(bundle2);

   // Fragment をスタックに追加する
   // メインレイアウトに対して追加先のビューのID、Fragment、Fragmentのタグ。
   // add() したときに既にバックスタックに同じタグの Fragment が存在する場合、
   // Fragment は新規作成されず、既にインスタンス化してある Fragment が再表示される。
   tx.add(R.id.layout_fragment_bottom, fragment_bottom,
     "layout_fragment_bottom");
  }

  tx.commit();

 }

 /*
  * 上下に配置されているFragmentの中にあるImageViewのクリックイベントのコールバック
  * 
  * @see com.example.fragment6.MainFragmentTop.FragmentTopCallback#onClick()
  */
 public void onClick(TextView v) {

  Log.d(TAG,
    "onClick TextView " + String.valueOf(v.getTag()) + " : "
      + v.getText());

  // Fragmentを管理するFragmentManagerを取得
  FragmentManager manager = getSupportFragmentManager();

  // TextViewに設定されているタグを元にどっちがクリックされたか判定する
  String tag = (String) v.getTag();
  if (tag == MainFragmentTop.TAG) {
   //
   Log.d(TAG, "onClick " + String.valueOf(v.getTag()));
   // MainFragmentTopがクリックされたら、MainFragmentBottomを取得してテキストを変更する
   MainFragmentBottom mfg = (MainFragmentBottom) manager
     .findFragmentByTag("layout_fragment_bottom");
   if (mfg != null) {
    mfg.setText("(´・ω・`)ショボーン");
   }
  } else if (tag == MainFragmentBottom.TAG) {
   //
   Log.d(TAG, "onClick " + String.valueOf(v.getTag()));
   // MainFragmentBottomがクリックされたら、MainFragmentTopを取得してテキストを変更する
   MainFragmentTop mfg = (MainFragmentTop) manager
     .findFragmentByTag("layout_fragment_top");
   if (mfg != null) {
    mfg.setText("(`・ω・´)シャキーン");
   }
  }

 }
}
実行結果画面は次のような感じになります!


以上です(`・ω・´)ゞビシッ!!

参考URL

0 件のコメント:

コメントを投稿