Utilice una barra de acción contextual personalizada para la selección de texto de WebView

He utilizado esta guía de Google y este tutorial para crear mi propia barra de acción contextual.

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; } }; 

Este menú está diseñado para aparecer cuando el usuario selecciona texto, por lo que reemplaza el menú de copia / pegado nativo. Ahora voy a mi problema.

Debido a que estoy reemplazando funciones para la selección de texto, también agregué un LongClickListener a un WebView e implementé el onLongClick(View v) para que pueda detectar cuando los usuarios hacen la selección.

  myWebView.setOnLongClickListener(new View.OnLongClickListener() { @Override public boolean onLongClick(View v) { if (mActionMode != null) { return false; } mActionMode = startActionMode(mActionModeCallback); v.setSelected(true); return true; } }); 

Cuando hago un clic largo, veo aparecer mi menú personalizado, pero no se destaca ningún texto.
Necesito tener la funcionalidad de selección de texto; Sin ella, mi menú es inútil.

¿Cómo puedo anular onLongClick(View v) , pero mantener la selección de texto proporcionada por Android?
Si eso no es posible, ¿puedo hacer la llamada a startActionMode(mActionModeCallback) en otro lugar para que el texto se seleccione como normal, pero mi menú personalizado también aparecerá?
Si ninguno de los dos es posible … ayuda.

¡HAY UNA MANERA MÁS FÁCIL! Vea la actualización a continuación: D


En aras de la integridad, aquí es cómo arreglé el problema:

Seguí la sugerencia de acuerdo con esta respuesta , con un poco más de ajuste para que coincida más estrechamente con el código anulado:

 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); } } 

En esencia, esto obliga a su CAB personalizado a aparecer en lugar de la CAB Android. Ahora usted tiene que modificar su devolución de llamada para que el resaltado de texto desaparezca junto con el 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; } } } 

Eso es todo al respecto. Tenga en cuenta que mientras esté usando MyWebView con el startActionMode anulado no hay NINGUNA MANERA de obtener el CAB nativo (el menú de copiar / pegar, en el caso de un WebView). Puede ser posible implementar ese tipo de comportamiento, pero no es así como funciona este código.


ACTUALIZACIÓN: ¡ Hay una manera mucho más fácil de hacer esto! La solución anterior funciona bien, pero aquí hay una manera alternativa, más fácil.

Esta solución proporciona menos control sobre el ActionMode , pero requiere mucho menos código que la solución anterior.

 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); } } 

Aquí hay un menú de ejemplo para empezar:

 <!-- 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> 

¡Eso es! ¡Ya terminaste! Ahora su menú personalizado aparecerá, no tendrá que preocuparse por la selección, y apenas tendrá que preocuparse por el ciclo de vida de ActionMode .

Esto funciona casi perfectamente con un WebView que ocupa toda su Activity principal. No estoy seguro de lo bien que funcionará si hay varias View en su Activity al mismo tiempo. Es probable que necesite algunos ajustes en ese caso.

La forma en que hice algo similar fue sólo sobreescribir el onTouchListener e invocar a GestureDetector para detectar cuando el WebView fue presionado y hacer lo que quería desde allí. He aquí un ejemplo de código que le permite capturar eventos de larga duración sin sacrificar la selección de texto en el WebView. Espero que esto ayude.

 @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 } } 
  • OnPageFinished () nunca llamado (webview)!
  • Soporte de lienzo HTML5 y vista Web de Android
  • ¿Agente de usuario móvil de Android?
  • Los identificadores de selección no aparecen en WebView (Android 4.0-4.3)
  • Mostrar página html que tiene algún objeto .swf con él
  • Uso de shadow-dom (Polymer) en Android 4.4 Webview
  • FlipAnimation causando problemas en 4.4.3
  • Java.lang.NullPointerException en android.webkit.WebViewClassic.setBaseLayer (WebViewClassic.java:5377)
  • Android webview permanecer en la aplicación
  • Cómo utilizar una fuente personalizada con WebView
  • WebView textarea no aparece el teclado
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.