web-dev-qa-db-fra.com

Utiliser une barre d'action contextuelle personnalisée pour la sélection de texte WebView

J'ai utilisé ce guide de Google et ce tutoriel pour produire ma propre barre d'action contextuelle.

private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() {

    // Called when the action mode is created; startActionMode() was called
    @Override
    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
        // Inflate a menu resource providing context menu items
        MenuInflater inflater = mode.getMenuInflater();
        inflater.inflate(R.menu.annotation_menu, menu);
        return true;
    }

    // Called each time the action mode is shown.
    // Always called after onCreateActionMode, but
    // may be called multiple times if the mode is invalidated.
    @Override
    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
        return false; // Return false if nothing is done
    }

    // Called when the user selects a contextual menu item
    @Override
    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
        switch (item.getItemId()) {
            case R.id.custom_button:
                // do some stuff
                break;
            case R.id.custom_button2:
                // do some other stuff
                break;
            default:
                // This essentially acts as a catch statement
                // If none of the other cases are true, return false
                // because the action was not handled
                return false;
        }
        finish(); // An action was handled, so close the CAB
        return true;
    }

    // Called when the user exits the action mode
    @Override
    public void onDestroyActionMode(ActionMode mode) {
        mActionMode = null;
    }
};

Ce menu est conçu pour apparaître lorsque l'utilisateur sélectionne du texte, il remplace donc le menu natif copier/coller. J'arrive maintenant à mon problème.

Étant donné que je remplace les fonctions de sélection de texte, j'ai également ajouté un LongClickListener à un WebView et mis en œuvre la méthode onLongClick(View v) afin de pouvoir détecter quand les utilisateurs font la sélection.

    myWebView.setOnLongClickListener(new View.OnLongClickListener() {

        @Override
        public boolean onLongClick(View v) {
            if (mActionMode != null) {
                return false;
            }

            mActionMode = startActionMode(mActionModeCallback);
            v.setSelected(true);
            return true;
        }
    });

Lorsque je clique longuement, je vois mon menu personnalisé apparaître, mais aucun texte n'est mis en surbrillance.
J'ai besoin d'avoir la fonctionnalité de sélection de texte; sans elle, mon menu est inutile.

Comment remplacer onLongClick(View v), mais conserver la sélection de texte fournie par Android?
Si cela n'est pas possible, puis-je appeler startActionMode(mActionModeCallback) ailleurs pour que le texte soit sélectionné normalement, mais mon menu personnalisé apparaîtra également?
Si aucun de ces éléments n'est possible ... aide.

22
Sean Beach

LÀ IS UNE MANIÈRE PLUS FACILE! Voir la mise à jour ci-dessous: D


J'ai suivi la suggestion selon cette réponse , avec un peu plus de réglages pour mieux correspondre au code remplacé:

public class MyWebView extends WebView {

    private ActionMode mActionMode;
    private mActionMode.Callback mActionModeCallback;

    @Override
    public ActionMode startActionMode(Callback callback) {
        ViewParent parent = getParent();
        if (parent == null) {
            return null;
        }
        mActionModeCallback = new CustomActionModeCallback();
        return parent.startActionModeForChild(this, mActionModeCallback);
    }
}

Essentiellement, cela force votre CAB personnalisé à apparaître au lieu du Android CAB. Maintenant, vous devez modifier votre rappel pour que la surbrillance du texte disparaisse avec le CAB:

public class MyWebView extends WebView {
    ...
    private class CustomActionModeCallback implements ActionMode.Callback {
        ...
        // Everything up to this point is the same as in the question

        // Called when the user exits the action mode
        @Override
        public void onDestroyActionMode(ActionMode mode) {
            clearFocus(); // This is the new code to remove the text highlight
             mActionMode = null;
        }
    }
}

C'est tout ce qu'on peut en dire. N'oubliez pas que tant que vous utilisez MyWebView avec le startActionMode remplacé, il n'y a AUCUNE FAÇON d'obtenir le CAB natif (le menu copier/coller, dans le cas d'une WebView). Il peut être possible d'implémenter ce type de comportement, mais ce n'est pas ainsi que ce code fonctionne.


METTRE À JOUR:

Cette solution fournit moins de contrôle sur le ActionMode, mais elle nécessite beaucoup moins de code que la solution ci-dessus.

public class MyActivity extends Activity {

    private ActionMode mActionMode = null;

    @Override
    public void onActionModeStarted(ActionMode mode) {
        if (mActionMode == null) {
            mActionMode = mode;
            Menu menu = mode.getMenu();
            // Remove the default menu items (select all, copy, paste, search)
            menu.clear();

            // If you want to keep any of the defaults,
            // remove the items you don't want individually:
            // menu.removeItem(Android.R.id.[id_of_item_to_remove])

            // Inflate your own menu items
            mode.getMenuInflater().inflate(R.menu.my_custom_menu, menu);
        }

        super.onActionModeStarted(mode);
    }

    // This method is what you should set as your item's onClick
    // <item Android:onClick="onContextualMenuItemClicked" />
    public void onContextualMenuItemClicked(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.example_item_1:
                // do some stuff
                break;
            case R.id.example_item_2:
                // do some different stuff
                break;
            default:
                // ...
                break;
        }

        // This will likely always be true, but check it anyway, just in case
        if (mActionMode != null) {
            mActionMode.finish();
        }
    }

    @Override
    public void onActionModeFinished(ActionMode mode) {
        mActionMode = null;
        super.onActionModeFinished(mode);
    }
}

Voici un exemple de menu pour vous aider à démarrer:

<!-- my_custom_menu.xml -->
<?xml version="1.0" encoding="utf-8"?>

<menu xmlns:Android="http://schemas.Android.com/apk/res/Android">

    <item
        Android:id="@+id/example_item_1"
        Android:icon="@drawable/ic_menu_example_1"
        Android:showAsAction="always"
        Android:onClick="onContextualMenuItemClicked"
        Android:title="@string/example_1">
    </item>

    <item
        Android:id="@+id/example_item_2"
        Android:icon="@drawable/ic_menu_example_2"
        Android:showAsAction="ifRoom"
        Android:onClick="onContextualMenuItemClicked"
        Android:title="@string/example_2">
    </item>

</menu>

C'est tout! Vous avez terminé! Maintenant, votre menu personnalisé apparaîtra, vous n'avez pas à vous soucier de la sélection, et vous avez à peine à vous soucier du cycle de vie de ActionMode.

Cela fonctionne presque parfaitement avec un WebView qui occupe tout son parent Activity. Je ne sais pas dans quelle mesure cela fonctionnera s'il y a plusieurs View dans votre Activity en même temps. Cela nécessitera probablement quelques ajustements dans ce cas.

35
Sean Beach

La façon dont j'ai fait quelque chose de similaire était de remplacer uniquement onTouchListener et d'appeler un GestureDetector pour détecter quand la WebView a été pressée longuement et faire ce que je voulais à partir de là. Voici un exemple de code qui vous permet d'intercepter des événements de longue pression sans sacrifier la sélection de texte dans WebView. J'espère que cela aide.

@Override
protected void onCreate(Bundle savedInstanceState) {
    WebView mWebView = (WebView) findViewById(R.id.myWebView);
    GestureDetector mGestureDetector = new GestureDetector(this, new CustomGestureListener());
    mWebView.setOnTouchListener(new OnTouchListener(){
        @Override
        public boolean onTouch(View view, MotionEvent arg1) {

            //Suggestion #1 - this just lets the touch to be handled by the system but allows you to detect long presses
            mGestureDetector.onTouchEvent(arg1);
            return false;

            //Suggestion #2 - this code will only let the touch be handled by the system if you don't detect a long press
            return mGestureDetector.onTouchEvent(arg1);
        }
    });
}

private class CustomGestureListener extends SimpleOnGestureListener {

    @Override
    public void onLongPress(MotionEvent e) {
        //do stuff
    }

}
1
anthonycr