【问题标题】:Communication between nested fragments in AndroidAndroid中嵌套片段之间的通信
【发布时间】:2016-09-14 13:23:33
【问题描述】:

我最近学习了how to make nested fragments in Android。不过,我不知道应该如何进行交流。

通过阅读fragment communication documentation 我知道

所有 Fragment 到 Fragment 的通信都是通过关联的 活动。两个 Fragment 永远不应该直接通信。

这对于 Activity 中的兄弟 Fragment 是有意义的,但对于父子 Fragment 通信而言意义不大。我是否需要一直到活动才能让子片段与父片段交谈?如果答案是简单的“是”,那么我可以做到。如果是“否”,那么代码设计会是什么样子?

我在Nested Fragment documentation 中看到可以使用getParentFragment() 来获取对父片段的引用。那么这是否意味着孩子应该直接与父母沟通呢?这似乎与正常片段与父活动通信所鼓励的相反。

【问题讨论】:

  • 必须创建 ParentFragment 实例方法
  • 您可以通过在您的应用中使用 Callbacks 来做到这一点。
  • 您还可以选择使用 EventBus,例如 OttoGreenRobot
  • EventBus 不是很好的解决方案。
  • @kostyabakay 你能给出原因吗?

标签: android android-fragments


【解决方案1】:

按照 Rahul Sharma 在 cmets 中的建议,我使用接口回调从子片段到父片段和 Activity 进行通信。我也submitted this answer to Code Review。我认为那里没有答案(在撰写本文时)表明这种设计模式没有重大问题。在我看来,这与官方fragment communication docs 中给出的一般指导一致。

示例项目

以下示例项目扩展了问题中给出的示例。它具有启动从 Fragment 到 Activity 以及从 Child Fragment 到 Parent Fragment 的向上通信的按钮。

我这样设置项目布局:

主要活动

Activity 实现了两个片段的侦听器,以便可以从它们获取消息。

可选 TODO:如果 Activity 想要启动与 Fragment 的通信,它可以直接获取对它们的引用,然后调用它们的公共方法之一。

import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;

public class MainActivity extends AppCompatActivity implements ParentFragment.OnFragmentInteractionListener, ChildFragment.OnChildFragmentToActivityInteractionListener {

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

        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
        ft.replace(R.id.parent_fragment_container, new ParentFragment());
        ft.commit();
    }

    @Override
    public void messageFromParentFragmentToActivity(String myString) {
        Log.i("TAG", myString);
    }

    @Override
    public void messageFromChildFragmentToActivity(String myString) {
        Log.i("TAG", myString);
    }
}

父片段

Parent Fragment 从 Child Fragment 实现侦听器,以便它可以接收来自它的消息。

可选 TODO:如果父 Fragment 想要启动与子 Fragment 的通信,它可以直接获取对它的引用,然后调用其公共方法之一。

import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;


public class ParentFragment extends Fragment implements View.OnClickListener, ChildFragment.OnChildFragmentInteractionListener {


    // **************** start interesting part ************************

    private OnFragmentInteractionListener mListener;


    @Override
    public void onClick(View v) {
        mListener.messageFromParentFragmentToActivity("I am the parent fragment.");
    }

    @Override
    public void messageFromChildToParent(String myString) {
        Log.i("TAG", myString);
    }

    public interface OnFragmentInteractionListener {
        void messageFromParentFragmentToActivity(String myString);
    }

    // **************** end interesting part ************************



    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        if (context instanceof OnFragmentInteractionListener) {
            mListener = (OnFragmentInteractionListener) context;
        } else {
            throw new RuntimeException(context.toString()
                    + " must implement OnFragmentInteractionListener");
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_parent, container, false);
        view.findViewById(R.id.parent_fragment_button).setOnClickListener(this);
        return view;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        Fragment childFragment = new ChildFragment();
        FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
        transaction.replace(R.id.child_fragment_container, childFragment).commit();
    }

    @Override
    public void onDetach() {
        super.onDetach();
        mListener = null;
    }

}

子片段

Child Fragment 为 Activity 和 Parent Fragment 定义了监听器接口。如果 Child Fragment 只需要与其中一个进行通信,则可以删除另一个接口。

import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;


public class ChildFragment extends Fragment implements View.OnClickListener {


    // **************** start interesting part ************************

    private OnChildFragmentToActivityInteractionListener mActivityListener;
    private OnChildFragmentInteractionListener mParentListener;

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.child_fragment_contact_activity_button:
                mActivityListener.messageFromChildFragmentToActivity("Hello, Activity. I am the child fragment.");
                break;
            case R.id.child_fragment_contact_parent_button:
                mParentListener.messageFromChildToParent("Hello, parent. I am your child.");
                break;
        }
    }

    public interface OnChildFragmentToActivityInteractionListener {
        void messageFromChildFragmentToActivity(String myString);
    }

    public interface OnChildFragmentInteractionListener {
        void messageFromChildToParent(String myString);
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);

        // check if Activity implements listener
        if (context instanceof OnChildFragmentToActivityInteractionListener) {
            mActivityListener = (OnChildFragmentToActivityInteractionListener) context;
        } else {
            throw new RuntimeException(context.toString()
                    + " must implement OnChildFragmentToActivityInteractionListener");
        }

        // check if parent Fragment implements listener
        if (getParentFragment() instanceof OnChildFragmentInteractionListener) {
            mParentListener = (OnChildFragmentInteractionListener) getParentFragment();
        } else {
            throw new RuntimeException("The parent fragment must implement OnChildFragmentInteractionListener");
        }
    }

    // **************** end interesting part ************************



    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_child, container, false);
        view.findViewById(R.id.child_fragment_contact_activity_button).setOnClickListener(this);
        view.findViewById(R.id.child_fragment_contact_parent_button).setOnClickListener(this);
        return view;
    }

    @Override
    public void onDetach() {
        super.onDetach();
        mActivityListener = null;
        mParentListener = null;
    }

}

【讨论】:

  • 很棒的帖子!我只想补充一点,您的两个“可选 TODO:”都可以通过使用以下方法引用 Fragment 或 ChildFragment 来实现:Activity.onAttachFragment(Fragment fragment) 和 Fragment.onAttachFragment(Fragment childFragment)
  • @Suragch EventBus 无法完成???您的回答很完美,但您不同意这种模式增加了应用程序的复杂性(与 Event Bus 相比)
  • @MiladFaridnia,可能是。我没有使用过EventBus。如果您想添加答案,我有兴趣查看它的示例。
  • @Suragch 我已经添加了示例。我已经测试了在片段(父片段和子片段)之间传递数据的代码,它运行良好。
  • 有了这个实现,你不能在任何其他活动下直接使用 ChildFragment,因为在这种情况下getParentFragment() 将返回null。为了可重用性,我认为将 Fragments 与其活动通信是最好的解决方案。 Child Fragment调用Activity,Activity调用父Fragment。父母可能会实现ChildFragment.OnChildFragmentInteractionListener。我们需要拨打更多电话,但不会受到限制。你怎么看?
【解决方案2】:

虽然@Suragch 的回答是正确的,但是我想添加另一种在FragmentsActivity 之间传递数据的方式。无论是Activity 还是Fragment,您都可以通过 3 个步骤通过事件总线传递数据:

1- 定义事件(消息):

public class OrderMessage {
    private final long orderId;
    /* Additional fields if needed */
    public OrderMessage(long orderId) {
        this.orderId = orderId;
    }
    public long getOrderId() {
        return orderId;
    }
}

2- 注册和注销事件: 为了能够接收事件,类必须注册/注销事件总线。 ActivitiesFragments 的最佳位置是 onStart()onStop()

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

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

为了能够接收事件,您必须订阅该事件。为此,请将 @Subscribe 注释添加到您类中的方法之一。

@Subscribe(threadMode = ThreadMode.MAIN)
   public void onMessage(OrderMessage message){
       /* Do something for example: */
       getContractDetails(message.getOrderId());
   }

3- 发布活动

EventBus.getDefault().post(new OrderMessage(recievedDataFromWeb.getOrderId()));

更多文档和示例可以在Here 找到。 还有其他库,例如:Otto

【讨论】:

  • 在这种情况下发送事件不是好方法。由于两个原因:1-它是可选的而不是强制性的。所以也许这个代码的其他一些用户忘记发送或接收。 2-流程可读性不是很好。假设您的意思是在子类中定义 Event,因为如果您在其他任何地方定义,您会增加代码的耦合。
【解决方案3】:

随着架构组件的发布,您可能应该看看viewmodel architecture component
结合实时数据,您将能够轻松地在任意嵌套的片段之间进行通信。 您还可以查看todoapp 以及他们如何处理那里的事件。

【讨论】:

    【解决方案4】:

    使用 ViewModel 在嵌套的 Fragment 之间进行通信

    现在不推荐使用旧的 ViewModel 和 LiveData。我想提供有关此流程目前的信息。

    在这个例子中,我使用了预定义的 TabLayout,可以在创建项目时选择它。

    这里我在父片段中有一个TextView——选项卡,在子片段中有一个EditText和一个按钮——自定义布局。它将子片段中输入的文本传递给父片段中的字段。

    我希望这对某人有所帮助。

    ViewModel 类

    public class FragViewModel extends ViewModel {
      private MutableLiveData<CharSequence> digit = new MutableLiveData<>();
    
      public void insertDigit(CharSequence inDigit){
          digit.setValue(inDigit);
      }
    
      public LiveData<CharSequence> getDigit(){
          return digit;
      }
    }
    

    这在很大程度上是来自 Android 开发人员ViewModel Overview 的逐字记录,只是对变量名称进行了小幅更改。概述用作以下父代码和子代码的指南。

    父片段代码

    public class Tab0Main extends Fragment {
    
    private FragViewModel model0;
    
    // This is the Child Fragment.
    private ChildFrag childFrag;
    
    private TextView textView;
    
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.tab0_main, container, false);
    
        // Setup the text field.
        textView = view.findViewById(R.id.tab0TextView);
    
        // Inserting the Child Fragment into the FrameLayout.
        childFrag = new ChildFrag();
        FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
        transaction.add(R.id.tab0Frame, childFrag).commit();
    
        return view;
    }
    
    
    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
    
        model0 = new ViewModelProvider(getActivity()).get(FragViewModel.class);
        model0.getDigit().observe(getViewLifecycleOwner(), new Observer<CharSequence>() {
            @Override
            public void onChanged(CharSequence charSequence) {
                textView.setText(charSequence);
            }
        });
    }
    

    子片段代码

    public class ChildFrag extends Fragment {
    
    private EditText fragEditText;
    private Button fragButton;
    
    // The ViewModel declaration
    FragViewModel model;
    
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.child_frag_layout, container, false);
    
        // The ViewModel Instantiation
        model = new ViewModelProvider(getActivity()).get(FragViewModel.class);
    
        fragEditText = view.findViewById(R.id.fragEditText);
        fragButton = view.findViewById(R.id.fragButton);
        fragButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                CharSequence in = fragEditText.getText();
                // Inserting a digit into the ViewModel carrier.
                model.insertDigit(in);
            }
        });
        return view;
      }
    }
    

    [![在此处输入图像描述][2]][2]

    【讨论】:

      猜你喜欢
      • 2017-02-14
      • 1970-01-01
      • 1970-01-01
      • 2013-08-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多