Основная связь между двумя фрагментами
У меня есть такой activity - MainActivity
, В этом Activity
У меня два fragments
, оба из которых я создал декларативно в XML.
Я пытаюсь передать String
ввода текста пользователем в Fragment A
для просмотра текста в Fragment B
, Однако это оказывается очень трудным. Кто-нибудь знает, как мне этого добиться?
Я знаю, что фрагмент может получить ссылку на свою активность, используя getActivity()
, Так что, думаю, я бы начал там?
15 ответов
Загляните на страницу разработчиков Android: http://developer.android.com/training/basics/fragments/communicating.html
По сути, вы определяете интерфейс в своем фрагменте А и позволяете своей деятельности реализовать этот интерфейс. Теперь вы можете вызвать метод интерфейса в вашем фрагменте, и ваша активность получит событие. Теперь в своей деятельности вы можете вызвать второй фрагмент, чтобы обновить текстовое представление с полученным значением
Ваша активность реализует ваш интерфейс (см. Фрагмент А ниже)
public class YourActivity implements FragmentA.TextClicked{
@Override
public void sendText(String text){
// Get Fragment B
FraB frag = (FragB)
getSupportFragmentManager().findFragmentById(R.id.fragment_b);
frag.updateText(text);
}
}
Фрагмент A определяет интерфейс и вызывает метод при необходимости
public class FragA extends Fragment{
TextClicked mCallback;
public interface TextClicked{
public void sendText(String text);
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// This makes sure that the container activity has implemented
// the callback interface. If not, it throws an exception
try {
mCallback = (TextClicked) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement TextClicked");
}
}
public void someMethod(){
mCallback.sendText("YOUR TEXT");
}
@Override
public void onDetach() {
mCallback = null; // => avoid leaking, thanks @Deepscorn
super.onDetach();
}
}
Фрагмент B имеет публичный метод, чтобы сделать что-то с текстом
public class FragB extends Fragment{
public void updateText(String text){
// Here you have it
}
}
Некоторые другие примеры (и даже документация на момент написания этой статьи) используют устаревшие onAttach
методы. Вот полностью обновленный пример.
Заметки
- Вы не хотите, чтобы фрагменты общались непосредственно друг с другом или с заданием. Это связывает их с определенной деятельностью и затрудняет повторное использование.
- Решением является создание интерфейса прослушивателя обратного вызова, который будет реализован в Activity. Когда Фрагмент хочет отправить сообщение другому Фрагменту или его родительской деятельности, он может сделать это через интерфейс.
- Это нормально для Activity, чтобы общаться напрямую со своими дочерними фрагментами public методами.
- Таким образом, Activity выполняет роль контроллера, передавая сообщения от одного фрагмента к другому.
Код
MainActivity.java
public class MainActivity extends AppCompatActivity implements GreenFragment.OnGreenFragmentListener {
private static final String BLUE_TAG = "blue";
private static final String GREEN_TAG = "green";
BlueFragment mBlueFragment;
GreenFragment mGreenFragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// add fragments
FragmentManager fragmentManager = getSupportFragmentManager();
mBlueFragment = (BlueFragment) fragmentManager.findFragmentByTag(BLUE_TAG);
if (mBlueFragment == null) {
mBlueFragment = new BlueFragment();
fragmentManager.beginTransaction().add(R.id.blue_fragment_container, mBlueFragment, BLUE_TAG).commit();
}
mGreenFragment = (GreenFragment) fragmentManager.findFragmentByTag(GREEN_TAG);
if (mGreenFragment == null) {
mGreenFragment = new GreenFragment();
fragmentManager.beginTransaction().add(R.id.green_fragment_container, mGreenFragment, GREEN_TAG).commit();
}
}
// The Activity handles receiving a message from one Fragment
// and passing it on to the other Fragment
@Override
public void messageFromGreenFragment(String message) {
mBlueFragment.youveGotMail(message);
}
}
GreenFragment.java
public class GreenFragment extends Fragment {
private OnGreenFragmentListener mCallback;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_green, container, false);
Button button = v.findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String message = "Hello, Blue! I'm Green.";
mCallback.messageFromGreenFragment(message);
}
});
return v;
}
// This is the interface that the Activity will implement
// so that this Fragment can communicate with the Activity.
public interface OnGreenFragmentListener {
void messageFromGreenFragment(String text);
}
// This method insures that the Activity has actually implemented our
// listener and that it isn't null.
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof OnGreenFragmentListener) {
mCallback = (OnGreenFragmentListener) context;
} else {
throw new RuntimeException(context.toString()
+ " must implement OnGreenFragmentListener");
}
}
@Override
public void onDetach() {
super.onDetach();
mCallback = null;
}
}
BlueFragment.java
public class BlueFragment extends Fragment {
private TextView mTextView;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_blue, container, false);
mTextView = v.findViewById(R.id.textview);
return v;
}
// This is a public method that the Activity can use to communicate
// directly with this Fragment
public void youveGotMail(String message) {
mTextView.setText(message);
}
}
XML
activity_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:orientation="vertical"
android:padding="16dp">
<!-- Green Fragment container -->
<FrameLayout
android:id="@+id/green_fragment_container"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_marginBottom="16dp" />
<!-- Blue Fragment container -->
<FrameLayout
android:id="@+id/blue_fragment_container"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>
fragment_green.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:background="#98e8ba"
android:padding="8dp"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/button"
android:text="send message to blue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
fragment_blue.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:background="#30c9fb"
android:padding="16dp"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/textview"
android:text="TextView"
android:textSize="24sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
Самый хороший и рекомендуемый способ - использовать общую ViewModel.
https://developer.android.com/topic/libraries/architecture/viewmodel
Из Google Doc:
public class SharedViewModel extends ViewModel {
private final MutableLiveData<Item> selected = new MutableLiveData<Item>();
public void select(Item item) {
selected.setValue(item);
}
public LiveData<Item> getSelected() {
return selected;
}
}
public class MasterFragment extends Fragment {
private SharedViewModel model;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
itemSelector.setOnClickListener(item -> {
model.select(item);
});
}
}
public class DetailFragment extends Fragment {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
model.getSelected().observe(this, { item ->
// Update the UI.
});
}
}
пс: два фрагмента никогда не общаются напрямую
Рассмотрим мои 2 фрагмента A и B, и предположим, что мне нужно передать данные из B в A.
Затем создайте интерфейс в B и передайте данные основному действию. Там создайте другой интерфейс и передайте данные фрагменту А.
Поделиться небольшим примером:
Фрагмент А выглядит
public class FragmentA extends Fragment implements InterfaceDataCommunicatorFromActivity {
public InterfaceDataCommunicatorFromActivity interfaceDataCommunicatorFromActivity;
String data;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// TODO Auto-generated method stub
return super.onCreateView(inflater, container, savedInstanceState);
}
@Override
public void updateData(String data) {
// TODO Auto-generated method stub
this.data = data;
//data is updated here which is from fragment B
}
@Override
public void onAttach(Activity activity) {
// TODO Auto-generated method stub
super.onAttach(activity);
try {
interfaceDataCommunicatorFromActivity = (InterfaceDataCommunicatorFromActivity) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement TextClicked");
}
}
}
FragmentB выглядит как
class FragmentB extends Fragment {
public InterfaceDataCommunicator interfaceDataCommunicator;
@Override
public void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
// call this inorder to send Data to interface
interfaceDataCommunicator.updateData("data");
}
public interface InterfaceDataCommunicator {
public void updateData(String data);
}
@Override
public void onAttach(Activity activity) {
// TODO Auto-generated method stub
super.onAttach(activity);
try {
interfaceDataCommunicator = (InterfaceDataCommunicator) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement TextClicked");
}
}
}
Основная деятельность
public class MainActivity extends Activity implements InterfaceDataCommunicator {
public InterfaceDataCommunicatorFromActivity interfaceDataCommunicatorFromActivity;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public void updateData(String data) {
// TODO Auto-generated method stub
interfaceDataCommunicatorFromActivity.updateData(data);
}
public interface InterfaceDataCommunicatorFromActivity {
public void updateData(String data);
}
}
Начиная с Fragment 1.3.0 у нас появился новый способ общения между фрагментами.
Начиная с Fragment 1.3.0, каждый FragmentManager реализует FragmentResultOwner. Это означает, что FragmentManager может действовать как центральное хранилище для результатов фрагментов. Это изменение позволяет компонентам взаимодействовать друг с другом, устанавливая результаты фрагментов и прослушивая эти результаты, при этом эти компоненты не имеют прямых ссылок друг на друга.
Слушатель фрагмента:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Use the Kotlin extension in the fragment-ktx artifact
setFragmentResultListener("requestKey") { requestKey, bundle ->
// We use a String here, but any type that can be put in a Bundle is supported
val result = bundle.getString("bundleKey")
// Do something with the result
}
}
Излучатель фрагментов:
button.setOnClickListener {
val result = "result"
// Use the Kotlin extension in the fragment-ktx artifact
setFragmentResult("requestKey", bundleOf("bundleKey" to result))
}
Посмотрите на https://github.com/greenrobot/EventBus или http://square.github.io/otto/
или даже... http://nerds.weddingpartyapp.com/tech/2014/12/24/implementing-an-event-bus-with-rxjava-rxbus/
Существует простой способ реализовать связь между фрагментами действия с использованием архитектурных компонентов. Данные могут передаваться между фрагментами действия с использованием ViewModel и LiveData.
Фрагменты, участвующие в коммуникации, должны использовать объекты модели того же представления, которые связаны с жизненным циклом деятельности. Объект модели представления содержит объект liveata, в который данные передаются одним фрагментом, а второй фрагмент прослушивает изменения в LiveData и получает данные, отправленные из первого фрагмента.
Для полного примера см. http://www.zoftino.com/passing-data-between-android-fragments-using-viewmodel
В основном, следующие способы связи между двумя фрагментами:
i) ViewModel
ii) Fragment Result API
iii) Interface
Я использую много фрагментов на вкладках, которым необходимо обмениваться данными между собой, например, вкладку сканирования ble, которая требует обновления идентификатора устройства на вкладке настроек.
Коммуникация беспорядок для чего-то простого, как один edittext. Мое решение состояло в том, чтобы сохранить данные в общих настройках и использовать фрагмент onResume для чтения и обновления. Я могу расширить поля в Sharedpreferences позже, если мне нужно.
Узнайте " setTargetFragment() "
Если startActivityForResult() устанавливает связь между двумя действиями, setTargetFragment () определяет отношение вызывающего / вызываемого абонента между двумя фрагментами.
Я даю своей деятельности интерфейс, который затем могут использовать все фрагменты. Если у вас есть много фрагментов в одном и том же упражнении, это экономит много переписывания кода и является более чистым решением / более модульным, чем создание индивидуального интерфейса для каждого фрагмента с похожими функциями. Мне также нравится, как это модульно. Недостатком является то, что некоторые фрагменты будут иметь доступ к функциям, которые им не нужны.
public class MyActivity extends AppCompatActivity
implements MyActivityInterface {
private List<String> mData;
@Override
public List<String> getData(){return mData;}
@Override
public void setData(List<String> data){mData = data;}
}
public interface MyActivityInterface {
List<String> getData();
void setData(List<String> data);
}
public class MyFragment extends Fragment {
private MyActivityInterface mActivity;
private List<String> activityData;
public void onButtonPress(){
activityData = mActivity.getData()
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof MyActivityInterface) {
mActivity = (MyActivityInterface) context;
} else {
throw new RuntimeException(context.toString()
+ " must implement MyActivityInterface");
}
}
@Override
public void onDetach() {
super.onDetach();
mActivity = null;
}
}
Вы можете использовать подход пользователя 2 для общения между 2
fragments
:
1)
Вы можете использовать
LiveData
наблюдать за изменением данных одного
fragment
в другой
Создать общую ViewModel
public class SharedViewModel extends ViewModel {
private MutableLiveData<String> name;
public void setNameData(String nameData) {
name.setValue(nameData);
}
public MutableLiveData<String> getNameData() {
if (name == null) {
name = new MutableLiveData<>();
}
return name;
}
}
Фрагмент первый
private SharedViewModel sharedViewModel;
public FragmentOne() {
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
sharedViewModel = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
submitButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
sharedViewModel.setNameData(submitText.getText().toString());
}
});
}
Фрагмент второй
private SharedViewModel sharedViewModel;
public FragmentTwo() {
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
sharedViewModel = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
sharedViewModel.getNameData().observe(this, nameObserver);
}
Observer<String> nameObserver = new Observer<String>() {
@Override
public void onChanged(String name) {
receivedText.setText(name);
}
};
Для получения дополнительной информации о модели просмотра вы можете обратиться к: mvvm-viewmodel-liveata , передать фрагменты
2)
Вы можете использовать eventbus для достижения того же
implementation 'org.greenrobot:eventbus:3.2'
Определить событие
public static class MessageEvent { /* Additional fields if needed */ }
Зарегистрировать / отменить регистрацию подписчика
@Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
@Override
public void onStop() {
super.onStop();
EventBus.getDefault().unregister(this);
}
Слушайте события
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(MessageEvent event) {/* Do something */};
Опубликовать события
EventBus.getDefault().post(new MessageEvent());
Обновить
Игнорировать этот ответ. Не то чтобы это не сработало. Но есть и лучшие методы. Более того, Android категорически препятствует прямому общению между фрагментами. Смотрите официальный документ. Спасибо пользователю @Wahib Ul Haq за подсказку.
Оригинальный ответ
Ну, вы можете создать частную переменную и установщик во фрагменте B и установить значение из самого фрагмента A,
FragmentB.java
private String inputString;
....
....
public void setInputString(String string){
inputString = string;
}
FragmentA.java
//go to fragment B
FragmentB frag = new FragmentB();
frag.setInputString(YOUR_STRING);
//create your fragment transaction object, set animation etc
fragTrans.replace(ITS_ARGUMENTS)
Или вы можете использовать активность, как вы предложили в вопросе.
Недавно я создал библиотеку, которая использует аннотации для генерации такого шаблона. https://github.com/zeroarst/callbackfragment
Вот пример. Нажмите на TextView
на DialogFragment
вызывает обратный вызов MainActivity
в onTextClicked
затем возьмите MyFagment
экземпляр для взаимодействия с.
public class MainActivity extends AppCompatActivity implements MyFragment.FragmentCallback, MyDialogFragment.DialogListener {
private static final String MY_FRAGM = "MY_FRAGMENT";
private static final String MY_DIALOG_FRAGM = "MY_DIALOG_FRAGMENT";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getSupportFragmentManager().beginTransaction()
.add(R.id.lo_fragm_container, MyFragmentCallbackable.create(), MY_FRAGM)
.commit();
findViewById(R.id.bt).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
MyDialogFragmentCallbackable.create().show(getSupportFragmentManager(), MY_DIALOG_FRAGM);
}
});
}
Toast mToast;
@Override
public void onClickButton(MyFragment fragment) {
if (mToast != null)
mToast.cancel();
mToast = Toast.makeText(this, "Callback from " + fragment.getTag() + " to " + this.getClass().getSimpleName(), Toast.LENGTH_SHORT);
mToast.show();
}
@Override
public void onTextClicked(MyDialogFragment fragment) {
MyFragment myFragm = (MyFragment) getSupportFragmentManager().findFragmentByTag(MY_FRAGM);
if (myFragm != null) {
myFragm.updateText("Callback from " + fragment.getTag() + " to " + myFragm.getTag());
}
}
}