如何在Fragment之间传递数据

83

我正在尝试在我的程序中两个片段之间传递数据。这只是一个简单的字符串,存储在列表中。该列表在片段A中被公开,当用户点击列表项时,我需要它出现在片段B中。内容提供程序似乎仅支持ID,所以那样行不通。有什么建议吗?


这可能会有所帮助:https://dev59.com/Umw05IYBdhLWcg3wmC0o - P_Pran
可能是重复的问题:Android:将数据(extras)传递给片段 - Jim G.
13个回答

25

为什么不使用 Bundle。从您的第一个 fragment 开始,这是如何设置它:

Fragment fragment = new Fragment();
Bundle bundle = new Bundle();
bundle.putInt(key, value);
fragment.setArguments(bundle);

然后在您的第二个片段中,使用以下方式检索数据:

Bundle bundle = this.getArguments();
int myInt = bundle.getInt(key, defaultValue);

Bundle拥有许多数据类型的方法。请参见http://developer.android.com/reference/android/os/Bundle.html


21

如果您使用Roboguice,您可以使用Roboguice中的EventManager来传递数据,而无需使用Activity作为接口。在我看来,这非常干净。

如果您不使用Roboguice,则也可以使用Otto作为事件总线:http://square.github.com/otto/

更新20150909:现在也可以使用Green Robot Event Bus或RxJava,具体取决于您的用例。


3
我完全赞同Donn在这里所说的话。使用总线来解决所有这些问题,停止手动编写监听器和回调函数。Otto 无敌。 - Charlie Collins
9
如果你喜欢Otto,你可能也会喜欢EventBus,后者还提供线程处理功能。 https://github.com/greenrobot/EventBus - Markus Junginger
4
如果两个片段同时可见,我可以看到事件总线策略起作用,但如果A片段(例如ListFragment)占据整个屏幕,并选择列表项启动占据整个屏幕的B片段,那么它该如何工作?据我所知,事件总线模式在onResume/onPause中使每个片段进行注册/取消注册,因此我不确定如何应用它。 - Matthew
@DonnFelker 我遇到了Matthew描述的情况,但我仍然没有清楚地看到它。如果只有一个活动和几个片段,它将如何工作?我担心最终会得到一个巨大的Activity :/ - AlvaroSantisteban
你也可以从Fowler那里实现MVP模式(被动视图或监督控制器)。这是解决许多关注点分离问题的方法。http://martinfowler.com/eaaDev/PassiveScreen.html和http://martinfowler.com/eaaDev/SupervisingPresenter.html - Donn Felker
显示剩余2条评论

17

根据 Fragment文档:

通常,您希望一个片段与另一个片段通信,例如基于用户事件更改内容。所有片段之间的通信都通过关联的 Activity 完成。两个片段不应直接通信。

因此,建议您查看文档中的基本片段培训文档。它们非常详尽,包含示例和指南。


12

假设你有一个名为AB的Activity,控制着A和B两个Fragment。 在Fragment A中,你需要一个接口,Activity AB可以实现它。 在这个Android示例代码里,他们用了:

private Callbacks mCallbacks = sDummyCallbacks;

/*一个回调接口,所有包含该Fragment的Activity都必须实现它。这种机制允许Activity被通知项目的选择。*/

public interface Callbacks {
/*Callback for when an item has been selected. */    
      public void onItemSelected(String id);
}

/*A dummy implementation of the {@link Callbacks} interface that does nothing. Used only when this fragment is not attached to an activity. */    
private static Callbacks sDummyCallbacks = new Callbacks() {
    @Override
    public void onItemSelected(String id) {
    }
};

回调接口被放置在其中一个Fragment中(假设为Fragment A)。我认为这个Callbacks接口的目的就像是一个嵌套类在Frag A中,任何Activity都可以实现它。所以如果Fragment A象一台电视,Callbacks就像是电视遥控器(接口),允许Activity AB使用Fragment A。我可能对细节理解有误,因为我刚开始学习,但我确保我的程序在所有屏幕尺寸上都完美运行了,并且我用的就是这个方法。

所以,在Fragment A中,我们有: (我从Android的示例程序中找到的)

@Override
public void onListItemClick(ListView listView, View view, int position, long id) {
super.onListItemClick(listView, view, position, id);
// Notify the active callbacks interface (the activity, if the
// fragment is attached to one) that an item has been selected.
mCallbacks.onItemSelected(DummyContent.ITEMS.get(position).id);
//mCallbacks.onItemSelected( PUT YOUR SHIT HERE. int, String, etc.);
//mCallbacks.onItemSelected (Object);
}

在 Activity AB 中,我们重写 onItemSelected 方法:

public class AB extends FragmentActivity implements ItemListFragment.Callbacks {
//...
@Override
//public void onItemSelected (CATCH YOUR SHIT HERE) {
//public void onItemSelected (Object obj) {
    public void onItemSelected(String id) {
    //Pass Data to Fragment B. For example:
    Bundle arguments = new Bundle();
    arguments.putString(“FragmentB_package”, id);
    FragmentB fragment = new FragmentB();
    fragment.setArguments(arguments);
    getSupportFragmentManager().beginTransaction().replace(R.id.item_detail_container, fragment).commit();
    }

所以在 Activity AB 中,你基本上把一切都放进 Bundle 中并将其传递给 B。如果您不确定如何使用 Bundle,请查看该类。

我基本上是按照 Android 提供的示例代码进行操作的。其中包含 DummyContent 的那个。当您创建一个新的 Android 应用程序包时,它的名称为 MasterDetailFlow。


8

1- 第一种方法是定义一个接口

public interface OnMessage{
    void sendMessage(int fragmentId, String message);
}

public interface OnReceive{
    void onReceive(String message);
}

2- 在您的活动中实现OnMessage接口

public class MyActivity implements OnMessage {
   ...
   @Override
   public void sendMessage(int fragmentId, String message){
       Fragment fragment = getSupportFragmentManager().findFragmentById(fragmentId);
       ((OnReceive) fragment).sendMessage();
   }
}

3- 在你的fragment中实现OnReceive接口

public class MyFragment implements OnReceive{
    ...
    @Override
    public void onReceive(String message){
        myTextView.setText("Received message:" + message);
    }
}

这是处理片段之间消息传递的基本版本。

另一种处理片段之间数据传递的方法是使用事件总线。

1- 注册/注销到事件总线

@Override
public void onStart() {
    super.onStart();
    EventBus.getDefault().register(this);
}

@Override
public void onStop() {
    EventBus.getDefault().unregister(this);
    super.onStop();
}

2- 定义一个事件类

public class Message{
    public final String message;

    public Message(String message){
        this.message = message;
    }
}

3- 在您的应用程序中的任何位置发布此事件

EventBus.getDefault().post(new Message("hello world"));

4- 订阅该事件以在您的 Fragment 中接收它

@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessage(Message event){
    mytextview.setText(event.message);
}

关于事件总线模式的更多细节、使用案例和示例项目,请参考此链接


请不要在多个问题中发布相同的答案。发表一个好的答案,然后投票/标记其他问题为重复。如果问题不是重复的,请根据问题量身定制您的答案。 - Martijn Pieters

3
在我的情况下,我需要将数据从FragmentB发送回FragmentA,因此Intents不是一个选项,因为该片段已经初始化。尽管以上所有答案都很好,但是实现它需要大量的样板代码,因此我选择了使用LocalBroadcastManager的更简单的方法,它确切地执行上述操作,但没有所有令人讨厌的样板代码。下面分享了一个示例。
在发送Fragment(Fragment B)中:
public class FragmentB {

    private void sendMessage() {
      Intent intent = new Intent("custom-event-name");
      intent.putExtra("message", "your message");
      LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
    }
 }

在要接收的消息片段(片段A)中:
  public class FragmentA {
    @Override
    public void onCreate(Bundle savedInstanceState) {

      ...

      // Register receiver
      LocalBroadcastManager.getInstance(this).registerReceiver(receiver,
          new IntentFilter("custom-event-name"));
    }

//    This will be called whenever an Intent with an action named "custom-event-name" is broadcasted.
    private BroadcastReceiver receiver = new BroadcastReceiver() {
      @Override
      public void onReceive(Context context, Intent intent) {
        String message = intent.getStringExtra("message");
      }
    };
}

希望这能帮助到某些人。

所以我使用了这段代码,但似乎 onReceive 被调用了两次,不知道为什么。 - Desolator
不要在片段中注册接收器,因为由于某种原因它会被注册两次。 - Desolator

2

这取决于片段的结构。如果您可以将Fragment Class B的某些方法以及目标TextView对象设置为静态,则可以直接在Fragment Class A上调用该方法。这比侦听器更好,因为该方法会立即执行,我们不需要额外的任务来负责在整个活动期间进行侦听。请参见下面的示例:

Fragment_class_B.setmyText(String yourstring);

在Fragment B中,您可以定义如下方法:
public static void setmyText(final String string) {
myTextView.setText(string);
}

只需确保在Fragment B中将myTextView设置为静态,并在Fragment A中正确导入Fragment B类即可。最近我在我的项目上执行了这个过程,它有效了。希望对你有所帮助。


2
那样做是可行的(好吧,直到片段被销毁并重新创建,比如设备旋转),但你正在紧密耦合片段;依赖片段(我们称之为“X”)在其代码中对另一个片段(“Y”)有硬编码依赖关系。通过父活动更加整洁,因为你可以让活动决定从哪里获取数据。例如,在手机上,你的活动可能使用X和Y片段,但在平板电脑上,你可能决定使用X和Z。最好让活动(知道哪些片段存在)作为中间人,并根据需要连接到Y或Z。 - Phil Haigh
从这个方法中调用Asyntask怎么样? - UnderGround

1
基本上实现界面与片段之间的通信接口。
1)主要活动
public class MainActivity extends Activity implements SendFragment.StartCommunication
{

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}

@Override
public void setComm(String msg) {
// TODO Auto-generated method stub
DisplayFragment mDisplayFragment = (DisplayFragment)getFragmentManager().findFragmentById(R.id.fragment2);
if(mDisplayFragment != null && mDisplayFragment.isInLayout())
{
mDisplayFragment.setText(msg);
}
else
{
Toast.makeText(this, "Error Sending Message", Toast.LENGTH_SHORT).show();
}
}
}

2) 发送者片段(片段到活动)

public class SendFragment extends Fragment
{
StartCommunication mStartCommunicationListner;
String msg = "hi";
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// TODO Auto-generated method stub
View mView = (View) inflater.inflate(R.layout.send_fragment, container);
final EditText mEditText = (EditText)mView.findViewById(R.id.editText1);
Button mButton = (Button) mView.findViewById(R.id.button1);
mButton.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
msg = mEditText.getText().toString();
sendMessage();
}
});
return mView;
}

interface StartCommunication
{
public void setComm(String msg);
}

@Override
public void onAttach(Activity activity) {
// TODO Auto-generated method stub
super.onAttach(activity);
if(activity instanceof StartCommunication)
{
mStartCommunicationListner = (StartCommunication)activity;
}
else
throw new ClassCastException();

}

public void sendMessage()
{
mStartCommunicationListner.setComm(msg);
}

}

接收器片段(Activity 到 Fragment)。
    public class DisplayFragment extends Fragment
{
View mView;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// TODO Auto-generated method stub
mView = (View) inflater.inflate(R.layout.display_frgmt_layout, container);
return mView;
}

void setText(String msg)
{
TextView mTextView = (TextView) mView.findViewById(R.id.textView1);
mTextView.setText(msg);
}

}

我使用了这个链接来解决同样的问题,希望有人会发现它有用。非常简单和基础的例子。

http://infobloggall.com/2014/06/22/communication-between-activity-and-fragments/


虽然这个链接可能回答了问题,但最好在此处包含答案的基本部分并提供参考链接。仅有链接的答案如果链接页面更改可能会变得无效。 - abarisone

1
我正在做类似的项目,我想我的代码可能有助于上述情况。
以下是我正在进行的概述:
我的项目有两个片段,分别称为“FragmentA”和“FragmentB”。
-FragmentA包含一个列表视图,当您单击FragmentA中的项目时,使用通信器接口将其索引传递给FragmentB
  • 设计模式完全基于Java接口的概念,即“接口引用变量可以引用子类对象”。
  • MainActivity实现fragmentA提供的接口(否则我们无法使接口引用变量指向MainActivity)。
  • 在下面的代码中,通过使用fragmentA中存在的“setCommunicator(Communicatot c)”方法,使通信器对象引用MainActivity的对象。
  • 我正在使用MainActivity的引用从FrgamentA触发接口的respond()方法。

    接口communcator在fragmentA内定义,这是为了提供最少的访问权限给communicator接口。

以下是我的完整工作代码

FragmentA.java

public class FragmentA extends Fragment implements OnItemClickListener {

ListView list;
Communicator communicater;


@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {
    // TODO Auto-generated method stub
    return inflater.inflate(R.layout.fragmenta, container,false);
}

public void setCommunicator(Communicator c){
    communicater=c;
}

@Override
public void onActivityCreated(Bundle savedInstanceState) {
    // TODO Auto-generated method stub
    super.onActivityCreated(savedInstanceState);
    communicater=(Communicator) getActivity();
    list = (ListView) getActivity().findViewById(R.id.lvModularListView);
    ArrayAdapter<?> adapter = ArrayAdapter.createFromResource(getActivity(),
            R.array.items, android.R.layout.simple_list_item_1);
    list.setAdapter(adapter);
    list.setOnItemClickListener(this);

}

@Override
public void onItemClick(AdapterView<?> arg0, View arg1, int index, long arg3) {
communicater.respond(index);

}

public interface Communicator{
    public void respond(int index);
}

}

fragmentB.java

public class FragmentA extends Fragment implements OnItemClickListener {

ListView list;
Communicator communicater;


@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {
    // TODO Auto-generated method stub
    return inflater.inflate(R.layout.fragmenta, container,false);
}

public void setCommunicator(Communicator c){
    communicater=c;
}

@Override
public void onActivityCreated(Bundle savedInstanceState) {
    // TODO Auto-generated method stub
    super.onActivityCreated(savedInstanceState);
    communicater=(Communicator) getActivity();
    list = (ListView) getActivity().findViewById(R.id.lvModularListView);
    ArrayAdapter<?> adapter = ArrayAdapter.createFromResource(getActivity(),
            R.array.items, android.R.layout.simple_list_item_1);
    list.setAdapter(adapter);
    list.setOnItemClickListener(this);

}

@Override
public void onItemClick(AdapterView<?> arg0, View arg1, int index, long arg3) {
communicater.respond(index);

}

public interface Communicator{
    public void respond(int index);
}

}

MainActivity.java

public class MainActivity extends Activity implements FragmentA.Communicator {
FragmentManager manager=getFragmentManager();
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    FragmentA fragA=(FragmentA) manager.findFragmentById(R.id.fragmenta);
    fragA.setCommunicator(this);


}

@Override
public void respond(int i) {
    // TODO Auto-generated method stub

FragmentB FragB=(FragmentB) manager.findFragmentById(R.id.fragmentb);
FragB.changetext(i);
}



}

发布的FragmentB代码与FragmentA相同,请更正。谢谢。 - Marka A

1

虽然此链接可能回答了问题,但最好在此处包含答案的关键部分并提供链接以供参考。仅有链接的答案可能会因链接页面更改而失效。 - Mez
这并没有提供对问题的答案。如果您想对作者进行批评或请求澄清,请在他们的帖子下方留下评论 - 您始终可以评论自己的帖子,并且一旦您拥有足够的声望,您就能够评论任何帖子 - bartektartanus
@Mez 我同意你的话,并会尝试在下次做到相同。 - Nikhil
@jewirth 我不知道为什么你对我有偏见,因为上面提供的所有答案都有链接到其他页面。我没有那么多的知名度,所以我不能评论其他帖子。否则我只会这样做。 - Nikhil
@bartektartanus 我不知道你为什么认为我提供的链接没有回答那个问题。不过我认为这个链接提供了一个有效的答案。 - Nikhil
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接