それを見つけた。ListView サブクラスの以下は、そのトリックを行います。Android 4.2.1 でテストしましたが、どの 4.x でも動作するはずです。
免責事項: このコードは、ListView の実装の詳細に依存し、リフレクションを使用してプライベート変数を変更します。これがどれほど悪いことか、私は誇張することはできません。これを本番コードで使用しないでください。
これは、概念の証明として、また好奇心を満たすために作成しました。このようなコードを使用したい場合 (残念ながら、特定の効果を得るために必要な場合があります)、以下で行うようにバージョン ガードを配置します。試してソースコードを確認したバージョンでのみ実行してください。それ以外の場合は、デフォルトの動作を呼び出すだけで使用する必要がありますreturn super.onTouchEvent(ev)
。
このコードを使用すると、最後の項目の下の空の部分で短いリストをスクロールできます (失敗します)。空白部分をクリックしてドラッグすると、オーバースクロールのグローが表示されます。これは、iPhone のようなオーバースクロール ラバーバンド効果を実装している場合に特に便利です。
それはどのように機能しますか?AbsListView.OnTouchEventを見てください。行 3397 で、motionPosition は現在選択されている項目のインデックスです。-1 (項目なし) の場合if
、数行後が false であり、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;
}