前回実装したタブ(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つ配置しただけです。
<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から呼ばれているかが確認できます。
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なので型を変えて代入できます。
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が終了します。
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;
}
}
}
<?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にタイトルをセットするメソッドを作ります。
package ...;
import ...;
public class MainActivity extends AppCompatActivity {
...
private Toolbar toolbar;
private void setToolbarTitl(String title) {
toolbar.setTitle(title);
}
}
ActivityからFragmentを操作する時と異なるのは、今回は操作対象の親であるActivityが1つしかないので判別する必要がないということです。よって次にFragmentを編集するだけで、これは実装できます。
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は両方向の操作が可能です。