Rajat Sharma
Rajat Sharma

Reputation: 522

How to detect scrollend of webview in android?

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.

Upvotes: 14

Views: 32686

Answers (7)

writer_chris
writer_chris

Reputation: 156

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

Upvotes: 3

Orange
Orange

Reputation: 389

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.

Upvotes: 1

Sachin Velaga
Sachin Velaga

Reputation: 21

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

Upvotes: 2

Abhishek
Abhishek

Reputation: 3398

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

}

Upvotes: 3

Aurel Nica
Aurel Nica

Reputation: 141

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
                        }

                }
            });

Upvotes: 14

Swati Sachdeva
Swati Sachdeva

Reputation: 263

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

Upvotes: 17

C&#252;neyt
C&#252;neyt

Reputation: 2585

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


            }
        });

Upvotes: 4

Related Questions