web-dev-qa-db-fra.com

Webview dans Scrollview

Je dois placer WebView dans ScrollView. Mais je dois mettre quelques vues dans la même vue de défilement avant la vue Web. Cela ressemble donc à ceci:

<ScrollView
  Android:layout_width="fill_parent"
  Android:layout_height="fill_parent"
  >
<LinearLayout
    Android:id="@+id/articleDetailPageContentLayout"
    Android:layout_width="fill_parent"
    Android:layout_height="fill_parent"
    Android:orientation="vertical">
  <LinearLayout
      Android:id="@+id/articleDetailRubricLine"
      Android:layout_width="fill_parent"
      Android:layout_height="3dip"
      Android:background="@color/fashion"/>

  <ImageView
      Android:id="@+id/articleDetailImageView"
      Android:layout_width="fill_parent"
      Android:layout_height="wrap_content"
      Android:adjustViewBounds="true"
      Android:scaleType="fitStart"
      Android:src="@drawable/article_detail_image_test"/>

  <TextView
      Android:layout_width="fill_parent"
      Android:layout_height="wrap_content"
      Android:padding="5dip"
      Android:text="PUBLISH DATE"/>

  <WebView
      Android:id="@+id/articleDetailContentView"
      Android:layout_width="fill_parent"
      Android:layout_height="wrap_content"
      Android:background="@color/fashion"
      Android:isScrollContainer="false"/>
</LinearLayout>

Je reçois des informations HTML du backend. Il n'a pas de balises corps ou tête, juste des données entourées de <p> ou <h4> ou d'autres balises. Il a également <img> balises là-dedans. Parfois, les images sont trop larges pour la largeur d'écran actuelle. J'ai donc ajouté quelques CSS au début du HTML. Je charge donc les données sur la vue Web comme ceci:

private final static String WEBVIEW_MIME_TYPE = "text/html";
    private final static String WEBVIEW_ENCODING = "utf-8";
String viewport = "<head><meta name=\"viewport\" content=\"target-densitydpi=device-dpi\" /></head>";
        String css = "<style type=\"text/css\">" +
            "img {width: 100%;}" +
            "</style>";
        articleContent.loadDataWithBaseURL("http://", viewport + css + articleDetail.getContent(), WEBVIEW_MIME_TYPE,
            WEBVIEW_ENCODING, "about:blank");

Parfois, une fois la page chargée, scrollview défile jusqu'à l'endroit où commence la visualisation Web. Et je ne sais pas comment résoudre ce problème.

De plus, il y a parfois un énorme espace vide blanc qui apparaît après le contenu de la vue Web. Je ne sais pas non plus quoi faire avec ça.

Parfois, les barres de défilement de scrollview démarrent Twitch au hasard pendant que je défile ...

Je sais que ce n'est pas correct de placer la vue Web dans la vue de défilement, mais il semble que je n'ai pas d'autre choix. Quelqu'un pourrait-il suggérer une méthode rigoureuse pour placer toutes les vues et tout le contenu HTML dans la vue Web?

29
Dmitriy

Mise à jour 2014-11-13: Puisque Android KitKat aucune des solutions décrites ci-dessous ne fonctionne - vous devrez rechercher différentes approches comme par ex. FadingActionBar de Manuel Peinado qui fournit un en-tête de défilement pour WebViews.

Mise à jour 2012-07-08: "Jeux Nobu" a gentiment créé une classe TitleBarWebView ramenant le comportement attendu à Android Jelly bean. Lorsqu'il est utilisé sur des plates-formes plus anciennes, il utilisera la méthode cachée setEmbeddedTitleBar() et lorsqu'il est utilisé sur Jelly bean ou au-dessus, il imitera le même comportement. Le code source est disponible sous la licence Apache 2 sur google code

Mise à jour 2012-06-30: Il semble que la méthode setEmbeddedTitleBar() ait été supprimée dans Android 4.1 aka Jelly bean :-(

Réponse originale:

Il est possible de placer un WebView dans un ScrollView et cela fonctionne. J'utilise ceci dans GoodNews sur les appareils Android 1.6. L'inconvénient principal est que l'utilisateur ne peut pas faire défiler ce qui signifie "diagonale": Si le contenu Web dépasse la largeur de l'écran, le ScrollView est responsable du défilement vertical au niveau du WebView pour le défilement horizontal. Comme un seul d'entre eux gère les événements tactiles, vous pouvez faire défiler horizontalement ou verticalement mais pas en diagonale.

Plus loin, il y a des problèmes ennuyeux tels que décrits par vous (par exemple, un espace vertical vide lors du chargement d'un contenu plus petit que le précédent). J'ai trouvé des solutions de contournement pour chacun d'entre eux dans GoodNews, mais je ne m'en souviens plus maintenant, car j'ai trouvé une bien meilleure solution:

Si vous ne mettez que WebView dans le ScrollView pour placer Controls above le contenu Web et que vous êtes d'accord pour ne prendre en charge que Android 2 et supérieur, vous pouvez alors utiliser la méthode interne cachée setEmbeddedTitleBar() de WebView. Il a été introduit dans l'API niveau 5 et (accidentellement?) Est devenu public pour exactement une version (je pense que c'était la version 3.0).

Cette méthode vous permet d'incorporer une mise en page dans le WebView qui sera placé au-dessus du contenu Web. Cette disposition fera défiler l'écran lors du défilement vertical, mais sera conservée à la même position horizontale lorsque le contenu Web défile horizontalement.

Comme cette méthode n'est pas exportée par l'API, vous devez utiliser les réflexions Java pour l'appeler. Je suggère de dériver une nouvelle classe comme suit:

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

Ensuite, dans votre fichier de mise en page, vous pouvez l'inclure en utilisant

<com.mycompany.widget.WebViewWithTitle
        xmlns:Android="http://schemas.Android.com/apk/res/Android"
        Android:id="@+id/content"
        Android:layout_width="fill_parent"
        Android:layout_height="fill_parent"
        />

et ailleurs dans votre code, vous pouvez appeler la méthode setTitle() avec l'ID de la mise en page à incorporer dans le WebView.

16
sven

utilisation:

Android:descendantFocusability="blocksDescendants"

sur la présentation d'habillage, cela empêchera le WebView de sauter à son début.

utilisation:

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

chaque fois que vous modifiez la taille WebView pour invalider la disposition, cela supprimera l'espacement vide.

18
Evgeni Roitburg

Voici l'implémentation de WebView contenant une autre vue "Barre de titre" en haut.

À quoi ça ressemble:

La barre rouge + 3 boutons est une "barre de titre", ci-dessous est la vue Web, tout est défilé et attaché ensemble dans un rectangle.

Il est propre, court, fonctionne de l'API 8 à 16 et plus (avec un petit effort, il peut également fonctionner sur l'API <8). Il n'utilise aucune fonction cachée telle que 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.restore();
   }
}

Utilisation: placez votre hiérarchie de vue de la barre de titre à l'intérieur de l'élément <WebView> dans la mise en page xml. WebView hérite de ViewGroup, il peut donc contenir des enfants, malgré le plugin ADT se plaignant qu'il ne peut pas. Exemple:

<com.test.TitleWebView
   Android:id="@+id/webView"
   Android:layout_width="match_parent"
   Android:layout_height="match_parent"
   Android:layerType="software" >

   <LinearLayout
      Android:id="@+id/title_bar"
      Android:layout_width="wrap_content"
      Android:layout_height="wrap_content"
      Android:background="#400" >

      <Button
         Android:id="@+id/button2"
         Android:layout_width="wrap_content"
         Android:layout_height="wrap_content"
         Android:text="Button" />

      <Button
         Android:id="@+id/button3"
         Android:layout_width="wrap_content"
         Android:layout_height="wrap_content"
         Android:text="Button" />

      <Button
         Android:id="@+id/button5"
         Android:layout_width="wrap_content"
         Android:layout_height="wrap_content"
         Android:text="Button" />
   </LinearLayout>
</com.test.TitleWebView>

Notez l'utilisation de layerType = "software", WebView dans le matériel sur API 11+ n'est pas correctement animé et dessiné quand il a une couche hw.

Le défilement fonctionne parfaitement, ainsi que les clics sur la barre de titre, les clics sur le Web, la sélection de texte sur le Web, etc.

9
Pointer Null

Merci pour votre question, @Dmitriy! Et merci également à @sven pour la réponse.

Mais j'espère qu'il existe une solution de contournement pour les cas où nous devons mettre WebView à l'intérieur de ScrollView. Comme sven l'a correctement remarqué, le problème principal est que la vue de défilement intercepte tous les événements tactiles qui peuvent être utilisés pour le défilement vertical. La solution de contournement est donc évidente - étendez la vue de défilement et remplacez la méthode ViewGroup.onInterceptTouchEvent(MotionEvent e) de telle manière que la vue de défilement devienne tolérante à ses vues enfant:

  1. traiter tous les événements tactiles pouvant être utilisés pour le défilement vertical.
  2. envoyez-les tous aux vues enfants.
  3. intercepter des événements avec défilement vertical uniquement (dx = 0, dy> 0)

Bien entendu, cette solution ne peut être utilisée qu'en couple avec une disposition appropriée. J'ai créé un exemple de mise en page qui peut illustrer l'idée principale.

    <RelativeLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
    xmlns:tools="http://schemas.Android.com/tools"
    Android:layout_width="match_parent"
    Android:layout_height="match_parent" >

    <com.rus1f1kat0r.view.TolerantScrollView
        Android:id="@+id/scrollView1"
        Android:layout_width="match_parent"
        Android:layout_height="match_parent"
        Android:layout_centerHorizontal="true"
        Android:layout_centerVertical="true" >

        <LinearLayout
            Android:layout_width="match_parent"
            Android:layout_height="wrap_content" 
            Android:orientation="vertical">

            <TextView
                Android:id="@+id/textView1"
                Android:layout_width="wrap_content"
                Android:layout_height="wrap_content"
                Android:text="Large Text"
                Android:textAppearance="?android:attr/textAppearanceLarge" />

            <TextView
                Android:id="@+id/textView2"
                Android:layout_width="wrap_content"
                Android:layout_height="wrap_content"
                Android:text="Large Text"
                Android:textAppearance="?android:attr/textAppearanceLarge" />

            <WebView
                Android:id="@+id/webView1"
                Android:layout_width="match_parent"
                Android:layout_height="wrap_content" />

            <TextView
                Android:id="@+id/textView3"
                Android:layout_width="wrap_content"
                Android:layout_height="wrap_content"
                Android:text="Medium Text"
                Android:textAppearance="?android:attr/textAppearanceMedium" />

           <TextView
               Android:id="@+id/textView4"
               Android:layout_width="wrap_content"
               Android:layout_height="wrap_content"
               Android:text="Medium Text"
               Android:textAppearance="?android:attr/textAppearanceMedium" />

        </LinearLayout>
    </com.rus1f1kat0r.view.TolerantScrollView>
</RelativeLayout>

Pour que l'idée principale elle-même soit d'éviter les conflits de défilement vertical en plaçant toutes les vues (en-tête, vue Web, pied de page) à l'intérieur d'une disposition linéaire verticale et de répartir correctement les événements tactiles afin de permettre le défilement horizontal et diagonal. Je ne sais pas si cela pourrait être utile, mais j'ai créé l'exemple de projet avec TolerantScrollView et l'exemple de mise en page, vous pouvez le télécharger ici .

De plus - j'ai vu la solution en accédant à l'API fermée via des réflexions et je suppose que c'est plutôt hucky. Cela ne fonctionne pas non plus pour moi à cause des artefacts de rectangle gris sur la vue Web sur certains appareils HTC avec Android ICS. Peut-être que quelqu'un sait quel est le problème et comment le résoudre?

5
rus1f1kat0R

Aucune de ces réponses ne fonctionne pour moi, alors voici ma solution. Tout d'abord, nous devons remplacer WebView pour pouvoir gérer son défilement. Vous pouvez trouver comment le faire ici: https://stackoverflow.com/a/14753235/128567

Créez ensuite votre xml avec deux vues, où une est WebView et une autre est votre mise en page de titre.

Voici mon code xml:

<FrameLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
  xmlns:tools="http://schemas.Android.com/tools"
  Android:layout_width="match_parent"
  Android:layout_height="match_parent" >

  <com.project.ObservableWebView
      Android:id="@+id/post_web_view"
      Android:layout_width="match_parent"
      Android:layout_height="wrap_content"
      Android:background="#fff" />

  <LinearLayout
      Android:id="@+id/post_header"
      Android:layout_width="match_parent"
      Android:layout_height="wrap_content"
      Android:background="#fff"
      Android:orientation="vertical"
      Android:paddingLeft="@dimen/common_ui_space"
      Android:paddingRight="@dimen/common_ui_space"
      Android:paddingTop="@dimen/common_ui_space" >

      ////some stuff

    </LinearLayout>

</FrameLayout>

Ensuite, gérer WebView faire défiler et déplacer le titre approprié pour faire défiler la valeur. Vous devez utiliser NineOldAndroids ici.

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

Et vous devez ajouter un remplissage à votre contenu WebView, afin qu'il ne superpose pas le titre:

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, "<div style=\"padding-top: " 
                  + mTitleLayout.getHeight() / dp + "px\">" + mPost.getContent() 
                  + "</div>", "text/html", "UTF8", null);
        }
});
2
rstk

Je sais que ce n'est pas correct de placer la vue Web dans la vue de défilement, mais il semble que je n'ai pas d'autre choix.

Oui, car ce que vous voulez ne fonctionnera pas de manière fiable.

Mais je dois mettre quelques vues dans la même vue de défilement avant la vue Web.

Supprimez le ScrollView. Changer la Android:layout_height de votre WebView à fill_parent. Le WebView remplira tout l'espace restant dans le LinearLayout non consommé par les autres widgets.

0
CommonsWare