14

Mine is a native app where I want to show/ hide a button if the user scrolls to the end of webview. I looked at an answer here and got the entire idea of how to register the callback via interface. My major issue is that I am not able to get my calculations right inside the onScrollChanged method. I have tried combination of things like getHeight(), getContentHeight(), top etc but it just seems to fire too early. I tried with a simple page like google's with comparatively lesser content as well as a news page.

These logics work fine for a normal scroll view. Since the webpage has a lot of content, it could be messing up the calculations. Pasting a sample code: does not work.

@Override
protected void onScrollChanged(int left, int top, int oldLeft, int oldTop) {
    if ( mOnWebViewBottomReachedListener != null ) {
        //if ( (getContentHeight() - (top + getHeight())) <= mMinDistance )
        int diff = (getBottom() - (getHeight() + getScrollY()));
        Log.e("values ", diff+" o");
        if ((int) Math.floor(getContentHeight() * getScaleY()) == top)
            mOnWebViewBottomReachedListener.onBottomReached(this);
    }
    super.onScrollChanged(left, top, oldLeft, oldTop);
}

Need help with this. Thanks.

Community
  • 1
  • 1
Rajat Sharma
  • 502
  • 4
  • 8
  • 17
  • dupe of http://stackoverflow.com/questions/20513141/weird-miscalculation-when-trying-to-detect-if-webview-scrolled-to-bottom ? – marcin.kosiba Jan 10 '14 at 11:44

7 Answers7

16
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
    int height = (int) Math.floor(this.getContentHeight() * this.getScale());  
    int webViewHeight = this.getMeasuredHeight();  
    if(this.getScrollY() + webViewHeight >= height){  
       Log.i("THE END", "reached");
    }
    super.onScrollChanged(l, t, oldl, oldt);
}

This logic works fine for a webview.

PhilLab
  • 4,777
  • 1
  • 25
  • 77
Swati Sachdeva
  • 253
  • 2
  • 11
  • 1
    nice try. but not works perfect because web view gives getContentHeight() of older web page when new page just loaded. – Abhishek Aug 05 '17 at 08:10
  • Just changed this.getScale() by newScale & get newScale in onScaleChanged method.Check more detail in https://developer.android.com/reference/android/webkit/WebViewClient.html#onScaleChanged(android.webkit.WebView,%20float,%20float) – Rajan Kashiyani Mar 16 '19 at 10:40
  • Worked for me when I changed getScale()(deprecated) to getScaleY(). I was trying to detect scroll to bottom. – ChinLoong Oct 07 '19 at 03:06
13

After a lot of google search all the solutions I found were calculating the height and determining the scrollOfset, but most of them are not reliable in most cases resulting in issues like described here Weird miscalculation when trying to detect if WebView scrolled to bottom

I have found the solution that works 100% is to determine it based on overScroll

webView.setOnOverScrolledCallback(new WebViewCustom.OnOverScrolledCallback() {
                @Override
                public void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
                    if(clampedY)
                        if(scrollY == 0) {
                            //top
                        }
                        else {
                            //bottom
                        }

                }
            });
Community
  • 1
  • 1
Aurel Nica
  • 131
  • 1
  • 2
4

Firstly extend your webview from this class. https://stackoverflow.com/a/14753235/3027225 After this upgrade your webView like this

 <com.YourPackageName.ObservableWebView
            android:id="@+id/webView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_alignParentLeft="true"
            android:layout_alignParentRight="true" />

And finally override the onScroll method like the following:

webView.setOnScrollChangedCallback(new OnScrollChangedCallback() {

            @Override
            public void onScroll(int l, int t) {
                  int tek = (int) Math.floor(webView.getContentHeight() * webView.getScale());
                  if(tek - webView.getScrollY() == webView.getHeight())
                     Toast.makeText(getActivity(), "End", Toast.LENGTH_SHORT).show();


            }
        });
Community
  • 1
  • 1
Cüneyt
  • 2,565
  • 25
  • 31
2

This three method combination work excellent for me

  1. Create two boolean variable two detect topReached or bottomReached.

  2. use computeVerticalScrollRange() to get vertical scroll range. computeVerticalScrollRange() will help you when content height is less(In such case onScrollChanged not called).

  3. use onScrollChanged to detect scrolling is happing. scrolling is happing means content height is greater than webView measured height.Now detect bottom end using newt or detect top using newt.
private boolean topReached = false, bottomReached = false;

 @Override
protected int computeVerticalScrollRange() {

    int readerViewHeight = getMeasuredHeight();

    int verticalScrollRange = super.computeVerticalScrollRange();


    if (readerViewHeight >= verticalScrollRange) {

        topReached = true;

        bottomReached = true;

    }

    return verticalScrollRange;
}

@Override
protected void onScrollChanged(int newLeft, int newTop, int oldLeft, int oldTop) {

    Logger.i(TAG, "onScrollChanged");

    topReached = false;

    bottomReached = false;

    Log.i(TAG, "onScrollChanged newLeft : " + newLeft);
    Log.i(TAG, "onScrollChanged newTop : " + newTop);
    Log.i(TAG, "onScrollChanged oldLeft : " + oldLeft);
    Log.i(TAG, "onScrollChanged oldTop : " + oldTop);

    int readerViewHeight = getMeasuredHeight();

    int contentHeight = getContentHeight();

    if (newTop == 0) {

        Log.d(TAG, "onScrollChanged : Top End");

        topReached = true;

    } else if (newTop + readerViewHeight >= contentHeight) {

        Log.d(TAG, "onScrollChanged : Bottom End");

        bottomReached = true;

    }

    super.onScrollChanged(newLeft, newTop, oldLeft, oldTop);

}
Abhishek
  • 3,348
  • 3
  • 15
  • 34
2

May a bit late to the party:

    enum class ScrollDirection(val direction: Int) {
        Upwards(-1),
        Downwards(1)
    }
        
     private fun detectOnScrollMaxDown() = with(binding) {
            webView.setOnScrollChangeListener { _, _, _, _, _ ->
                val isDownDirectPossible = webView.canScrollVertically(ScrollDirection.Downwards.direction)
                if (!isDownDirectPossible) Log.d("Reached the bottom? ","${!isDownDirectPossible}")
            }
        }
writer_chris
  • 146
  • 3
  • 9
1

Checked this on android version 8-11. it works 100%. I have achieved it by using computeVerticalScrollRange & computeVerticalScrollExtent.Following is an example of webview inside an activity/fragment.

Create a custom webview class.

public class MyWebView extends WebView {
    public MyWebView(Context context) {
        super(context);
    }

    public MyWebView(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public int computeVerticalScrollRange() {
        return super.computeVerticalScrollRange();
    }

    @Override
    protected int computeVerticalScrollExtent() {
        return super.computeVerticalScrollExtent();
    }
}

In your layout file , implement webview like this and include this layout file into your activity or fragment view file if you need to.

 <in.package.folder.MyWebView
        android:id="@+id/webview"
        android:layout_width="match_parent"
        android:layout_height="0dp"/>

In your activity/Fragment where you want to have the webview

  1. declare variable
private MyWebView webView;
  1. find the element id and initialize the webView
 webView = (MyWebView) findViewById(R.id.webview);
  1. Use computeVerticalScrollRange & computeVerticalScrollExtent methods defined in the webview class to calculate the end of scroll. 3rd parameter i1 ( Scroll Y) will be equal to (computeVerticalScrollRange - computeVerticalScrollExtent) if the scroll has reached the end of the webview content
 webView.setOnScrollChangeListener((view, i, i1, i2, i3) -> {
            int r1 = webView.computeVerticalScrollRange();
            int r2 = webView.computeVerticalScrollExtent();
            if (i1 == (r1 - r2)) {
                "REACHED END OF THE SCROLL"
            } 
        });
Ole Pannier
  • 3,208
  • 9
  • 22
  • 33
  • webView.computerVerticalScrollExtent() value never changes while scrolling . I tried the scrollY (i.e., i1 in your code) in place of webView.computeVerticalScrollExtent() and it worked fine . – newbie_coder Mar 18 '22 at 08:45
0

The following Kotlin codes work for me:

    web_view.setOnScrollChangeListener { v, scrollX, scrollY, oldScrollX, oldScrollY ->
        val measuredHeight: Int = web_view.measuredHeight
        if(measuredHeight + scrollY == web_view.computeVerticalScrollRange()){
            Log.d("WebView","Reach bottom")
        }
    }

Tested for Android 12 & Android 10. API level 31.

Orange
  • 349
  • 3
  • 13