美文网首页
组件间通信方案(一):Activity和Fragment低藕通信

组件间通信方案(一):Activity和Fragment低藕通信

作者: bug喵喵 | 来源:发表于2021-01-06 16:03 被阅读0次

用户场景

设想这样一种用户场景:
在个人信息展示界面, 用户点击编辑按钮, 跳转到编辑界面, 完成编辑后, 回退到个人信息展示界面, 此时界面上应该展示最新的数据.
这里涉及到编辑后的数据如何及时更新到个人信息界面, 也即Activity&Fragment之间如何通信的问题.

分析

由于Fragment需要依托于Activity存在, 故有如下几种通信场景:

image

乍看上去可能不太好理解, 来依次分析下这几种场景.

1.Activity与Activity通信

此场景最为常见, 用户从Activity A跳转到Activity B, 通过Activity A的startActivity(Intent intent)方法, 可以在Intent中附加信息, 在打开Activity B的同时将这些信息传递给Activity B.
那么Activity B如何传递信息回Activity A呢? 使用setResult()方法即可. 很基础的用法, 下面列出一些需要注意的点:

a

调用setResult()后Activity A并不会立刻调用onActivityResult()回调, 而是当Activity B finish后才会调用.

2.Activity与其中的Fragment通信

Activity -> Fragment

Activity A中包含了Fragment B. 如何传递数据给Fragment B呢?
建议使用Fragment.setArguments().
我们也可以通过Fragment的构造方法传递参数, 但这是不推荐的. 当配置发生变化(语言发生变化, 屏幕发生旋转等), Activity会发生重建, 依附于其上的Fragment也会重建, 系统会调用Fragment的默认无参构造方法(如果我们实现了有参的构造方法, 会导致系统在重建Fragment时找不到无参构造方法, 会崩溃.) 而我们通过setArgument设置的参数在重建时会自动恢复, 这样就不会丢失数据了. 我们看一个使用setArgument的实例:

SomeFragment fragment = new SomeFragment();
Bundle args = new Bundle();
args.putString(SOME_EXTRA, "some_args");
fragment.setArguments(args);
getSupportFragmentManager()
                        .beginTransaction()
                        .replace(R.id.fragment_container, fragment)
                        .commit();

通过这种方式创建的Fragment在屏幕旋转等情况下还能保持通过setArgument设置的参数.

上面说的是创建Fragment时Activity如何传递参数给Fragment, 当Fragment创建后, Activity如何向Fragment发送消息呢? 我们要用到下面两个方法:

getSupportFragmentManager().findFragmentById(R.id.fragment_container);
getSupportFragmentManager().findFragmentByTag("some_tag");

这两个方法可以获取到想要的Fragment实例, 进而可以调用其方法向其传递消息.

Fragment -> Activity

那么Fragment如何与其宿主Activity通信呢? 诚然, 我们可以通过在Fragment中调用getActivity()获取其宿主Activity, 并将其强制转换为宿主Activity的类型, 并调用其中的方法, 达到Fragment向其Activity传递参数的目的, 如下:

SomeActivity activity = (SomeActivity) getActivity();
activity.someMethod();

Fragment被设计为可复用的模块化Activity组件, 为了保证其可复用性, 我们应该避免在Fragment中直接调用其他Fragment或Activity的方法, 因为这样会导致Fragment与特定的Activity/Fragment耦合, 无法实现其可复用性.
我们应该在Fragment中定义一个接口, 需要与之通信的Activity实现其接口:

class SomeFragment extends Fragment {
    ...
    interface ICommunication {
        void communicate(...);
    }

    void sendMessageToActivity() {
        ((ICommunication)getActivity()).communicate(...);
    }
}

class ParentActivity extend FragmentActivity implements ICommunication {
    @Override
    void communicate(...) {
        //TODO do some thing
    }
}

这样做, 宿主Activity需要实现Fragment定义好的通信接口, 保证了Fragment的可复用性.
PS: 是否有更好的方式?

3.同Activity中的两个平级Fragment通信

我们为了保证Fragment的可复用性, 绝对不能在一个Fragment中直接调用另一个Fragment的方法. 那两个平级的Fragment如何通信呢, 很简单, 通过父Activity中中转一下就可以了, 用上面列出的两种方法.

image

4.子Activity中的Fragment和父Activity如何通信?

2 --> 1

Fragment先与子Activity通信, 子Activity再通过setResult将消息传递给父Activity, done.

image

5.子Fragment与父Fragment间通信

我们先看简单的情况, 父Fragment如何与子Fragment通信?

创建时

老生常谈了, 用setArguments()向子Fragment传递参数, 这样在配置变化重建Fragment时, 参数也能恢复.

运行时

因为子Fragment是直接在父Fragment里new出来的, 父Fragment是保存有子Fragment的引用的, 既然有引用, 就可以直接调用子Fragment的成员方法了. 因为父Fragment就是要new特定类型的子Fragment, 不存在可复用性的影响, 类似宿主Activity持有Fragment引用.

再来看复杂一点的情况, 子Fragment如何与父Fragment通信? 不要跟我说用:

getActivity().getSupportFragmentManager().findFragmentById(R.id.fragment_container);

子Fragment设计出来是为了复用的, 这样做, 子Fragment就知道一定有一个特定类型的父Fragment, 还要去调用父Fragment特定的方法, 这样谈何复用?
正确的姿势在这里:
父Fragment在创建子Fragment时, 这样操作:

public static final int REQUEST_CODE = 1024;
...
ChildFragment childFragment = ChildFragment.newInstance(someArguments);
childFragment.setTargetFragment(this, REQUEST_CODE);  //this是父Fragment的引用, 向上转型为Fragment
//show child fragment...

这里子Fragment将父Fragment设置为其TargetFragment, 并设置了requestCode.
当子Fragment需要传递数据回父Fragment时:

public class ChildFragment extends Fragment {
    ...
    //需要传递数据时调用
    private void setResult(SomeType someData) {
        Intent intent = new Intent();
        intent.putExtra(SOME_EXTRA, someData);
        getTargetFragment().onActivityResult(getTargetRequestCode(), Activity.RESULT_OK, intent);
    }
}

是不是很惊讶, 居然调用了Fragment.onActivityResult()方法.

image

该方法是Fragment实现的一个通用空方法, 我们继承的Fragment都有这个方法, 由于是通用的, 也就不存在破坏复用性的问题.
getTargetRequestCode()可以拿到子Fragment创建时传入的requestCode, Intent用来装载数据. 我们在父Fragment中复写onActivityResult()方法, 这样就可以传回数据到父Fragment啦~

public class ParentFragment extends Fragment {
    ...
    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        //我们根据requestCode判断是哪个子Fragment传回的数据
        //从data中拿到传回的数据
    }
}

6.父Activity的Fragment与子Activity的Fragment之间通信

这种情况就很简单啦, 套用上面的5点, 相信你可以解决~

相关文章

网友评论

      本文标题:组件间通信方案(一):Activity和Fragment低藕通信

      本文链接:https://www.haomeiwen.com/subject/imonoktx.html