FragmentTransaction機能とアプリをリリースする上での心構え

2012年2月3日(金)
石立 宏志

MyWebViewFragmentの修正

まずはMyWebViewFragmentを修正しましょう。

前回までのサンプルアプリでは画面右側のFragmentは必ずMyWebViewFragmentでした。しかし、先ほどの修正を加えた結果、画面右側のFragmentはMyDetailViewFragmentとなることもありえます。ですので、MyWebViewFragmentはWebページを表示することだけに専念して、戻るキーを押した際のページバックの処理は他にまかせることにします。

MyWebViewFragmentからgoBack()メソッドとloadUrl()メソッドを消して、URL文字列を引数に取るコンストラクタを追加しましょう。

  private final String url;
 
  public MyWebViewFragment(final String url) {
    this.url = url;
  }

MyWebViewFragmentが表示された際にコンストラクタで渡されたURLを表示するため、onActivityCreated()メソッドに以下のコードを追加します。

  if (url != null) {
    getWebView().loadUrl(url);
  }

goBack()メソッド、loadUrl()メソッドを削除したことで、他のクラスでエラーが出ていると思いますので修正します。まずはloadUrl()メソッドを使っていた箇所から修正しましょう。

HelloFragmentActivityのMyListFragment.UrlClickListner()を設定している箇所を以下のように修正します。

  listFragment.setUrlClickListener(new MyListFragment.UrlClickListener() {
    @Override
    public void onUrlClick(final String url) {
      MyWebViewFragment webViewFragment = new MyWebViewFragment(url);
      FragmentTransaction tran =
        getFragmentManager().beginTransaction();
      tran.replace(R.id.fragment2, webViewFragment);
      tran.addToBackStack(null);
      tran.commit();
    }
  });

また、onBackPressed()メソッドを以下のように修正します。

  @Override
  public void onBackPressed() {
    if (getFragmentManager().getBackStackEntryCount() == 0) {
        finish();
    }
    getFragmentManager().popBackStack();
  }

上の2つの修正により、これまでMyWebViewFragmentで行っていた画面遷移の制御をFragmentTransactionによる制御に変更しています。

FragmentTransaction機能は、Fragment間の画面遷移や遷移時のエフェクトを制御する機能です。表示するFragmentを切り替えたり、切り替えたFragmentをActivityのようにスタックに積み(addToBackStack())、あとから戻れる(popBackStack())ようにしたりする機能が備わっています。

これにより、Fragmentの種類がどんなものであっても、そのFragmentの実体を意識せずに画面遷移や戻る遷移が実装できるようになります。

FragmentTransaction機能を使うことで画面遷移などの制御はできるので、Fragmentを使う場合は、Fragment間のつながりや特定のFragmentに依存するコードは極力使わないような設計にすることが望ましいと思います。

最後に、MyWebViewFragmentは引数つきのコンストラクタを持つようになったため、レイアウトXMLファイルに直接MyWebViewFragmentを記述することはできません。ですので、layoutフォルダ内のmain.xml内のMyWebViewFragmentを設定していた箇所を以下のように修正してください。

  <FrameLayout
    android:id="@+id/fragment2"
    android:layout_height="match_parent"
    android:layout_width="0dp"
    android:layout_weight="3"/>

これでアプリを実行してみてください。前回のサンプルアプリと同様の挙動となりますが、若干動きが重くなっているかと思います。これはアイテムをタップする毎にMyWebViewFragmentを生成しているためとなります。

データの編集をリストに反映する

これまでの修正でMyWebViewFragmentがWebページを表示するだけの機能となりました。次はMyListFragmentとMyDetailViewFragmentを修正し、データの編集をリストに反映できるようにしましょう。

まずはMyListFragmentを修正します。先ほど、リストアイテムの長押しに対応するコードを追加しましたが、そこでは直接MyDetailViewFragmentをインスタンス化し、Fragmentの入れ替えをしていました。しかし、この書き方ではMyListFragmentとMyDetailViewFragmentのつながりが強くなってしまい切り離せない形になっています。

こちらは前回同様、長押しされた位置を通知するインターフェースをMyListFragmentに追加し、HelloFragmentActivityに通知するようにしましょう。

MyListFragmentに以下のコードを追加してください。

  private LongClickListener longClickListener;
  /**
  * 
  */
  public void setLongClickListener(
      LongClickListener longClickListener) {
    this.longClickListener = longClickListener;
  }
  public interface LongClickListener {
    public void onLongClick(final int pos);
  }

また、onActivityCreated()メソッド内のsetOnLongClickListnerの部分を以下の内容に修正してください。

    getListView().setOnItemLongClickListener(
                new AdapterView.OnItemLongClickListener() {
      @Override
      public boolean onItemLongClick(
            final AdapterView<?> listView,
            final View v,
            final int pos,
            final long id) {
        if (longClickListener != null) {
            longClickListener.onLongClick(pos);
            return true;
        } else {
            return false;
        }
      }
  });

これで長押しされた位置を通知する準備はできました。これに合わせてHelloFragmentActivityを修正しましょう。

HelloFragmentActivityのonCreate()メソッドに以下の内容を追加してください。

  listFragment.setLongClickListener(new MyListFragment.LongClickListener() {
    @Override
    public void onLongClick(final int pos) {
      Map<String, String> m = DATA.get(pos);
      MyDetailViewFragment detailViewFragment =
        new MyDetailViewFragment(m.get("title"), m.get("url"));
      FragmentTransaction tran =
        getFragmentManager().beginTransaction();
      tran.replace(R.id.fragment2, detailViewFragment);
      tran.addToBackStack(null);
      tran.commit();
    }
  });

これでMyListFragmentに関わる修正が完了しました。
次はMyDetailViewFragmentを修正します。MyDetailViewFragmentもOKボタンが押された際に通知するインターフェースを設ける必要があります。

MyDetailViewFragmentに以下のコードを追加します。

  private CommitEditListener commitEditListener;
  public interface CommitEditListener {
    public void onCommitEdit(String title, String url);
  }
  public void setCommitEditListener(CommitEditListener commitEditListener) {
    this.commitEditListener = commitEditListener;
  }

また、onActivityCreated()メソッドを以下のように修正します。

  @Override
  public void onActivityCreated(final Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
 
    final EditText titleEdit =
        (EditText) getView().findViewById(R.id.editTitle);
    final EditText titleUrl =
        (EditText) getView().findViewById(R.id.editUrl);
 
    titleEdit.setText(title);
    titleUrl.setText(url);
 
    getView().findViewById(R.id.buttonOK).
    setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(final View v) {
        if (commitEditListener != null) {
            commitEditListener.onCommitEdit(
              titleEdit.getText().toString(),
              titleUrl.getText().toString());
        }
      }
    });
    getView().findViewById(R.id.buttonCancel).
    setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(final View v) {
        getFragmentManager().popBackStack();
      }
    });
  }

これで編集した内容を通知できるようになりました。

また、キャンセルボタンには前に表示していたFragmentに戻るコードを記述しています。通知されたURLはHelloFragmentActivityで処理するようにします。

HelloFragmentActivityのsetLongClickListenerを設定している部分を以下のように修正します。

  listFragment.setLongClickListener(new MyListFragment.LongClickListener() {
    @Override
    public void onLongClick(final int pos) {
      Map<String, String> m = DATA.get(pos);
      MyDetailViewFragment detailViewFragment =
          new MyDetailViewFragment(m.get("title"), m.get("url"));
      detailViewFragment.setCommitEditListener(
          new MyDetailViewFragment.CommitEditListener() {
        @Override
        public void onCommitEdit(final String title, final String url) {
          Map<String, String> changed =
            new HashMap<String, String>();
          changed.put("title", title);
          changed.put("url", url);
          DATA.set(pos, changed);
          listFragment.setUrlList(DATA);
          getFragmentManager().popBackStack();
        }
      });
      FragmentTransaction tran =
        getFragmentManager().beginTransaction();
      tran.replace(R.id.fragment2, detailViewFragment);
      tran.addToBackStack(null);
      tran.commit();
    }
  });

これで修正は完了です。

アプリを実行してみて、リストアイテムを長押しし編集後、OKボタンをクリックしてください。ちゃんとリストアイテムの表示が変更されるようになっています。

図3:編集後のリストアイテム(クリックで拡大)

FragmentTransaction機能を使うことで、Webページや編集画面をシームレスに遷移させられるようになりました。これでサンプルアプリの修正は終わりますが、いままで学んだことをベースに以下のような機能を付け足してみてはいかがでしょうか。

  • Webページ側を長押しすると左側のリストにタイトルとURLが追加される
  • リストアイテムの並べ替え
  • Webページのリンクをタップした際にMyWebViewFragmentを生成し、Fragmentを用いてページ遷移も管理する

なお、ここまでのソース一式は、会員限定特典としてダウンロードできます。記事末尾をご確認ください。

  • 「HelloFragment」サンプルプログラム

  • 「HelloSmartphone」サンプルプログラム

テックファーム株式会社

2002年にIBMに入社、Webシステム開発やオフショア開発のサポートなどを経験。2005年にテックファーム株式会社に入社し、主にFeliCaやNFCなど非接触ICを使ったモバイルアプリケーションの開発に携わっていました。現在はスマートフォン向けのアプリケーション開発やソリューション提案をメインに活動しています。

連載バックナンバー

Think ITメルマガ会員登録受付中

Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

Think ITメルマガ会員のサービス内容を見る

他にもこの記事が読まれています