1. 程式人生 > >android 之EditText長按遮蔽ActionMode context選單但保留選擇工具功能

android 之EditText長按遮蔽ActionMode context選單但保留選擇工具功能

最近專案要求遮蔽EditText 長按出來的ActionMode選單,但是要保留選擇文字功能。這個遮蔽百度會出現各種方法,這裡說一下我的思路:

1.遮蔽百度可知setCustomSelectionActionModeCallback即可,

editTextExt.setCustomSelectionActionModeCallback(new Callback() { 
            @Override
            public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 
                return
false; } @Override public void onDestroyActionMode(ActionMode mode) { } @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { //這裡可以新增自己的選單選項(前提是要返回true的) return false;//返回false 就是遮蔽ActionMode選單
} @Override public boolean onActionItemClicked(ActionMode mode, MenuItem item) { return false; } })

以上方式經過測試可以取消 editText長按彈出的上下文選單,但是同時選擇文字的工具也不能用了,這個百度了很久沒找到答案(有的說重寫onTouchEvent方法在裡邊自己實現選擇文字功能,我試了1.實現麻煩 2使用者體驗不好檢視原始碼可以看到裡邊有很多東西的)所以決定看看原始碼看看goole是怎麼實現actionMode選單的。

2.呼叫顯示系統的選擇文字工具
摸索了很久,先從editText.setCustomSelectionActionModeCallback方法下手既然返回false就能不顯示上下文選單,那從這裡入手應該是最直接的。通過查詢發現這個方法在父類 TextView中,

  public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
        createEditorIfNeeded();
        mEditor.mCustomSelectionActionModeCallback = actionModeCallback;
    }  

可以看到actionModeCallback是賦值給了mEditor變數,好了去找這個mEditor變數吧,找到發現mEditor的型別是Editor類,好傢伙Editor類在Eclipse中死活找不到乾脆搜原始碼吧,搜尋Editor.java關鍵字找到了android.widget.Editor類,就在同一個包下不過是隱藏的類找個類後查詢變數mCustomSelectionActionModeCallback 可以看到關鍵的地方了

 /**
     * An ActionMode Callback class that is used to provide actions while in text selection mode.
     *
     * The default callback provides a subset of Select All, Cut, Copy and Paste actions, depending
     * on which of these this TextView supports.
     */
    private class SelectionActionModeCallback implements ActionMode.Callback {

        @Override
        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
            TypedArray styledAttributes = mTextView.getContext().obtainStyledAttributes(
                    com.android.internal.R.styleable.SelectionModeDrawables);

            mode.setTitle(mTextView.getContext().getString(
                    com.android.internal.R.string.textSelectionCABTitle));
            mode.setSubtitle(null);
            mode.setTitleOptionalHint(true);
            //這裡定義了全選item,由於篇幅我刪了(拷貝 剪下 貼上等)
            menu.add(0, TextView.ID_SELECT_ALL, 0, com.android.internal.R.string.selectAll).
                    setIcon(styledAttributes.getResourceId(
                            R.styleable.SelectionModeDrawables_actionModeSelectAllDrawable, 0)).
                    setAlphabeticShortcut('a').
                    setShowAsAction(
                            MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);

            styledAttributes.recycle();

            if (mCustomSelectionActionModeCallback != null) {
                if (!mCustomSelectionActionModeCallback.onCreateActionMode(mode, menu)) {
                    // The custom mode can choose to cancel the action mode
                    //這裡的註釋說的很清楚了 放回false就能cancel the action mode
                    return false;
                }
            }
            //關鍵是這裡 如果不顯示menu則SelectionController就不顯示,找到問題的關鍵了就是它
            if (menu.hasVisibleItems() || mode.getCustomView() != null) {
                getSelectionController().show();
                mTextView.setHasTransientState(true);
                return true;
            } else {
                return false;
            }
        }

原來google做了判斷,你要是遮蔽了menu就不顯示選擇工具了,難怪,那我想顯示就只能自己手動呼叫getSelectionController().show();方法了,怎麼呼叫?反射這是java的大招啊,利用反射試試

@Override
            public boolean onCreateActionMode(ActionMode mode, Menu menu) { 
                try {
                      Field mEditor = TextView.class.getDeclaredField("mEditor");//找到 TextView中的成員變數mEditor  
                      mEditor.setAccessible(true); 
                      Object object= mEditor.get(editTextExt);//根具持有物件拿到mEditor變數裡的值 (android.widget.Editor類的例項)
                        //--------------------顯示選擇控制工具------------------------------//
                         Class mClass=Class.forName("android.widget.Editor");//拿到隱藏類Editor; 
                         Method  method=mClass.getDeclaredMethod("getSelectionController");//取得方法  getSelectionController 
                         method.setAccessible(true);//取消訪問私有方法的合法性檢查     
                         Object resultobject=method.invoke(object);//呼叫方法,返回SelectionModifierCursorController類的例項

                         Method show=resultobject.getClass().getDeclaredMethod("show");//查詢 SelectionModifierCursorController類中的show方法
                         show.invoke(resultobject);//執行SelectionModifierCursorController類的例項的show方法
                         editTextExt.setHasTransientState(true);  

                } catch (Exception e) { 
                    e.printStackTrace();
                }
                return false;//return false 隱藏actionMod選單
            }

反射總結起來就是(個人理解):class.getDeclaredField(“mEditor”)//查詢變數,
Class.getDeclaredMethod(“getSelectionController”)//查詢方法,
拿到變數裡的值/賦值給變數就是:Field.get(持有者物件) ,Field.set(持有者物件,value);
方法呼叫就是Object resultObject=Method.invoke(持有者物件,params), 掌握這幾點一般的使用應該沒啥大問題
通過反射呼叫getSelectionController().show();的確可以顯示選擇文字控制工具了,但是有個問題需要雙擊才能顯示,長按只有touchup就不顯了猜想應該是touchup事件觸發了某些東西,最初的猜想是因為遮蔽了actionMod顯示所以肯定還有地方檢察了actionMod選單是否存在順著這個思路去查詢
首先查詢SelectionActionModeCallback類看哪裡例項化了它,

// Do not start the action mode when extracted text will show up full screen, which would
        // immediately hide the newly created action bar and would be visually distracting.
        if (!willExtract) {
            ActionMode.Callback actionModeCallback = new SelectionActionModeCallback();
            mSelectionActionMode = mTextView.startActionMode(actionModeCallback);
        }

看到是賦值給了mSelectionActionMode變量了,繼續查詢發現了一個stopSelectionActionMode()方法,這個停止肯定會隱藏選擇工具,繼續查詢看看那裡呼叫了stopSelectionActionMode()方法,找到很多但是聯絡到長按能顯示選擇工具擡起就馬上隱藏了可以聯想到最有可能的方法 public boolean performLongClick(boolean handled);看到傳進來的引數handled可以影響到stopSelectionActionMode();是否執行好了再看看那裡呼叫了performLongClick方法 發現在Editor類中找不到了那肯定是擁有Editor的類TextView呼叫了,進去查詢發現TextView中也有一個performLongClick方法

 @Override
    public boolean performLongClick() {
        boolean handled = false; 
        if (super.performLongClick()) {
            handled = true;
        } 
        if (mEditor != null) {
            handled |= mEditor.performLongClick(handled);
        } 
        if (handled) { performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
            if (mEditor != null) mEditor.mDiscardNextActionUp = true;// mEditor.mDiscardNextActionUp關鍵的一句
        } 
        return handled;
    }

看到給mEditor的mDiscardNextActionUp賦值為true了,明顯長按的某種情況是放棄了ActionUp的事件處理,猜想極有可能手動把mDiscardNextActionUp賦值為true就可以了嘗試一下

@Override
            public boolean onCreateActionMode(ActionMode mode, Menu menu) { 
                try {
                      Field mEditor = TextView.class.getDeclaredField("mEditor");//找到 TextView中的成員變數mEditor  
                      mEditor.setAccessible(true); 
                      Object object= mEditor.get(editTextExt);//根具持有物件拿到mEditor變數裡的值 (android.widget.Editor類的例項)
                        //--------------------顯示選擇控制工具------------------------------//
                         Class mClass=Class.forName("android.widget.Editor");//拿到隱藏類Editor; 
                         Method  method=mClass.getDeclaredMethod("getSelectionController");//取得方法  getSelectionController 
                         method.setAccessible(true);//取消訪問私有方法的合法性檢查     
                         Object resultobject=method.invoke(object);//呼叫方法,返回SelectionModifierCursorController類的例項

                         Method show=resultobject.getClass().getDeclaredMethod("show");//查詢 SelectionModifierCursorController類中的show方法
                         show.invoke(resultobject);//執行SelectionModifierCursorController類的例項的show方法
                         editTextExt.setHasTransientState(true); 

                         //--------------------忽略最後一次TouchUP事件-----------------------------------------------//
                         Field  mSelectionActionMode=mClass.getDeclaredField("mDiscardNextActionUp");//查詢變數Editor類中mDiscardNextActionUp
                         mSelectionActionMode.setAccessible(true); 
                         mSelectionActionMode.set(object,true);//賦值為true 

                } catch (Exception e) { 
                    e.printStackTrace();
                }
                return false;//return false 隱藏actionMod選單
            }

發現果然OK ,這樣實現了遮蔽EditText長按彈出的actionMode選單,又保留的選擇文字的選擇工具。