0

我有一个包含不同数量项目的 ListView。它可以滚动,也可以适合一个屏幕。当用户点击并拖动列表并进行过度滚动时,您将获得漂亮的过度滚动边缘发光效果。

如果列表没有滚动,因为它只有很少的元素,但是您点击并拖动其中一个项目,您仍然会获得边缘发光效果。这很好,也是我想要的。

问题是,如果您单击最后一项下方并拖动,则没有任何反应。在这种情况下,我也希望它“尝试滚动”。如果没有视觉反馈会令人困惑,并且列表似乎冻结了。

+--------------------------+
| Item 1           (x)     |     Tap and drag here works.  The list
+--------------------------+     doesn't move, but the user gets a
| Item 2                   |     visual overscroll indicator on the
+--------------------------+     top or the bottom.
| Item 3                   |
+--------------------------+
|                          |
|                  (x)     |     Tap and drag here does nothing.
|                          |
|                          |
|                          |
|                          |
|                          |
+--------------------------+

如何让整个 ListView 对滚动尝试做出反应?

我确保它涵盖了所有可用空间,而不仅仅是物品所需的空间(设置android:layout_height="fill_parent"并尝试了不同的 BG 颜色以确保)。启动滚动的代码在哪里?如果我必须重写某些东西很好,因为我已经在使用我自己的 ListView 子类。

4

1 回答 1

0

发现了。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;
}
于 2013-02-12T22:06:33.510 回答