Android隐藏EditText长按菜单中分享功能探索

栏目: IOS · Android · 发布时间: 5年前

内容简介:常见的EditText长按菜单如下oppo小米

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


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Paradigms of Artificial Intelligence Programming

Paradigms of Artificial Intelligence Programming

Peter Norvig / Morgan Kaufmann / 1991-10-01 / USD 77.95

Paradigms of AI Programming is the first text to teach advanced Common Lisp techniques in the context of building major AI systems. By reconstructing authentic, complex AI programs using state-of-the-......一起来看看 《Paradigms of Artificial Intelligence Programming》 这本书的介绍吧!

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换