Reputation: 3274
I'm creating an app that is getting a dynamic HTML website from an API. My main challenge is that I need to auto-adjust the height
of the WebView
in real-time because the property android:layout_height="wrap_content"
is not working as expected and doesn't auto-adjust its height
is always 0dp
, and it's never shown.
The API that I consume can return a simple text like this:
<p>Lorem ipsum dolor sit amet consectetur adipiscing elit, malesuada per consequat conubia accumsan vulputate tincidunt, porttitor cum cras pretium diam inceptos. Augue fringilla id laoreet metus quisque eu accumsan ultrices fusce, vel hendrerit phasellus mollis arcu consequat risus suscipit, nostra orci lobortis at quis sed integer cubilia. Quisque turpis congue euismod class tristique magna at eros aenean quam, cum facilisis malesuada per auctor cubilia leo ultrices pharetra, praesent habitant ut nec feugiat velit pulvinar libero tellus.</p>
Or complex HTMLs with images, tables, minor formatting, etc.
From my side, I have tried to set the WebView
height as wrap_content
(as explained before) and as a workaround, and at the same time, I hid it before loading, reloaded it after it was done, and displayed it again after the content was fully loaded, but nothing worked properly. The height
was either 0dp
or a small number like 32dp
, pretty much meaningless and invisible since almost every HTML is long.
This is my current code with some potential examples to test:
XML:
<?xml version="1.0" encoding="UTF-8" ?>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<LinearLayout
android:id="@+id/llOverview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/min_val"
android:orientation="vertical">
<ProgressBar
android:id="@+id/indeterminateBar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:indeterminate="true"
android:max="100"
android:layout_height="wrap_content" />
<TextView
android:layout_marginTop="@dimen/min_half_val"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="@dimen/text_size"
android:id="@+id/lblDescription" />
<android.webkit.WebView
android:id="@+id/webViewExtra"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
</ScrollView>
C#:
var lblDescription = FindViewById<TextView>(Resource.Id.lblDescription);
lblDescription.Text = @"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Potenti nullam ac tortor vitae purus faucibus ornare. Tellus elementum sagittis vitae et leo duis. Condimentum mattis pellentesque id nibh tortor id aliquet lectus proin. Elementum curabitur vitae nunc sed velit dignissim sodales ut. Vitae aliquet nec ullamcorper sit amet risus nullam eget. Quis imperdiet massa tincidunt nunc pulvinar sapien et ligula. Quam adipiscing vitae proin sagittis. Bibendum at varius vel pharetra vel turpis nunc. Bibendum at varius vel pharetra vel. Id diam vel quam elementum. Magna etiam tempor orci eu lobortis elementum nibh tellus. Ligula ullamcorper malesuada proin libero nunc. Gravida quis blandit turpis cursus. Ut pharetra sit amet aliquam id diam maecenas. Nisl rhoncus mattis rhoncus urna neque. Tempus egestas sed sed risus pretium quam vulputate dignissim. Et netus et malesuada fames ac. Malesuada fames ac turpis egestas maecenas pharetra convallis.";
var webView = FindViewById<Android.Webkit.WebView>(Resource.Id.webViewExtra);
webView.Visibility = ViewStates.Gone;
webView.Settings.VerticalScrollBarEnabled = true;
webView.Settings.JavaScriptEnabled = true;
var html = "<p>Lorem ipsum dolor sit amet consectetur adipiscing elit, malesuada per consequat conubia accumsan vulputate tincidunt, porttitor cum cras pretium diam inceptos. Augue fringilla id laoreet metus quisque eu accumsan ultrices fusce, vel hendrerit phasellus mollis arcu consequat risus suscipit, nostra orci lobortis at quis sed integer cubilia. Quisque turpis congue euismod class tristique magna at eros aenean quam, cum facilisis malesuada per auctor cubilia leo ultrices pharetra, praesent habitant ut nec feugiat velit pulvinar libero tellus.</p><p>Lorem ipsum dolor sit amet consectetur adipiscing elit, malesuada per consequat conubia accumsan vulputate tincidunt, porttitor cum cras pretium diam inceptos. Augue fringilla id laoreet metus quisque eu accumsan ultrices fusce, vel hendrerit phasellus mollis arcu consequat risus suscipit, nostra orci lobortis at quis sed integer cubilia. Quisque turpis congue euismod class tristique magna at eros aenean quam, cum facilisis malesuada per auctor cubilia leo ultrices pharetra, praesent habitant ut nec feugiat velit pulvinar libero tellus.</p><p>Lorem ipsum dolor sit amet consectetur adipiscing elit, malesuada per consequat conubia accumsan vulputate tincidunt, porttitor cum cras pretium diam inceptos. Augue fringilla id laoreet metus quisque eu accumsan ultrices fusce, vel hendrerit phasellus mollis arcu consequat risus suscipit, nostra orci lobortis at quis sed integer cubilia. Quisque turpis congue euismod class tristique magna at eros aenean quam, cum facilisis malesuada per auctor cubilia leo ultrices pharetra, praesent habitant ut nec feugiat velit pulvinar libero tellus.</p><p>Lorem ipsum dolor sit amet consectetur adipiscing elit, malesuada per consequat conubia accumsan vulputate tincidunt, porttitor cum cras pretium diam inceptos. Augue fringilla id laoreet metus quisque eu accumsan ultrices fusce, vel hendrerit phasellus mollis arcu consequat risus suscipit, nostra orci lobortis at quis sed integer cubilia. Quisque turpis congue euismod class tristique magna at eros aenean quam, cum facilisis malesuada per auctor cubilia leo ultrices pharetra, praesent habitant ut nec feugiat velit pulvinar libero tellus.</p><p>Lorem ipsum dolor sit amet consectetur adipiscing elit, malesuada per consequat conubia accumsan vulputate tincidunt, porttitor cum cras pretium diam inceptos. Augue fringilla id laoreet metus quisque eu accumsan ultrices fusce, vel hendrerit phasellus mollis arcu consequat risus suscipit, nostra orci lobortis at quis sed integer cubilia. Quisque turpis congue euismod class tristique magna at eros aenean quam, cum facilisis malesuada per auctor cubilia leo ultrices pharetra, praesent habitant ut nec feugiat velit pulvinar libero tellus.</p><p>Lorem ipsum dolor sit amet consectetur adipiscing elit, malesuada per consequat conubia accumsan vulputate tincidunt, porttitor cum cras pretium diam inceptos. Augue fringilla id laoreet metus quisque eu accumsan ultrices fusce, vel hendrerit phasellus mollis arcu consequat risus suscipit, nostra orci lobortis at quis sed integer cubilia. Quisque turpis congue euismod class tristique magna at eros aenean quam, cum facilisis malesuada per auctor cubilia leo ultrices pharetra, praesent habitant ut nec feugiat velit pulvinar libero tellus.</p><p>Lorem ipsum dolor sit amet consectetur adipiscing elit, malesuada per consequat conubia accumsan vulputate tincidunt, porttitor cum cras pretium diam inceptos. Augue fringilla id laoreet metus quisque eu accumsan ultrices fusce, vel hendrerit phasellus mollis arcu consequat risus suscipit, nostra orci lobortis at quis sed integer cubilia. Quisque turpis congue euismod class tristique magna at eros aenean quam, cum facilisis malesuada per auctor cubilia leo ultrices pharetra, praesent habitant ut nec feugiat velit pulvinar libero tellus.</p><p>Lorem ipsum dolor sit amet consectetur adipiscing elit, malesuada per consequat conubia accumsan vulputate tincidunt, porttitor cum cras pretium diam inceptos. Augue fringilla id laoreet metus quisque eu accumsan ultrices fusce, vel hendrerit phasellus mollis arcu consequat risus suscipit, nostra orci lobortis at quis sed integer cubilia. Quisque turpis congue euismod class tristique magna at eros aenean quam, cum facilisis malesuada per auctor cubilia leo ultrices pharetra, praesent habitant ut nec feugiat velit pulvinar libero tellus.</p>";
if (!string.IsNullOrEmpty(html))
{
html = $@"<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<meta name='viewport' content='width=device-width, initial-scale=1'>
</head>
<body style='margin: 0px; background: #fafafa'>{html}</body>
</html>";
webView.SetWebViewClient(new JSOverviewHelper());
webView.LoadData(html, "text/html", "UTF-8");
webView.Reload();
}
public class JSOverviewHelper : WebViewClient
{
public override void OnPageFinished(WebView webView, string url)
{
base.OnPageFinished(webView, url);
webView.Visibility = Android.Views.ViewStates.Visible;
}
}
In addition to that, I already tried to calculate the height
using a JS function, and didn't work as expected. The returned height
tended to be the current height
of the WebView + 1px
. However, if I ran the same function using chrome://inspect
then it returned the correct value that was odd.
This is the function to test this experiment using the OnPageFinished
event in the WebView
:
public override void OnPageFinished(WebView webView, string url)
{
base.OnPageFinished(webView, url);
webView.Visibility = ViewStates.Visible;
Thread.Sleep(1000);
webView.EvaluateJavascript(@"(function() {
var body = document.body,
html = document.documentElement;
return Math.max(body.scrollHeight, body.offsetHeight,
html.clientHeight, html.scrollHeight, html.offsetHeight);
})();", new WebViewValueCallback(webView));
}
public class WebViewValueCallback : Object, IValueCallback
{
private readonly WebView webView;
public WebViewValueCallback(WebView webView)
{
this.webView = webView;
}
public void OnReceiveValue(Object value)
{
var result = Convert.ToString(value);
ViewGroup.LayoutParams vc = webView.LayoutParameters;
vc.Height = int.Parse(Convert.ToString(value));
webView.LayoutParameters = vc;
}
}
Additionally, I tried using the OnPageCommitVisible
(it was triggered before OnPageFinished
) and OnProgressChanged
using a custom class based on WebChromeClient
as suggested here, and when it reached 100% the JS function still returned a different value than the expected one, it always returned either 0 or the current height of WebView + 1px
as before.
What else did I try? I set a predefined height in the WebView
to test the app, created a static page, and loaded the data with JS, but still, the returned height was incorrect.
Any idea how I can adjust the height to the latest one after it's loaded? Thanks.
P.S.:
TextView
is mandatory because it shows an extract before the website that can be optional. Therefore, it cannot be removed.Upvotes: 2
Views: 1301
Reputation: 3274
After several attempts, I was able to create a "viable" solution for my case:
WebView
and surround it with a LinearLayout
with the following configuration:<LinearLayout
android:id="@+id/llWebViewExtras"
android:layout_width="match_parent"
android:layout_height="0dp"
android:orientation="vertical">
<android.webkit.WebView
android:id="@+id/webViewExtra"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
LinearLayout
.public static LinearLayout LlWebViewExtras { get; set; }
private WebView webView;
protected override void OnCreate(Bundle savedInstanceState)
{
LlWebViewExtras = FindViewById<LinearLayout>(Resource.Id.llWebViewExtras);
webView = FindViewById<WebView>(Resource.Id.webViewExtra);
webView.Settings.JavaScriptEnabled = true;
webView.VerticalScrollBarEnabled = true;
string script = $"javascript:myFunction();";
webView.AddJavascriptInterface(new CallJSInterface(), "CSharp");
webView.SetWebViewClient(new JSHelper(script));
webView.LoadUrl("file:///android_asset/website/mywebsite.html");
}
public class JSHelper : WebViewClient
{
private readonly string script;
public JSHelper(string script)
{
this.script = script;
}
public override void OnPageFinished(WebView webView, string url)
{
base.OnPageFinished(webView, url);
webView.Visibility = ViewStates.Visible;
webView.EvaluateJavascript(script, null);
}
}
LinearLayout
height, and change it to dps in the Main Thread.public class CallJSInterface : Java.Lang.Object
{
[Export]
[JavascriptInterface]
public void CurrentHeight(int height)
{
MainThread.BeginInvokeOnMainThread(() =>
{
var llWebViewExtras = MyActivity.LlWebViewExtras;
using ViewGroup.LayoutParams vc = llWebViewExtras.LayoutParameters;
vc.Height = ConvertPixelsToDp(height);
llWebViewExtras.LayoutParameters = vc;
});
}
public static int ConvertPixelsToDp(float px)
{
return (int) (px * Resources.System.DisplayMetrics.Density);
}
}
function myFunction() {
CSharp.CurrentHeight(latestHeight());
}
function getLatestHeight() {
let cHeight = getHeight();
if (cHeight === 0) {
//content is the element that contains the data. I used a Div
let content = document.getElementById("content");
let contentTmp = document.getElementById("content");
contentTmp = contentTmp.cloneNode(true);
contentTmp.id = "tmpcontent";
content.style.visibility = "hidden";
contentTmp.offsetHeight + 0;
document.body.appendChild(contentTmp);
document.body.removeChild(document.getElementById("content"));
content.style.visibility = "visible";
return getHeight();
}
return cHeight;
}
function getHeight() {
let body = document.body,
html = document.documentElement;
return Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight, body.getBoundingClientRect().height);
}
This solution I proposed is quite alright, but it is not bulletproof. It tends to fail over time and returns 0 as its height
. I even configured to reload the WebView
in these cases and check several times its height every couple of milliseconds without any successful result.
I added some extra validation based on this answer: https://stackoverflow.com/a/27729544/1928691
It doubles the height but at least it doesn't return the 0 in its height.
Also, I created this additional workaround for these cases:
webView.SetOnTouchListener(new OnTouchListener(webView));
public class OnTouchListener : Java.Lang.Object, View.IOnTouchListener
{
private readonly int maxTop;
private readonly int maxBottom;
private readonly WebView webView;
private float downX, downY;
private int totalY;
private int scrollByX, scrollByY;
public OnTouchListener(WebView webView)
{
var mainDisplayInfo = DeviceDisplay.MainDisplayInfo;
// set scroll limits
maxTop = 0;
maxBottom = (int)mainDisplayInfo.Height;
this.webView = webView;
}
public bool OnTouch(View v, MotionEvent e)
{
float currentX, currentY;
if (e.Action == MotionEventActions.Down)
{
downX = e.GetX();
downY = e.GetY();
}
else if (e.Action == MotionEventActions.Move)
{
currentX = e.GetX();
currentY = e.GetY();
scrollByX = (int)(downX - currentX);
scrollByY = (int)(downY - currentY);
// scrolling to top of image (pic moving to the bottom)
if (currentY > downY)
{
if (totalY == maxTop)
{
scrollByY = 0;
}
if (totalY > maxTop)
{
totalY += scrollByY;
}
if (totalY < maxTop)
{
scrollByY = maxTop - (totalY - scrollByY);
totalY = maxTop;
}
}
// scrolling to bottom of image (pic moving to the top)
if (currentY < downY)
{
if (totalY == maxBottom)
{
scrollByY = 0;
}
if (totalY < maxBottom)
{
totalY += scrollByY;
}
if (totalY > maxBottom)
{
scrollByY = maxBottom - (totalY - scrollByY);
totalY = maxBottom;
}
}
webView.ScrollBy(scrollByX, scrollByY);
downX = currentX;
downY = currentY;
}
return true;
}
}
However, if you know how to fix it and get to the real height, I'll be extremely glad.
P.S.:
I even opened a ticket in the Chromium project, and they accepted the last part as a bug:
https://bugs.chromium.org/p/chromium/issues/detail?id=1232745
Upvotes: 4