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

7
  • See the accepted answer of stackoverflow.com/questions/9718245/webview-in-scrollview Commented Jan 17, 2016 at 8: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 Commented Jan 17, 2016 at 8:38
  • I'm not looking to solve a problem with native element ONLY above the webview, that is much easier Commented Jan 17, 2016 at 10:24
  • have you checked here stackoverflow.com/a/13353874/5202007 Commented Jan 17, 2016 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 Commented Jan 17, 2016 at 15:40

5 Answers 5

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.

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

2 Comments

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
works perfectly for integrating inside a recyclerview. though using WebView.getSettings().setUseWideViewPort(true) seems to mess it up. for me it works great. thanks
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

2 Comments

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

Comments

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

1 Comment

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

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.