android的弹出菜单,使用activity来实现,但是长按的时间太短,容易与其他view的触摸逻辑相冲突,代码如下
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_draw);
//其他代码
_view.setOnCreateContextMenuListener(this);
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
menu.add(1, 10001, 0, "添加");
menu.add(1, 10002, 1, "删除");
menu.add(1, 10003, 2, "切换");
super.onCreateContextMenu(menu, v, menuInfo);
}
@Override
public boolean onContextItemSelected(MenuItem item) {
switch (item.getItemId()) {
case 10001: {
//添加
break;
}
case 10002: {
//删除
break;
}
case 10003: {
//切换;
break;
}
default:
break;
}
return super.onContextItemSelected(item);
}
为此,我们需要对长按进行额外的处理,于是需要对_view的onTouch事件进行相应的处理,为了便于调用,将长按的检测逻辑封装成了相应的类,代码如下
/**
* 长按工作器
*/
class LongTouchWorker{
/**
* 长按时两次点差的最大偏移量,超过此偏移量则不是长按
*/
private int _longTouchOffset = 50;
private int _lastMotionX;
private int _lastMotionY;
private int _longTouchDelay_ms;
private LongTouchCallback _longTouchCallback=null;
public LongTouchWorker(int longTouchDelay_ms,LongTouchCallback longTouchCallback){
_longTouchDelay_ms =longTouchDelay_ms;
_longTouchCallback=longTouchCallback;
}
private Handler _longTouchHandle=new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Log.d(TAG,"LongTouchHandle");
}
};
private Runnable _longTouchRunnable=new Runnable() {
@Override
public void run() {
if(_longTouchCallback!=null)
{
_longTouchCallback.longTouch(_lastMotionX,_lastMotionY);
}
}
};
public void onTouch(View v, MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
int pointerCount = event.getPointerCount();
if(pointerCount>1){
//多点触控,直接取消长按
cancelLongTouch();
}
else {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
// 弹起时,移除已有Runnable回调,弹起就算长按结束了(不需要考虑用户是否长按了超过预设的时间)
cancelLongTouch();
Log.d(TAG, "LongTouch UP");
break;
case MotionEvent.ACTION_MOVE:
if (Math.abs(_lastMotionX - x) > _longTouchOffset
|| Math.abs(_lastMotionY - y) > _longTouchOffset) {
// 移动误差阈值
// xy方向判断
// 移动超过阈值,则表示移动了,就不是长按(看需求),移除 已有的Runnable回调
cancelLongTouch();
}
Log.d(TAG, "LongTouch Move");
break;
case MotionEvent.ACTION_DOWN:
// 每次按下重新计时
// 按下前,先移除 已有的Runnable回调,防止用户多次单击导致多次回调长按事件的bug
cancelLongTouch();
_lastMotionX = x;
_lastMotionY = y;
// 按下时,开始计时
_longTouchHandle.postDelayed(_longTouchRunnable, _longTouchDelay_ms);
Log.d(TAG, "LongTouch Down");
break;
}
}
}
private void cancelLongTouch(){
_longTouchHandle.removeCallbacks(_longTouchRunnable);
if(_longTouchCallback!=null){
_longTouchCallback.cancelLongTouch();
}
}
}
/**
* 长按的回调
*/
public interface LongTouchCallback{
/**
* 取消长按
*/
void cancelLongTouch();
/**
* 长按
*/
void longTouch(int x,int y);
}
在LongTouchWorker中通过onTouch的检测来判断是否为长按,判断过程要注意几点
1.弹起(UP)、按下(DOWN)、移动(MOVE)的逻辑,尤其是MOVE时要设置感应的偏移量,因为手指按下时,点的位置是有可能偏移的。
2.Handle和Runnable的配合,remove和postDelay的控制
3.通过接口LongTouchCallback来回调,触发相应的longTouch和cancelLongTouch事件。
那要如何使用LongTouchWorker呢?假如我们的view是自定义的,比如LongTouchView,在LongTouchView的onTouch事件调用LongTouchWorker,示例代码如下
/**
* 自定义LongTouchView
*/
public class LongTouchView extends View {
private LongTouchWorker _longTouchWorker=null;
/**
* 设置长按工作器
* @param longTouchDelay_ms 长按的延迟时间(毫秒)
* @param longTouchCallback 长按的回调
*/
public void set_longTouchWorker(int longTouchDelay_ms,LongTouchCallback longTouchCallback){
_longTouchWorker=new LongTouchWorker(longTouchDelay_ms,longTouchCallback);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if( _longTouchWorker!=null)
{
_longTouchWorker.onTouch(this,event);
}
return super.onTouchEvent(event);
}
}
有了LongTouchWorker之后,我们就可以解决前面弹出菜单时,长按时间太短的问题,示例代码如下
_longTouchView.set_longTouchWorker(_longTouchDelay_ms, new LongTouchCallback() {
@Override
public void cancelLongTouch() {
}
@Override
public void longTouch(int x, int y) {
showPopupMenu(x, y);
}
});
对于showPopupMenu弹出菜单的位置控制,可以参看《PopupMenu弹出位置的控制》