Webview dans Scrollview

Je dois placer WebView dans ScrollView. Mais je dois mettre quelques vues dans le même scrollview avant webview. Donc, il ressemble à 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 body ou head, juste des données entourées de <p> ou <h4> ou d'autres balises. Il y a aussi des balises <img> là-dedans. Parfois, les images sont trop larges pour la largeur actuelle de l'écran. J'ai donc ajouté du css au début du HTML. Donc, je charge des données sur webview comme ce:

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, lorsque la page est chargée, scrollview défile à l'endroit où webview commence. Et je ne sais pas comment résoudre ce problème.

Aussi, parfois il y a un énorme espace vide blanc apparaît après le contenu webview. Aussi, je ne sais pas quoi faire avec ça.

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

Je sais que ce n'est pas juste de placer webview dans scrollview, mais il semble que je n'ai pas d'autre choix. Quelqu'un pourrait-il suggérer rigth façon de placer toutes les vues et tout le contenu HTML à webview?

29
demandé sur Dmitriy 2012-03-15 14:54:33

6 réponses

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

Mise à jour 2012-07-08: "Nobu games" a gentiment créé un TitleBarWebView Classe 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 quand 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() a été supprimée dans Android 4.1 alias Jelly Bean : - (

Réponse originale:

Il est possible de mettre un WebView en ScrollView et il fonctionne. Je l'utilise dans GoodNews sur les appareils Android 1.6. Le principal inconvénient est que les l'utilisateur ne peut pas faire défiler "diagonale" ce qui signifie: Si le contenu Web dépasse la largeur de l'écran, le ScrollView est responsable du défilement vertical au 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 comme décrit par vous (par exemple, espace vertical vide lors du chargement d'un contenu plus petit que le précédent). J'ai trouvé des solutions pour tous dans GoodNews, mais je ne peux pas me souvenir d'eux maintenant, parce que j'ai trouvé une bien meilleure solution:

Si vous mettez seulement le WebView dans le ScrollView pour placer des contrôles au-dessus de le contenu web et que vous êtes autorisé à prendre en charge uniquement Android 2 et supérieur, vous pouvez utiliser la méthode interne cachée setEmbeddedTitleBar() du WebView. Il a été introduit dans le niveau API 5 et (accidentellement?) est devenu public pour exactement une version (je pense que c'était 3.0).

Cette méthode vous permet d'intégrer une mise en page WebView, qui sera placé au-dessus du contenu web. Cette mise en page défilera sur l'écran lors du défilement vertical, mais sera maintenue à 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 Java réflexions de 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 le setTitle() méthode avec L'ID de la mise en page à intégrer dans le WebView.

16
répondu sven 2014-11-13 14:31:15

Utilisation:

android:descendantFocusability="blocksDescendants"

Sur la mise en page d'emballage, 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 mise en page, cela supprimera l'espacement vide.

18
répondu Evgeni Roitburg 2016-01-21 09:08:13

Voici l'implémentation de WebView contenant une autre vue "barre de titre" en haut de celle-ci.

À quoi ça ressemble:

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

C'est propre, court, fonctionne de L'API 8 à 16 et plus (avec un petit effort, il peut également fonctionner sur API

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: mettez votre vue de la barre de titre hiérarchie à l'intérieur de l'élément dans layout xml. WebView hérite de ViewGroup, donc il peut 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 L'API 11+ n'est pas correctement animé et dessiné quand il a une couche hw.

Défilement fonctionne parfaitement, ainsi que les clics sur la barre de titre, les clics sur le web, la sélection de texte dans le web, etc.

9
répondu Pointer Null 2012-07-11 23:05:47

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

Mais j'espère qu'il y a une solution de contournement pour les cas où nous devons mettre WebView dans le ScrollView. Comme sven a correctement remarqué le problème principal est que scroll view intercepte tous les événements tactiles qui peuvent être utilisés pour le défilement vertical. Donc, la solution de contournement est évidente-étendre la vue de défilement et remplacer la méthode ViewGroup.onInterceptTouchEvent(MotionEvent e) de telle sorte que la vue de défilement devienne tolérante à son enfant vues:

  1. traite tous les événements tactiles qui peuvent être utilisés pour le défilement vertical.
  2. expédition de l'enfant vues.
  3. intercepter les événements avec défilement vertical uniquement (dx = 0, dy > 0)

Bien sûr, cette solution ne peut être utilisée qu'en couple avec une mise en page appropriée. J'ai fait la mise en page de l'échantillon 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>

De sorte que l'idée principale elle-même est d'éviter les conflits de défilement vertical en mettant toutes les vues (en-tête, webview, footer) à l'intérieur de la mise en page linéaire verticale, et répartit correctement les événements tactiles afin de permettre le défilement horizontal et diagonal. Je ne suis pas sûr que cela puisse être utile, mais j'ai créé l'exemple de projet avec TolerantScrollView et layout sample, vous pouvez le télécharger ici.

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

5
répondu rus1f1kat0R 2012-10-19 02:11:53

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

Ensuite, créez votre xml avec deux vues, où l'une est WebView et l'autre est votre mise en page de titre.

C'est 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>

Suivant est la poignée WebView défiler et déplacer le titre approprié à la valeur de défilement. 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 superposera 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
répondu rstk 2017-05-23 12:17:17

Je sais que ce n'est pas juste de placer webview dans scrollview, mais il semble que je n'ai pas d'autre choix.

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

Mais je dois mettre quelques vues dans le même scrollview avant webview.

Supprimer le ScrollView. Changez le android:layout_height de votre WebView en fill_parent. Le {[2] } remplira tout l'espace restant dans le LinearLayout non consommé par les autres widgets.

0
répondu CommonsWare 2012-03-15 12:05:55