安卓View点击/曝光的监听方式

Android点击、曝光事件的监听分散在代码的各个角落, 不利于开发维护。

其实可以将这2种行为收敛到一起, 运行时监听Activity的生命周期,即ActivityLifeCallBack回调; 在onResume或onStart函数里判断当前acitivity是否添加了自定义ViewGroup(暂且命名为TrackerViewGroup), 如果未添加则添加;
在onDestory函数中移除该自定义View, 即其父View调用removeView。

一、触发曝光的方法
添加一层ViewGroup目的是监听滑动事件和View显示状态变更; 从而将曝光埋点的触发时机收敛到自定义View中。
安卓View点击/曝光的监听方式_第1张图片
1、activity切换前台会执行dispatchWindowFocusChanged函数;
2、手指在屏幕上滑动时dispatchToutchEvent函数里监听ACTION_MOVE, 并触发检索当前视图曝光流程, 每次只曝光差异View(从可见到不可见)并刷新缓存为当前可见view;
3、实现GestureDector的目的是监听Fling行为;
4、当子View变化时,例如ListView/RecyclerView滑入滑出item, 当前视图中的TextView执行setText操作也会触发父容器的onLayout。
安卓View点击/曝光的监听方式_第2张图片

public class TrackerViewGroup extends FrameLayout implements GestureDetector.OnGestureListener {
  ....
  
  public TrackerViewGroup(Context context) {
    super(context);
    this.mGestureDetector = new GestureDetector(context, this); //监听Fling事件
  }
  public TrackerFrameLayout(Context context, AttributeSet attrs) {
    super(context, attrs);
  }

  @Override public boolean dispatchTouchEvent(MotionEvent ev) {
    mGestureDetector.onTouchEvent(ev);
    switch (ev.getAction()) {
      case MotionEvent.ACTION_DOWN:
        ....
        break;
      case MotionEvent.ACTION_MOVE:
        //判断滑动距离超过阈值时,Activity递归判断当前哪些View可见并跟上次缓存相比较。 每次曝光差异的View
        break;
      case MotionEvent.ACTION_UP:
        break;
    }
    return super.dispatchTouchEvent(ev);
  }

  @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    //触发曝光流程且只曝光与上次的差异view
    super.onLayout(changed, left, top, right, bottom);
  }

  @Override public boolean onDown(MotionEvent motionEvent) {
    return false;
  }
  //GestureDetector无法判断滑动结束即IDLE,只触发Fling开始事件
  @Override public boolean onFling(MotionEvent motionEvent, MotionEvent motionEvent1, float v,
      float v1) {
    this.postDelayed(new Runnable() {
      @Override public void run() {
         //触发曝光流程, 即递归判断当前Activity哪些View可见。 只曝光差异部分
      }
    }, 1000);   //经验值
    return false;
  }

  @Override public void dispatchWindowFocusChanged(boolean hasFocus) {
     //曝光当前activity所有可见View, 不判断与上次曝光view的差异
    super.dispatchWindowFocusChanged(hasFocus);
  }

  @Override protected void dispatchVisibilityChanged(View changedView, int visibility) {
      //曝光当前activity所有可见View, 不判断与上次曝光view的差异
    super.dispatchVisibilityChanged(changedView, visibility);
  }

Activity包含ViewPager时需要特殊处理, 在递归遍历TrackerViewGroup子View/ViewGroup时,如果是ViewPage要添加滑动监听, 从而能够在ViewPager切换时触发曝光。

View从可见到不可见时触发曝光逻辑。例如ListView/RecyclerView划入item时并不触发曝光, 而是在划出屏幕、按home键、启动/关闭activity时触发item曝光逻辑;

二、监听点击事件
View.java的performClick函数会判断mAccessibilityDelegate非空时执行对应的回调, 在点击事件时发送AccessibilityEvent.TYPE_VIEW_CLICKED。 所以监听View的点击事件只需要实例化一个AccessibilityDelegate即可。

    /**
     * Delegate for injecting accessibility functionality.
     */
    AccessibilityDelegate mAccessibilityDelegate;
    
    public boolean performClick() {
        final boolean result;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }

        **sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);**
        return result;
    }

三、如何绑定数据
当递归判断某个View已显示时需要执行曝光逻辑, 曝光的参数可以关联到View上, 即通过View.setTag(id, object)的方式实现。 定义2个id值, 分别存储View的唯一标识和曝光参数(通常是Map类型)。 view绑定数据时例如:
view.setTag(R.id.trace_name, 进程唯一的值);
view.setTag(R.id.content, map);

曝光或点击需要取数据时view.getTag(R.id.content)。

四、遍历View
类似于递归子View, 以TrackerViewGroup为根view遍历所有子view/viewgroup, 并和缓存的view(所有设置了viewtag的view,在onLayout时)曝光状态做比较, 只能曝光差异部分; 遍历前要比较上次执行时间和当前时间间隔,低于阈值时返回(避免频繁递归导致性能问题)。
参考判断View是否可见, 显示比例可以设置阈值, 例如一个View显示了70%是否执行曝光逻辑等;

参考:
缓存Activity页面级别的参数,推荐使用静态WeakedHashMap, 解决代码层级太深、传值不便的情况。示例代码: static WeakHashMap> map = new WeakHashMap<>(); //context是Activity, map存储当前activity共享的一些参数。

参考: WeakHashMap原理

你可能感兴趣的:(曝光,Android)