今天查看 Fragment 的官方文档,发现2点值得细究的地方:
关于第1点,参见另一篇博文《雪习新知识:一张图看懂 Java 内部类》。
本文探讨第2点,感觉这是一种好的设计思想。
官方示例中,MainActivity 里面有2个Fragment:
TitleFragment 有成员类 OnArticleSelectedListener 接口,MainActivity 实现该接口,在 TitleFragment 绑定 MainActivity 时让接口指向 MainActivity,TitleFragment 处理点击事件时调用接口的方法,结果就调用 MainActivity 实现接口的方法了,在MainActivity 实现接口的方法接收数据,或接着传递给 DetailFragment 等。
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);
}
···
}
}
Host 不再实现接口,而在 MemberClass 中增加 setOnClickListener(OnClickListener click); 方法,该方法在 Host 中调用,同样可以使用 Host 中成员变量和方法。
我们应用上面的思想来实现类似大众点评APP底部的标签切换效果,大众点评APP底部的标签如下图。
我们要实现的实际效果如下:
对于第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) {
···
}
});
···
}
假如 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()
上面说的过于抽象了,举个具体的例子,以微信朋友圈的评论功能为例。
使用静态嵌套类(static nested class)进行单向通信。
在 Host 中定义静态嵌套类 AbsFragment,然后其他的 Fragment 继承该抽象类,在 AbsFragment 定义各种 getter() 方法,返回 Host 的成员变量,子 Fragment 就能获得 Host 的成员变量了。由于只能从子 Fragment 中获得 Host 的变量,故通信是单向的。
to be continued…