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?
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
.
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.
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.
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:
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?
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);
}
});
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.