12

I have a web page for testing purposes ( https://storage.googleapis.com/htmltestingbucket/nested_scroll_helper.html ) that just prints a counter of the scroll event the html has caught in a fixed header

When the Android WebView is the only scroll-able element in the fragment everything is fine and the WebView sends the scroll events to the page

If I want to add native elements above and below the WebView then things get much more complex.

<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:custom="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

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

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="20dp"
            android:text="SOMETHING ABOVE THE WEBVIEW" />

        <WebView
            android:id="@+id/webview"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="20dp"
            android:text="SOMETHING BELOW THE WEBVIEW" />

    </LinearLayout>
</ScrollView>

I know it's not good to have a WebView inside a ScrollView but I have to provide a single scrolling experience with hybrid content and proper scrolling events in the html document. I found plenty of questions on the matter but I was able to create a full end-to-end solution


Also, I know lint has an Offical check for that:

NestedScrolling --------------- Summary: Nested scrolling widgets

Priority: 7 / 10 Severity: Warning Category: Correctness

A scrolling widget such as a ScrollView should not contain any nested scrolling widgets since this has various usability issues

And yet, I can't implement the web view content in native so I need an alternative way to do that

Shai Levy
  • 715
  • 9
  • 25
  • See the accepted answer of http://stackoverflow.com/questions/9718245/webview-in-scrollview – Shafi Jan 17 '16 at 08:27
  • I saw it before I posted, notice:"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." --> I'm not looking to solve a problem with native element above the webview, that is much easier – Shai Levy Jan 17 '16 at 08:38
  • I'm not looking to solve a problem with native element ONLY above the webview, that is much easier – Shai Levy Jan 17 '16 at 10:24
  • have you checked here http://stackoverflow.com/a/13353874/5202007 – Mohammad Tauqir Jan 17 '16 at 15:30
  • Yes, before I posted.. You can try with the HTML I provided. the problem is not lack of scrolling.. it's lack of html scoring events – Shai Levy Jan 17 '16 at 15:40
  • A suggestion, make the webview wrap_content, the html height of body to auto - so the webview has the height of the entire content and then actually doesn't scroll vertically - only as part of the scroll view. Would that solve your requirement? – Raanan Jan 19 '16 at 09:18
  • Hi Raanan, it won't solve the issue because the html needs to be aware of the scrolling – Shai Levy Jan 19 '16 at 11:11

5 Answers5

1

To Keep Webview inside scrollview here you need to measure height of the webview and set it in layout params.

Here i have tried to give answer for the scrollable webview.

<ScrollView
        android:layout_width="fill_parent" android:background="#FF744931"
        android:layout_height="fill_parent">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            tools:ignore="WebViewLayout">

            <TextView
                android:id="@+id/txtVoiceSeachQuery"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textColor="#FF0000"
                android:textSize="26sp" />

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:padding="20dp"
                android:text="SOMETHING ABOVE THE WEBVIEW" />

            <com.example.ScrollableWebView
                android:id="@+id/webview"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                app:isWebViewInsideScroll="true" />

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:padding="20dp"
                android:text="SOMETHING BELOW THE WEBVIEW" />

        </LinearLayout>
    </ScrollView>

res/values/attrs.xml

To add attribute for the Custom Control

<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ScrollableWebView">
    <attr name="isWebViewInsideScroll" format="boolean"></attr>
</declare-styleable>
</resources>

ScrollableWebView

public class ScrollableWebView extends WebView {
    private boolean webViewInsideScroll = true;
    public static final String RESOURCE_NAMESPACE = "http://schemas.android.com/apk/res-auto";

    public ScrollableWebView(Context context) {
        super(context);
    }

    public ScrollableWebView(Context context, AttributeSet attrs) {
        super(context, attrs);
        setWebViewInsideScroll(attrs.getAttributeBooleanValue
                (RESOURCE_NAMESPACE, "isWebViewInsideScroll", true));
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (isWebViewInsideScroll()) {
            int expandSpec = MeasureSpec.makeMeasureSpec(MEASURED_SIZE_MASK, MeasureSpec.AT_MOST);
            super.onMeasure(widthMeasureSpec, expandSpec);
            ViewGroup.LayoutParams params = getLayoutParams();
            params.height = getMeasuredHeight();
            setLayoutParams(params);
        } else {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    }

    public boolean isWebViewInsideScroll() {
        return webViewInsideScroll;
    }

    public void setWebViewInsideScroll(boolean webViewInsideScroll) {
        this.webViewInsideScroll = webViewInsideScroll;
    }
}

To fetch attribute value you can also use Stylable but here i have done without using it.

ScrollableWebView webview = (ScrollableWebView) findViewById(R.id.webview);
        webview.loadUrl("https://storage.googleapis.com/htmltestingbucket/nested_scroll_helper.html");

Below is link of output

If you dont want to create attribute file & add Custom attributes in res/values/attrs.xml than you can ignore that file & check this pastebin here i gave without any custom attribute like isWebViewInsideScroll. you can remove it from xml layout too. Let me know if anything.

user1140237
  • 5,015
  • 1
  • 28
  • 56
  • Thanks for the effort and investment but your answer isn't good since it doesn't solve what I asked for. in fact the screenshots you provided already showed it. Just open the link I provided in chrome mobile or desktop, see how it behaves (or any browser). The black text should stay on top of the webview and print the JS scrolling events it received.. in fact, you don't event need the onMeasure() you wrote to put a WebView in a scroll-view – Shai Levy Jan 19 '16 at 10:47
  • works perfectly for integrating inside a recyclerview. though using WebView.getSettings().setUseWideViewPort(true) seems to mess it up. for me it works great. thanks – Ramin Apr 23 '16 at 20:02
0

if you place you webview inside scrollview you will not get html scrolling effect, because your webview content will not scroll ( it will be placed full lengtth inside scrollview.)
To face your need to place elements above and below you can listen to webview scroll and use DragViewHelper or nineoldandroids to move header and footer, so user will think, they are single element (you dont need scrollview).

webView.setOnScrollChangeListener(new View.OnScrollChangeListener() {
  @Override
  public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {

      ViewHelper.setTranslationY(headerTextView, -event.getY());

  }
});


public class ObservableWebView extends WebView {
private OnScrollChangeListener onScrollChangeListener;

public ObservableWebView(Context context) {
    super(context);
}

public ObservableWebView(Context context, AttributeSet attrs) {
    super(context, attrs);
}

public ObservableWebView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
}

@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
    super.onScrollChanged(l, t, oldl, oldt);
    if (onScrollChangeListener != null) {
        onScrollChangeListener.onScrollChange(this, l, t, oldl, oldt);
    }
}

public void setOnScrollChangeListener(OnScrollChangeListener onScrollChangeListener) {
    this.onScrollChangeListener = onScrollChangeListener;
}

public OnScrollChangeListener getOnScrollChangeListener() {
    return onScrollChangeListener;
}

public interface OnScrollChangeListener {
    /**
     * Called when the scroll position of a view changes.
     *
     * @param v          The view whose scroll position has changed.
     * @param scrollX    Current horizontal scroll origin.
     * @param scrollY    Current vertical scroll origin.
     * @param oldScrollX Previous horizontal scroll origin.
     * @param oldScrollY Previous vertical scroll origin.
     */
    void onScrollChange(WebView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY);
}
}

This example should help you to hide header, i used nineoldandroid for it

Yarh
  • 4,459
  • 5
  • 45
  • 95
  • I will try it, my problem is that around my webview have lots of elements including popup and etc. So it's very (!!!) cumbersome to manage everything my self. I'm wondering If I can encapsulate it into my own view that extends a WebView but also implements all the functionality of a scrollview. The problem is that I need to also extend a FrameLayout to implement my own scroll view.. every road I peek on gets more messy.. – Shai Levy Jan 19 '16 at 11:58
  • But as as a side note, while I appreciate your answer a lot , my question is titled "HTML scrolling events in an Android WebView that's inside a ScrollView" and you are suggesting I drop ScrollView.. I've put a bounty on this exactly because I don't want to drop the ScollView... I DO need it. The question was simplified to show only textview above and below, the real App is much more complex – Shai Levy Jan 19 '16 at 12:01
0

It seems the most elegant way I could find to handle this is as following: - Listen to the SrollView scrolls:You can use an ObservableScrollView or call setOnScrollChangeListener() from API level 23 - Calculate the scroll Y offset in pixels - Call the WebView.evaluateJavascript() - Pass it all the details of the scroll event So the general concepts is passing: "$(document).trigger('scroll');" as the first param evaluateJavascript

I'm still testing the details and working out the kinks but it Seems like the better way to go, I will try to edit this answer with more info as I solve this

If anyway has a better solution for I would like to hear it

Shai Levy
  • 715
  • 9
  • 25
0

I have the same issue recently and I found your posts here :)

I have a WebView nested in a ScrollVIew. And the page which I loaded into WebView need to call a JS function when it scroll to the end, but in scroll view , web page's window.onscroll = functionXXX() {} never get called.

Finally, I have to set a OnScrollListener to the ScrollView, and call my JS function manually by the code below

@Override
public void onScroll(int scrollY) {   
    if (!scrollView.canScrollVertically(scrollY)) {
        mWebView.loadUrl("javascript:functionXXX();");
    }
}

Maybe our situations are different, but I hope it will give u some inspiration :)

TimmyMa
  • 175
  • 1
  • 1
  • 8
  • I had a different case but thanks for updating. you should probably call evaluateJavascript() instead of the loadUrl . as the loadUrl triggers some event listeners – Shai Levy Nov 14 '16 at 14:05
-1

in fact it is not so good put an scrollable view into another. Try to use this:

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

And

<WebView
    android:id="@+id/webview"
    android:layout_width="match_parent"
    android:layout_height="match_parent" 
    android:layout_weight="1"/>