Android

ActivityとFragment、両方向の操作

More than 1 year has passed since last update.

前回実装したタブ(TabLayoutとViewPagerを利用したタブの実装 - Qiita)をさらに拡張していきます。今回はActivityからViewPager内のFragmentのメソッドを呼ぶ方法と、ViewPager内のFragmentからActivityのメソッドを呼ぶ方法を紹介します。


ActivityからFragmentを操作する


使用例

ActivityのToolbarに作成したメニューがクリックされた時に、Fragment内のEditTextに入力された文字列を保存して、Activityを終了する。


流れ

1.FragmentにToolbarのメニューがクリックする時に呼ばれるpublicなメソッドを作成する。

2.Toolbarのメニューがクリックされた時、ViewPagerでどのFragmentが表示されているかを取得する。

3.取得されたFragmentによって、1で作成したメソッドを呼ぶ。

4.Activityを終了する。


コード

使用例に沿ったものを作っていきます。今回、文字列の保存はSharedPreferencesで行います。

とりあえずこんな感じのLayoutにしました。EditTextを1つ配置しただけです。


layout/fragment_main2.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
tools:context="...Main2Fragment">

<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="fragment2" />

<EditText
android:id="@+id/editText2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="text" />
</LinearLayout>


Fragmentを変更します。closeAndSave()メソッドをToolbarのメニューがクリックされた時に呼びます。

onViewCreated()メソッドで保存してある文字列をEditTextにセットするようにしました。これで、実際にFragmentのメソッドがActivityから呼ばれているかが確認できます。


Main2Fragment.java

package ...;

import ...;

public class Main2Fragment extends Fragment {

private EditText editText;
private SharedPreferences preferences;

...

@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);

editText = view.findViewById(R.id.editText2);

preferences = getContext().getSharedPreferences("data", Context.MODE_PRIVATE);
editText.setText(preferences.getString("text", ""));
}

public void closeAndSave() {
SharedPreferences.Editor editor = preferences.edit();
editor.putString("text", editText.getText().toString());
editor.apply();
}
}


次にAdapterを変更します。これはActivityが、ViewPagerがどのFragmentを表示しているかをAdapterから取得する必要があるためです。

新たに、setPrimaryItem()というオーバーライドメソッドを表記しました。このメソッドはViewPagerのページが変え始められた時と、表示するページが確定した時に呼ばれます。要するにViewPagerをスワイプして右のページを表示した時、スワイプし始めた瞬間に1回呼ばれ、指を離して右のページの表示が完了した時にもう1回呼ばれます(logで確認してみて下さい)。ここで、positionとfragmentにそれぞれ引数を代入していますが、今回ViewPagerのページ切り替え中には特に何もしないので、1回のページ切り替えで2回呼ばれるメソッドのうち、基本的にページが確定した時に代入された値のみを使うことになります。

引数のObjectはViewPagerで表示されているもの、すなわちこの場合はFragmentなので型を変えて代入できます。


OriginalFragmentPagerAdapter.java

package ...;

import ...;

public class OriginalFragmentPagerAdapter extends FragmentPagerAdapter {

private int position;
private Fragment fragment;

...

@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
super.setPrimaryItem(container, position, object);

this.position = position;
fragment = (Fragment) object;
}

public int getPosition() {
return position;
}

public Fragment getFragment() {
return fragment;
}
}


最後にActivityです。Toolbarにメニューを表示するところから書きます。

メニューがクリックされた時にclose()メソッドが呼ばれ流ようにしました。まずpositionをAdapterから取得しています。今、Toolbarのメニューをクリックして呼びたいメソッドを書いたのはMain2Fragmentだけなので、Main2Fragmentが表示されている、すなわちpositionが1の時のみMain2FragmentのcloseAndSave()メソッドが呼ばれるようにしています。よって、現段階ではMain1Fragmentが表示されているときにメニューをクリックしてもActivityが終了するだけです。

また、onMenuItemClick()メソッドの最後のfinish()というコードはActivityを終了するコード(メソッド)です。よって、close()メソッド内のFragmentから呼ばれたメソッドが終了したタイミングでActivityが終了します。


MainActivity.java

package ...;

import ...;

public class MainActivity extends AppCompatActivity {

private OriginalFragmentPagerAdapter adapter;

@Override
protected void onCreate(Bundle savedInstanceState) {

...

Toolbar toolbar = findViewById(R.id.toolbar);
toolbar.inflateMenu(R.menu.menu_main);
toolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_save:
close();
break;
}
finish();
return false;
}
});
}

private void close() {
int position = adapter.getPosition();
switch (position) {
case 0:
break;
case 1:
((Main2Fragment) adapter.getFragment()).closeAndSave();
break;
}
}
}



menu/menu_main.xml

<?xml version="1.0" encoding="utf-8"?>

<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">

<item
android:id="@+id/menu_save"
android:icon="@drawable/ic_done_white_24dp"
android:title="Save"
app:showAsAction="always" />
</menu>



FragmentからActivityを操作する


使用例

あまり、使用する機会があるのかわかりませんがとりあえず。。。

EditTextで入力された文字列をリアルタイムで(入力と同時に)Activity上のToolbarのタイトルに反映させる。


流れ

1.Activityに、引数で受け取った文字列をToolbarのタイトルにセットするパブリックなメソッドを作る。

2.EditTextに入力を検知するリスナーをセットして、文字列が変更されるたびに1で作成したメソッドをよび、文字列を渡す。


コード

今度はMain1Fragmentに実装します。

Layoutは前回のまま使用できるので、まずはActivityにToolbarにタイトルをセットするメソッドを作ります。


MainActivity.java

package ...;

import ...;

public class MainActivity extends AppCompatActivity {

...

private Toolbar toolbar;

private void setToolbarTitl(String title) {
toolbar.setTitle(title);
}
}


ActivityからFragmentを操作する時と異なるのは、今回は操作対象の親であるActivityが1つしかないので判別する必要がないということです。よって次にFragmentを編集するだけで、これは実装できます。


Main1Fragment.java

package ...;

import ...;

public class Main1Fragment extends Fragment {

...

@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {

...

editText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}

@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
((MainActivity) getContext()).setToolbarTitle(s.toString());
}

@Override
public void afterTextChanged(Editable s) {
}
});
}
}



最後に

このようにActivityとFragmentは両方向の操作が可能です。