Android中嵌套片段之间的通信

时间:2016-09-14 13:23:34

标签: android android-fragments

我最近学会了how to make nested fragments in Android。但是,我不知道应该如何进行沟通。

enter image description here

从阅读fragment communication documentation我知道

  

所有片段到片段的通信都是通过关联的方式完成的   活动。两个碎片永远不应该直接沟通。

这对于活动中的兄弟片段是有意义的,但它对于父子片段通信没有多大意义。我是否需要一直到活动只是为了让Child Fragment与Parent Fragment交谈?如果答案是简单的"是"那我就能做到。如果它是" no",那么代码设计会是什么样子?

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

4 个答案:

答案 0 :(得分:26)

根据Rahul Sharma在评论中的建议,我使用接口回调从Child Fragment到Parent Fragment和Activity进行通信。我也是submitted this answer to Code Review。我在那里(在撰写本文时)没有回答是这个设计模式没有重大问题的迹象。在我看来,这与官方fragment communication docs中给出的一般指导是一致的。

示例项目

以下示例项目展开了问题中给出的示例。它具有按钮,用于启动从片段到活动以及从子片段到父片段的向上通信。

我设置了这样的项目布局:

enter image description here

主要活动

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

可选TODO:如果活动想要启动与片段的通信,它可以直接引用它们,然后调用它们的一个公共方法。

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:如果Parent Fragment想要启动与Child 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;
    }

}

答案 1 :(得分:4)

虽然@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

答案 2 :(得分:1)

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

答案 3 :(得分:1)

使用ViewModel在嵌套片段之间进行通信

现在已弃用旧的ViewModel和LiveData。我想提供有关此过程的信息。

在此示例中,我使用的是预定义的TabLayout,可以在创建项目时选择它。

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

我希望这对某人有帮助。

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的逐字记录,只是对变量名进行了少量更改。概述被用作以下父代和子代代码的指南。

父Fragement代码

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;
  }
}


enter image description here