Основная связь между двумя фрагментами

У меня есть такой 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);
}

}

Есть несколько способов связи между фрагментами.

  • Традиционный способ общения через interface Пример
  • Через ViewModel, если вы следуете шаблона MVVMпримеру
  • BroadcastReceivers: через LocalBraodcastManager Пример или EventBus Пример и т. Д.

Начиная с 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))
}

Существует простой способ реализовать связь между фрагментами действия с использованием архитектурных компонентов. Данные могут передаваться между фрагментами действия с использованием 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());
    }
}

}

Другие вопросы по тегам