Wen

Android 实现 listener: Observer Pattern (观察者模式)

Android Developer在写UI的时候经常有要用到listener的情况,比如A上监听到了某操作,然后B上要显示某种UI效果。实现时用到的设计模式就是Observer Pattern (观察者模式).

Observer Pattern和Listener介绍


想象这样一种情况,当Object A发生了某些改变的时候,比如它被点击了,或者它收到了来自server的response了,我们需要某些其他的Objects如Object BObject C也做一定响应,比如显示一些Text或者弹出对话框。在这个过程中,最先接收到事件的Object A就是被观察者(Subject);由A去触发的,需要根据A的行为响应的Object BObject C则是观察者(Observer),也就是Listener。当被观察者变化时,观察者(们)要做相应变化。这就是观察者模式了。

举个栗子。假如我们有一个EditText输入框,在这个输入框输入结束的时候,我们想让别的几个TextView都显示用户输入的text,同时数据库在这个时候进行插入操作,并且当前activity结束并start另一个activity。这个时候我们就需要这个模式。其中,那个EditText就是被观察者(Subject), 其余出现的类,比如那几个textview, 数据库类和当前的activity都属于观察者(observer),因为它们都需要在被观察者变化的时候也做出相应的改变。

那么一般这种关系的代码一般怎么设计呢?我们这里把被观察者类叫Subject, 观察者类叫Observer, 那么Subject里可以维护一个list,这个list里存的就是所有的Observer,这样当Subject发生某些变化时,才能知道要去通知谁。为了方便管理,所有的Observer最好有一个一样的方法来供Subject调用,否则Subject也不知道调用各个Observer的哪个方法(因为每个Observer可能都不同类)。所以最好Observer们都去实现一个interface。这样Subject也就不用管Observer具体都是什么类了,直接以那个Interface代替,直接调用interface定义的方法。

代码实现举例


这里被观察者是一个EditText,类名叫MyEditTextSubject,观察者有一个TextView和一个ImageView,分别叫MyTextViewObserverMyImageViewObserver。用户在MyEditTextSubject上输入完成按下回车键的时候,MyTextViewObserver要显示用户输入,MyImageViewObserver要根据用户输入显示一个图片。这里需要至少定义四个类,除了上面的三个类还有一个Interface,需要MyTextViewObserverMyImageViewObserver来实现。

从外面往里面看吧。先想想在Activity里要怎么写。首先Activity的layout里要设置好这些类的位置和大小等信息,这部分代码就不贴了,然后要在Activity里findViewById找到这三个类,然后把MyTextViewObserverMyImageViewObserver加入到MyEditTextSubject的listener list里面。至于如何触发这个事件在MyEditTextSubject里面写。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class HomePageActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.homepage);
MyEditTextSubject editText = (MyEditTextSubject) findViewById(R.id.my_edit_text);
MyTextViewObserver textView = (MyTextViewObserver) findViewById(R.id.my_text_view);
MyImageViewObserver imageView = (MyImageViewObserver) findViewById(R.id.my_image_view);
editText.addListener(textView);
editText.addListener(imageView);
}
}

简单吧,这样我们就把Observer们都放到了Subject的list里,具体我们想监听什么和做什么就可以在各自的类里面实现了,外面的Activity就不管这些了。

被监听者Subject


接下来我们看看一个典型的被监听者Subject类都要定义哪些东西:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class MyEditTextSubject extends EditText{
List<MyListener> listeners = new ArrayList<MyListener>();
public MyEditTextSubject(Context context, AttributeSet attrs) {
super(context, attrs);
setOnEditorActionListener(new EditText.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_SEARCH ||
actionId == EditorInfo.IME_ACTION_DONE ||
event.getAction() == KeyEvent.ACTION_DOWN &&
event.getKeyCode() == KeyEvent.KEYCODE_ENTER) {
// 就在这个时候触发各个Observer,这里抽象成了统一的listener interface
for(MyListener listener: listeners){
listener.update(v.getText().toString());
}
}
return false; // pass on to other listeners.
}
});
}
public void addListener(MyListener listener){
listeners.add(listener);
}
public void removeListener(MyListener listener){
listeners.remove(listener);
}
}

在Subject类里一般要有addListener()removeListener()。我们上面的代码就是在这个edittext输入完成点击回车的时候,把edittext里的string传给list里所有的Observer,通过他们内部定义的update()这个函数。

Observer观察者


先看Observer们都要实现的这个interface吧,这里observer只要实现一个函数就够了,那就是update()。update()里的输入其实可以是任何东西,包括可以把Subject本身传过去,这里我们只需要MyEditTextSubject里当前的输入所以就只传这个string值。

1
2
3
public interface MyListener {
public void update(String content);
}

然后Observer来实现这个update()方法。

1
2
3
4
5
6
7
8
9
public class MyTextViewObserver extends TextView implements MyListener{
public MyTextViewObserver(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void update(String content) {
this.setText(content);
}
}

上面是我们的textview的observer,可以看到它的update()就是把自己设成传进来的string。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MyImageViewObserver extends ImageView implements MyListener{
public MyImageViewObserver(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void update(String content) {
if(content.equals("Bird")){
setImageResource(R.drawable.bird);
}
else{
setImageResource(R.drawable.lion);
}
}
}

MyImageViewObserver同样实现了update()方法,如果传进来的string是“Bird”的话它就显示一个叫bird的drawable图片,否则显示另外一张。

总结


上面的代码虽然简单,但也算组成了一套观察者设计模式。它的本质是当修改目标对象的状态的时候,就会触发相应的多个观察者,当要实现一对多的关系的时候也要想到这个模式。而且这个过程还可以是动态的,因为可以addListener和removeListener来动态选择观察者。希望本文能起到一个抛砖引玉的作用。

转载请注明出处:http://zhaowen.io/post/Android_Listener_Pattern/