I want my webview to autoscroll. Below is what I have tried, it does scroll the webview but it never stops i.e. it continues even after the webview has no content to display so it just displays the white screen. Please tell me how can it be fixed.
webview.setPictureListener(new PictureListener() {
public void onNewPicture(WebView view, Picture picture) {
webview.scrollBy(0, 1);
}
});
Try my code and hope it helps :)
scroll down:
mWebView.post(new Runnable() {
public void run() {
if (mWebView.getContentHeight() * mWebView.getScale() >= mWebView.getScrollY() ){
mWebView.scrollBy(0, (int)mWebView.getHeight());
}
}
});
Scroll Up
mWebView.post(new Runnable() {
public void run() {
if (mWebView.getScrollY() - mWebView.getHeight() > 0){
mWebView.scrollBy(0, -(int)mWebView.getHeight());
}else{
mWebView.scrollTo(0, 0);
}
}
});
This code work for auto scroll as well as your scroll goes down then automatically come to top and then again start scroll.
Check and if any query update me.
Timer repeatTask = new Timer();
repeatTask.scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
runOnUiThread(new Runnable() {
#Override
public void run() {
int height = (int) Math.floor(webView.getContentHeight() * webView.getScale());
int webViewHeight = webView.getMeasuredHeight();
if (webView.getScrollY() + webViewHeight >= height) {
webView.scrollBy(0, 0);
webView.scrollTo(0, 0);
} else {
webView.scrollBy(webView.getTop(), webView.getBottom());
}
}
});
}
}, 0, 5000);//delayed auto scroll time
As best as I can tell, the only way to do this consistently with modern Android is with JavaScript:
webView.webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView?, url: String?) {
webView.evaluateJavascript("""
|var _fullScrollIntervalID = setInterval(function() {
| if (window.scrollY + window.innerHeight >= document.body.scrollHeight) {
| window.clearInterval(_fullScrollIntervalID);
| } else {
| window.scrollBy(0, 10);
| }
|}, 17);
""".trimMargin(), null)
}
}
The JavaScript APIs are aware of the size of the content.
This solution doesn't take into account changing viewport sizes, window.innerHeight rounding errors, if document.body isn't the scrolling element, etc.
As for a Java-based solution, it seems the Java APIs give the size of the view, rather than the length of the page:
height
contentHeight
measuredHeight
bottom
Maybe this changed when enableSlowWholeDocumentDraw was introduced.
One Java API that is aware of the content length of the page is canScrollVertically but it returns false for a short time after onPageFinished is called. You could use some kind of delay to get around this. For example:
webView.webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView?, url: String?) {
ScrollRunnable().run()
}
}
// ...
val h = Handler()
inner class ScrollRunnable() : Runnable {
override fun run() {
// introduce some delay here!
if (webView.canScrollVertically(1)) {
webView.scrollBy(0, 10)
h.postDelayed(this, 17L)
}
}
}
I tested this on an Android API26 emulator and an Android TV API 22 emulator.
Related
The problem occurs on Android.
I have implemented a custom renderer for a WebView to get the capability of resizing the height request base on its content.
I took this from a xamarin forum post.
[assembly: ExportRenderer(typeof(AutoHeightWebView), typeof(AutoHeightWebViewRenderer))]
namespace MyProject.Droid.Renderers
{
public class AutoHeightWebViewRenderer : WebViewRenderer
{
public AutoHeightWebViewRenderer(Context context): base(context) {}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
{
base.OnElementChanged(e);
if (e.NewElement is AutoHeightWebView webViewControl)
{
if (e.OldElement == null)
{
Control.SetWebViewClient(new ExtendedWebViewClient(webViewControl));
}
}
}
class ExtendedWebViewClient : Android.Webkit.WebViewClient
{
private readonly AutoHeightWebView _control;
public ExtendedWebViewClient(AutoHeightWebView control)
{
_control = control;
}
public override async void OnPageFinished(Android.Webkit.WebView view, string url)
{
if (_control != null)
{
int i = 10;
while (view.ContentHeight == 0 && i-- > 0) // wait here till content is rendered
{
await System.Threading.Tasks.Task.Delay(100);
}
_control.HeightRequest = view.ContentHeight;
}
base.OnPageFinished(view, url);
}
}
}
}
Based on a certain logic, I change the source of the WebView and use the custom renderer to resize the view.
This works when the size is increased but not when the content size is smaller than the one before...
The be clearer, if I set the source of the WebView to a html file that is 200px height and change it to a html file that is 1000px, it works fine and I can see all the content. BUT, if I try to go back to my 200px html file, I get a 800px blank space underneath since the content doesn't change on the view.ContentHeight and keep the value of 1000px.
I followed this issue/thread and didn't find a solution to resolve this problem : https://github.com/xamarin/Xamarin.Forms/issues/1711
I have seen a lot of topics on Android saying that we need to recreate the webview. Is there any other solution?
I found a way to do it based on this thread.
I first tried with a JS command that returns the body height : document.body.scrollHeight.
The problem was the same, it always returned the largest size, but never decreased the size.
As the JS command have no problem to increase the size, I had to set the height to 0, set a 100ms delay (arbitrary) and then get the height of the HTML with the JS command above and set the HeightRequest of the view.
With this, the HTML body height will not decrease... It will always start from 0 and increase to the HTML body size.
Final result :
public class AutoHeightWebViewRenderer : WebViewRenderer
{
static AutoHeightWebView _xwebView = null;
public AutoHeightWebViewRenderer(Android.Content.Context context) : base(context)
{
}
class DynamicSizeWebViewClient : WebViewClient
{
public async override void OnPageFinished(Android.Webkit.WebView view, string url)
{
try
{
if (_xwebView != null)
{
view.Settings.JavaScriptEnabled = true;
view.Settings.DomStorageEnabled = true;
_xwebView.HeightRequest = 0d;
await Task.Delay(100);
string result = await _xwebView.EvaluateJavaScriptAsync("(function(){return document.body.scrollHeight;})()");
_xwebView.HeightRequest = Convert.ToDouble(result);
}
base.OnPageFinished(view, url);
}
catch (Exception ex)
{
Console.WriteLine($"{ex.Message}");
}
}
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
{
base.OnElementChanged(e);
_xwebView = e.NewElement as AutoHeightWebView;
if (e.OldElement == null)
{
Control.SetWebViewClient(new DynamicSizeWebViewClient());
}
}
}
Unable to override Here SDK to disable sound effect on the onSpeedExceeded event.
Using the Here Developer tutorial, (https://developer.here.com/blog/android-premium-sdk-speed-limit-warning-example), I succeeded in running the sample app. But...
While driving, when I exceed the speed limit, there is a doot doot doot. I want to override this behaviour as I intend to use my own sounds.
I guessed that I might override the code by creating a NavigationManager.SpeedWarningListener. Unfortunately I can not disable or defeat the 'onSpeedExceeded' sound effects.
NavigationManager.SpeedWarningListener speedWarningListener = new NavigationManager.SpeedWarningListener() {
#Override
public void onSpeedExceeded(String s, float v) {
//super.onSpeedExceeded(s, v);
//Log.v(Global.TAG, "onSpeedExceeded");
Global.SpeedLimitExceeded = true;
}
#Override
public void onSpeedExceededEnd(String s, float v) {
//super.onSpeedExceededEnd(s, v);
//Log.v(Global.TAG, "onSpeedExceededEnd");
Global.SpeedLimitExceeded = false;
}
};
EDITED ANSWER: This method needs to be amended to stop the speed warning:
private void startNavigationManager() {
NavigationManager.Error navError = NavigationManager.getInstance().startTracking();
// added by suggestion from stackoverflow
NavigationManager.getInstance().stopSpeedWarning();
if (navError != NavigationManager.Error.NONE) {
Log.d(Global.TAG, "NavigationManager: false");
//handle error navError.toString());
} else {
//Log.d(Global.TAG, "NavigationManager: true");
}
}
Please set speedWarningEnabled accordingly for NMANavigationManager
navigationManager:didUpdateSpeedingStatus:forCurrentSpeed:speedLimit: will be sent to the delegate when speeding is detected or when a correction is made.
Also refer http://developer.here.com/documentation/ios-premium/api_reference_jazzy/Classes/NMANavigationManager.html
I have a array of jokes
String jokes[]={"x","y","z","j"};
i am using two methods next and previous to go front and back.
public void nextJoke(View view) {
if (jokeNumber < jokes.length - 1) {
jokeNumber++;
tv1.setText(jokes[jokeNumber]);
}
}
public void prevJoke(View view) {
if (jokeNumber > 0) {
jokeNumber--;
tv1.setText(jokes[jokeNumber]);
}
I want to give this list a endless scroll functionality .
That means if the user next or previous - and list reaches the last three elements , it should again start from first element.
How can i achieve this functionality .
This is more of a programming question .
Any Help will be highly appreciated.
Using the remainder operator, you'll get the required behavior.
Replace:
jokeNumber++;
with:
jokeNumber = ++jokeNumber % jokes.length;
and do the same for jokeNumber--;
On next:
next(){
jokeNumber++;
if(jokeNumber>=jokes.length) { jokeNumber =0;}
}
on previous:
previous(){
jokeNumber--;
if(jokeNumber<=0) { jokeNumber =jokes.length;}
}
public void nextJoke(View view) {
if (jokeNumber < jokes.length ) {
jokeNumber = ++jokeNumber % jokes.length;
tv1.setText(jokes[jokeNumber]); } }
public void prevJoke(View view) {
if (jokeNumber > 0) {
jokeNumber = --jokeNumber % jokes.length;
tv1.setText(jokes[jokeNumber]);
}
I try to catch webview longclicks to show a context menu. (see code below)
When longclicking an image, I always get the image-URL as extra (for a not linked image with IMAGE_TYPE and for a linked image with SRC_IMAGE_ANCHOR_TYPE).
But how can I get the Link-URL (and not the image-URL) for an image with a hyperlink?
Best,
Sebastian
mywebview.setOnLongClickListener(new OnLongClickListener() {
public boolean onLongClick(View v) {
final WebView webview = (WebView) v;
final WebView.HitTestResult result = webview.getHitTestResult();
if (result.getType() == SRC_ANCHOR_TYPE) {
return true;
}
if (result.getType() == SRC_IMAGE_ANCHOR_TYPE) {
return true;
}
if (result.getType() == IMAGE_TYPE) {
return true;
}
return false;
}
});
None of solutions above worked for me on Android 4.2.2. So I looked into source code of default android web browser. I extracted solution to this exact problem - get link-URL from image link.
Source:
https://github.com/android/platform_packages_apps_browser/blob/master/src/com/android/browser/Controller.java
Extracted solution:
LongClick listener:
...
mWebview.setOnLongClickListener(new OnLongClickListener() {
#Override
public boolean onLongClick(View v) {
HitTestResult result = mWebview.getHitTestResult();
if (result.getType() == HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
Message msg = mHandler.obtainMessage();
mWebview.requestFocusNodeHref(msg);
}
}
});
...
Handler to get the URL:
private Handler mHandler = new Handler() {
#Override
public void handleMessage(Message msg) {
// Get link-URL.
String url = (String) msg.getData().get("url");
// Do something with it.
if (url != null) ...
}
};
I know this is an old issue, but I recently came across this issue. Based on Perry_ml answer, I used the following Kotlin code to resolve it:
webView.setOnLongClickListener {
val result = webView.hitTestResult
if (result.type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
val handler = Handler()
val message = handler.obtainMessage()
webView.requestFocusNodeHref(message)
val url = message.data.getString("url")
// Do something with url, return true as touch has been handled
true
} else {
false
}
}
I posted some information about it here.
I checked the source code of the WebView and it seems that the image uri is the only extra data you can get for SRC_IMAGE_ANCHOR_TYPE. But don't be mad here I have a quick and dirty workaround for you:
webview.setOnLongClickListener(new OnLongClickListener() {
#Override
public boolean onLongClick(View v) {
final WebView webview = (WebView) v;
final HitTestResult result = webview.getHitTestResult();
if(result.getType()==HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
webview.setWebViewClient(new WebViewClient(){
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// 2. and here we get the url (remember to remove the WebView client and return true so that the hyperlink will not be really triggered)
mUrl = url; // mUrl is a member variant of the activity
view.setWebViewClient(null);
return true;
}
});
// 1. the picture must be focused, so we simulate a DPAD enter event to trigger the hyperlink
KeyEvent event1 = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_CENTER);
webview.dispatchKeyEvent(event1);
KeyEvent event2 = new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_CENTER);
webview.dispatchKeyEvent(event2);
// 3. now you can do something with the anchor url (and then clear the mUrl for future usage)
String url = mUrl;
if (url!=null) {
Toast.makeText(webview.getContext(), url, Toast.LENGTH_SHORT).show();
}
mUrl = null;
}
return false;
}
});
I tried the code on a low-end Android 2.1 device and a high-end Android 4.0 device, both work like a charm.
Regards
Ziteng Chen
Ziteng Chen solution works up to Android 4.0 (API Level 15) but for some reason the KeyEvent down & up doesn't work in API LEVEL 16+ (Android 4.1+ JELLY_BEAN). It doesn't fire the WebView's loadUrl. So I had to replace the dispatchKeyEvent with dispatchTouchEvent. Here's the code:
...
MotionEvent touchDown = MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, touchX, touchY, 0);
webView.dispatchTouchEvent(touchDown);
touchDown.recycle();
MotionEvent touchUp = MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, touchX, touchY, 0);
webView.dispatchTouchEvent(touchUp);
touchUp.recycle();
String url = mUrl;
...
You'd probably have to wait (use an AsyncTask) to get the mUrl in slower devices where it's null immediately after firing the dispatchTouchEvents
Hope it helps.
Instead of calling this function myWebView.requestFocusNodeHref(msg);, try calling this function myWebView.requestImageRef(msg);
I'm trying to create a popup window that only appears the first time the application starts. I want it to display some text and have a button to close the popup. However, I'm having troubles getting the PopupWindow to even work. I've tried two different ways of doing it:
First I have an XML file which declares the layout of the popup called popup.xml (a textview inside a linearlayout) and I've added this in the OnCreate() of my main Activity:
PopupWindow pw = new PopupWindow(findViewById(R.id.popup), 100, 100, true);
pw.showAtLocation(findViewById(R.id.main), Gravity.CENTER, 0, 0);
Second I did the exact same with this code:
final LayoutInflater inflater = (LayoutInflater)this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
PopupWindow pw = new PopupWindow(inflater.inflate(R.layout.popup, (ViewGroup) findViewById(R.layout.main) ), 100, 100, true);
pw.showAtLocation(findViewById(R.id.main_page_layout), Gravity.CENTER, 0, 0);
The first throws a NullPointerException and the second throws a BadTokenException and says "Unable to add window -- token null is not valid"
What in the world am I doing wrong? I'm extremely novice so please bear with me.
To avoid BadTokenException, you need to defer showing the popup until after all the lifecycle methods are called (-> activity window is displayed):
findViewById(R.id.main_page_layout).post(new Runnable() {
public void run() {
pw.showAtLocation(findViewById(R.id.main_page_layout), Gravity.CENTER, 0, 0);
}
});
Solution provided by Kordzik will not work if you launch 2 activities consecutively:
startActivity(ActivityWithPopup.class);
startActivity(ActivityThatShouldBeAboveTheActivivtyWithPopup.class);
If you add popup that way in a case like this, you will get the same crash because ActivityWithPopup won't be attached to Window in this case.
More universal solusion is onAttachedToWindow and onDetachedFromWindow.
And also there is no need for postDelayed(Runnable, 100). Because this 100 millis does not guaranties anything
#Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
Log.d(TAG, "onAttachedToWindow");
showPopup();
}
#Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
Log.d(TAG, "onDetachedFromWindow");
popup.dismiss();
}
The accepted answer did not work for me. I still received BadTokenException. So I just called the Runnable from a Handler with delay as such:
new Handler().postDelayed(new Runnable() {
public void run() {
showPopup();
}
}, 100);
use class Context eg. MainActivity.this instead of getApplicationContext()
There are two scenarios when this exception could occur. One is mentioned by kordzik. Other scenario is mentioned here: http://blackriver.to/2012/08/android-annoying-exception-unable-to-add-window-is-your-activity-running/
Make sure you handle both of them
the solution is to set the spinner mode to dialog as below:
android:spinnerMode="dialog"
or
Spinner(Context context, int mode)
tnxs RamallahDroid
See This.
Depending on the use case, for types of pop-up to display a message, setting the pop-up type to TYPE_TOAST using setWindowLayoutType() avoids the issue, as this type of pop-up is not dependent on the underlying activity.
Edit: One of the side effects: no interaction in the popup window for API <= 18, as the touchable / focusable events would be removed by the system. ( http://www.jianshu.com/p/634cd056b90c )
I end up with using TYPE_PHONE (as the app happens to have the permission SYSTEM_ALERT_WINDOW, otherwise this won't work too).
You can check the rootview if it has the token. You can get the parent layout defined from your activity xml, mRootView
if (mRootView != null && mRootView.getWindowToken() != null) {
popupWindow.showAtLocation();
}
Check that findViewById returns something - you might be calling it too early, before the layout is built
Also you may want to post logcat output for the exceptions you're getting
You can also try to use this check:
public void showPopupProgress (){
new Handler().post(new Runnable() {
#Override
public void run() {
if (getWindow().getDecorView().getWindowVisibility() == View.GONE) {
showPopupProgress();
return;
}
popup.showAtLocation(.....);
}
});
}
If you show a PopupWindow in another PopupWindow, do not use the view in first POP, use the origin parent view.
pop.showAtLocation(parentView, ... );
I had the same problem (BadTokenException) with AlertDialog on dialog.show(). I was making an AlertDialog by following some example. In my case the reason of that problem was a string
dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_TOAST)
Everything became working after I removed it.
Maybe it's time for a newer solution. This methods checks 5 times every 50ms if the parent view for the PopupWindow has a token. I use it inside my customized PopupWindow.
private fun tryToShowTooltip(tooltipLayout: View) {
Flowable.fromCallable { parentView.windowToken != null }
.map { hasWindowToken ->
if (hasWindowToken) {
return#map hasWindowToken
}
throw RetryException()
}
.retryWhen { errors: Flowable<Throwable> ->
errors.zipWith(
Flowable.range(1, RETRY_COUNT),
BiFunction<Throwable, Int, Int> { error: Throwable, retryCount: Int ->
if (retryCount >= RETRY_COUNT) {
throw error
} else {
retryCount
}
})
.flatMap { retryCount: Int ->
Flowable.timer(retryCount * MIN_TIME_OUT_MS, TimeUnit.MILLISECONDS)
}
}
.onErrorReturn {
false
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ hasWindowToken ->
if (hasWindowToken && !isShowing) {
showAtLocation(tooltipLayout, Gravity.NO_GRAVITY, 100, 100)
}
}, { t: Throwable? ->
//error logging
})
}
with
companion object {
private const val RETRY_COUNT = 5
private const val MIN_TIME_OUT_MS = 50L
}
class RetryException : Throwable()
You can specify the y-offset to account for the status bar from the pw.showAtLocation method...