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?
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
.
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.
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
<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.
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:
- traite tous les événements tactiles qui peuvent être utilisés pour le défilement vertical.
- expédition de l'enfant vues.
- 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?
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);
}
});
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.