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彈出位置的控制》