服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - Android - Android EditText长按菜单中分享功能的隐藏方法

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

2022-09-27 16:01焦世春 Android

Android EditText控件是经常使用的控件,下面这篇文章主要给大家介绍了关于Android中EditText长按菜单中分享功能的隐藏方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面来一

常见的edittext长按菜单如下

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

oppo

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

小米

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

最终解决方案

这里先说下最终解决方案

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

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

代码如下

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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中进行跳转拦截,如果是调用系统的分享/搜索功能,则不允许跳转

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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中查找

?
1
2
3
4
5
6
7
8
9
10
11
textview.java
 public boolean performlongclick() {
 ···省略部分代码
 if (meditor != null) {
 handled |= meditor.performlongclick(handled);
 meditor.misbeinglongclicked = false;
 }
 
 ···省略部分代码
 return handled;
 }

可看到调用了 meditor.performlongclick(handled)方法

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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()

看着很像了

看下第二步中展示的内容

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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的构造方法

?
1
2
3
4
5
6
7
8
9
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方法

?
1
2
3
4
5
6
7
8
9
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方法

?
1
2
3
4
5
6
7
8
9
10
11
12
13
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的方法

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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的显示和更新位置

看一下该方法的实现

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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方法

?
1
2
3
4
5
6
editor.java ->handleview
 @override
 public boolean ontouchevent(motionevent ev) {
 updatefloatingtoolbarvisibility(ev);
 ···
 }

updatefloatingtoolbarvisibility(ev)真相在这里,该方法进行悬浮菜单栏的展示
经过进一步查找,可以看到会调用下面selectionactionmodehelper的这个方法

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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()

?
1
2
3
4
5
6
7
8
private void invalidateactionmode(@nullable selectionresult result) {
···
final actionmode actionmode = meditor.gettextactionmode();
if (actionmode != null) {
actionmode.invalidate();
}
···
}

最后看下mtextactionmode 如何在editor中赋值

?
1
2
3
4
5
6
7
8
9
10
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方法中

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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中

?
1
2
3
4
5
textview.java
 public void setcustomselectionactionmodecallback(actionmode.callback actionmodecallback) {
 createeditorifneeded();
 meditor.mcustomselectionactionmodecallback = actionmodecallback;
 }

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

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

总结

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

原文链接:https://juejin.im/post/5c720ef6f265da2d943f6989

延伸 · 阅读

精彩推荐