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

常见的EditText长按菜单如下

oppo

小米

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

最终解决方案

这里先说下最终解决方案

像华为/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展示

点击选中的图标可以展示菜单,看下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来展示的。不过可以对菜单中的分享等功能进行禁止跳转,解决方法看最上面

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • Android编程之ListView和EditText发布帖子隐藏软键盘功能详解

    本文实例讲述了Android编程之ListView和EditText发布帖子隐藏软键盘功能.分享给大家供大家参考,具体如下: 在Android开发中,手动调用软件盘的隐藏和显示有时候也是非常常见的需求. EditText控件实现了点击打开软键盘输入功能,but why ? 为什么EditText可以点击弹出keyboard,而TextView却不可以,EditText继承TextView做了哪些修改呢?关于这些问题得查看相关具体代码如何实现可以参考,看似简单的控件其实系统封装实现的很复杂.这里告

  • Android文本输入框(EditText)输入密码时显示与隐藏

    代码很简单,这里就不多废话了. 复制代码 代码如下: package cc.c; import android.app.Activity; import android.os.Bundle; import android.text.Selection; import android.text.Spannable; import android.text.method.HideReturnsTransformationMethod; import android.text.method.Passw

  • Android点击EditText文本框之外任何地方隐藏键盘的解决办法

    1,实现方法一: 通过给当前界面布局文件的父layout设置点击事件(相当于给整个Activity设置点击事件),在事件里进行键盘隐藏 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/traceroute_rootview" android:layout_width="fill_parent" android:

  • Android编程实现点击EditText之外的控件隐藏软键盘功能

    本文实例讲述了Android编程实现点击EditText之外的控件隐藏软键盘功能.分享给大家供大家参考,具体如下: 工具类 ... public static void hideKeyboard(Context ctx) { if (ctx != null) { View view = ((Activity) ctx).getCurrentFocus(); if (view != null) { InputMethodManager inputManager = (InputMethodMana

  • Android中实现EditText密码显示隐藏的方法

    在Google发布了support:design:23+以后我们发现有这么一个东西TextInputLayout,先看下效果图: <android.support.design.widget.TextInputLayout android:id="@+id/pwdLayout" android:layout_width="match_parent" android:layout_height="wrap_content" app:passw

  • Android EditText密码的隐藏和显示功能

    Android EditText密码的隐藏和显示功能 实现效果图: 实现代码: 首先在xml里创建两个控件 EditText和CheckBox 然后就很简单了 dt1=(EditText)findViewById(R.id.password); cb1=(CheckBox)findViewById(R.id.checkbox_1); cb1.setOnCheckedChangeListener(new OnCheckedChangeListener() { @Override public vo

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

    常见的EditText长按菜单如下 oppo 小米 需求是隐藏掉其中的分享/搜索功能,禁止将内容分享到其他应用. 最终解决方案 这里先说下最终解决方案 像华为/oppo等手机,该菜单实际是谷歌系统的即没有改过源代码,像小米的菜单则是自定义,该部分的源代码改动过. 两方面修改: 1.谷歌系统自带的 通过 EditText.setCustomSelectionActionModeCallback()方法设置自定义的选中后动作模式接口,只保留需要的菜单项 代码如下 editText.customSel

  • Android 自定义弹出菜单和对话框功能实例代码

    Android 开发当中,可能会存在许多自定义布局的需求,比如自定义弹出菜单(popupWindow),以及自定义对话框(Dialog). 话不多说,直接上图片. 先讲第一种,自定义PopUpWindow 1.popupWindow protected void showPopWindow(View view, final int pos){ WindowManager wm= (WindowManager) myContext.getSystemService(Context.WINDOW_S

  • Android仿QQ空间动态界面分享功能

    先看看效果: 用极少的代码实现了 动态详情 及 二级评论 的 数据获取与处理 和 UI显示与交互,并且高解耦.高复用.高灵活. 动态列表界面MomentListFragment支持 下拉刷新与上拉加载 和 模糊搜索,反复快速滑动仍然非常流畅. 缓存机制使得数据可在启动界面后瞬间加载完成. 动态详情界面MomentActivity支持 (取消)点赞.(删除)评论.点击姓名跳到个人详情 等. 只有1张图片时图片放大显示,超过1张则按九宫格显示. 用到的CommentContainerView和Mom

  • Android实现长按back键退出应用程序的方法

    本文实例讲述了Android实现长按back键退出应用程序的方法.分享给大家供大家参考.具体分析如下: 最近在做一个Android上的应用,碰到一个问题就是如何实现长按back键退出应用程序.在网上查找了很多资料,发现几乎没有这样的实现,大部分在处理时是双击back键来退出应用程序.参考了一下双击back键退出应用程序的代码,网上主流的一种方法是下面这种方法,实现起来比较简单: @Override public boolean onKeyDown(int keyCode, KeyEvent ev

  • Android实现在xml文件中引用自定义View的方法分析

    本文实例讲述了Android实现在xml文件中引用自定义View的方法.分享给大家供大家参考,具体如下: 在xml中引用自定义view 方法一: <view class="com.test.copytext.CopyText" android:layout_width="fill_parent" android:layout_height="wrap_content" /> 方法二: <view class="com.

  • Android编程实现在adapter中进行数据操作的方法

    本文实例讲述了Android编程实现在adapter中进行数据操作的方法.分享给大家供大家参考,具体如下: package com.cvte.apkclassify; import java.util.ArrayList; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.p

  • Android sdutio配置Zxing进行扫码功能的实现方法

    github开源项目(Zxing)demo 最快的调用Zxing方法 1.关联第三方库 2.调用基础的扫码 3.获取返回值 具体代码如下: //1.默认选项启动意图 new IntentIntegrator(MainActivity.this).initiateScan(); // `this` is the current Activity //2.获取得到的结果: @Override protected void onActivityResult(int requestCode, int r

  • 微信小程序实现带参数的分享功能(两种方法)

    微信小程序分享功能的实现方法有两种: 第一种 在page.js中实现onShareAppMessage,便可在小程序右上角选择分享该页面 onShareAppMessage: function () { return { title: '弹出分享时显示的分享标题', desc: '分享页面的内容', path: '/page/user?id=123' // 路径,传递参数到指定页面. } } 第二种 自定义按钮实现分享,在page中添加一个带有open-type='share'的button标签

  • 详解Android 8.1.0 Service 中 弹出 Dialog的方法

    场景:在Service 中开启线程下载升级包,当下载完系统升级包,弹出一个Dialog 提示用户. 注意,Android 系统版本不一样,可能会有不一样的表现.当前是基于 Android 8.1.0 的 Service 中弹 Dialog. 首先,就是要在功能清单列表中声明权限,以下两个都必须声明: <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/><!--这行代码必须存在,

  • 网页中右键功能的实现方法之contextMenu的使用

    本文介绍一种网页中实现右键功能的方案–contextMenu. 1.下载 下载地址 https://github.com/swisnl/jQuery-contextMenu 下载得到压缩文件jQuery-contextMenu-master.zip 解压后,使用dist目录下css.js. 2.使用方法 使用步骤: (1) 引用css.js. (2) html.js代码. 简单例子如下: 代码test.html: <!DOCTYPE html> <html lang="en&q

随机推荐