Android EditText長按菜單中分享功能的隱藏方法
常見的EditText長按菜單如下

oppo

小米
需求是隱藏掉其中的分享/搜索功能,禁止將內(nèi)容分享到其他應(yīng)用。
最終解決方案
這里先說下最終解決方案
像華為/oppo等手機,該菜單實際是谷歌系統(tǒng)的即沒有改過源代碼,像小米的菜單則是自定義,該部分的源代碼改動過。
兩方面修改:
1.谷歌系統(tǒng)自帶的 通過 EditText.setCustomSelectionActionModeCallback()方法設(shè)置自定義的選中后動作模式接口,只保留需要的菜單項
代碼如下
editText.customSelectionActionModeCallback = object : ActionMode.Callback {
override fun onCreateActionMode(
mode: ActionMode?,
menu: Menu?
): Boolean {
menu?.let {
val size = menu.size()
for (i in size - 1 downTo 0) {
val item = menu.getItem(i)
val itemId = item.itemId
//只保留需要的菜單項
if (itemId != android.R.id.cut
&& itemId != android.R.id.copy
&& itemId != android.R.id.selectAll
&& itemId != android.R.id.paste
) {
menu.removeItem(itemId)
}
}
}
return true
}
override fun onActionItemClicked(
mode: ActionMode?,
item: MenuItem?
): Boolean {
return false
}
override fun onPrepareActionMode(
mode: ActionMode?,
menu: Menu?
): Boolean {
return false
}
override fun onDestroyActionMode(mode: ActionMode?) {
}
}
2.小米等手機自定義菜單無法進行隱藏,可以是分享、搜索等功能失效,即在BaseActivity的startActivityForResult中進行跳轉(zhuǎn)攔截,如果是調(diào)用系統(tǒng)的分享/搜索功能,則不允許跳轉(zhuǎn)
override fun startActivityForResult(
intent: Intent?,
requestCode: Int
) {
if (!canStart(intent)) return
super.startActivityForResult(intent, requestCode)
}
@SuppressLint("RestrictedApi")
@RequiresApi(Build.VERSION_CODES.JELLY_BEAN)
override fun startActivityForResult(
intent: Intent?,
requestCode: Int,
options: Bundle?
) {
if (!canStart(intent)) return
super.startActivityForResult(intent, requestCode, options)
}
private fun canStart(intent: Intent?): Boolean {
return intent?.let {
val action = it.action
action != Intent.ACTION_CHOOSER//分享
&& action != Intent.ACTION_VIEW//跳轉(zhuǎn)到瀏覽器
&& action != Intent.ACTION_SEARCH//搜索
} ?: false
}
如果以上不滿足要求,只能通過自定義長按菜單來實現(xiàn)自定義的菜單欄。
解決思路(RTFSC)
分析源碼菜單的創(chuàng)建和點擊事件
既然是長按松手后彈出的,應(yīng)該在onTouchEvent中的ACTION_UP事件或者在performLongClick中,從兩方面著手
先看perfomLongEvent EditText沒有實現(xiàn) 去它的父類TextView中查找
TextView.java
public boolean performLongClick() {
···省略部分代碼
if (mEditor != null) {
handled |= mEditor.performLongClick(handled);
mEditor.mIsBeingLongClicked = false;
}
···省略部分代碼
return handled;
}
可看到調(diào)用了 mEditor.performLongClick(handled)方法
Editor.java
public boolean performLongClick(boolean handled) {
if (!handled && !isPositionOnText(mLastDownPositionX, mLastDownPositionY)
&& mInsertionControllerEnabled) {
final int offset = mTextView.getOffsetForPosition(mLastDownPositionX,
mLastDownPositionY);//獲取當前松手時的偏移量
Selection.setSelection((Spannable) mTextView.getText(), offset);//設(shè)置選中的內(nèi)容
getInsertionController().show();//插入控制器展示
mIsInsertionActionModeStartPending = true;
handled = true;
···
}
if (!handled && mTextActionMode != null) {
if (touchPositionIsInSelection()) {
startDragAndDrop();//開始拖動
···
} else {
stopTextActionMode();
selectCurrentWordAndStartDrag();//選中當前單詞并且開始拖動
···
}
handled = true;
}
if (!handled) {
handled = selectCurrentWordAndStartDrag();//選中當前單詞并且開始拖動
···
}
}
return handled;
}
從上面代碼分析
1.長按時會先選中內(nèi)容 Selection.setSelection((Spannable) mTextView.getText(), offset)
2.顯示插入控制器 getInsertionController().show()
3.開始拖動/選中單詞后拖動 startDragAndDrop()/ selectCurrentWordAndStartDrag()
看著很像了
看下第二步中展示的內(nèi)容
Editor.java -> InsertionPointCursorController
public void show() {
getHandle().show();
if (mSelectionModifierCursorController != null) {
mSelectionModifierCursorController.hide();
}
}
···
private InsertionHandleView getHandle() {
if (mSelectHandleCenter == null) {
mSelectHandleCenter = mTextView.getContext().getDrawable(
mTextView.mTextSelectHandleRes);
}
if (mHandle == null) {
mHandle = new InsertionHandleView(mSelectHandleCenter);
}
return mHandle;
}
實際是InsertionHandleView 執(zhí)行了show方法。 查看其父類HandlerView的構(gòu)造方法
private HandleView(Drawable drawableLtr, Drawable drawableRtl, final int id) {
super(mTextView.getContext());
···
mContainer = new PopupWindow(mTextView.getContext(), null,
com.android.internal.R.attr.textSelectHandleWindowStyle);
···
mContainer.setContentView(this);
···
}
由源碼可看出 HandlerView實際上是PopWindow的View。 即選中的圖標實際上是popwidow
看源碼可看出HandleView有兩個實現(xiàn)類 InsertionHandleView 和SelectionHandleView 由名字可看出一個是插入的,一個選擇的 看下HandleView的show方法
Editor.java ->HandleView
public void show() {
if (isShowing()) return;
getPositionListener().addSubscriber(this, true );
// Make sure the offset is always considered new, even when focusing at same position
mPreviousOffset = -1;
positionAtCursorOffset(getCurrentCursorOffset(), false, false);
}
看下positionAtCursorOffset方法
Editor.java ->HandleView
protected void positionAtCursorOffset(int offset, boolean forceUpdatePosition,
boolean fromTouchScreen) {
···
if (offsetChanged || forceUpdatePosition) {
if (offsetChanged) {
updateSelection(offset);
···
}
···
}
}
里面有一個updateSelection更新選中的位置,該方法會導(dǎo)致EditText重繪,再看show方法的getPositionListener().addSubscriber(this, true )
getPositionListener()返回的實際上是ViewTreeObserver.OnPreDrawListener的實現(xiàn)類PositionListener
重繪會調(diào)用onPreDraw的方法
Editor.java-> PositionListener
@Override
public boolean onPreDraw() {
···
for (int i = 0; i < MAXIMUM_NUMBER_OF_LISTENERS; i++) {
···
positionListener.updatePosition(mPositionX, mPositionY,
mPositionHasChanged, mScrollHasChanged);
···
}
···
return true;
}
調(diào)用了positionListener.updatePosition方法, positionListener這個實現(xiàn)類對應(yīng)的是HandlerView
重點在HandleView的updatePosition方法,該方法進行popWindow的顯示和更新位置
看一下該方法的實現(xiàn)
Editor.java ->HandleView
@Override
public void updatePosition(int parentPositionX, int parentPositionY,
boolean parentPositionChanged, boolean parentScrolled) {
···
if (isShowing()) {
mContainer.update(pts[0], pts[1], -1, -1);
} else {
mContainer.showAtLocation(mTextView, Gravity.NO_GRAVITY, pts[0], pts[1]);
}
}
···
}
}
到此我們知道選中的圖標即下面紅框內(nèi)的實際上popWindow展示

點擊選中的圖標可以展示菜單,看下HandleView的onTouchEvent方法
Editor.java ->HandleView
@Override
public boolean onTouchEvent(MotionEvent ev) {
updateFloatingToolbarVisibility(ev);
···
}
updateFloatingToolbarVisibility(ev)真相在這里,該方法進行懸浮菜單欄的展示
經(jīng)過進一步查找,可以看到會調(diào)用下面SelectionActionModeHelper的這個方法
SelectionActionModeHelper.java
public void invalidateActionModeAsync() {
cancelAsyncTask();
if (skipTextClassification()) {
invalidateActionMode(null);
} else {
resetTextClassificationHelper();
mTextClassificationAsyncTask = new TextClassificationAsyncTask(
mTextView,
mTextClassificationHelper.getTimeoutDuration(),
mTextClassificationHelper::classifyText,
this::invalidateActionMode)
.execute();
}
}
會啟動一個叫TextClassificationAsyncTask的異步任務(wù),該異步任務(wù)最后會執(zhí)行mEditor.getTextActionMode().invalidate()
private void invalidateActionMode(@Nullable SelectionResult result) {
···
final ActionMode actionMode = mEditor.getTextActionMode();
if (actionMode != null) {
actionMode.invalidate();
}
···
}
最后看下mTextActionMode 如何在Editor中賦值
Editor.java
void startInsertionActionMode() {
···
ActionMode.Callback actionModeCallback =
new TextActionModeCallback(false /* hasSelection */);
mTextActionMode = mTextView.startActionMode(
actionModeCallback, ActionMode.TYPE_FLOATING);
···
}
看下mTextView.startActionMode的注釋,在View類中,Start an action mode with the given type. 根據(jù)給的類型,開啟一個動作模式,該模式是一個TYPE_FLOATING模式,菜單的生成就在TextActionModeCallback類中
在TextActionModeCallback的onCreateActionMode方法中
Editor.java ->TextActionModeCallback
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
mode.setTitle(null);
mode.setSubtitle(null);
mode.setTitleOptionalHint(true);
//生成菜單
populateMenuWithItems(menu);
Callback customCallback = getCustomCallback();
if (customCallback != null) {
if (!customCallback.onCreateActionMode(mode, menu)) {
// The custom mode can choose to cancel the action mode, dismiss selection.
Selection.setSelection((Spannable) mTextView.getText(),
mTextView.getSelectionEnd());
return false;
}
}
···
}
生成的菜單的方法populateMenuWithItems(menu)中,生成完菜單會執(zhí)行自定義的回調(diào)getCustomCallback() , 看下該回調(diào)如何賦值。
在TextView中
TextView.java
public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
createEditorIfNeeded();
mEditor.mCustomSelectionActionModeCallback = actionModeCallback;
}
因此我們可以在自定義回調(diào)的onCreateActionMode方法中,刪除不需要的菜單項。
但該方法對小米手機無效,小米手機的菜單展示,不是通過startActionMode來展示的。不過可以對菜單中的分享等功能進行禁止跳轉(zhuǎn),解決方法看最上面
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。
- Android 登錄頁面的實現(xiàn)代碼(密碼顯示隱藏、EditText 圖標切換、限制輸入長度)
- Android EditText隨輸入法一起移動并懸浮在輸入法之上的示例代碼
- Android EditText每4位自動添加空格效果
- Android EditText追加空格、限制字符等方法示例
- Android中多個EditText輸入效果的解決方式
- Android實現(xiàn)微信朋友圈評論EditText效果
- Android EditText監(jiān)聽回車鍵并處理兩次回調(diào)問題
- Android實現(xiàn)EditText添加下劃線
- Android自定義密碼輸入EditTextLayout
- Android EditText限制輸入整數(shù)和小數(shù)的位數(shù)的方法示例
- Android使用EditText小技巧匯總
相關(guān)文章
Android WebView 不支持 H5 input type="file" 解決方法
這篇文章主要介紹了Android WebView 不支持 H5 input type="file" 解決方法,需要的朋友可以參考下2017-06-06
android編程實現(xiàn)為程序創(chuàng)建快捷方式的方法
這篇文章主要介紹了android編程實現(xiàn)為程序創(chuàng)建快捷方式的方法,實例分析了Android創(chuàng)建程序快捷方式的相關(guān)設(shè)置與功能實現(xiàn)技巧,需要的朋友可以參考下2015-11-11
android自定義控件和自定義回調(diào)函數(shù)步驟示例
這篇文章主要介紹了android自定義控件步驟示例,包括為View類增加屬性、響應(yīng)用戶消息、自定義回調(diào)函數(shù)等方法2014-01-01
如何修改Android Studio創(chuàng)建module時默認的compileSdkVersion
這篇文章主要給大家介紹了如何修改Android Studio創(chuàng)建module時默認的compileSdkVersion的相關(guān)資料,文中介紹的非常詳細,對大家具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧。2017-05-05
AndroidStudio 3.6 中 R.layout 找不到對應(yīng)的xml文件問題及解決方法
這篇文章主要介紹了AndroidStudio 3.6 中 R.layout 找不到對應(yīng)的xml文件問題,本文給出了解決方法對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-03-03
Android 系統(tǒng)服務(wù)TelecomService啟動過程原理分析
這篇文章主要介紹了Android 系統(tǒng)服務(wù)TelecomService啟動過程原理分析,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07
Android進階NestedScroll嵌套滑動機制實現(xiàn)吸頂效果詳解
這篇文章主要為大家介紹了Android進階NestedScroll嵌套滑動機制實現(xiàn)吸頂效果詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-01-01

