按钮的 GridView 无法识别 onFling 手势

onFling gesture not recognized for a GridView of Buttons

根据以下代码片段,我想知道为什么按钮的 GridView 无法识别 onFling 手势(出于个人原因我使用按钮而不是其他视图):

这是我的 MainActivity:

public class MainActivity extends AppCompatActivity {
    private GridView grid;
    GestureDetector gDetector;

    private static final int SWIPE_MIN_DISTANCE = 120;
    private static final int SWIPE_MAX_OFF_PATH = 250;
    private static final int SWIPE_THRESHOLD_VELOCITY = 200;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        grid = (GridView)findViewById(R.id.grid);

        // 4X4 grid.
        grid.setNumColumns(4);

        ArrayList<Button> mButtons = new ArrayList<Button>();
        Button button = null;

        for (int i = 0; i < 16; i++) {
            button = new Button(this);
            button.setText(i + "");
            mButtons.add(button);
        }

        grid.setAdapter(new CustomAdapter(this, mButtons));

        gDetector = new GestureDetector(this, new SimpleOnGestureListener() {
            @Override
            public boolean onDown(MotionEvent event) {
                return true;
            }

            @Override
            public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
                               float velocityY) {
                final int position = grid.pointToPosition(Math.round(e1.getX()), Math.round(e1.getY()));

                if (Math.abs(e1.getY() - e2.getY()) > SWIPE_MAX_OFF_PATH) {
                    if (Math.abs(e1.getX() - e2.getX()) > SWIPE_MAX_OFF_PATH
                        || Math.abs(velocityY) < SWIPE_THRESHOLD_VELOCITY) {
                        return false;
                    }
                    if (e1.getY() - e2.getY() > SWIPE_MIN_DISTANCE) {
                        Toast.makeText(MainActivity.this, "top at index " + position, Toast.LENGTH_SHORT).show();
                    } else if (e2.getY() - e1.getY() > SWIPE_MIN_DISTANCE) {
                        Toast.makeText(MainActivity.this, "bottom at index " + position, Toast.LENGTH_SHORT).show();
                    }
                } else {
                    if (Math.abs(velocityX) < SWIPE_THRESHOLD_VELOCITY) {
                        return false;
                    }
                    if (e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE) {
                        Toast.makeText(MainActivity.this, "left at index " + position, Toast.LENGTH_SHORT).show();
                    } else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE) {
                        Toast.makeText(MainActivity.this, "right at index " + position, Toast.LENGTH_SHORT).show();
                    }
                }

                return super.onFling(e1, e2, velocityX, velocityY);
            }
        });
}

@Override
public boolean onTouchEvent(MotionEvent event) {
    return gDetector.onTouchEvent(event);
}

...这是我的自定义适配器:

public class CustomAdapter extends BaseAdapter {
    private ArrayList<Button> mButtons = null;
    private Context ctx;

    public CustomAdapter(Context ctx, ArrayList<Button> button) {
        this.ctx = ctx;
        this.mButtons = button;
    }

    @Override
    public int getCount() {
        return mButtons.size();
    }

    @Override
    public Object getItem(int position) {return (Object) mButtons.get(position);}

    @Override
    public long getItemId(int position) {

        // Position and id are synonymous.
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        Button button;

        // Assigns a view to convertView should it be null, otherwise, casts convertView to the
        // correct View type.
        if (convertView == null) {
            button = mButtons.get(position);
        } else {
            button = (Button) convertView;
        }

        return button;
    }
}

... 显然,当 GridView 设置为 wrap_content 时,滑动 onFling 只会在屏幕的下半部分被识别,而当设置 GridView 时,滑动根本不起作用至 match_parent。这是设置在 wrap_content 的网格,滑动仅在封闭的正方形中起作用,如下所示:

非常感谢。

由于按钮在到达 Activity 的 onTouchEvent() 之前已经消耗了触摸事件,因此手势检测器永远不会接收到按钮上的事件。您可能需要覆盖按钮的父视图 class,例如GridView,拦截触摸事件。

这是一个 class GestureDetectGridView 的例子,它扩展了 GridView.

public class GestureDetectGridView extends GridView {
    private GestureDetector gDetector;
    private boolean flingConfirmed = false;
    private float mTouchX;
    private float mTouchY;

    private static final int SWIPE_MIN_DISTANCE = 120;
    private static final int SWIPE_MAX_OFF_PATH = 250;
    private static final int SWIPE_THRESHOLD_VELOCITY = 200;

    // Boiler plate view constructors
    public GestureDetectGridView(Context context) {
        super(context);
        init(context);
    }

    public GestureDetectGridView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public GestureDetectGridView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    @TargetApi(21)
    public GestureDetectGridView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init(context);
    }

    // Sets up gesture detector, moved from your original MainActivity

    private void init(final Context context) {
        gDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
            @Override
            public boolean onDown(MotionEvent event) {
                return true;
            }

            @Override
            public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
                                   float velocityY) {
                final int position = GestureDetectGridView.this.pointToPosition(Math.round(e1.getX()), Math.round(e1.getY()));

                if (Math.abs(e1.getY() - e2.getY()) > SWIPE_MAX_OFF_PATH) {
                    if (Math.abs(e1.getX() - e2.getX()) > SWIPE_MAX_OFF_PATH
                            || Math.abs(velocityY) < SWIPE_THRESHOLD_VELOCITY) {
                        return false;
                    }
                    if (e1.getY() - e2.getY() > SWIPE_MIN_DISTANCE) {
                        Toast.makeText(context, "top at index " + position, Toast.LENGTH_SHORT).show();
                    } else if (e2.getY() - e1.getY() > SWIPE_MIN_DISTANCE) {
                        Toast.makeText(context, "bottom at index " + position, Toast.LENGTH_SHORT).show();
                    }
                } else {
                    if (Math.abs(velocityX) < SWIPE_THRESHOLD_VELOCITY) {
                        return false;
                    }
                    if (e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE) {
                        Toast.makeText(context, "left at index " + position, Toast.LENGTH_SHORT).show();
                    } else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE) {
                        Toast.makeText(context, "right at index " + position, Toast.LENGTH_SHORT).show();
                    }
                }
                return super.onFling(e1, e2, velocityX, velocityY);
            }
        });
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        int action = ev.getActionMasked();
        gDetector.onTouchEvent(ev);
        // Determine whether we need to start intercepting all touch events
        // such that the buttons would no longer receive further touch events
        // Return true if the fling gesture is confirmed
        if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
            flingConfirmed = false;
        } else if (action == MotionEvent.ACTION_DOWN) {
            mTouchX = ev.getX();
            mTouchY = ev.getY();
        } else {
            // short cut just in case
            if (flingConfirmed) {
                return true;
            }
            float dX = (Math.abs(ev.getX() - mTouchX));
            float dY = (Math.abs(ev.getY() - mTouchY));
            if ((dX > SWIPE_MIN_DISTANCE) || (dY > SWIPE_MIN_DISTANCE)) {
                flingConfirmed = true;
                return true;
            }
        }
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        return gDetector.onTouchEvent(ev);
    }

}

要使用它,请更改布局的 GridView / activity 以使用此 class,并从 activity 中删除手势检测代码(其中已移至此 class)。您可能需要使用回调等来处理视图外的事件。