My Android app uses a WebView to display a bunch of HTML code that I generate 'on the fly'. The HTML code is loaded using the following code:
StringBuilder builder = new StringBuilder();
// HTML
builder.append("<html><head><link rel=\"stylesheet\" href=\"file:///android_asset/style.css\" type=\"text/css\">");
builder.append("</link></head><body>");
builder.append(getThreadBody());
builder.append("</body></html>");
webview.loadDataWithBaseURL("file:///android_asset/", builder.toString(), "text/html", "utf-8", null);
This all works really nice. Note that I'm not loading an actual HTML file, I'm merely creating a string that represents some (hopefully valid) HTML and load it in the WebView.
Anyway, the HTML I generate (the part in the 'getThreadBody' method) contains named anchors, for example like this;
<div>
<a name="949823">Anchor 1</a>
<div>All kinds of stuff</div>
<a name="895984">Anchor 2</a>
<div>...</div>
</div>
Now in some cases I want the WebView to navigate to one of those anchors as soon as I load the HTML. As far as I understand the WebView supports navigating (scrolling in this case) to named anchors, but the catch is that nobody is clicking any hyperlinks to those anchors, I want the WebView to scroll when it is loaded (it is no problem if there is a short delay between loading the HTML and scrolling, my point is that it should not require user interaction).
I believe the behavior can be achieved by using the WebView's loadUrl method and supplying a URL with the anchor in place, eg
webview.loadUrl("file:///android_asset/page.html#895984", ...)
However, since I am not saving the HTML to any file I cannot use this method... Of course saving the HTML to a temporary file may be a solution but I'd like to keep that as a last resort, there must be a simpler way?
How can I achieve this? Thanks!
Resolved
Here's the code that works. I found a short delay was required to let the page load before executing the javascript otherwise it was kind of 50/50 whether it worked or not...
StringBuilder builder = new StringBuilder();
// HTML
builder.append("<html><head><link rel=\"stylesheet\" href=\"file:///android_asset/style.css\" type=\"text/css\">");
builder.append("</link>");
builder.append("<script>");
builder.append("function scrollAnchor(id) {");
builder.append("window.location.hash = id;}");
builder.append("</script>");
builder.append("</head><body>");
builder.append(getThreadBody());
builder.append("</body></html>");
webContents.loadDataWithBaseURL("file:///android_asset/", builder.toString(), "text/html", "utf-8", null);
Timer timer = new Timer();
timer.schedule(new TimerTask() {
#Override
public void run() {
String id = "895884";
webContents.loadUrl("javascript:scrollAnchor(" + id + ");");
}
}
}, 250);
How about control it by JavaScript? I didn't try it but maybe this is a clue.
builder.append(getThreadBody());
builder.append("<script>window.location.hash="949823";</script>");
builder.append("</body></html>");
Remember enable javascript for WebView.
----Additional Answer----
I saw that you use TimerTask to load the javascript, That works but I think there is another better way. WebView have a callback named onPageFinished and it will be trigger when WebView finish loading webpage. You could inject your JS there.
webContents.setWebViewClient(new WebViewClient(){
#Override
public void onPageFinished(WebView view, String url) {
String id = "895884";
webContents.loadUrl("javascript:scrollAnchor(" + id + ");");
}
});
Hope this is useful!
First of all, you don't need to use javascript. If you loaded content with
webContents.loadDataWithBaseURL("some://dummy/url",...);
you can simply scroll to anchor with
webContents.loadUrl("some://dummy/url#anchor");
Secondly, doing that on onPageFinished without additional control results in never ending loop! When loadUrl finishes onPageFinished get's called again and again.
If you have external action (eg: onclick event), try this code
public static void scrollToAnchor(String id) {
sWebView.loadUrl("javascript:document.getElementById(\"" + id + "\").scrollIntoView()");
}
If you have in your layout the webview inside a nestedScrollView the nesting will prevent the scrolling events from working properly. Once you take it out it will work without any need to reload or add javascript.
Additionally if you want to keep the nestedScrollView and dont care if the user cannot scroll past that section you can add fillViewPort=true to the nestedScrollView and android:layout_height="wrap_content" to the webview.
It would look something like:
<androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.google.android.material.appbar.AppBarLayout>
...
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewPort=true
app:layout_behavior="#string/appbar_scrolling_view_behavior">
<WebView
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
if you load the html from the assets:
webView.loadUrl("file:///android_asset/htmls/mypage.html#anchor");
and you can move to the anchor inside a web page:
webView.loadUrl("http://www.stackoverflow.com/mypage.html#anchor");
Related
I need to load an HTML page with a form that has only one number input field. The desired behavior is:
When the user enters some data in this field and clicks on forward button from the Android's keyboard, a function is executed and a value appears, without taking him to another page.
The problem is that when the user clicks forward, the app crashes due to a FileUriExposedException. After searching a bit, I saw that this error is related to this bug.
One of the solutions shown in (2) was add an extra invisible form field, but does anyone know another solution? Because I think that changing all HTML forms will be something much more time consuming than trying a solution via Android.
Not sure if avoiding clicking forward would help, but here is a way you can submit to the form without the user typing in the form directly, if you have them enter the number into an edittext, save its value and then use something like this. You can set the webview visibility to invisible if you want to start and then make it visible after it submits. You can find out the IDs of the elements in the form by right clicking and hitting Inspect.
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.getSettings().setDomStorageEnabled(true);
mWebView.loadUrl(getString(R.string.form_url));
mWebView.setWebViewClient(new WebViewClient(){
#Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
String fieldID;
String submitButtonID;
int numberTypedInByUser
mWebView.loadUrl("javascript: {" +
"document.getElementById('"+fieldID+"').value = '"+numberTypedInByUser+"';" +
"var z = document.getElementById('"+submitButtonID+"').click(); };");
}
});
I want my webview to show a webpage but not to have any interaction with the page at all.
Just show it. I an showing an iframe but when you click on the image in it it goes to a link. That link is to big for the frame. I need to disable being able to click it.
i have tried
WebView
android:id="#+id/wv_AmberAlert"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="false"
android:longClickable ="false"
and
wv_Amber_Alert.setFocusableInTouchMode(false);
wv_Amber_Alert.setFocusable(false);
wv_Amber_Alert.setOnTouchListener(null);
wv_Amber_Alert.setOnClickListener(null);
You can try attaching a WebViewClient to the WebView and override the onLoadResource() method to stop loading of the url:
WebViewClient client = new WebViewClient() {
#Override
public void onLoadResource(WebView view, String url) {
view.stopLoading();
}
};
wv_Amber_Alert.setWebViewClient(client);
I like to add an "overlay" FrameLayout over the WebView.
Make it the same size and position of your webview and make sure android:clickable="true"
When the webview is loading, I set my overlay to be visible. Vice versa for when loading completes.
It's also nice to add a semi-transparent background to the overlay - this explains to the user that the webview is loading and hasn't just frozen up (since interactions now dont work).
here's what I'm trying to achieve: I assemble a local html page with some tags and internet resources in it, and then load the html page webpage with WebView, and there's a "View More" button at the end of the page, when somebody clicks the button, I'll re-assemble the html page(with old data and new data, like refreshing twitter to load more twits) and have the WebView load it again. But as the user has scrolled to certain location when he clicks the button so I'd like to let the WebView to scroll to the very point where the user was before he clicks the button.
And here's what do by now, I create a WebViewClient and implements:
public void onPageFinished(WebView view, String url) {
view.scrollTo(last_X, last_Y);
}
It does not work as I expect it to, I have 2 concerns, guys please help me out:
1. this callback function does not work all the time. for most of the time, it works, but sometimes, for unknown reasons, it just does not work.
2. even if it works, it's not exactly what I want. as I mentioned, the html page contains some internet resources like
<a href='whateversite/whateverimage'><img src='whateversite/whateverimage'></img></a>
So the scrollTo function only got called when all images are loaded, it takes too much time and it's unnecessary, is there any way to start scrolling when the page is loaded but before all other resources got loaded? Per say, as long as webview.getContentHeight() > 0, it's OK to scroll.
Apologize for my poor english that I'll have to use load of words to try to make myself clear.
Guys, please halp~
Do something like this :
view.post(new Runnable() {
public void run() {
view.scrollTo(last_X, last_Y);
}
});
I've done alot of Googling on this and haven't found an answer. I have a webview as part of an overall layout with other controls. I use the webview to show formatted text (recipe descriptions with bold and italic fonts) and URLs. The URLs point to Youtube and are fully qualified (ie. http://www.youtube.com/watch?v=fyzyhuVgzRE). When I click on the link in my web view, the web view control itself disappears from the layout (leaving the rest of the controls on the page intact) and the default web browser does not launch.
It behaves as if I've set the visibility of the web view to GONE, but I do not manipulate the visibility of any of the controls on the layout.
One interesting clue in the LogCat output is an INFO level message from the OS:
MotionRecognitionManager .unregisterListener : / listener count = 0->0,
listener=android.webkit.ZoomManager$1#41ac2fc0
Any ideas what would cause this behavior?
EDIT
Here is how I setup the webview in the onCreate() method of the activity:
webView1 = (WebView)findViewById(R.id.webView1);
webView1.getSettings().setJavaScriptEnabled(true);
webView1.getSettings().setAllowFileAccess(true);
webView1.setWebViewClient(new MyWebViewClient());
And the MyWebViewClient class:
private class MyWebViewClient extends WebViewClient {
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url != null && url.startsWith("http://")) {
view.getContext().startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
return true;
} else {
return false;
}
}
}
Oddly enough the shouldOverrideUrlLoading is never called.
Well, I'm a dope. It turns out that the double quotes in the href attribute of the anchor were doubled. My system integrates with a MySQL db over the web and I have to do alot of transformation of data between MySQL/PHP and Android/SQLite. During one of these transformations I double-escaped the quotes in the href. So what should have been:
Link Text
was instead rendered as:
Link Text
In the web view the resulting URL looked fine. It was underscored properly and looked like any other URL. It is odd though that simple doubling the quotes causes the behavior. I'll see if there is a known bug like this for Android. At the very least the WebView should through some kind of exception, not simply disappear from the screen.
I faced this type of behavior when webview tries to load content it cant handle. Intercept ref clicks using WebViewClient and send intent to open this ref in external application. Check this thread.
I have seen one other SO question on this and it was not resolved.
I am using LoadData() with application generated html.
I noticed when I add a few blank lines after new text the page scrolls and the text takes a moment to magically appear (no scrolling, it just appears as in a fade-in.).
Watching .getContentHeight() it does not update in the onPageFinshed() event, but it is updated on subsequent additions of text on the page.
I created a timer event to watch getContentHeight. Even after I found getContentHeight had changed and issued a browser.pageDown(true) the page still did on scroll to the bottom.
It seems to be working fine when entering the activity for the first time. My problem only happens with I append a few lines to newInnerHTML.
Additionally the view involved has a header and footer with the browser in the middle. The webView is NOT behind the footer.
Is this a bug, or am I missing something?
TIA,
Jim
this code is within the onCreate for the activity.
final WebView browser = (WebView) findViewById(R.id.webview);
browser.setWebViewClient(new WebViewClient() {
#Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(browser, url);
// browser.computeScroll(); //didn't help
Log.d(TAG,"page getContentHeight: " + browser.getContentHeight());
Log.d(TAG,"page finished");
browser.pageDown(true);
}
});
This method is after the onCreate for the activity
private void UpdateMessageDisplay() {
//loadData fires onPageFinished but without the new data
browser.loadData("<html><body>" + newInnerHTML + "</body></html>", "text/html", "utf-8");
//reload() again fires onPageFinished but this time with new data
browser.reload();
}
I received a partial answer here, which does get the pageDown(true) to fire.
webview loaddata scroll setting not updating till clicked
There are still some issues.