安卓实现替换EditText粘贴的内容 您所在的位置:网站首页 安卓Editable 安卓实现替换EditText粘贴的内容

安卓实现替换EditText粘贴的内容

#安卓实现替换EditText粘贴的内容| 来源: 网络整理| 查看: 265

效果图

在点击粘贴之后弹出了一个toast提示,既然可以做到弹出toast,那想干其他事情还不简单。比如,将用户粘贴的文本替换成其他文本,这才是研究实现这个功能的原因。

先说一下实现方式,需要继承EditText/AppCompatEditText,再重写onTextContextMenuItem方法,先直接上代码。

public class CustomEditText extends AppCompatEditText { public CustomeEditText(Context context) { super(context); } public CustomEditText(Context context, AttributeSet attrs) { super(context, attrs); } public CustomEditText(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override public boolean onTextContextMenuItem(int id) { if (id == android.R.id.paste) { paste(); return true; } return super.onTextContextMenuItem(id); } private void paste() { //TODO 在这里实现想要实现的功能 Toast.makeText(getContext(),"paste",Toast.LENGTH_SHORT).show(); } }

再稍微说一下思路吧

先说明一下,下面很多代码是根据猜代码去找的,而不是逐行阅读找到相应的代码,所以如果不能接受这种方式就没必要继续看(我也知道这不是一种好的方式,但看不懂源码只能靠猜代码)。

观察弹出粘贴菜单的过程,发现是通过长按输入框弹出来的,所以找了一下TextView内部setLongClickListener的调用,最后没有找到。所以搜索onTouchEvent方法,看到onTouch下面有这样一段代码。

if (mEditor != null) { mEditor.onTouchEvent(event); if (mEditor.mSelectionModifierCursorController != null && mEditor.mSelectionModifierCursorController.isDragAcceleratorActive()) { return true; } }

所以就查看Editor是怎么实现这个功能的,在Editor的onTouch方法里面调用了这个方法:updateFloatingToolbarVisibility。这个方法没有注释,但从名称上看就已经知道了是更新长按EditText的弹窗的显示状态。

void onTouchEvent(MotionEvent event) { final boolean filterOutEvent = shouldFilterOutTouchEvent(event); mLastButtonState = event.getButtonState(); if (filterOutEvent) { if (event.getActionMasked() == MotionEvent.ACTION_UP) { mDiscardNextActionUp = true; } return; } updateTapState(event); updateFloatingToolbarVisibility(event); ... } updateFloatingToolbarVisibility的源码 private void updateFloatingToolbarVisibility(MotionEvent event) { if (mTextActionMode != null) { switch (event.getActionMasked()) { case MotionEvent.ACTION_MOVE: hideFloatingToolbar(ActionMode.DEFAULT_HIDE_DURATION); break; case MotionEvent.ACTION_UP: // fall through case MotionEvent.ACTION_CANCEL: showFloatingToolbar(); } } }

可以看到在ACTION_MOVE的时候隐藏Toolbar,在ACTION_UP和ACTION_CANCEL的时候显示Toolbar。

下面猜代码开始了。

没有去思考mTextActionMode是什么时候初始化的,只是在源码中找到这样一段代码。

/** * Start an Insertion action mode. */ void startInsertionActionMode() { if (mInsertionActionModeRunnable != null) { mTextView.removeCallbacks(mInsertionActionModeRunnable); } if (extractedTextModeWillBeStarted()) { return; } stopTextActionMode(); ActionMode.Callback actionModeCallback = new TextActionModeCallback(TextActionMode.INSERTION); mTextActionMode = mTextView.startActionMode( actionModeCallback, ActionMode.TYPE_FLOATING); if (mTextActionMode != null && getInsertionController() != null) { getInsertionController().show(); } }

这里对mTextActionMode做初始化操作,不过View.startActionMode听都没听过,所以不知道这个方法是干嘛的,所以看了一下actionModeCallback的实现。

发现是一个继承自ActionMode.Callback2的类

private class TextActionModeCallback extends ActionMode.Callback2 { ... }

这里面有一个方法,onCreateActionMode,看一下文档

/** * Called when action mode is first created. The menu supplied will be used to * generate action buttons for the action mode. * * @param mode ActionMode being created * @param menu Menu used to populate action buttons * @return true if the action mode should be created, false if entering this * mode should be aborted. */ public boolean onCreateActionMode(ActionMode mode, Menu menu);

大概的意思是首次创建action mode的时候调用,提供的menu将用于为action mode生成操作按钮。

TextActionModeCallback对onCreateActionMode的实现

@Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { mAssistClickHandlers.clear(); mode.setTitle(null); mode.setSubtitle(null); mode.setTitleOptionalHint(true); populateMenuWithItems(menu); ... }

这里的populateMenuWithItems就是生成menu的方法

private void populateMenuWithItems(Menu menu) { ... if (mTextView.canPaste()) { menu.add(Menu.NONE, TextView.ID_PASTE, MENU_ITEM_ORDER_PASTE, com.android.internal.R.string.paste) .setAlphabeticShortcut('v') .setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); } ... }

可以看到这里通过TextView判断能否粘贴,如果可以粘贴就往menu添加一个item,这里的itemId使用的是TextView.ID_PASTE。

说实话,由于对ActionMode完全不了解,到了这里之后就没思路了。所以这个时候只能去查看TextActionModeCallback的最顶层的接口:android.view.ActiomMode.Callback,看到里面有一个叫onActionItemClicked的方法。

/** * Called to report a user click on an action button. * * @param mode The current ActionMode * @param item The item that was clicked * @return true if this callback handled the event, false if the standard MenuItem * invocation should continue. */ public boolean onActionItemClicked(ActionMode mode, MenuItem item);

再看一下TextActionModeCallback对该方法的实现

@Override public boolean onActionItemClicked(ActionMode mode, MenuItem item) { getSelectionActionModeHelper() .onSelectionAction(item.getItemId(), item.getTitle().toString()); if (mProcessTextIntentActionsHandler.performMenuItemAction(item)) { return true; } Callback customCallback = getCustomCallback(); if (customCallback != null && customCallback.onActionItemClicked(mode, item)) { return true; } if (item.getGroupId() == TextView.ID_ASSIST && onAssistMenuItemClicked(item)) { return true; } return mTextView.onTextContextMenuItem(item.getItemId()); }

前面的代码我都没看,所以不清楚前面的代码的作用,我只看到了最后一行,调用了TextView的onTextContextMenuItem方法

再看一下这个方法的具体实现

/** * Called when a context menu option for the text view is selected. Currently * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut}, * {@link android.R.id#copy}, {@link android.R.id#paste} or {@link android.R.id#shareText}. * * @return true if the context menu item action was performed. */ public boolean onTextContextMenuItem(int id) { int min = 0; int max = mText.length(); if (isFocused()) { final int selStart = getSelectionStart(); final int selEnd = getSelectionEnd(); min = Math.max(0, Math.min(selStart, selEnd)); max = Math.max(0, Math.max(selStart, selEnd)); } switch (id) { case ID_SELECT_ALL: final boolean hadSelection = hasSelection(); selectAllText(); if (mEditor != null && hadSelection) { mEditor.invalidateActionModeAsync(); } return true; case ID_UNDO: if (mEditor != null) { mEditor.undo(); } return true; // Returns true even if nothing was undone. case ID_REDO: if (mEditor != null) { mEditor.redo(); } return true; // Returns true even if nothing was undone. case ID_PASTE: paste(min, max, true /* withFormatting */); return true; ... } return false; }

可以看到,里面对IN_PASTE这个id进行判断,和上面的populateMenuWidthItems的itemId是一致的。看到paste这个方法,发现是一个private的,所以没办法重写这个方法。

/** * Paste clipboard content between min and max positions. */ private void paste(int min, int max, boolean withFormatting) { ClipboardManager clipboard = getClipboardManagerForUser(); ClipData clip = clipboard.getPrimaryClip(); if (clip != null) { boolean didFirst = false; for (int i = 0; i < clip.getItemCount(); i++) { final CharSequence paste; if (withFormatting) { paste = clip.getItemAt(i).coerceToStyledText(getContext()); } else { // Get an item as text and remove all spans by toString(). final CharSequence text = clip.getItemAt(i).coerceToText(getContext()); paste = (text instanceof Spanned) ? text.toString() : text; } if (paste != null) { if (!didFirst) { Selection.setSelection(mSpannable, max); ((Editable) mText).replace(min, max, paste); didFirst = true; } else { ((Editable) mText).insert(getSelectionEnd(), "\n"); ((Editable) mText).insert(getSelectionEnd(), paste); } } } sLastCutCopyOrTextChangedTime = 0; } }

既然没办法重写paste方法,并且onTextContextMenuItem是public且不是final,那就重写onTextContextMenuItem方法,

但这个时候又发现一个问题,TextView.IN_PASTE静态常量是包私有,不过好在这个常量是引用R文件的一个id,所以问题不大。

static final int ID_PASTE = android.R.id.paste;

所以这个时候重写之后就可以判断itemId是否为android.R.id.paste,如果是,就return true,所以就有了开头的那段代码。

如果有哪里写得不好,望指正,共同进步。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

      专题文章
        CopyRight 2018-2019 实验室设备网 版权所有