当前位置: 移动技术网 > IT编程>移动开发>Android > Android EditText长按菜单中分享功能的隐藏方法

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

2019年07月23日  | 移动技术网IT编程  | 我要评论

天津爆炸致85死,山东历史名人,水陆突击队

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

总结

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

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网