Webview en Scrollview

Tengo que colocar WebView en ScrollView. Pero tengo que poner algunas vistas en la misma vista de desplazamiento antes de la vista web. Entonces se ve así:

       

Obtengo información HTML desde el back-end. No tiene ninguna etiqueta de cuerpo o cabeza, solo datos rodeados por

o

o algunas otras tags. También tiene tags allí. A veces, las imágenes son demasiado anchas para el ancho de pantalla actual. Así que agregué algunos CSS al comienzo del HTML. Así que carga datos a la vista web así:

 private final static String WEBVIEW_MIME_TYPE = "text/html"; private final static String WEBVIEW_ENCODING = "utf-8"; String viewport = ""; String css = "" + "img {width: 100%;}" + ""; articleContent.loadDataWithBaseURL("http://", viewport + css + articleDetail.getContent(), WEBVIEW_MIME_TYPE, WEBVIEW_ENCODING, "about:blank"); 

A veces, cuando se carga la página, scrollview se desplaza al lugar donde comienza la vista web. Y no sé cómo arreglar eso.

Además, a veces aparece un gran espacio vacío en blanco después del contenido de la vista web. Tampoco sé qué hacer con eso.

A veces las barras de desplazamiento de scrollview comienzan a moverse aleatoriamente mientras yo me desplazo …

Sé que no es correcto colocar webview en scrollview, pero parece que no tengo otra opción. ¿Alguien podría sugerir la forma correcta de colocar todas las vistas y todo el contenido HTML en la vista web?

Actualización del 14/11/2014: ya que Android KitKat no está funcionando ninguna de las soluciones que se describen a continuación, deberá buscar diferentes enfoques como, por ejemplo , FadingActionBar de Manuel Peinado, que proporciona un encabezado de desplazamiento para WebViews.

Actualización 2012-07-08: “Los juegos de Nobu” crearon amablemente una clase TitleBarWebView que devolvió el comportamiento esperado a Android Jelly Bean. Cuando se usa en plataformas anteriores, usará el setEmbeddedTitleBar() oculto setEmbeddedTitleBar() y cuando se use en Jelly Bean o superior, imitará el mismo comportamiento. El código fuente está disponible bajo la licencia Apache 2 en el código de google

Actualización 30/06/2012: Parece que el método setEmbeddedTitleBar() se ha eliminado en Android 4.1 también conocido como Jelly Bean 🙁

Respuesta original:

Es posible colocar un WebView en un ScrollView y funciona. Estoy usando esto en GoodNews en dispositivos con Android 1.6. El principal inconveniente es que el usuario no puede desplazarse por el significado “diagonal”: si el contenido web excede el ancho de la pantalla, ScrollView es responsable del desplazamiento vertical en WebView para el desplazamiento horizontal. Como solo uno de ellos maneja los eventos táctiles, puede desplazarse horizontal o verticalmente pero no en diagonal.

Además, hay algunos problemas molestos como los describe (por ejemplo, espacio vertical vacío al cargar un contenido más pequeño que el anterior). He encontrado soluciones para todas ellas en GoodNews, pero ahora no puedo recordarlas, porque encontré una solución mucho mejor:

Si solo coloca WebView en ScrollView para colocar los controles sobre el contenido web y setEmbeddedTitleBar() aceptar solamente Android 2 y superior, puede usar el método interno oculto setEmbeddedTitleBar() del WebView . Se ha introducido en el nivel 5 de la API y (accidentalmente?) Se hizo público para exactamente una versión (creo que era 3.0).

Este método le permite incrustar un diseño en el WebView que se colocará sobre el contenido web. Este diseño se desplazará por la pantalla cuando se desplaza verticalmente, pero se mantendrá en la misma posición horizontal cuando el contenido web se desplaza horizontalmente.

Como este método no es exportado por la API, debe usar reflections de Java para llamarlo. Sugiero derivar una nueva clase como sigue:

 public final class WebViewWithTitle extends ExtendedWebView { private static final String LOG_TAG = "WebViewWithTitle"; private Method setEmbeddedTitleBarMethod = null; public WebViewWithTitle(Context context) { super(context); init(); } public WebViewWithTitle(Context context, AttributeSet attrs) { super(context, attrs); init(); } private void init() { try { setEmbeddedTitleBarMethod = WebView.class.getMethod("setEmbeddedTitleBar", View.class); } catch (Exception ex) { Log.e(LOG_TAG, "could not find setEmbeddedTitleBar", ex); } } public void setTitle(View view) { if (setEmbeddedTitleBarMethod != null) { try { setEmbeddedTitleBarMethod.invoke(this, view); } catch (Exception ex) { Log.e(LOG_TAG, "failed to call setEmbeddedTitleBar", ex); } } } public void setTitle(int resId) { setTitle(inflate(getContext(), resId, null)); } } 

Luego, en su archivo de diseño puede incluir esto usando

  

y en otro lugar de su código puede llamar al método setTitle() con el ID del diseño que se integrará en WebView .

utilizar:

 android:descendantFocusability="blocksDescendants" 

en el diseño de envoltura esto evitará que la WebView salte a su inicio.

utilizar:

 webView.clearView(); mNewsContent.requestLayout(); 

cada vez que cambie el tamaño de WebView para invalidar el diseño, esto eliminará el espacio vacío.

Aquí está la implementación de WebView que contiene otra vista de “Barra de título” en la parte superior.

Como luce:

La barra roja + 3 botones es una “Barra de título”, debajo está la vista web, todo se desplaza y se recorta en un rectángulo.

Es limpio, corto, funciona desde API 8 hasta 16 y hasta (con un pequeño esfuerzo puede funcionar también en API <8). No utiliza funciones ocultas como WebView.setEmbeddedTitleBar.

 public class TitleWebView extends WebView{ public TitleWebView(Context context, AttributeSet attrs){ super(context, attrs); } private int titleHeight; @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){ super.onMeasure(widthMeasureSpec, heightMeasureSpec); // determine height of title bar View title = getChildAt(0); titleHeight = title==null ? 0 : title.getMeasuredHeight(); } @Override public boolean onInterceptTouchEvent(MotionEvent ev){ return true; // don't pass our touch events to children (title bar), we send these in dispatchTouchEvent } private boolean touchInTitleBar; @Override public boolean dispatchTouchEvent(MotionEvent me){ boolean wasInTitle = false; switch(me.getActionMasked()){ case MotionEvent.ACTION_DOWN: touchInTitleBar = (me.getY() <= visibleTitleHeight()); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: wasInTitle = touchInTitleBar; touchInTitleBar = false; break; } if(touchInTitleBar || wasInTitle) { View title = getChildAt(0); if(title!=null) { // this touch belongs to title bar, dispatch it here me.offsetLocation(0, getScrollY()); return title.dispatchTouchEvent(me); } } // this is our touch, offset and process me.offsetLocation(0, -titleHeight); return super.dispatchTouchEvent(me); } /** * @return visible height of title (may return negative values) */ private int visibleTitleHeight(){ return titleHeight-getScrollY(); } @Override protected void onScrollChanged(int l, int t, int oldl, int oldt){ super.onScrollChanged(l, t, oldl, oldt); View title = getChildAt(0); if(title!=null) // undo horizontal scroll, so that title scrolls only vertically title.offsetLeftAndRight(l - title.getLeft()); } @Override protected void onDraw(Canvas c){ c.save(); int tH = visibleTitleHeight(); if(tH>0) { // clip so that it doesn't clear background under title bar int sx = getScrollX(), sy = getScrollY(); c.clipRect(sx, sy+tH, sx+getWidth(), sy+getHeight()); } c.translate(0, titleHeight); super.onDraw(c); c.restre(); } } 

Uso: coloque la jerarquía de la vista de la barra de título dentro del elemento en el diseño xml. WebView hereda ViewGroup, por lo que puede contener elementos secundarios, a pesar de que el complemento ADT se queja de que no puede. Ejemplo:

        

Tenga en cuenta el uso de layerType = “software”, WebView en hardware en API 11+ no está animado correctamente y dibujado cuando tiene capa de hw.

El desplazamiento funciona perfectamente, al igual que los clics en la barra de título, los clics en la web, la selección de texto en la web, etc.

Gracias por tu pregunta, @Dmitriy! Y también muchas gracias a @sven por la respuesta.

Pero espero que haya una solución para los casos en que tenemos que colocar WebView dentro de ScrollView. Como sven notó correctamente, el problema principal es que la vista de desplazamiento intercepta todos los eventos táctiles que se pueden usar para el desplazamiento vertical. Así que la solución es obvia: amplíe la vista de desplazamiento y anule el ViewGroup.onInterceptTouchEvent(MotionEvent e) de tal forma que la vista de desplazamiento sea tolerante a sus vistas secundarias:

  1. procesar todos los eventos táctiles que se pueden usar para el desplazamiento vertical.
  2. enviar todos ellos a las vistas secundarias.
  3. Interceptar eventos con desplazamiento vertical solamente (dx = 0, dy> 0)

Por supuesto, esta solución solo se puede usar en pareja con el diseño adecuado. He hecho un diseño de muestra que puede ilustrar la idea principal.

             

De modo que la idea principal en sí es evitar los conflictos verticales de desplazamiento al colocar todas las vistas (encabezado, vista web, pie de página) dentro del diseño lineal vertical y enviar eventos táctiles correctamente para permitir el desplazamiento horizontal y diagonal. No estoy seguro de si podría ser útil, pero he creado el proyecto de muestra con TolerantScrollView y la muestra de diseño, puede descargarlo aquí .

Además, he visto la solución con acceso a la API cerrada a través de reflexiones y supongo que es bastante hortera. Tampoco funciona para mí debido a artefactos rectangulares grises sobre la vista web en algunos dispositivos HTC con Android ICS. ¿Tal vez alguien sabe cuál es el problema y cómo podemos resolverlo?

Ninguna de estas respuestas funciona para mí, así que aquí está mi solución. Antes que nada, debemos anular WebView para poder manejar su desplazamiento. Puede encontrar cómo hacerlo aquí: https://stackoverflow.com/a/14753235/1285670

Luego crea tu xml con dos vistas, donde una es WebView y otra es tu diseño de título.

Este es mi código xml:

    ////some stuff   

A continuación, se controla el desplazamiento de WebView y se mueve el título adecuado para desplazarse por el valor. Debe usar NineOldAndroids aquí.

 @Override public void onScroll(int l, int t, int oldl, int oldt) { if (t < mTitleLayout.getHeight()) { ViewHelper.setTranslationY(mTitleLayout, -t); } else if (oldt < mTitleLayout.getHeight()) { ViewHelper.setTranslationY(mTitleLayout, -mTitleLayout.getHeight()); } } 

Y debe agregar relleno a su contenido WebView , para que no se superponga título:

 v.getViewTreeObserver().addOnGlobalLayoutListener( new OnGlobalLayoutListener() { @SuppressWarnings("deprecation") @SuppressLint("NewApi") @Override public void onGlobalLayout() { if (mTitleLayout.getHeight() != 0) { if (Build.VERSION.SDK_INT >= 16) v.getViewTreeObserver().removeOnGlobalLayoutListener(this); else v.getViewTreeObserver().removeGlobalOnLayoutListener(this); } mWebView.loadDataWithBaseURL(null, "
" + mPost.getContent() + "
", "text/html", "UTF8", null); } });

Sé que no es correcto colocar webview en scrollview, pero parece que no tengo otra opción.

Sí lo haces, porque lo que quieres no funcionará de manera confiable.

Pero tengo que poner algunas vistas en la misma vista de desplazamiento antes de la vista web.

Eliminar el ScrollView . Cambia el android:layout_height de tu WebView a fill_parent . El WebView llenará todo el espacio restante en el LinearLayout no consumido por los otros widgets.