31

I have to place WebView into ScrollView. But I have to put some views into the same scrollview before webview. So it looks like this:

<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> 

I'm getting some HTML info from backend. It has no any body or head tags, just data surrounded by <p> or <h4> or some other tags. Also it has <img> tags in there. Sometimes pictures are too wide for current screen width. So I added some css in the begining of HTML. So I loads data to webview like this:

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

Sometimes when page loaded, scrollview scrolls to place where webview begins. And I don't know how to fix that.

Also, sometimes there is huge white empty space appears after webview content. I also don't know what to do with that.

Sometimes scrollview's scrollbars starts twitch randomly while I scrolling...

I know that it's not right to place webview into scrollview, but it seems like I have no other choise. Could anyone suggest rigth way to place all views and all HTML content to webview?

0

6 Answers 6

21

use:

android:descendantFocusability="blocksDescendants" 

on the wrapping layout this will prevent the WebView from jumping to its start.

use:

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

every time you change the WebView size to invalidate the layout, this will remove the empty spacing.

Sign up to request clarification or add additional context in comments.

3 Comments

+1 for the first part of the answer. Unfortunately, the second part does not work (maybe due to the fact that it is not entirely clear what you are referring to with the mNewsContent name).
android:descendantFocusability="blocksDescendants" this work for me!
If you disable focusability you can't use input fields within the webview.
16

Update 2014-11-13: Since Android KitKat neither of the solutions described below are working -- you will need to look for different approaches like e.g. Manuel Peinado's FadingActionBar which provides a scrolling header for WebViews.

Update 2012-07-08: "Nobu games" kindly created a TitleBarWebView class bringing the expected behavior back to Android Jelly Bean. When used on older platforms it will use the hidden setEmbeddedTitleBar() method and when used on Jelly Bean or above it will mimic the same behavior. The source code is available under the Apache 2 license at google code

Update 2012-06-30: It seems as if the setEmbeddedTitleBar() method has been removed in Android 4.1 aka Jelly Bean :-(

Original answer:

It is possible to place a WebView into a ScrollView and it does work. I am using this in GoodNews on Android 1.6 devices. The main drawback is that the user cannot scroll "diagonal" meaning: If the web content exceeds the width of the screen the ScrollView is responsible for vertical scrolling at the WebView for horizontal scrolling. As only one of them handles the touch events you can either scroll horizontally or vertically but not diagonal.

Further on there are some annoying problems as described by you (e.g. empty vertical space when loading a content smaller than the previous one). I've found workarounds for all of them in GoodNews, but cannot remember them now, because I've found a much better solution:

If you only put the WebView into the ScrollView to place Controls above the web content and you are OK to support only Android 2 and above, then you can use the hidden internal setEmbeddedTitleBar() method of the WebView. It has been introduced in API level 5 and (accidentally?) became public for exactly one release (I think it was 3.0).

This method allows you to embed a layout into the WebView which will be placed above the web content. This layout will scroll out the screen when scrolling vertically but will be kept at the same horizontal position when the web content is scrolled horizontally.

As this method isn't exported by the API you need to use Java reflections to call it. I suggest to derive a new class as followed:

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

Then in your layout file you can include this using

<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" /> 

and somewhere else in your code you can call the setTitle() method with the ID of the layout to be embedded into the WebView.

4 Comments

"I've found workarounds for all of them in GoodNews." How did you solve the diagonal scrolling issue?
@Mark, the solution described in this answer implements a header based on hidden features in the old WebView so that diagonal scrolling worked without problems (see details above). Unfortunately this solution isn't working anymore since JellyBean. See my updates at the beginning of the answer.
Your proposal from 2014-11-13 look promising. Unfortunately it does not work under Android 5.0 and on some 4.0.X devices. imgur.com/GEXzRWT I've located the problem to the ObservableWebViewWithHeader class in the method onDraw(). Seems like the translate method behaves differently on certain Android version. Does anybody have an idea how to solve this?
@Johan I have the same problem with you ! The real problem is not the translate. If you correct the translate use visibleHeaderHeight. You can see the content scrolled out of webview won't come back! Anyone can solve this?
9

Here is implementation of WebView containing another "Title bar" view at top of it.

How it looks:

Red bar + 3 buttons is a "Title bar", below is web view, all is scrolled and clipped together in one rectangle.

It's clean, short, works all way from API 8 to 16 and up (with small effort it can work also on API<8). It doesn't use any hidden functions such as 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(); } } 

Usage: put your title bar view hierarchy inside of <WebView> element in layout xml. WebView inherits ViewGroup, so it can contain children, despite of ADT plugin complaining that it can't. Example:

<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> 

Note usage of layerType="software", WebView in hardware on API 11+ isn't properly animated and drawn when it has hw layer.

Scrolling works perfectly, as well as clicks on title bar, clicks on web, selecting text in web, etc.

2 Comments

-Pointer Null This works really well but it works only with vertical view and when orientation changes to horizontal it is very difficult to scroll.
Excellent for most cases, however, this wont work well with text inputs and other input elements.
5

Thanks for your question, @Dmitriy! And also thanks a lot to @sven for the answer.

But I hope there is a workaround for the cases where we need to put WebView inside the ScrollView. As sven correctly noticed the main issue is that scroll view intercepts all the touch events that can be used for the vertical scrolling. So workaround is obvious - extend scroll view and override ViewGroup.onInterceptTouchEvent(MotionEvent e) method in such a way the scroll view become tolerant to it's child views:

  1. process all touch events that can be used for vertical scrolling.
  2. dispatch all of them to the child views.
  3. intercept events with vertical scrolling only (dx = 0, dy > 0)

Ofcourse this solution can only be used in couple with appropriate layout. I have made sample layout that can illustrate the main idea.

 <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> 

So that the main idea itself is to avoid vertical scrolling conflicts by putting all the views (header, webview, footer) inside vertical linear layout, and correctly dispatch touch events in order to allow horizontal and diagonal scrolling. I'm not sure whether it could be useful, but I have created the sample project with TolerantScrollView and layout sample, you can download it here.

What's more - I have viewed the solution with accessing to the closed API via reflections and I guess it is rather hucky. It is also don't work for me because of gray rectangle artifacts over the webview on some HTC devices with Android ICS. Maybe someone knows what is the problem and how we can solve it?

3 Comments

Do you use a transparent background ?
Your solution is very nice, but it doesn't allow for text selection to be made properly. When user drags the selection icon to move it vertically, the entire ScrollView moves instead. How would you modify your code to fix that?
By the moment we are working on the another solution that is better suits to dev guides and has less bugs and inconsistences. I'll provide you with the code snippets once ready.
2

None of this answers work for me, so here is my solution. First of all we need to override WebView to have ability to handle its scrolling. You can find how to do it here: https://stackoverflow.com/a/14753235/1285670

Then create your xml with two view, where one is WebView and another is your title layout.

This is my xml code:

<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> 

Next is handle WebView scroll and move title appropriate to scroll value. You must use NineOldAndroids here.

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

And you must add padding to your WebView content, so it will not overlay title:

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

3 Comments

whats the value of dp here
@YogeshSeralia Just density of screen. You can get it from context.getResources().getDisplayMetrics().density
@YogeshSeralia but not there is better way to convert betwwen px and dp, use TypedValue.applyDimension()
0

I know that it's not right to place webview into scrollview, but it seems like I have no other choise.

Yes you do, because what you want will not work reliably.

But I have to put some views into the same scrollview before webview.

Delete the ScrollView. Change the android:layout_height of your WebView to fill_parent. The WebView will fill up all remaining space in the LinearLayout not consumed by the other widgets.

10 Comments

if I do like you said, only webview area will be scrollable. But I need that whole area with ImageView and TextView be able to scroll.
@Dmitriy: Then put the text and the image in the HTML and get rid of the widgets, so all you have is a WebView, and the whole thing can scroll in unison.
@CommonsWare "because what you want will not work reliably." Can you please elaborate? I have the exact same situation with a WebView within a ScrollView being loaded with different content and no re-sizing properly, or the ScrollView's scrollbar glitching/twitching constantly. Putting everything inside the WebView is not an option for me.
@JarrodSmith: "Can you please elaborate?" -- there is nothing to elaborate on. It does not work reliably, as you are discovering. Do something else. "Putting everything inside the WebView is not an option for me" -- then redesign the app so either is is an option for you, or eliminate the ScrollView, or eliminate the WebView.
@CommonsWare Ok thanks - I guess I was just wondering if it would work if I disabled scrolling on the WebView, and set it to wrap_content. I don't need the WebView itself to scroll its content. Is there an alternative way of displaying properly styled CSS/HTML in Android?
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.