Android EditText长按菜单中分享功能的隐藏方法

作者:焦世春 时间:2021-08-27 19:30:45 

常见的EditText长按菜单如下

Android EditText长按菜单中分享功能的隐藏方法

oppo

Android EditText长按菜单中分享功能的隐藏方法

小米

需求是隐藏掉其中的分享/搜索功能,禁止将内容分享到其他应用。

最终解决方案

这里先说下最终解决方案

像华为/oppo等手机,该菜单实际是谷歌系统的即没有改过源代码,像小米的菜单则是自定义,该部分的源代码改动过。
两方面修改:

1.谷歌系统自带的 通过 EditText.setCustomSelectionActionModeCallback()方法设置自定义的选中后动作模式接口,只保留需要的菜单项

代码如下


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中进行跳转拦截,如果是调用系统的分享/搜索功能,则不允许跳转


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//跳转到浏览器
&& action != Intent.ACTION_SEARCH//搜索
} ?: false
}

如果以上不满足要求,只能通过自定义长按菜单来实现自定义的菜单栏。

解决思路(RTFSC)

分析源码菜单的创建和点击事件

既然是长按松手后弹出的,应该在onTouchEvent中的ACTION_UP事件或者在performLongClick中,从两方面着手
先看perfomLongEvent EditText没有实现 去它的父类TextView中查找


TextView.java
public boolean performLongClick() {
···省略部分代码
if (mEditor != null) {
handled |= mEditor.performLongClick(handled);
mEditor.mIsBeingLongClicked = false;
}

···省略部分代码
return handled;
}

可看到调用了 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);//设置选中的内容
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.长按时会先选中内容 Selection.setSelection((Spannable) mTextView.getText(), offset)

2.显示插入控制器  getInsertionController().show()

3.开始拖动/选中单词后拖动 startDragAndDrop()/ selectCurrentWordAndStartDrag()

看着很像了

看下第二步中展示的内容


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 执行了show方法。  查看其父类HandlerView的构造方法


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有两个实现类 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更新选中的位置,该方法会导致EditText重绘,再看show方法的getPositionListener().addSubscriber(this, true )

getPositionListener()返回的实际上是ViewTreeObserver.OnPreDrawListener的实现类PositionListener
重绘会调用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;
}

调用了positionListener.updatePosition方法, positionListener这个实现类对应的是HandlerView

重点在HandleView的updatePosition方法,该方法进行popWindow的显示和更新位置

看一下该方法的实现


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]);
}
}
···
}
}

到此我们知道选中的图标即下面红框内的实际上popWindow展示

Android EditText长按菜单中分享功能的隐藏方法

点击选中的图标可以展示菜单,看下HandleView的onTouchEvent方法


Editor.java ->HandleView
@Override
public boolean onTouchEvent(MotionEvent ev) {
updateFloatingToolbarVisibility(ev);
···
}

updateFloatingToolbarVisibility(ev)真相在这里,该方法进行悬浮菜单栏的展示
经过进一步查找,可以看到会调用下面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的异步任务,该异步任务最后会执行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. 根据给的类型,开启一个动作模式,该模式是一个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)中,生成完菜单会执行自定义的回调getCustomCallback() , 看下该回调如何赋值。

在TextView中


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

因此我们可以在自定义回调的onCreateActionMode方法中,删除不需要的菜单项。

但该方法对小米手机无效,小米手机的菜单展示,不是通过startActionMode来展示的。不过可以对菜单中的分享等功能进行禁止跳转,解决方法看最上面

来源:https://juejin.im/post/5c720ef6f265da2d943f6989

标签:android,隐藏,edittext
0
投稿

猜你喜欢

  • C#通过指针读取文件的方法

    2023-02-21 03:08:10
  • Android 通过TCP协议上传指定目录文件的方法

    2023-11-07 23:34:11
  • C#实现文件上传及文件下载功能实例代码

    2022-12-13 23:57:23
  • 详解C#如何优雅地终止线程

    2023-11-21 11:41:31
  • 详解Java异常处理中finally子句的运用

    2023-11-29 10:10:30
  • Java7之forkjoin简介_动力节点Java学院整理

    2023-08-31 14:08:28
  • Java之BigDecimal的坑及解决

    2022-05-17 01:09:01
  • Java中Maven项目导出jar包配置的示例代码

    2023-01-26 20:09:32
  • 详解Android Handler的使用

    2022-11-17 12:59:18
  • java队列实现方法(顺序队列,链式队列,循环队列)

    2023-06-24 01:43:17
  • Unity实现文本转贴图

    2022-05-10 19:53:04
  • Java爬虫范例之使用Htmlunit爬取学校教务网课程表信息

    2021-07-17 00:52:51
  • Android UI效果之绘图篇(二)

    2022-12-06 00:49:15
  • Java中反射的学习笔记分享

    2021-12-18 14:41:43
  • Java树形结构数据生成导出excel文件方法记录

    2021-08-20 05:19:18
  • Android中的广播、服务、数据库、通知、包等术语的原理和介绍(图解)

    2023-01-20 13:57:52
  • openCV中meanshift算法查找目标的实现

    2023-04-03 21:24:21
  • Android启动屏实现左右滑动切换查看功能

    2022-11-02 02:18:02
  • 深入分析C# 线程同步

    2023-05-09 00:49:44
  • 在Android设备上搭建Web服务器的方法

    2023-06-23 23:38:36
  • asp之家 软件编程 m.aspxhome.com