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

常见的EditText长按菜单如下

6d9c60a7ef78190ae635b32c298f6b25.png

oppo

065fb48929b4916af1b9c8aa135d3089.png

小米

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

最终解决方案

这里先说下最终解决方案

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

6fe9bd8f308b252ca4b4d0676c521e3a.png

点击选中的图标可以展示菜单,看下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
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 苹果的应用软件可以通过App Store下载,但在开发者发布新版本之前,需要使用苹果签名证书进行测试和开发。这个过程需要支付一定的费用,对开发者来说有时会不方便。因此,很多人在尝试更便宜的方式来签署自己的应用程序。 其,一个很不错的选择就是使用免费签名证书。这些免费证书由第三方公司提供,经过苹果的审核才会被批准。使用这些证书时,开发者可以签名自己的应用程序,然后安装到自己的设备上进行测试。 免费签名证书的分享越来越受欢迎,因为它能够减少一些开发者的开销。许多人创建了专门的网站或社交媒体帐户来分享他们的签名证书,供其他开发者使用。当然,使用免费证书时需要小心,尤其是在寻找可信的证书供应商时,骗局也是有的。 总的来说,使用免费签名证书是一种不错的选择,但开发者需要谨慎行事,并确保下载只来自可靠的供应商。当然,如果开发者可以承担一些额外的费用,购买为期一年的苹果签名证书则是更为安全和可靠的选择。 ### 回答2: 苹果App免费签名证书分享是一种方法,让开发者可以免费为他们的应用程序获得签名证书。签名证书是苹果公司提供的一种数字身份认证机制,通过这种机制,苹果可以验证应用程序的来源和完整性。 为了保证应用程序的安全性,苹果在其App Store上只允许在App Store上架的应用程序进行下载和安装。这就意味着,开发者必须先将他们的应用程序提交给苹果进行审核,若审核通过,苹果会为该应用程序分配一个唯一的签名证书。应用程序必须使用正确的签名证书才能在iOS设备上运行。 然而,苹果的签名证书是有限制的,每个开发者账号每年只有一定数量的签名证书分配。在过去,开发者必须购买专业开发者账号才能获得更多的签名证书,这对于个人开发者或初学者来说是一笔不菲的费用。 为了解决这个问题,一些第三方服务商开发了免费签名证书分享的解决方案。通过这种方案,开发者可以与其他开发者共享签名证书,从而节省了额外的费用。 免费签名证书分享的原理相对简单。首先,一个开发者创建一个开发者账号,并获得苹果的签名证书。然后,他可以将这个签名证书分享给其他开发者,使他们能够使用同一个签名证书来签署他们的应用程序。 需要注意的是,免费签名证书分享的解决方案并非官方提供,并且可能存在一些风险。苹果公司可以随时更改政策,禁止使用共享的签名证书。此外,由于使用共享证书的应用程序实际上由不同的开发者创建,苹果可能会对这些应用程序进行更严格的审核。 总之,苹果App免费签名证书分享是一种通过共享签名证书的方法,使开发者能够节省额外费用,并将其应用程序发布到iOS平台。然而,需要谨慎使用,并了解其的潜在风险。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值