燕过留声:由 Activity 和 Fragment 的通信方法想到的【WIP】

今天查看 Fragment 的官方文档,发现2点值得细究的地方:

  • Fragment 和 DetailActivity 均被定义为 static,而非定义在单独 .java 文件中;
  • Activity 和 Fragment 之间以及 Fragment 和 Fragment 之间的通信方法;

关于第1点,参见另一篇博文《雪习新知识:一张图看懂 Java 内部类》。
本文探讨第2点,感觉这是一种好的设计思想。

1. 原理

官方示例中,MainActivity 里面有2个Fragment:

  • 左边的是文章标题列表 TitleFragment;
  • 右边的是文章正文 DetailFragment;

    燕过留声:由 Activity 和 Fragment 的通信方法想到的【WIP】_第1张图片

TitleFragment 有成员类 OnArticleSelectedListener 接口,MainActivity 实现该接口,在 TitleFragment 绑定 MainActivity 时让接口指向 MainActivity,TitleFragment 处理点击事件时调用接口的方法,结果就调用 MainActivity 实现接口的方法了,在MainActivity 实现接口的方法接收数据,或接着传递给 DetailFragment 等。


燕过留声:由 Activity 和 Fragment 的通信方法想到的【WIP】_第2张图片
UML 类图

在 MemberClass 的构造方法中实现:

public MemberClass(Host host) {
    mListener = (OnClickListener)host;
}

这样,MemberClass 中存在 Host 的引用 mListener,父类类型的 mListener 指向其子类 Host 的对象(其实这就是多态,在运行时自动调用子类实现的方法 onClick()),从而实现调用 Host 中的成员变量和方法,完成通信。

public class MainActivity extends Activity implements TitlesFragment.OnArticleSelectedListener {
    ···

    @Override
    public void onArticleSelected(Uri aticleUri) {

    }

    public static class TitlesFragment extends ListFragment {
        OnArticleSelectedListener mListener;

        ···

        @Override
        public void onAttach(Activity activity) {
            super.onAttach(activity);
            mListener = (OnArticleSelectedListener) activity;
        }

        @Override
        public void onListItemClick(ListView l, View v, int position, long id) {
            Uri noteUri = ContentUris.withAppendedId(ArticleColumns.CONTENT_URI, id);
            mListener.onArticleSelected(noteUri);
        }

        public interface OnArticleSelectedListener {
            public void onArticleSelected(Uri aticleUri);
        }
        ···
    }
}

2. 变形

Host 不再实现接口,而在 MemberClass 中增加 setOnClickListener(OnClickListener click); 方法,该方法在 Host 中调用,同样可以使用 Host 中成员变量和方法。

3. 应用

我们应用上面的思想来实现类似大众点评APP底部的标签切换效果,大众点评APP底部的标签如下图。


燕过留声:由 Activity 和 Fragment 的通信方法想到的【WIP】_第3张图片

我们要实现的实际效果如下:

4. 回归

对于第2节提到的变形的方式,感觉好熟悉,翻看之前自己写的代码,恍然大悟:在可点击的按钮上添加监听事件不都是这种方式吗?!

关键点在 SDK 的 View.java 源码:

public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
    public OnClickListener mListener;
    ···
    public void onKeyUp(int keyCode, KeyEvent event) {
        ···
        return performClick();
        ···
    }

    public boolean performClick() {
        ···
        li.mOnClickListener.onClick(this);
        ···
    }

    public interface OnClickListener() {
        void onClick(View v);
    }

    public void setOnClickListener(OnClickListener l) {
        if (!isClickable()) {
            setClickable(true);
        }
        getListenerInfo().mOnClickListener = l;
    }
    ···
}

当点击事件的最后一个动作“KeyUp”发生时,调用 OnClickListener.onClick(),即触发监听事件。

Button 等控件继承了 View 的 interface OnClickListener 、mOnClickListener 和 setOnClickListener() 等 public 成员,这样就与变形方式保持一致了,其中 MainActivity 是 Host,Button 是 MemberClass。两种方式核心代码对比如下:

public class MainActivity extends Activity implements View.OnClickListener{
    ···
    Button btn = (Button) findViewById(R.id.btn);
    btn.setOnClickListener(this);

    @Override
    public void onClick(View v) {
        ···
    }
    ···
}

或者是

public class MainActivity extends Activity {
    ···
    Button btn = (Button) findViewById(R.id.btn);
    btn.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            ···
        }
    });
    ···
}

4. 引申

假如 MemberClass 是一个自定义的 View,很复杂的 View,里面有很多交互事件,包括点击、长按、滑动等等,而且有些交互是要用到 Host 的成员变量。在这种情况下,MemberClass extends ViewGroup implements View.OnClickListener,在 onClick(View v) 方法中调用 Host.setXXXListener(this/new XXXListener()) 即可。

其实,Host.setXXXListener(this) (Host implements XXXListener)和 Host.setXXXListener(new XXXListener()) 是等效的,对于 Host 是透明的,在 Host 看来,MemberClass.mXXXListener.onXXX() 调用的都是在 Host 中实现的 onXXX(){},onXXX(){} 中显然是可以使用 Host 的成员变量的。

对于 Host -> MemberClass(ViewGroup),MemberClass -> View(Buton,ImageView 等),是 2 层完全相同的层级关系,以点击事件为例,事件的传递是从内到外的,view.onClick() -> MemberClass.mXXXListner.onXXX() -> Host.onXXX()

上面说的过于抽象了,举个具体的例子,以微信朋友圈的评论功能为例。


燕过留声:由 Activity 和 Fragment 的通信方法想到的【WIP】_第4张图片

如上图,输入评论的输入框是在 Host 即Activity (当然也可能是 Fragment)中的,而评论最终是要显示在 MembClass 即 ListView 的某个 Item 中,而且输入框中出现“回复xxx”,显然“xxx”是从 Item 中传到 Activity 中的,这就是 Host 和 MemberClass 的双向通信,可以通过本文中的方法解决,具体解决方法参见 《Android 仿微信点赞和评论弹出框》。

5. 另一种通信方式

使用静态嵌套类(static nested class)进行单向通信。

在 Host 中定义静态嵌套类 AbsFragment,然后其他的 Fragment 继承该抽象类,在 AbsFragment 定义各种 getter() 方法,返回 Host 的成员变量,子 Fragment 就能获得 Host 的成员变量了。由于只能从子 Fragment 中获得 Host 的变量,故通信是单向的。

to be continued…

你可能感兴趣的:(设计)