42

Using WebViewClient and/or the WebChromeClient you can get a listener for when the page has loaded, however this is sometimes called before the WebView has any content in it, before it has displayed anything.

What would be a efficient method for determining when the WebView has displayed it's content?

Edit: (Trying to be more clear)

When I load a page in a WebView, I want to set the scroll to a specific position. It seems that the scroll position cannot be set until the page is loaded and it has an actual content height. So, I have tried two different approaches to determining when the page has finished loading, onPageFinished() from the WebViewClient and also onProgressChanged() from the WebChromeClient. Both of these tell me when the page has finished loading.

However, the problem is that sometimes it is called before the page has been displayed and therefore the page has no height and the scroll call does nothing.

I am trying to find a solid way to determine when the page is ready to be scrolled, ie. when it has its content height.

I imagine I could setup a checking loop after it finished loading to keep looking for when the height is available but that seemed like quite the hack. Hoping there is a cleaner way.

cottonBallPaws
  • 21,220
  • 37
  • 123
  • 171
  • Visit:https://stackoverflow.com/questions/20942623/which-can-replace-capturepicture-function/52929553#52929553 – Vali Zhao Oct 23 '18 at 10:33

11 Answers11

41

I successfully used Richard's answer with a PictureListener for a few years, but I no longer recommend this as the best solution.

This is for two reasons:

  1. webView.setPictureListener and PictureListener are both deprecated.
  2. Using this listener will cause the WebView to allocate a new Picture often. This allocation is expensive and this can have some significant performance impacts or even cause native crashes on JellyBean MR1.

Instead I recommend creating a subclass of WebView and overriding invalidate() like so:

@Override
public void invalidate() {
    super.invalidate();

    if (getContentHeight() > 0) {
        // WebView has displayed some content and is scrollable.
    }
}

If you still want to use the PictureListener method, you will get better performance if you setPictureListener back to null after you are done with it.

Community
  • 1
  • 1
cottonBallPaws
  • 21,220
  • 37
  • 123
  • 171
  • 4
    This is the best solution I have found. However, there are some occasions when invalidate() is not called when WebView content in a WebView is updated. One unusual case is when the previous content is positioned right at the top when loadDataWithBaseURL is called so I also added a delayed call to invalidate after calling loadDataWithBaseURL. – Martin May 06 '14 at 21:17
  • 5
    Indeed the best solution. Adding `getProgress() == 100` helped me to make sure the content is completely rendered. – Torsten Römer Nov 29 '14 at 04:43
  • I know this is a bit late, but newbie here: where do I put the above mentioned code? Everywhere I put it, Android Console says not recognized. – user4951834 Jan 06 '16 at 03:57
  • @user4951834 create a subclass of WebView and put it in there. Then use your custom WebView class instead of the original one. The WebView has a method called invalidate() and this will override its original implementation. – cottonBallPaws Jan 07 '16 at 19:42
  • @cottonBallPaws Sorry, this may sound like a dumb question, but can I get an example of the code? What do you mean create a subclass? – user4951834 Jan 08 '16 at 08:46
  • Can you please elaborate on this answer a little more? For example, what would you call within the `if` statement? – Fizzix Feb 23 '16 at 05:32
  • 1
    @Fizzix if the content height is > 0, then the webview has displayed content. so inside that if statement represents whatever you want to do when that has occurred. – cottonBallPaws Feb 23 '16 at 13:47
  • Hey i tried this solution Actually its better than other but the problem that i always get the height is like half of the actual height any help with this? – Antwan Mar 29 '16 at 08:53
16

EDIT: Since I first posted this, this method has been deprecated in Android API 12. Thanks, Google! I will leave the original answer in tact:


Yes-- there IS a listener for knowing when the content has finished loading. I had to create one on my project so I could do a splash screen which stayed up until the webview had loaded the page.

It's called .setPictureListener.

For example:

mWebView.setPictureListener(new MyPictureListener());
//... and then later on....
class MyPictureListener implements PictureListener {

    @Override
    public void onNewPicture(WebView view, Picture arg1) {
      // put code here that needs to run when the page has finished loading and
      // a new "picture" is on the webview.      
    }    
} 
Richard
  • 1,912
  • 20
  • 28
  • 3
    Awesome. This works exactly as I had hoped. A warning to others using this though... It does get called fairly often as you move around the page so make sure whatever happens in the onNewPicture method is quick. – cottonBallPaws Feb 09 '11 at 15:14
  • 1
    @Paul: My understanding was that this listener fired when the webview updated it's "picture", that is, what it was showing on screen, regardless of *what* it was showing. But if it's true that you need to have at least one image on the page, you could always add a simple solid-color .gif or .jpg somewhere which would blend into the background and not be visible to the user, but would cause this listener to fire off. But I can't confirm that is even needed, as I've never tested a webview with just text. Can someone else reply to clarify? – Richard May 12 '11 at 13:11
  • 2
    No, I was mistaken. The "picture" the WebKit docs are referring to is the rendering of the browser contents. I was initially confused as to why the above technique wasn't working for me, because the event isn't fired unless the rendering *actually appears on the screen*. – Paul Lammertsma May 12 '11 at 22:44
  • @Paul: I see, though I use this technique myself to know when to make a splash screen disappear and show the webview (I want it to show after the content is loaded). And while that is going on, the webview's visibility is set to "invisible". So I don't think it actually needs to be on the phone's screen to fire. If you have your webview set to "gone", perhaps that is the issue? – Richard May 17 '11 at 16:43
  • Perhaps that's the problem. I'll check if that's the case when I have the opportunity. – Paul Lammertsma May 17 '11 at 17:01
  • This method was deprecated in API level 12 – Gu1234 Jul 16 '13 at 11:27
8

The suggested solutions here are hacky at best, while there is a perfectly clean solution, in accordance with the Android docs:

WebViewClient client = new WebViewClient() {
    @Override
    public void onPageFinished(WebView view, String url) {
        Log.d("MYAPP", "Page loaded");
    }
};
webView.setWebViewClient(client);
Paul Lammertsma
  • 37,593
  • 16
  • 136
  • 187
  • 21
    I wish that was the answer, but in my experience onPageFinished can be, and is a great deal of the time, called before the content is displayed/scrollable/measurable. So if you try to scroll in the onPageFinished method, the content height might still be 0. – cottonBallPaws May 05 '11 at 17:21
  • 1
    Ah, right. The docs actually suggest using [onNewPicture(WebView, Picture)](http://developer.android.com/reference/android/webkit/WebView.PictureListener.html#onNewPicture\(android.webkit.WebView,%20android.graphics.Picture\)), but from my experience, it doesn't always fire. – Paul Lammertsma May 05 '11 at 18:00
  • 1
    Follow-up: onNewPicture is only fired if the WebView is visible and displaying within the boundaries of the device. So if you want to use it to calculate the dimensions before displaying the WebView, it seems like this won't help. – Paul Lammertsma May 05 '11 at 18:14
  • onNewPicture(WebView, Picture) is deprecated long time ago (for now). – kevin Feb 18 '14 at 05:05
7

I read this discussion, and I'm facing the same problem too.

I searched a lot, but they're not the solution they promise to be.

And I don't want to use the deprecated `onNewPicture` callback.

In my own situation I only need to restore the `WebView` scroll position when the activity is started, and I think this is the most cases since when the activity task stack goes to background, Android automatically save the `WebView` state, so when it pops to the foreground, it will looks the same. If it gets killed when under background, I'm sure you'll manage to save the state in Activity lifecycle methods like `onPause`.

So, instead of extending `WebView` and overriding blah, blah, blah..., I use a Handler and post model.

private final Handler mHandler = new Handler();
// in onCreate
mHandler.postDelayed(new Runnable() {

                @Override
                public void run() {
                    if (mWebView.getContentHeight() > 0) {
                        mWebView.scrollTo(0, mLastPosition);
                        Log.d("scrolling", "true");
                        mHandler.removeCallbacks(this);
                    } else {
                        mHandler.postDelayed(this, 100);
                    }
                }
            }, 100);

This code block will peridically check if the WebView page can be manipulated, if so, it does the scrolling and remove the callback, otherwise it loops. I test this and find out it ususally won't take very long.

Hope this helps!

user3103871
  • 71
  • 1
  • 2
  • I tried WebViewClient.onPageFinished, WebChromeClient.onProgressChanged, and even the deprecated PictureListener.onNewPicture and they were all unreliable, but this approach is working well. However, if you need to clear the WebView content, setting the URL to about:blank does not return the contentHeight to 0 as you might expect. The only way I could find to clear the content and know when the WebView is ready to accept new content is to create a new WebView object. – arlomedia Nov 18 '14 at 05:06
  • This works in my case, but instead of `mWebView.getContentHeight() > 0`, I calculated the height of the device screen. Just to see that there are options. – sandalone Jul 17 '15 at 13:08
  • It's the only device that worked in my case, where I navigate between pages I build myself and preserve the scroll position when navigating to a page that has been visited. Similarly to @sandalone, I note the contentHeight (WebView.getContentHeight) of a page prior to moving away from it, and then I run code similar to the above to scroll to the saved position only when the result returned by getContentHeight matches the stored value. I run it in onPageFinished. – Papa Smurf Jun 12 '19 at 20:31
2

I created a custom view extends webview, which overrides the "onSizeChanged" method. The onSizeChanged happens after the webview loads everything in it.

OatsMantou
  • 193
  • 3
  • 7
  • 2
    This works perfectly the first time a page is loaded, but after the first one is loaded this is never called again so it doesn't work if the user will be moving onto other pages. – cottonBallPaws Jan 01 '11 at 19:34
1

This is not a direct answer for the question asked, but you can alternatively use the WebChromeClient. I find it to me more reliable and I have it configured to display loading progress. I was having the same issue with WebView hiding the ProgressBar before the page was fully loaded and I replaced WebView with WebView client.

I use the following code

final WebView wvMax = (WebView) findViewById(R.id.webViewTnSmax);
final ProgressBar pbMax = (ProgressBar) findViewById(R.id.progressBarTnSmax);

wvMax.setWebChromeClient(new WebChromeClient() {
    public void onProgressChanged(WebView view, int progress) {
        pbMax.setVisibility(View.VISIBLE);
        pbMax.setProgress(progress);
        if (progress == 100) {
            pbMax.setVisibility(View.GONE); // Make the bar disappear after URL is loaded
        }
    }

    public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
        // do something
    }
});
theczechsensation
  • 4,215
  • 2
  • 24
  • 25
1

I Know its Old but it can HELP, the answers here are not perfectly working, the best that i found is to Override the onDraw on the Extended WebView

  @Override
        protected void onDraw(Canvas canvas) {
    //do your math/stuff/magic/blackmagic/hackish here

            super.onDraw(canvas);
        }

so every scroll down/up every fontZoomChange

almost everything(didnt tested all) will call the onDraw

i use this to work with a SeekBar as Vertical ScrollBar

;)

its important to check if null, and add some others if to avoid useless method call

1

I've fixed this issue creating a Custom View which overrides onDraw method and calling my own listener. This is the code:

public class CustomWebView extends WebView{

public interface FinishedDrawListener{
    void onDrawFinished(WebView view, String url);
}

private FinishedDrawListener drawListener;

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

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

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

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

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    if(drawListener != null){
        drawListener.onDrawFinished(this, this.getUrl());
    }
}

public void setOnFinishedDrawListener(FinishedDrawListener listener){
    this.drawListener = listener;
}   

I think that it's not the best way to do it but it works for my application.

emaleavil
  • 591
  • 7
  • 14
1

EDIT: I no longer recommend this solution, but I'll leave it here for the sake of information.

In case this helps anyone else, the way I ended up doing this was by subclassing the WebView and overriding the computeVeritcalScrollExtent() method.

@Override
protected int computeVerticalScrollExtent() {
    // do you checking here

    return super.computeVerticalScrollExtent();
}

This will be called anytime the WebView calculates its vertical scrollbar. However, the first time it is called where the getContentHeight() > 0, then that will be the first time the content is displayed.

cottonBallPaws
  • 21,220
  • 37
  • 123
  • 171
0
onLoadResource(WebView view, String url)

Notify the host application that the WebView will load the resource specified by the given url.

Still not clear of what the objective is here, but you might try

public void doUpdateVisitedHistory (WebView view, String url, boolean isReload)

I would "assume" that the page has completed loading before it is added to history...

Aaron Saunders
  • 33,180
  • 5
  • 60
  • 80
  • Thanks for the reply but doesn't that just tell the app when the specific resource is about to begin loading not when it has just been displayed? onLoadResource is called for each resource on the on page, these are all called before onPageFinished. What I am looking for is something that would be called after onPageFinished, right after the page has actually be displayed to the user. – cottonBallPaws Oct 31 '10 at 21:36
  • you should update the question to make it clearer... maybe someone will give you a more suitable answer? – Aaron Saunders Nov 01 '10 at 00:22
  • 1
    sorry, for some reason I didn't see you responded. Thanks for your patience. I edited my question to try to make it clearer. Also, I tried the doUpdateVisitedHistory() idea and it was called while the page was loading so it won't work. Thanks again. – cottonBallPaws Nov 04 '10 at 06:46
-1

Its working fine on all devices :- Load Web View ProgressDialog

Rishabh Agrawal
  • 861
  • 2
  • 15
  • 25
  • This doesn't work. The height might be null in onPageFinished. – Johan S Jun 18 '13 at 07:01
  • This approach is relevant only if you are interested in the fact that web page data has been loaded. It has nothing to do with drawing/painting the data. – Michal Vician May 23 '14 at 12:59