I have an image inside of my web view. The image is so big and I've enabled scrolls but the problem is that I want to set default view (first look on image before doing scroll or zoom ) to specific part of image. Any idea ?
WebView callUsView2= (WebView) rootView.findViewById(R.id.callUsView2);
WebSettings callUsView2Setting = callUsView2.getSettings();
callUsView2Setting.setBuiltInZoomControls(true);
callUsView2Setting.setDisplayZoomControls(true);
callUsView2.setVerticalScrollBarEnabled(true);
callUsView2.setHorizontalScrollBarEnabled(true);
callUsView2.loadUrl("file:///android_asset/map.png");
Result >
What I want >
ANSWER >>
callUsView2.setWebChromeClient(new WebChromeClient(){
#Override
public void onProgressChanged(WebView view, int progress) {
if ( view.getProgress()==100) {
// I save Y w/in Bundle so orientation changes [in addition to
// initial loads] will reposition to last location
jumpToY(1000 , 1800 );
}
}
private void jumpToY (final int xLocation , final int yLocation ) {
callUsView2.postDelayed( new Runnable () {
#Override
public void run() {
callUsView2.scrollTo(xLocation, yLocation);
}
}, 300);
}
} );
If the image is big and you want to show a specific part, you may want to zoom and scroll to a specific part.
What you can do is for example change the scale:
callUsView2.setInitialScale(some value which fits your needs);
and scroll if you wish to change the position:
callUsView2.scrollTo(x,y);
Related
I want to scroll up and blur the image above it.I want image to be static and blur only when we scrolling up.
Use https://github.com/kikoso/android-stackblur
_stackBlurManager = new StackBlurManager(getBitmapFromAsset(this, "android_platform_256.png"));
_stackBlurManager.process(progress*5);
_imageView.setImageBitmap(_stackBlurManager.returnBlurredImage() );
Then you have to find the position of the user. Every view can have a scrolling listener, just like that:
view.getViewTreeObserver().addOnScrollChangedListener(new OnScrollChangedListener() {
#Override
public void onScrollChanged() {
int scrollX = rootScrollView.getScrollX(); //for horizontalScrollView
int scrollY = rootScrollView.getScrollY(); //for verticalScrollView
int progress = // DO SOME MATH HERE TO GET THE PROGRESS FOR THE BLUR
}
});
Just mix the two things according to your situation.
I am rendering some very simple HTML (just some text and a small image) in a WebView off screen (not set as content view of an activity) so I can create a Bitmap from the content.
The way to know when the content is fully rendered I have based on this answer:
final AtomicBoolean rendered = new AtomicBoolean(false);
final WebView view = new WebView(this) {
#Override
public void invalidate() {
if (getProgress() == 100 && getContentHeight() > 0) {
if (! rendered.get()) {
rendered.set(true);
// Content should be fully rendered
}
}
super.invalidate();
}
};
// Load and lay out content
view.loadUrl(url);
view.setInitialScale(100);
view.layout(0, 0, 240, 420);
I've tested this successfully on 4.1.2, 4.2.2, 4.4.2 and 5.0. But with 4.0.3, it seems invalidate() is never called.
While trying all kinds of things I found out that showing a Toast and doing a delayed (1 sec.) call to invalidate() solves the problem:
#Override
public void onPageFinished(final WebView view, final String url) {
Toast.makeText(MainActivity.this, "Page loaded", Toast.LENGTH_SHORT).show();
handler.postDelayed(new Runnable() {
#Override
public void run() {
if (! rendered.get()) {
view.invalidate();
}
}
}, 1000);
}
Of course I don't consider this a valid solution, but while the delayed call to invalidate() somehow makes sense to me, I really wonder what side effect of the Toast does the trick here. Does it do some damage causing a redraw or something like that?
Sorry can not help you very much since I would like to make same operation of you (get screenshot from offline webview control) but in my case I'm not able to receive invalidate() event until the webview is offline. If I set as visible by inserting into the main window the invalidate() start to be generated but in case of offline (invisible) no invalidate() call is generated. The code you developed for get such result is only the snippet you posted here or there is some additional function to call for have the webview "active" in offline mode also?
About your problem what I can suggest is to override the other invalidate() definitions like:
public void invalidate(Rect dirty)
public void invalidate(int l, int t, int r, int b)
since in my tests I noted also these last was called.
Thank you
After a lot more testing on different (virtual) devices, this is the way that works reliable for me so far. Note that the "hack" for 4.0.3 mentioned in my original question still applies but is not included here.
Overriding invalidate() did not prove to be always reliable on all tested versions, so I am using PictureListener.onNewPicture(WebView view, Picture picture) even though it is deprecated.
In my activity:
final WebView view = new WebView(this);
// Zooming in can improve font quality
final float scale = 2.0;
// Unfortunately, there is no method view.getContentWidth()
final int contentWidth = 240;
view.setPictureListener(new PictureListener() {
#Override
public void onNewPicture(final WebView view, final Picture picture) {
if (view.getProgress() == 100 && view.getContentHeight() > 0) {
view.setPictureListener(null);
// Content is now fully rendered
final int width = Math.round(contentWidth * scale);
final int height = Math.round(view.getContentHeight() * scale);
final Bitmap bitmap = getBitmap(view, width, height);
// Display or print bitmap...
}
}
});
view.loadUrl(url);
view.setInitialScale(Math.round(scale * 100));
// Width and height must be at least 1
view.layout(0, 0, 1, 1);
And the getBitmap() method:
private Bitmap getBitmap(
final WebView view, final int width, final int height) {
final Bitmap bitmap = Bitmap.createBitmap(
width, height, Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(bitmap);
view.draw(canvas);
return bitmap;
}
This is the best I can offer. The app has not been tested extensively yet, but so far it never failed to correctly render a small document including a small GIF image.
am trying to figure out the max scroll position that the WebView can reach, i've tried the webView.pageDown(true) but the result is delayed ( i cant scroll it down, then up infront of the user, and this method doesn't work every time), i've tried also webView.getContentHeight() and the height isn't correct for Arabic content.
Please Advice
ok, i figured out the answer
you can get the real content height using
(int) Math.floor(webView.getContentHeight() * webView.getScale());
when you get the real height, then just override the scroll method in webview to listen to scroll event, if the scroll reach the real height, your webview is in the bottom of the scroll.
#Override
public void onScroll(int l, int t) {
int height = (int) Math.floor(webView.getContentHeight() * webView.getScale());
int webViewHeight = webView.getHeight();
int cutoff = height - webViewHeight - 10; // Don't be too strict on the cutoff point
if (t >= cutoff) {
setDisclaimerButtonEnabled(true);
}
}
The non-strictness is required, is because I found on the Samsung S5 the bottom most scroll value was only 1 pixel value away from the bottom most value!
Loading / Visible button only when webview reached / scrolled to bottom.
Create JavaScript class :
public class JavaScriptInterface {
#android.webkit.JavascriptInterface
public void didScrollToBottom() {
Log.d(TAG, "Scroll to Bottom");
myHandler.post(new Runnable() {
#Override
public void run() {
btnAccept.setVisibility(View.VISIBLE);
}
});
}
}
In onCreate() :
final JavaScriptInterface jsInterface = new JavaScriptInterface();
myWebView.addJavascriptInterface(jsInterface, "AndroidFunction");
The browsers in Android 2.3+ do a good job at maintaining the scrolled position of content on an orientation changed.
I'm trying to achieve the same thing for a WebView which I display within an activity but without success.
I've tested the manifest change (android:configChanges="orientation|keyboardHidden") to avoid activity recreation on a rotation however because of the different aspect ratios the scroll position does not get set to where I want. Furthermore this is not a solution for me as I need to have different layouts in portrait and landscape.
I've tried saving the WebView state and restoring it but this resuls in the content being displayed at the top again.
Furthermore attempting to scroll in onPageFinished using scrollTo doesn't work even though the height of the WebView is non-zero at this point.
Any ideas? Thanks in advance. Peter.
Partial Solution:
My colleague managed to get scrolling working via a javascript solution. For simple scrolling to the same vertical position, the WebView's 'Y' scroll position is saved in onSaveInstanceState state. The following is then added to onPageFinished:
public void onPageFinished(final WebView view, final String url) {
if (mScrollY > 0) {
final StringBuilder sb = new StringBuilder("javascript:window.scrollTo(0, ");
sb.append(mScrollY);
sb.append("/ window.devicePixelRatio);");
view.loadUrl(sb.toString());
}
super.onPageFinished(view, url);
}
You do get the slight flicker as the content jumps from the beginning to the new scroll position but it is barely noticeable. The next step is try a percentage based method (based on differences in height) and also investigate having the WebView save and restore its state.
To restore the current position of a WebView during orientation change I'm afraid you will have to do it manually.
I used this method:
Calculate actual percent of scroll in the WebView
Save it in the onRetainNonConfigurationInstance
Restore the position of the WebView when it's recreated
Because the width and height of the WebView is not the same in portrait and landscape mode, I use a percent to represent the user scroll position.
Step by step:
1) Calculate actual percent of scroll in the WebView
// Calculate the % of scroll progress in the actual web page content
private float calculateProgression(WebView content) {
float positionTopView = content.getTop();
float contentHeight = content.getContentHeight();
float currentScrollPosition = content.getScrollY();
float percentWebview = (currentScrollPosition - positionTopView) / contentHeight;
return percentWebview;
}
2) Save it in the onRetainNonConfigurationInstance
Save the progress just before the orientation change
#Override
public Object onRetainNonConfigurationInstance() {
OrientationChangeData objectToSave = new OrientationChangeData();
objectToSave.mProgress = calculateProgression(mWebView);
return objectToSave;
}
// Container class used to save data during the orientation change
private final static class OrientationChangeData {
public float mProgress;
}
3) Restore the position of the WebView when it's recreated
Get the progress from the orientation change data
private boolean mHasToRestoreState = false;
private float mProgressToRestore;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mWebView = (WebView) findViewById(R.id.WebView);
mWebView.setWebViewClient(new MyWebViewClient());
mWebView.loadUrl("http://stackoverflow.com/");
OrientationChangeData data = (OrientationChangeData) getLastNonConfigurationInstance();
if (data != null) {
mHasToRestoreState = true;
mProgressToRestore = data.mProgress;
}
}
To restore the current position you will have to wait the page to be reloaded (
this method can be problematic if your page takes a long time to load)
private class MyWebViewClient extends WebViewClient {
#Override
public void onPageFinished(WebView view, String url) {
if (mHasToRestoreState) {
mHasToRestoreState = false;
view.postDelayed(new Runnable() {
#Override
public void run() {
float webviewsize = mWebView.getContentHeight() - mWebView.getTop();
float positionInWV = webviewsize * mProgressToRestore;
int positionY = Math.round(mWebView.getTop() + positionInWV);
mWebView.scrollTo(0, positionY);
}
// Delay the scrollTo to make it work
}, 300);
}
super.onPageFinished(view, url);
}
}
During my test I encounter that you need to wait a little after the onPageFinished method is called to make the scroll working. 300ms should be ok. This delay make the display to flick (first display at scroll 0 then go to the correct position).
Maybe there is an other better way to do it but I'm not aware of.
Why you should consider this answer over accepted answer:
Accepted answer provides decent and simple way to save scroll position, however it is far from perfect. The problem with that approach is that sometimes during rotation you won't even see any of the elements you saw on the screen before rotation. Element that was at the top of the screen can now be at the bottom after rotation. Saving position via percent of scroll is not very accurate and on large documents this inaccuracy can add up.
So here is another method: it's way more complicated, but it almost guarantees that you'll see exactly the same element after rotation that you saw before rotation. In my opinion, this leads to a much better user experience, especially on a large documents.
======
First of all, we will track current scroll position via javascript. This will allow us to know exactly which element is currently at the top of the screen and how much is it scrolled.
First, ensure that javascript is enabled for your WebView:
webView.getSettings().setJavaScriptEnabled(true);
Next, we need to create java class that will accept information from within javascript:
public class WebScrollListener {
private String element;
private int margin;
#JavascriptInterface
public void onScrollPositionChange(String topElementCssSelector, int topElementTopMargin) {
Log.d("WebScrollListener", "Scroll position changed: " + topElementCssSelector + " " + topElementTopMargin);
element = topElementCssSelector;
margin = topElementTopMargin;
}
}
Then we add this class to WebView:
scrollListener = new WebScrollListener(); // save this in an instance variable
webView.addJavascriptInterface(scrollListener, "WebScrollListener");
Now we need to insert javascript code into html page. This script will send scroll data to java (if you are generation html, just append this script; otherwise, you might need to resort to calling document.write() via webView.loadUrl("javascript:document.write(" + script + ")");):
<script>
// We will find first visible element on the screen
// by probing document with the document.elementFromPoint function;
// we need to make sure that we dont just return
// body element or any element that is very large;
// best case scenario is if we get any element that
// doesn't contain other elements, but any small element is good enough;
var findSmallElementOnScreen = function() {
var SIZE_LIMIT = 1024;
var elem = undefined;
var offsetY = 0;
while (!elem) {
var e = document.elementFromPoint(100, offsetY);
if (e.getBoundingClientRect().height < SIZE_LIMIT) {
elem = e;
} else {
offsetY += 50;
}
}
return elem;
};
// Convert dom element to css selector for later use
var getCssSelector = function(el) {
if (!(el instanceof Element))
return;
var path = [];
while (el.nodeType === Node.ELEMENT_NODE) {
var selector = el.nodeName.toLowerCase();
if (el.id) {
selector += '#' + el.id;
path.unshift(selector);
break;
} else {
var sib = el, nth = 1;
while (sib = sib.previousElementSibling) {
if (sib.nodeName.toLowerCase() == selector)
nth++;
}
if (nth != 1)
selector += ':nth-of-type('+nth+')';
}
path.unshift(selector);
el = el.parentNode;
}
return path.join(' > ');
};
// Send topmost element and its top offset to java
var reportScrollPosition = function() {
var elem = findSmallElementOnScreen();
if (elem) {
var selector = getCssSelector(elem);
var offset = elem.getBoundingClientRect().top;
WebScrollListener.onScrollPositionChange(selector, offset);
}
}
// We will report scroll position every time when scroll position changes,
// but timer will ensure that this doesn't happen more often than needed
// (scroll event fires way too rapidly)
var previousTimeout = undefined;
window.addEventListener('scroll', function() {
clearTimeout(previousTimeout);
previousTimeout = setTimeout(reportScrollPosition, 200);
});
</script>
If you run your app at this point, you should already see messages in logcat telling you that the new scroll position is received.
Now we need to save webView state:
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
webView.saveState(outState);
outState.putString("scrollElement", scrollListener.element);
outState.putInt("scrollMargin", scrollListener.margin);
}
Then we read it in the onCreate (for Activity) or onCreateView (for fragment) method:
if (savedInstanceState != null) {
webView.restoreState(savedInstanceState);
initialScrollElement = savedInstanceState.getString("scrollElement");
initialScrollMargin = savedInstanceState.getInt("scrollMargin");
}
We also need to add WebViewClient to our webView and override onPageFinished method:
#Override
public void onPageFinished(final WebView view, String url) {
if (initialScrollElement != null) {
// It's very hard to detect when web page actually finished loading;
// At the time onPageFinished is called, page might still not be parsed
// Any javascript inside <script>...</script> tags might still not be executed;
// Dom tree might still be incomplete;
// So we are gonna use a combination of delays and checks to ensure
// that scroll position is only restored after page has actually finished loading
webView.postDelayed(new Runnable() {
#Override
public void run() {
String javascript = "(function ( selectorToRestore, positionToRestore ) {\n" +
" var previousTop = 0;\n" +
" var check = function() {\n" +
" var elem = document.querySelector(selectorToRestore);\n" +
" if (!elem) {\n" +
" setTimeout(check, 100);\n" +
" return;\n" +
" }\n" +
" var currentTop = elem.getBoundingClientRect().top;\n" +
" if (currentTop !== previousTop) {\n" +
" previousTop = currentTop;\n" +
" setTimeout(check, 100);\n" +
" } else {\n" +
" window.scrollBy(0, currentTop - positionToRestore);\n" +
" }\n" +
" };\n" +
" check();\n" +
"}('" + initialScrollElement + "', " + initialScrollMargin + "));";
webView.loadUrl("javascript:" + javascript);
initialScrollElement = null;
}
}, 300);
}
}
This is it. After screen rotation, element that was at the top of your screen should now remain there.
onPageFinished may not be called because you are not reloading the page, you are just changing the orientation, not sure if this causes a reload or not.
Try using scrollTo in the onConfigurationChanged method of your activity.
The aspect change will most likely always cause the current location in your WebView to not be the right location to scroll to afterwards. You could be sneaky and determine the top most visible element in the WebView and after an orientation change implant an anchor at that point in the source and redirect the user to it...
to calculate the right percentage of WebView, it is important to count mWebView.getScale() also. Actually, return value of getScrollY() is respectively with mWebView.getContentHeight()*mWebView.getScale().
In the partial solution described in the original post, you can improve the solution if change the following code
private float calculateProgression(WebView content) {
float contentHeight = content.getContentHeight();
float currentScrollPosition = content.getScrollY();
float percentWebview = currentScrollPosition/ contentHeight;
return percentWebview;
}
due to the top position of the component, when you are using content.getTop () is not necessary; So what it does is add to the actual position of the scroll Y position where the component is located with respect to it parent and runs down the content. I hope my explanation is clear.
The way that we have managed to achieve this to have a local reference to the WebView within our Fragments.
/**
* WebView reference
*/
private WebView webView;
Then setRetainInstance to true in onCreate
#Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
// Argument loading settings
}
Then when onCreateView is called, return the existing local instance of the WebView, otherwise instantiate a new copy setting up the content and other settings. The key step he is when reattaching the WebView to remove the parent which you can see in the else clause below.
#Override
public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
final Bundle savedInstanceState) {
final View view;
if (webView == null) {
view = inflater.inflate(R.layout.webview_dialog, container);
webView = (WebView) view.findViewById(R.id.dialog_web_view);
// Setup webview and content etc...
} else {
((ViewGroup) webView.getParent()).removeView(webView);
view = webView;
}
return view;
}
I used the partial solution described in the original post, but instead put the javascript snippet inside onPageStarted(), instead of onPageFinished(). Seems to work for me with no jumping.
My use case is slightly different: I'm trying to keep the horizontal position after the user refreshes the page.
But I've been able to use your solution and it works perfectly for me :)
I have a WebView and 2 urls to open it it. What I want to do is, when i set a zoom level for 1st url, and then i go to 2nd url, it should also have the same zoom level. Right now, the zoom level resets for both.
Thanks,
Farha
this will be applicable in this scenario i believe
mWebView.setInitialScale(ZOOM_LEVEL);
where ZOOM_LEVEL for example can be
25 for 25%
150 for 150%
use the webSettings class
webview.getSettings().setDefaultZoom(WebSettings.ZoomDensity.FAR);
Notice that although webSettings is available since API Level 1, WebSettings.ZoomDensity is available since API Level 7. Works well for all device resolutions.
Also, in order to enable zoom on the webView, add the following code:
webView.getSettings().setBuiltInZoomControls(true);
setDefaultZoom is deprecated.
webview.getSettings().setDefaultZoom(WebSettings.ZoomDensity.FAR);
So instead of that you can use like below,
webview.setInitialScale(1);
webview.getSettings().setLoadWithOverviewMode(true);
webview.getSettings().setUseWideViewPort(true);
This help you to remove
setDefaultZoom(WebSettings.ZoomDensity.FAR).
Before you leave the first page, use
int scale = 100 * webView.getScale();
Then after you load the second page, use
webView.setInitialScale( scale );
#John gave the right idea, but one command is enough, since you can get and set before the page shows:
private class MyWebViewClient extends WebViewClient {
public void onPageFinished(WebView view, String url) {
view.setInitialScale((int)(100*view.getScale()));
}
}
then just set this as your WebView's client:
webview.setWebViewClient(new MyWebViewClient());
You can set the zoom to 0.1f
mWebView.zoomBy(0.1f);
try this thing
int default_zoom_level=100;
Point Scroll=new Point(0,0);
webview1.setInitialScale(default_zoom_level);
webview1.loadData("url");
After doing zoomIn/ZoomOut or Scrolling.Since Scale may be get to change so calculate scale level and scrolling along X and Y Axis.
int current_scale_level=(int)webview1.getScale()*100;
Scroll.x=webview1.getScrollX();
Scroll.y=webview1.getScrollY();
then before loading of next webview do this
webview2.setInitialScale(current_scale_level);
webview2.loadData("url");
webview2.scrollTo(Scroll.x,Scroll.y);
I found this blog helpful:
It will help you set initial zoom level and on seek-change content of webview resizes
// Set the initial progress of seek bar
mSeekBar.setProgress(mWebView.getSettings().getTextZoom()/25);
// Set a change listener for seek bar
mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
#Override
public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
/*
public abstract void setTextZoom (int textZoom)
Sets the text zoom of the page in percent. The default is 100.
Parameters
textZoom : the text zoom in percent
*/
// Zoom the web page text
// We will allow text zooming 25% to 300%
mWebView.getSettings().setTextZoom(i*25);
}
#Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
#Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
http://android--code.blogspot.in/2016/01/android-how-to-change-webview-text-size.html
Zooming of WebView can be controlled programatically by zoomIn() and zoomOut() methods