Based on the following code snippets, I was wondering why the onFling gesture doesn't get recognized for a GridView of Buttons (I use Buttons instead of other Views for personal reasons):
Here's my 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);
}
... here's my CustomAdapter:
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;
}
}
... and apparently the swipe, onFling, would only get recognized on the bottom half of the screen when GridView is set to wrap_content, whereas the swipe wouldn't work at all when GridView is set to match_parent. Here's the grid set at wrap_content with the swipe only working in the enclosed square as follows:
Much appreciated.
Since the buttons have consumed the touch events before it reaches the onTouchEvent()
of the Activity, the gesture detector never receives the events over the buttons. You may need to override a parent view class of the buttons, e.g. GridView
, to intercept the touch events.
Here is an example of a class GestureDetectGridView
which extends 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);
}
}
To use this, change the GridView
of the layout / activity to use this class instead, and remove the gesture detection codes from the activity (which have been moved into this class). You may need to use callbacks etc. to handle the events outside the view.