你的位置:首页 > 操作系统

[操作系统]事件监听:诀别Android繁琐的事件注册机制——view.setOnXXXXListener 滚犊子


  好久没写过随笔了......windows phone生态没起来,属于.net阵营的我最近工作不是太忙,闲暇之余就心血来潮开始研究安卓。先简单扯两句这几天学习下来对java事件监听机制的一点感触。客观地讲,java的事件监听机制相比.net好原始,暂不说委托、lamda、泛型等的繁琐,仅一个事件监听,就需要各种listener才能实现,比如安卓里到处都是view.setOnXXXXListener。被C#“语法糖”和宇宙第一IDE惯坏的我真心有点不习惯,于是就决定写个工具来封装这些烦人的listener。开始切入正题。

  1. 目标
  2. 成果
  3. 实现概况
  4. 具体实现及代码
  5. 总结

一、目标

  摆脱安卓里各种listener的繁琐,像写一般的方法似的写各种事件。

二、成果

  只要写一个类(这里以MainActivityEvent命名的类为例)继承EventManager,然后在对应的MainActivity里的onCreate方法里初始化这个类(new MainActivityEvent(this))即可完成注册。剩下的就只需要在MainActivityEvent类里写对应的事件响应逻辑就可以了。

三、实现概况

   3.1 MainActivity里注册。

    

  3.2 MainActivityEvent的实现。

    

  3.3 封装的相关类型。

    

四、具体实现及代码

   4.1 EventType.java

    
package com.example.personal.events;/** * Event type. */public enum EventType {  /**   * signature: (View v, int keyCode, KeyEvent event)   * return: boolean.   */  OnKey,  /**   * signature: (View v, MotionEvent event)   * return: boolean.   */  OnTouch,  /**   * signature: (View v, MotionEvent event)   * return: boolean.   */  OnHover,  /**   * signature: (View v, MotionEvent event)   * return: boolean.   */  OnGenericMotion,  /**   * signature: (View v)   * return: boolean.   */  OnLongClick,  /**   * signature: (View v, DragEvent event)   * return: boolean.   */  OnDrag,  /**   * signature: (View v, boolean hasFocus)   * return: void.   */  OnFocusChange,  /**   * signature: (View v)   * return: void.   */  OnClick,  /**   * signature: (ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo)   * return: void.   */  OnCreateContextMenu,    //TODO: Not supported for api version issues or other special reasons.//  /**//   * signature: (View v)//   *///  OnViewAttachedToWindow,//  /**//   * signature: (View v)//   *///  OnViewDetachedFromWindow,//  /**//   * signature: (View v)//   *///  OnContextClick,//  /**//   * signature: (int visibility)//   *///  OnSystemUiVisibilityChange}

View Code

  4.2 EventAnnotation.java

    
package com.example.personal.events;import java.lang.annotation.Documented;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Documented@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface EventAnnotation {  /**   * View id.   *   * @return the view id that the event binds to.   */  int value();  /**   * Event type. If not specified, onClick will be set by default.   *   * @return the event type of the method binds to.   */  EventType eventType() default EventType.OnClick;}

View Code

  4.3 EventManager.java

    
package com.example.personal.events;import android.app.Activity;import android.view.ContextMenu;import android.view.DragEvent;import android.view.KeyEvent;import android.view.MotionEvent;import android.view.View;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;import java.util.HashMap;import java.util.Map;/** * A abstract class to encapsulate most of the view event listeners. When bind an event to a view, in derived class, * you just need to declare a method that same with the event's signature, and annotate with {@link EventAnnotation}. * Note: the return value match is not required but recommended. For method that requires return boolean type, * {@code true} will be returned by default if the method return type is not. */public abstract class EventManager    implements    View.OnKeyListener,    View.OnTouchListener,    View.OnHoverListener,    View.OnGenericMotionListener,    View.OnLongClickListener,    View.OnDragListener,    View.OnFocusChangeListener,    View.OnClickListener,    View.OnCreateContextMenuListener {  private final Map<Integer, Map<EventType, Method>> eventMap;  protected final Activity activity;  protected EventManager(Activity activity) {    this.activity = activity;    eventMap = new HashMap<>();    registerEvents();  }  private void setListener(View view, EventType eventType) {    switch (eventType) {      case OnKey:        view.setOnKeyListener(this);        break;      case OnTouch:        view.setOnTouchListener(this);        break;      case OnHover:        view.setOnHoverListener(this);        break;      case OnGenericMotion:        view.setOnGenericMotionListener(this);        break;      case OnLongClick:        view.setOnLongClickListener(this);        break;      case OnDrag:        view.setOnDragListener(this);        break;      case OnFocusChange:        view.setOnFocusChangeListener(this);        break;      case OnClick:        view.setOnClickListener(this);        break;      case OnCreateContextMenu:        view.setOnCreateContextMenuListener(this);        break;    }  }  private void registerEvents() {    for (Method method : this.getClass().getDeclaredMethods()) {      if (method.isAnnotationPresent(EventAnnotation.class)) {        EventAnnotation annotation = method.getAnnotation(EventAnnotation.class);        int viewId = annotation.value();        View view = activity.findViewById(viewId);        if (view != null) {          method.setAccessible(true);          EventType eventType = annotation.eventType();          setListener(view, eventType);          Map<EventType, Method> actionMap;          if (eventMap.containsKey(viewId)) {            actionMap = eventMap.get(viewId);            actionMap.put(eventType, method);          } else {            actionMap = new HashMap<>();            actionMap.put(eventType, method);            eventMap.put(viewId, actionMap);          }        }      }    }  }  private Object invokeAction(EventType eventType, Object... args) {    View view = null;    for (Object obj : args) {      if (obj instanceof View) {        view = (View) obj;        break;      }    }    if (view == null) {      return null;    }    Method action = eventMap.get(view.getId()).get(eventType);    Object result = null;    if (action != null) {      try {        result = action.invoke(this, args);      } catch (IllegalAccessException e) {        e.printStackTrace();      } catch (InvocationTargetException e) {        e.printStackTrace();      }    }    return result;  }  private boolean invokeActionReturnStatus(EventType eventType, Object... args) {    Object result = invokeAction(eventType, args);    return result instanceof Boolean ? true : (boolean) result;  }  @Override  public void onClick(View v) {    invokeAction(EventType.OnClick, v);  }  @Override  public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {    invokeAction(EventType.OnCreateContextMenu, menu, v, menuInfo);  }  @Override  public boolean onDrag(View v, DragEvent event) {    return invokeActionReturnStatus(EventType.OnDrag, v, event);  }  @Override  public void onFocusChange(View v, boolean hasFocus) {    invokeAction(EventType.OnFocusChange, v, hasFocus);  }  @Override  public boolean onGenericMotion(View v, MotionEvent event) {    return invokeActionReturnStatus(EventType.OnGenericMotion, v, event);  }  @Override  public boolean onHover(View v, MotionEvent event) {    return invokeActionReturnStatus(EventType.OnHover, v, event);  }  @Override  public boolean onKey(View v, int keyCode, KeyEvent event) {    return invokeActionReturnStatus(EventType.OnKey, v, keyCode, event);  }  @Override  public boolean onLongClick(View v) {    return invokeActionReturnStatus(EventType.OnLongClick, v);  }  @Override  public boolean onTouch(View v, MotionEvent event) {    return invokeActionReturnStatus(EventType.OnTouch, v, event);  }}

View Code

五、总结

  源码下载:code.rar

  为了减少重复实现listener接口的繁琐,自己写了点api以便使用。通过封装,比较深刻的理解了java事件监听的机制,为安卓的入门打开了一个缝隙。有空打算再对SQLiteOpenHelper和SQLiteDatabase封装一个类似TableManager和Constraint之类的东西,来方便与Sqlite进行交互。并打算对Adapter封装一个ModelAdapter之类的东西来实现list item的缓存(不得不吐槽一下,adapter似乎在数据绑定的机制上做的不太好)。谨以此做一下记录和分享,如有问题,欢迎斧正,如果建议,欢迎反馈,此谢!