• Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.
  • This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money.

1. Introduction

National Day fishing, is also not idle, he remembered before doing a class notes app, text to the system to choose options menu to add a new entry, click the entrance, you can select text content, pass to our app (some friend already know it can do many things), thought, to share with you, Specific use is actually very simple, and listen to us slowly, do not try to row away, there is a realization of the principle of analysis


Below we from how to use and source these two angles to expand the introduction: ActionMode and system text selection menu, as for why put these two together, I believe you after reading this article, will have their own opinions

2.ActionMode

Click on the “Official ActionMode User Guide”

Why should I write an official manual when there is one? 2. Write examples and play with 🤣😅

ActionMode is an abstract class that focuses user interaction on performing association operations. ActionMode has two modes: TYPE_PRIMARY(default mode) and TYPE_FLOATING(floating toolbar).

A. How to use it

ActionMode has a Callback inside, which the comment says is available

View.startActionMode(actionmode. Callback) or view. startActionMode(actionmode. Callback,int type)

The startActionMode in the Activity is also the view. startActionMode in the call

It is also very simple to use, as shown in the following example:

  • 1. Resource XML that provides a context menu

      
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:title="@string/menu_favorite"
        android:id="@+id/item_action_favorite"
        android:icon="@drawable/ic_favorite_svg"/>
    <item
        android:title="@string/menu_cjsh"
        android:id="@+id/item_action_cjsh"/>
</menu>
Copy the code
  • 2. ImplementActionMode.Callbackinterface
var actionMode:ActionMode? = null
val actionModeCallback = object: ActionMode.Callback{
    override fun onCreateActionMode(mode: ActionMode? , menu:Menu?).: Boolean {
        // Provides a menu resource for context menu items, as described in the official documentation user guidemode? .menuInflater? .inflate(R.menu.context_menu,menu)return true
    }
    override fun onPrepareActionMode(mode: ActionMode? , menu:Menu?).: Boolean {
        return false
    }
    override fun onActionItemClicked(mode: ActionMode? , item:MenuItem?).: Boolean {
        return when(item? .itemId) { R.id.item_action_favorite -> { Toast.makeText(applicationContext,"❤️ collected successfully",Toast.LENGTH_SHORT).show() mode? .finish()true}...else -> false}}override fun onDestroyActionMode(mode: ActionMode?). {
        actionMode = null}}Copy the code
  • 3. Enable associated operation mode
//type = TYPE_PRIMARY
actionMode = it.startActionMode(actionModeCallback)

//type = TYPE_FLOATING
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
     actionMode = it.startActionMode(actionModeCallback, ActionMode.TYPE_FLOATING)
}
Copy the code



Example – Demonstrates the effect

B. Source code analysis

When view. startActionMode is called, it goes down here

//android.view.View

public ActionMode startActionMode(ActionMode.Callback callback, int type) {
        ViewParent parent = getParent();
        if (parent == null) return null;
        try {
            // Start recursively calling ViewGroup's startActionModeForChild
            return parent.startActionModeForChild(this, callback, type);
        } catch (AbstractMethodError ame) {
            // Use the default actionmode. TYPE_PRIMARY to start the operation mode for the specified view
            return parent.startActionModeForChild(this, callback); }}Copy the code

A recursive call to ViewGroup’s startActionModeForChild is eventually executed in the DecorView’s startActionMode method

//com.android.internal.policy.DecorView

private ActionMode startActionMode(
        View originatingView, ActionMode.Callback callback, int type) {
    ActionMode.Callback2 wrappedCallback = new ActionModeCallback2Wrapper(callback);
    ActionMode mode = null;
    / / mWindow PhoneWindow
    if(mWindow.getCallback() ! =null && !mWindow.isDestroyed()) {
        try {
            / / here will trigger AppCompatWindowCallback# onWindowStartingActionMode
            mode = mWindow.getCallback().onWindowStartingActionMode(wrappedCallback, type);
        } catch(AbstractMethodError ame) { ...... }}if(mode ! =null) {... }else {
        // In the example of this article
        // This is where type= actionmode. TYPE_FLOATING is executed when called startMode
        // Internally execute createFloatingActionMode
        // Then internally initialize a FloatingToolbar and return FloatingActionMode
        //FloatingToolbar: is a FloatingToolbar that displays context menu items, internally implemented using popWindow
        mode = createActionMode(type, wrappedCallback, originatingView);
        if(mode ! =null && wrappedCallback.onCreateActionMode(mode, mode.getMenu())) {
            setHandledActionMode(mode);
        } else {
            mode = null; }}if(mode ! =null&& mWindow.getCallback() ! =null && !mWindow.isDestroyed()) {
        try {
            // Call back the empty implementation of the onActionModeStarted method in the Activity
            // Notifies the Activity that ActionMode has started
            mWindow.getCallback().onActionModeStarted(mode);
        } catch (AbstractMethodError ame) {
        }
    }
    return mode;
}
Copy the code

We have a look at AppCompatWindowCallback# onWindowStartingActionMode internal implementation

//androidx.appcompat.app.AppCompatDelegateImpl.AppCompatWindowCallback

public android.view.ActionMode onWindowStartingActionMode(
    android.view.ActionMode.Callback callback, int type) {
    if (isHandleNativeActionModesEnabled()) {
        switch (type) {
            case android.view.ActionMode.TYPE_PRIMARY:
                The TYPE_PRIMARY type triggers this method call
                returnstartAsSupportActionMode(callback); }}/ / does not meet the above conditions, onWindowStartingActionMode will perform to the Activity
    return super.onWindowStartingActionMode(callback, type);
}
Copy the code

Take a look at startAsSupportActionMode

//androidx.appcompat.app.AppCompatDelegateImpl.AppCompatWindowCallback

final android.view.ActionMode startAsSupportActionMode( android.view.ActionMode.Callback callback) {
    Actionmode.callback wrapper
    final SupportActionModeWrapper.CallbackWrapper callbackWrapper =
            new SupportActionModeWrapper.CallbackWrapper(mContext, callback);
    
    // Scroll down, there is analysis
    final androidx.appcompat.view.ActionMode supportActionMode =
            startSupportActionMode(callbackWrapper);

    if(supportActionMode ! =null) {
        // Return to the wrapped ActionMode
        return callbackWrapper.getActionModeWrapper(supportActionMode);
    }
    return null;
}
Copy the code

Let’s take a look at the startSupportActionMode above

//androidx.appcompat.app.AppCompatDelegateImpl

public ActionMode startSupportActionMode(@NonNull final ActionMode.Callback callback) {...// Wrap Callback to clear internal references when action Mode is destroyed
    final ActionMode.Callback wrappedCallback = new ActionModeCallbackWrapperV9(callback);
    ActionBar ab = getSupportActionBar();
    if(ab ! =null) {
        // The supportActionBar here is WindowDecorActionBarmActionMode = ab.startActionMode(wrappedCallback); . }...return mActionMode;
}
Copy the code

Ab. StartActionMode (wrappedCallback) is implemented as follows

//androidx.appcompat.app.WindowDecorActionBar

public ActionMode startActionMode(ActionMode.Callback callback) {...// MenuBuilder is initialized internally and menuBuilder.callback is bound
    ActionModeImpl mode = new ActionModeImpl(mContextView.getContext(), callback);
    WindowDecorActionBar#dispatchOnCreate
    // this will eventually Callback to actionmode.callback in our example demo above
    // implement the onCreateActionMode method, then parse menu. XML and populate it with XML in menu
    if (mode.dispatchOnCreate()) {
        mActionMode = mode;
        // State changes or view updates
        mode.invalidate();
        / / mContextView ActionBarContextView
        // Initialize a View that returns button mClose and add mClose to ActionBarContextView
        // Get mMenuView from mMenuLayoutRes
        // Then initialize multiple MenuViews. itemViews and fill them with mmenuViews and update the position of the MenuView in the container
        // Execute mmenuView. requestLayout to refresh the view
        mContextView.initForMode(mode);
        // Perform VISIBLE animation for DecorToolBar and AppBarContextView
        animateToMode(true);
        // Send events for window state changes (only aware when using AccessbilityService)
        mContextView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
        return mode;
    }
    return null;
}
Copy the code

C. summary

(1) After.view. startActionMode is called, Through will eventually perform recursive calls to DecorView startActionMode method (2) DecorView internal calls AppCompatWindowCallback# onWindowStartingActionMode when type = The ActionMode. TYPE_PRIMARY: Internally fire WindowDecorActionBar#startActionMode, then initialize ActionModeImpl and execute the dispatchOnCreate method, The dispatchOnCreate method will eventually Callback to the onCreateActionMode method inside the ActionMode.Callback interface, The developer calls menuconstructor.inflate to populate the XML into the Menu (MenuBuilder) and then adds mClose(ImageView) to the ActionBarContextView to initialize the mMenuView, ItemView and update the order of the view position. Call requestLayout to refresh the view. Implement VISIBLE animation of DecorToolBar’s INVISIBLE and AppBarContextView to control hiding and display; When type = actionmode. TYPE_FLOATING: MWindow. GetCallback (.) onWindowStartingActionMode method (namely AppCompatWindowCallback# onWindowStartingActionMode) returns null, The createActionMode method is then executed and internally the createFloatingActionMode method is executed: initialize a FloatingToolbar and return FloatingActionMode; FloatingToolbar: is a FloatingToolbar that displays context menu items, internally implemented via popWindow;

3. System text selection menu

Let’s take a look at the following two questions:

  • A. How to pop up the system text selection menu? System text selection menu inside is how to achieve?
  • B. How to add your own app’s options to the system text selection menu?

A. How do I display the system text selection menu? And internal implementation

For example, when we use TextView to display text, if we want the content to be selected, we can display the copy and select button. In this case, we can use the TextView method setTextIsSelectable(Boolean Selectable). How does it work? Let’s open up the TextView source and look at the setTextIsSelectable method

//android.widget.TextView

public void setTextIsSelectable(boolean selectable) {
    if(! selectable && mEditor ==null) return; 
    // Initialize the Editor
    createEditorIfNeeded();
    // Prevent duplicate Settings
    if (mEditor.mTextIsSelectable == selectable) return;
    / / update the mTextIsSelectablemEditor.mTextIsSelectable = selectable; . }Copy the code

The mTextIsSelectable () variable was used when the Editor’s inner class TextActionModeCallback was initialized. ActionMode was also used in this class

//android.widget.Editor

void startInsertionActionMode(a) {... ActionMode.Callback actionModeCallback =new TextActionModeCallback(TextActionMode.INSERTION);
    // The ActionMode of TYPE_FLOATING is launched, and the popWindow is implemented inside
    mTextActionMode = mTextView.startActionMode(
            actionModeCallback, ActionMode.TYPE_FLOATING);
    if(mTextActionMode ! =null&& getInsertionController() ! =null) {
        // If the cursor insertion controller exists, a new menu will pop up for the user to paste contentgetInsertionController().show(); }}Copy the code

B. How to add your own app’s options to the system text selection menu?

We need to figure out where does the system add these options? So TextView, again, we mentioned TextActionModeCallback, and we know from ActionMode analysis that in onCreateActionMode, the contents of menu. XML will be filled into menu, So how does the system come with it? Read on for analysis:

//android.widget.Editor.TextActionModeCallback

private class TextActionModeCallback extends ActionMode.Callback2 {...@Override
    public boolean onCreateActionMode(ActionMode mode, Menu menu) {...// The menu options are dynamically added by the system, such as: cut, paste, copy, share, etc
        populateMenuWithItems(menu);
        Callback customCallback = getCustomCallback();
        if(customCallback ! =null) {
            if(! customCallback.onCreateActionMode(mode, menu)) {// If the above condition is true, deselect the text
                Selection.setSelection((Spannable) mTextView.getText(),
                        mTextView.getSelectionEnd());
                return false; }}if (mTextView.canProcessText()) {
            // If the current text supports sharing, copying, and the length is greater than 0 && the selected length is greater than 0
            ACTION_PROCESS_TEXT (intent.action_process_text, intent.action_process_text, intent.action_process_textmProcessTextIntentActionsHandler.onInitializeMenu(menu); }...return true; }...private void populateMenuWithItems(Menu menu) {
        if (mTextView.canCut()) {/ / shearmenu.add(Menu.NONE, TextView.ID_CUT,......) ; }if (mTextView.canCopy()) {/ / copymenu.add(Menu.NONE, TextView.ID_COPY, ......) ; }...if (mTextView.canRequestAutofill()) {// Auto-fillmenu.add(Menu.NONE, TextView.ID_AUTOFILL,......) ; }if (mTextView.canPasteAsPlainText()) {/ / pastemenu.add(Menu.NONE,TextView.ID_PASTE_AS_PLAIN_TEXT,......) ; }... }... }Copy the code

We see mProcessTextIntentActionsHandler. OnInitializeMenu (menu) this method

//android.widget.Editor.ProcessTextIntentActionsHandler
public void onInitializeMenu(Menu menu) {
    // Load the list of all action activities containing PROCESS_TEXT
    loadSupportedActivities();
    final int size = mSupportedActivities.size();
    for (int i = 0; i < size; i++) {
        final ResolveInfo resolveInfo = mSupportedActivities.get(i);
        // Get the supported list and add it dynamically to menumenu.add(Menu.NONE, Menu.NONE, Editor.MENU_ITEM_ORDER_PROCESS_TEXT_INTENT_ACTIONS_START + i, getLabel(resolveInfo)) .setIntent(createProcessTextIntentForResolveInfo(resolveInfo)) .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); }}// Load all intents that support processing PROCESS_TEXT
private void loadSupportedActivities(a) {
    mSupportedActivities.clear();
    if(! mContext.canStartActivityForResult()) {return;
    }
    PackageManager packageManager = mTextView.getContext().getPackageManager();
    // Query the intent of the condition
    List<ResolveInfo> unfiltered =
            packageManager.queryIntentActivities(createProcessTextIntent(), 0);
    for (ResolveInfo info : unfiltered) {
        if(isSupportedActivity(info)) { mSupportedActivities.add(info); }}}// Handle the Intent for PROCESS_TEXT
private Intent createProcessTextIntent(a) {
    return new Intent()
            .setAction(Intent.ACTION_PROCESS_TEXT)
            .setType("text/plain");
}
Copy the code

Combined with the ActionMode content we analyzed above, this is all understood, so we add an option entry to the text selection menu of the system is not very simple?

  • 1. Add the intent filter to androidmanifest.xml
<activity
 android:name=".CustomTextActivity"
 android:excludeFromRecents="false" 
 android:configChanges="locale|orientation|keyboardHidden|screenSize"
 android:label="Like ❤️+ collect ❤️= institute ❤️">   
 <intent-filter> 
   <action android:name="android.intent.action.PROCESS_TEXT" />
   <category android:name="android.intent.category.DEFAULT" />
   <data android:mimeType="text/plain"/>
</intent-filter>
</activity>
Copy the code
  • 2. Deal with intent
class CustomTextActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?). {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_custom_text)
    val selectedText = if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) intent? .getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT)else null
    textShowContent.text = "$selectedText\n\n Text content length:${selectedText? .length? :0}"}}Copy the code



System Text Selection menu – Add options (demo example)

Previous articles recommended: 3.Jetpack Compose UI creation layout drawing process + principle — detailed explanation of the concept (full of work) Jetpack App Startup How to use and principle analysis 5.Jetpack comement-STRINGS IST Kit library 6 Source code analysis | ThreadedRenderer null pointer, the Choreographer meet 7, by the way. Source code analysis | events is how to the Activity? 9. Do not fall into the loop of the need to keep alive