在片段和活动之间通信 - 最佳实践

67

这个问题主要是征求关于如何处理我的应用程序的最佳方式的意见。我有三个片段由一个活动处理。片段A有一个可点击元素,即照片,而片段B有4个可点击元素,即按钮。当单击照片时,其他片段仅显示详细信息。我正在使用ActionBarSherlock。

屏幕截图

前进和后退按钮需要将照片更改为下一个或上一个姿势。我可以将照片和按钮保持在同一片段中,但希望将它们分开以便在平板电脑上重新排列它们。

我需要一些建议 - 我应该合并片段A和B吗?如果不是,我将需要想出如何为3个可点击项实现接口。

我考虑使用Roboguice,但我已经扩展了使用SherlockFragmentActivity,所以无法使用它。我看到有提到Otto,但我没有看到如何在项目中包含它的好教程。您认为最佳的设计实践应该是什么?

我还需要帮助弄清楚如何在片段和活动之间通信。我希望在应用程序中保留一些数据“全局”,例如姿势ID。除了股票Android开发人员的信息外,有没有一些示例代码可以看到?那不是很有用。

顺便说一下,我已经将每个姿势的所有信息存储在SQLite数据库中。那是容易的部分。


实际上,您可以将Rogoguice与ActionbarSherlock一起使用,请查看https://github.com/rtyley/roboguice-sherlock。 - rubenlop88
Otto更加简单,打包成一个独立的.jar文件,您可以将其放置在应用程序的libs/文件夹中。 - rubenlop88
1
@rubenlop88 仅为了从 Fragment 传递一些数据到 Activity 而添加一个库? - Marian Paździoch
11个回答

87

在活动和片段之间进行通信的最简单方法是使用接口。基本思想是在给定的片段A内定义一个接口,然后让活动实现该接口。

一旦它实现了该接口,你可以在重写的方法中做任何你想要做的事情。

接口的另一个重要部分是,你必须从你的片段调用抽象方法并记住将其强制转换为你的活动。如果没有正确执行,则应该捕获ClassCastException。

Simple Developer Blog上有一个很好的教程,介绍如何做这种事情。

希望对您有所帮助!


1
有人知道为什么Android开发者页面上关于使用警报对话框的示例没有使用接口与活动进行通信吗?http://developer.android.com/reference/android/app/DialogFragment.html#AlertDialog - cjayem13
2
接口是不错的。但为了更松散的耦合,请使用广播监听器。 - DeaMon1
当您的活动为片段提供多个不同的服务时,因为片段本身无法执行许多上下文相关的操作,那么会发生什么情况呢?在我的情况下,我已经让我的活动实现了4个不同的接口来与片段进行通信,然后我放弃了并让片段知道了这个活动,因为在我的情况下,这些片段永远不会被父活动包含,如果它们确实被包含,我可能还需要修复其他一些问题才能使其平稳运行。 - Rikki Gibson
1
现代的方法是使用事件来解耦您的项目代码。虽然这个回答是2014年的,虽然在某种程度上可以工作,但不应该是这种情况的唯一解决方案! - Eenvincible
@Eenvincible 是的,你说得对,请相应地更新你的答案,以便未来来到这里的人参考。 - Parag Kadam
显示剩余5条评论

24

21

它由一个回调接口实现:

首先,我们必须创建一个接口:

public interface UpdateFrag {
     void updatefrag();
}

在Activity中执行以下代码:

UpdateFrag updatfrag ;

public void updateApi(UpdateFrag listener) {
        updatfrag = listener;
}

从Activity中触发回调的事件:

updatfrag.updatefrag();
在Fragment中,实现CreateView接口,并执行以下代码:
 ((Home)getActivity()).updateApi(new UpdateFrag() {
        @Override
        public void updatefrag() {
              .....your stuff......
        }
 });

这个很好用 - 谢谢分享好的解释 :) - fifi_22
网络上最好的答案之一。谢谢分享。 - Bokili Production

9

要在ActivityFragment之间进行通信,有几种选择,但是经过大量阅读和多次实践,我发现可以这样概括:

  • Activity希望与子Fragment进行通信 => 只需在您的Fragment类中编写公共方法,然后让Activity调用它们即可。
  • Fragment希望与父Activity通信 => 这需要更多的工作,正如官方的Android链接https://developer.android.com/training/basics/fragments/communicating所建议的那样,定义一个由Activity实现的interface将是一个很好的主意,并且这个interface将为任何想要与该Fragment通信的Activity建立契约。例如,如果您有一个FragmentA,它希望与包含它的任何activity通信,则定义FragmentAInterface,它将定义FragmentA可以调用哪些方法以供决定使用它的activities使用。
  • Fragment希望与其他Fragment通信 => 这是情况最为“复杂”的情况。由于您可能需要从FragmentA传递数据到FragmentB,反之亦然,这可能会导致定义2个interface,即由FragmentB实现的FragmentAInterface和由FragmentA实现的FragmentAInterface。这将开始使事情变得混乱。并且想象一下如果您还有几个Fragment在场,并且即使父级activity也希望与它们通信。好吧,这种情况是建立共享ViewModel的完美时刻,用于activity及其fragment。更多信息请参见https://developer.android.com/topic/libraries/architecture/viewmodel。基本上,您需要定义一个SharedViewModel类,其中包含您要在它们之间共享数据的所有数据。
< p > < code > ViewModel 案例使事情变得更简单,因为您不必添加使代码变脏和混乱的额外逻辑。此外,它将允许您将数据的收集(通过对 SQLite 数据库或 API 的调用)与 < code > Controller (< code > activities 和 < code > fragments )分开。


5

我制作了一个注释库,可以为你进行转换。来看看这个链接。 https://github.com/zeroarst/callbackfragment/

@CallbackFragment
public class MyFragment extends Fragment {

    @Callback
    interface FragmentCallback {
       void onClickButton(MyFragment fragment);
    }    
    private FragmentCallback mCallback;

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.bt1
                mCallback.onClickButton(this);
                break;
            case R.id.bt2
                // Because we give mandatory = false so this might be null if not implemented by the host.
                if (mCallbackNotForce != null)
                mCallbackNotForce.onClickButton(this);
                break;
        }
    }
}

它会生成您的片段的子类,然后将其添加到FragmentManager中。
public class MainActivity extends AppCompatActivity implements MyFragment.FragmentCallback {

    @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();
    }

    Toast mToast;

    @Override
    public void onClickButton(MyFragment fragment) {
        if (mToast != null)
            mToast.cancel();
        mToast = Toast.makeText(this, "Callback from " + fragment.getTag(), Toast.LENGTH_SHORT);
        mToast.show();
    }
}

4

Google推荐的方法

如果你查看这个页面,你会发现Google建议你使用ViewModel来在FragmentActivity之间共享数据。

添加此依赖项:

implementation "androidx.activity:activity-ktx:$activity_version"

首先,定义你将要使用来传递数据的ViewModel
class ItemViewModel : ViewModel() {
    private val mutableSelectedItem = MutableLiveData<Item>()
    val selectedItem: LiveData<Item> get() = mutableSelectedItem

    fun selectItem(item: Item) {
        mutableSelectedItem.value = item
    }
}

其次,在Activity中实例化ViewModel

class MainActivity : AppCompatActivity() {
    // Using the viewModels() Kotlin property delegate from the activity-ktx
    // artifact to retrieve the ViewModel in the activity scope
    private val viewModel: ItemViewModel by viewModels()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewModel.selectedItem.observe(this, Observer { item ->
            // Perform an action with the latest item data
        })
    }
}

第三步,需要在Fragment内实例化ViewModel
class ListFragment : Fragment() {
    // Using the activityViewModels() Kotlin property delegate from the
    // fragment-ktx artifact to retrieve the ViewModel in the activity scope
    private val viewModel: ItemViewModel by activityViewModels()

    // Called when the item is clicked
    fun onItemClicked(item: Item) {
        // Set a new item
        viewModel.selectItem(item)
    }
}

现在您可以编辑此代码,创建新的观察者或设置方法。


2
有几种方法可以在活动、碎片、服务等之间进行通信。明显的一种方法是使用接口进行通信。然而,这不是一种高效的通信方式。您必须实现监听器等。
我的建议是使用事件总线。事件总线是发布/订阅模式的实现。
您可以在活动中订阅事件,然后在碎片等中发布这些事件。
我的博客文章中,您可以找到更多关于此模式的详细信息,还有一个示例项目来展示用法。

1

是的,那种方式看起来很混乱,我有两个片段需要通过活动以某种方式相互通信。是否有更好的教程可以教我如何做到这一点? - Kristy Welsh
1
@KristyWelsh:我想知道你是否已经开发出自己的关于“Fragment”到底是什么以及如何工作的概念,而不是理解“Fragment”设计目标的实际概念。使用“接口”与“Activity”通信的概念与使“Activity”实现“View.OnClickListener”并使“Button”按下导致向“ListView”(例如)添加项目没有什么不同。 你可能有一个带有FragmentA和FragmentB但不带FragmentC的“Activity” - 在这种情况下,让FragmentA“知道”如何直接与FragmentC交谈的概念是多余的。 - Squonk

0

我正在使用Intents来将操作返回到主活动。主活动通过覆盖onNewIntent(Intent intent)来监听这些操作。主活动将这些操作转换为相应的片段,例如。

因此,您可以像这样做:

public class MainActivity extends Activity  {

    public static final String INTENT_ACTION_SHOW_FOO = "show_foo";
    public static final String INTENT_ACTION_SHOW_BAR = "show_bar";


   @Override
   protected void onNewIntent(Intent intent) {
        routeIntent(intent);
   }

  private void routeIntent(Intent intent) {
       String action = intent.getAction();
       if (action != null) {               
            switch (action) {
            case INTENT_ACTION_SHOW_FOO:
                // for example show the corresponding fragment
                loadFragment(FooFragment);
                break;
            case INTENT_ACTION_SHOW_BAR:
                loadFragment(BarFragment);
                break;               
        }
    }
}

然后在任何片段内显示foo片段:

Intent intent = new Intent(context, MainActivity.class);
intent.setAction(INTENT_ACTION_SHOW_FOO);
// Prevent activity to be re-instantiated if it is already running.
// Instead, the onNewEvent() is triggered
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
getContext().startActivity(intent);

0

您可以在片段中创建一个带有函数声明的公共接口,并在活动中实现该接口。然后,您可以从片段调用该函数。


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