Android的自定義長按

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章