mouse scrolling with RecyclerView

i’ve recently began using RecyclerView in some of my projects, and one thing that stood out as being broken was that mouse scrolling no longer worked. while this is perhaps only a theoretical issue, it is fairly vexing when developing on an emulator, especially while dealing with long lists of data. i’ve opened a bug about it.

this is solved by using onGenericMotionEvent. as per the documentation, “Generic motion events describe joystick movements, mouse hovers, track pad touches, scroll wheel movements and other input events.” for our RecyclerView, we basically have two options - one is to use setOnGenericMotionListener, and the other is to have our own subclass of RecyclerView to handle this.

below, you will see the subclass solution (which can be easily adopted for use with setOnGenericMotionListener as well). the code here is adopted from AbsListView.

public class CustomRecyclerView extends RecyclerView {
  private Context mContext;
  private float mVerticalScrollFactor;

  // constructors omitted for brevity

  private float getVerticalScrollFactor() {
    if (mVerticalScrollFactor == 0) {
      TypedValue outValue = new TypedValue();
      if (!mContext.getTheme().resolveAttribute(
          R.attr.listPreferredItemHeight, outValue, true)) {
        throw new IllegalStateException(
            "Expected theme to define listPreferredItemHeight.");
      }
      mVerticalScrollFactor = outValue.getDimension(
          mContext.getResources().getDisplayMetrics());
    }
    return mVerticalScrollFactor;
  }

  @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
  @Override
  public boolean onGenericMotionEvent(MotionEvent event) {
    if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
      if (event.getAction() == MotionEvent.ACTION_SCROLL &&
          getScrollState() == SCROLL_STATE_IDLE) {
        final float vscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
        if (vscroll != 0) {
          final int delta = -1 * (int) (vscroll * getVerticalScrollFactor());
          if (ViewCompat.canScrollVertically(this, delta > 0 ? 1 : -1)) {
            scrollBy(0, delta);
            return true;
          }
        }
      }
    }
    return super.onGenericMotionEvent(event);
  }
}
comments powered by Disqus