按钮的 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)。您可能需要使用回调等来处理视图外的事件。
根据以下代码片段,我想知道为什么按钮的 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)。您可能需要使用回调等来处理视图外的事件。