在片段和其容器活动之间传递数据

198

这是否不够?你可以发送任何你想要的东西,也许你可以解释更多,你想要实现什么? - Marcin Milejski
从文档中我不明白如何将一个值或字符串从活动传递到片段,或者反过来。谢谢。 - tyb
2
处理来自片段的异步调用并返回数据的最佳方法是在两侧实现BroadcastReceivers。如果您正在使用不特定数量的片段,则无论如何都应使其异步化。 - Marek Sebera
@punisher_malade 不,对我来说可以运行。 - Vadim Kotov
16个回答

338

尝试使用接口。

任何需要将数据传递回其包含的活动的片段都应声明一个接口来处理和传递数据。然后确保您的包含的活动实现这些接口。例如:

JAVA

在您的片段中,声明接口...

public interface OnDataPass {
    public void onDataPass(String data);
}

然后,在 containing class 的 onAttach 方法中将其对接口的实现与片段相连接,如下所示:

OnDataPass dataPasser;

@Override
public void onAttach(Context context) {
    super.onAttach(context);
    dataPasser = (OnDataPass) context;
}

在你的片段中,当你需要处理数据传递时,只需在dataPasser对象上调用它:

public void passData(String data) {
    dataPasser.onDataPass(data);
}

最后,在实现了 OnDataPass 接口的包含活动中...

@Override
public void onDataPass(String data) {
    Log.d("LOG","hello " + data);
}

KOTLIN

第一步:创建接口

interface OnDataPass {
    fun onDataPass(data: String)
}

第二步,然后在onAttach方法(YourFragment)中将包含类的接口实现与片段连接起来,如下所示:

lateinit var dataPasser: OnDataPass

override fun onAttach(context: Context) {
    super.onAttach(context)
    dataPasser = context as OnDataPass
}

第三步。在您的片段内,当您需要处理数据传递时,只需在 dataPasser 对象上调用它:

fun passData(data: String){
    dataPasser.onDataPass(data)
}

第四步。最后,在您的活动中实现OnDataPass。

class MyActivity : AppCompatActivity(), OnDataPass {}

override fun onDataPass(data: String) {
    Log.d("LOG","hello " + data)
}

3
谢谢好的回答,言简意赅。这里也很好地解释了(http://developer.android.com/training/basics/fragments/communicating.html)。我之前没有意识到可以实现多个接口,我已经在实现一个“ActionBar.TabListener”,现在需要添加一个额外的接口。 - Eugene van der Merwe
6
这绝对是正确的方向,而且在我看来也是唯一正确的方式。它可以实现活动和碎片之间的双向交互,并且能够在一个活动中拥有多个片段(无论是通过选项卡还是多重布局),从而提供了一种通信方式,使得片段之间可以互相交流。 - Christopher
2
我有一个疑问...这是官方答案,从官方开发者网站上来看,他们说这是正确的通信frag-act-frag的方式,但为什么可以通过强制转换getActivity()来实现呢? - unmultimedio
4
为什么有些人需要额外的接口来做这件事?为什么您认为以下解决方案不好,或者您的解决方案更好?((MyActivity) getActivity).myMethod(...) - spacifici
4
onAttach(Activity activity) 已经过时,请使用 onAttach(Context context)。 - Chris.C
显示剩余11条评论

225

在你的片段中,你可以调用getActivity()

这将让你访问创建该片段的活动。从那里,你可以显然地调用任何在活动中的访问方法。

例如,对于在你的活动中称为getResult()的方法:

((MyActivity) getActivity()).getResult();

86
因为您正在访问 YOUR Activity 中的函数(而不是 Android 父 Activity),所以您需要对 getActivity() 进行强制类型转换: ((MyActivity) getActivity()).getResult(); - Nick
4
从碎片返回到活动的方法可能是什么? - Vasil Valchev
7
@VasilValchev,你可以创建一个接口并强制让活动去实现它,然后在你的片段中调用方法来传递数据。使用onAttach方法来检查活动是否实现了该接口。 - Ivan Nikolov
4
@IvanNikolov的回答非常优秀。你可以在Fragments培训链接中找到详细的解释。 - bogdan
11
这不是一个好的做法或模式。使用接口的答案才是正确的。 - moictab
显示剩余3条评论

25

最简单但不推荐的方法

您可以从片段中访问活动数据:

活动:

public class MyActivity extends Activity {

    private String myString = "hello";

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

    public String getMyData() {
        return myString;
    }
}

片段(Fragment):

public class MyFragment extends Fragment {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

        MyActivity activity = (MyActivity) getActivity();
        String myDataFromActivity = activity.getMyData();
        return view;
    }
}

7
这种写法会增加代码的耦合性,是个不好的做法。现在你不能在除了“我的活动”之外的其他活动中使用该片段。 - AmeyaB
为什么这种方式被认为是不良实践,接口是唯一的解决方案? - androidXP
如果所有的活动都是从BaseActivity扩展的,或者我们像这样进行强制转换:BaseActivity activity = (BaseActivity) getActivity(); 那么它将在所有活动上工作。 - Zar E Ahmer
@Ameya 我认为你可以使用 switch 或 if 语句来检查该片段是从哪个活动调用的。 - M A F
@MAF更好的方法是使用在Fragment内或外定义的接口。使Activity实现该接口并将其转换为该接口。现在你的Fragment可以被任何实现了该接口的Activity使用。 - AmeyaB

23
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {
    Bundle b = getActivity().getIntent().getExtras();
            wid = b.getString("wid");
            rid = b.getString("rid");
            View view = inflater.inflate(R.layout.categoryfragment, container, false);
    return view;
 }

14
这是如何在Fragment中获取它,但如果你的Fragment在XML布局文件中,那么调用该Fragment的类如何设置它呢? - Zapnologica

17

在片段和其宿主活动之间传递数据

活动:

        Bundle bundle = new Bundle();
        bundle.putString("message", "Alo Elena!");
        FragmentClass fragInfo = new FragmentClass();
        fragInfo.setArguments(bundle);
        transaction.replace(R.id.fragment_single, fragInfo);
        transaction.commit();

片段:

读取片段中的值

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        String myValue = this.getArguments().getString("message");
        ...
        ...
        ...
        }

你好 @Elenasys。我该如何从活动向片段发送捆绑数据?我从片段转到了活动,现在需要返回到已经创建的片段(仅调用 onStart、onResume 等),并且需要在活动中获取的数据。有没有什么方法可以做到这一点,或者我做错了什么? - Michal Moravik

8
我不知道这是否是最好的方法,但我在谷歌上搜索了很长时间,找到了如何将Bundle从片段传递到其容器活动,但我发现大多数都是将数据从活动发送到片段(这对于我这个新手来说有点混淆)。
后来,我尝试自己的方式,实际上正是我想要的。如果有人像我一样寻找同样的东西,我会在这里发布它。
//从片段传递数据。
Bundle gameData = new Bundle();
        gameData.putStringArrayList(Constant.KEY_PLAYERS_ARR,players);
        gameData.putString(Constant.KEY_TEAM_NAME,custom_team_name);
        gameData.putInt(Constant.KEY_REQUESTED_OVER,requestedOver);

        Intent intent = getActivity().getIntent();
        intent.putExtras(gameData);

// 从容器活动中的捆绑包获取数据。

Bundle gameData = getIntent().getExtras();
        if (gameData != null)
        {
            int over = gameData.getInt(Constant.KEY_REQUESTED_OVER);
            ArrayList<String> players = gameData.getStringArrayList(Constant.KEY_PLAYERS_ARR);
            String team = gameData.getString(Constant.KEY_TEAM_NAME);

        }
        else if (gameData == null)
        {
            Toast.makeText(this, "Bundle is null", Toast.LENGTH_SHORT).show();
        }

7

接口是最好的解决方案之一:

Glue 接口:

public interface DataProviderFromActivity {

    public String getName();
    public String getId);

}  

我的活动:

public class MyActivity implements DataProviderFromActivity{

    String name = "Makarov";
    String id = "sys533";

    ... ... ... ... ... .... .... 
    ... ... ... ... ... .... .... 

    public String getName(){
        return name;
    };
    public String getId(){
        return id;
    };
}

我的碎片:

public class MyFragment extends Fragment{

    String fragName = "";
    String fragId = "";

    ... ... ... ... ... .... .... 
    ... ... ... ... ... .... .... 

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        DataProviderFromActivity myActivity= (DataProviderFromActivity) getActivity();
        fragName = myActivity.getName();
        fragId = myActivity.getId();

        ... ... ... ... ... .... .... 
        ... ... ... ... ... .... .... 

        updateFragmentView();
    }
}

嗨@HassanMakarov,我喜欢你的界面,简单而干净。但我有一个问题。我不知道它是否特定于我的viewpager,但我一次只能在最多2个片段中获取数据?当活动创建时,在片段1和2中出现字符串“Makarov”和“sys533”,但在选择片段2或3之前,这些字符串不会出现在片段3中。有什么想法吗? - JC23
@JC23 我猜问题与片段事务有关。当 MainActivity 启动时,设置了哪个片段? - MD TAREQ HASSAN
@JC23 我的做法是:不使用 Tab/ViewPager,而是使用 Toolbar 和 NavigationView。在 onNavigationItemSelected() 方法里,我执行 fragment 的事务,以相应地设置对应的 fragment。 - MD TAREQ HASSAN

2

你可以简单地使用EventBus,它易于使用且效果很好。

使用EventBus只需3步

  1. 定义事件:

    public static class MessageEvent { /* 如果需要,可以添加其他字段 */ }

  2. 准备订阅者:声明并注释您的订阅方法, 可选择指定线程模式:

    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onMessageEvent(MessageEvent event) {/* 执行某些操作 */};

注册和取消注册您的订阅者。例如,在Android上, 活动和片段通常应根据其生命周期进行注册:

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

 @Override
 public void onStop() {
     super.onStop();
     EventBus.getDefault().unregister(this);
 }
  1. 发布事件:

    EventBus.getDefault().post(new MessageEvent());


2

我使用了一个实现了日期监听器的AppCompatActivity。由于我需要编写一个日期范围选择器,所以必须使用Fragment。我还需要容器来接收选定的日期并将它们返回给父活动。

对于容器活动,这是类声明:

public class AppCompatDateRange extends AppCompatActivity implements
    DateIniRangeFragment.OnDateIniSelectedListener, DateFimRangeFragment.OnDateFimSelectedListener

回调函数的接口:

@Override
public void onDateIniSelected(String dataIni) {
    Log.i("data inicial:", dataIni);
}

@Override
public void onDateFimSelected(String dataFim) {
    Log.i("data final:", dataFim);
}

回调函数是字符串,因为日期是查询选择的参数。
碎片的代码(基于初始日期片段):
public class DateIniRangeFragment extends Fragment {
OnDateIniSelectedListener callbackIni;

private DatePicker startDatePicker;

public DateIniRangeFragment() {
    ///required empty constructor
}

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
}

///through this interface the fragment sends data to the container activity
public interface OnDateIniSelectedListener {
    void onDateIniSelected(String dataIni);
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    ///layout for the fragment
    View v = inflater.inflate(R.layout.date_ini_fragment, container, false);

    ///initial date for the picker, in this case, current date
    startDatePicker = (DatePicker) v.findViewById(R.id.start_date_picker_appcompat);
    Calendar c = Calendar.getInstance();
    int ano = c.get(Calendar.YEAR);
    int mes = c.get(Calendar.MONTH);
    int dia = c.get(Calendar.DAY_OF_MONTH);
    startDatePicker.setSpinnersShown(false);
    startDatePicker.init(ano, mes, dia, dateSetListener);

    return v;
}

///listener that receives the selected date
private DatePicker.OnDateChangedListener dateSetListener = new DatePicker.OnDateChangedListener() {
    public void onDateChanged(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
        if (view.isShown()) { ///if the datepicker is on the screen
            String sDataIni = year + "-" + (monthOfYear + 1) + "-" + dayOfMonth;
            callbackIni.onDateIniSelected(sDataIni); //apply date to callback, string format
        }
    }
};

@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);

    /*
    * this function guarantees that the container activity implemented the callback interface
    * */
    try {
        callbackIni = (OnDateIniSelectedListener) activity;
    } catch (ClassCastException e) {
        throw new ClassCastException(activity.toString() + " deve implementar OnDateIniSelectedListener");
    }
}

为了组合容器和片段,我使用了一个扩展了FragmentPagerAdapter的自定义类配合ViewPager(AppCompat)来实现,没有使用对话框。


1

来自谷歌官方指南

Fragment 1.3.0-alpha04开始,每个FragmentManager都实现了FragmentResultOwner。这意味着一个FragmentManager可以作为fragment结果的中央存储库。这个变化允许组件通过设置fragment结果并监听这些结果来相互通信,而不需要这些组件直接引用彼此。

在Fragment中: 子fragment在其FragmentManager上设置结果。一旦fragment启动,父fragment就会收到结果:

button.setOnClickListener {
    val result = "result"
    // Use the Kotlin extension in the fragment-ktx artifact
    setFragmentResult("requestKey", bundleOf("bundleKey" to result))
}

在宿主Activity中: 要在宿主Activity中接收片段结果,请使用getSupportFragmentManager()在片段管理器上设置结果监听器。
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        supportFragmentManager
                .setFragmentResultListener("requestKey", this) { 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
        }
    }
}

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