最近项目要求屏蔽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菜单,又保留的选择文字的选择工具。