发现了。ListView 子类中的以下内容可以解决问题。我在 Android 4.2.1 上对其进行了测试,但它应该可以在任何 4.x 上运行。
免责声明:此代码依赖于 ListView 的实现细节,并使用反射来修改私有变量。我不能夸大这是多么邪恶。您不应该在生产代码中使用它!
我把它作为概念证明并满足我的好奇心。如果您想使用这样的代码(不幸的是,有时需要获得某些效果),请在其周围放置版本保护,如下所示。仅在您尝试过并检查过源代码的版本上运行。在其他情况下,您应该只使用调用默认行为,例如return super.onTouchEvent(ev)
.
此代码允许您(未成功尝试)通过最后一项下方的空白部分滚动短列表。单击并拖动空白区域,您应该会看到过度滚动发光。如果您已经实现了类似过度滚动橡皮筋效果的 iPhone,这将特别酷。
它是如何工作的?看看AbsListView.OnTouchEvent。在第 3397 行,motionPosition 是当前选中的项目索引。如果是 -1(没有项目),if
后面几行是假的,mTouchMode
不会被设置。在这种情况下,我将 ListView 手动置于滚动模式。
static final int TOUCH_MODE_DOWN = 0;
static final int TOUCH_MODE_SCROLL = 3;
static final int TOUCH_MODE_FLING = 4;
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN ||
Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN_MR1) {
final int x = (int) ev.getX();
final int y = (int) ev.getY();
int motionPosition = pointToPosition(x, y);
int action = (ev.getAction() & MotionEvent.ACTION_MASK);
if (action == MotionEvent.ACTION_DOWN) {
if (motionPosition == -1) {
setFieldInt("mActivePointerId", ev.getPointerId(0));
if (!getAdapterViewFieldBool("mDataChanged")) {
if ((getFieldInt("mTouchMode") != TOUCH_MODE_FLING)) {
setFieldInt("mTouchMode", TOUCH_MODE_DOWN);
}
}
setFieldInt("mMotionViewOriginalTop", 0);
setFieldInt("mMotionX", x);
setFieldInt("mMotionY", y);
setFieldInt("mMotionPosition", motionPosition);
setFieldInt("mLastY", Integer.MIN_VALUE);
}
}
}
return super.onTouchEvent(ev);
}
private void setFieldInt(String fieldName, int value) {
try {
Field field = AbsListView.class.getDeclaredField(fieldName);
field.setAccessible(true);
field.setInt(this, value);
}
catch (NoSuchFieldException e) { Log.v(TAG, "exception", e); }
catch (IllegalAccessException e) { Log.v(TAG, "exception", e); }
}
private int getFieldInt(String fieldName) {
try {
Field field = AbsListView.class.getDeclaredField(fieldName);
field.setAccessible(true);
return field.getInt(this);
}
catch (NoSuchFieldException e) { Log.v(TAG, "exception", e); }
catch (IllegalAccessException e) { Log.v(TAG, "exception", e); }
return 0;
}
private boolean getAdapterViewFieldBool(String fieldName) {
try {
Field field = AdapterView.class.getDeclaredField(fieldName);
field.setAccessible(true);
return field.getBoolean(this);
}
catch (NoSuchFieldException e) { Log.v(TAG, "exception", e); }
catch (IllegalAccessException e) { Log.v(TAG, "exception", e); }
return false;
}