Android how to get location of a html element for using in positioning a android view over it - android-webview

In Android layout it has a RelativeLayout which contains a LinearLayout (ignored some other views in the layout).
< ...> //other layout
<NestedScrollView
android:id="#+id/scroll_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="none"
android:fillViewport="false">
<RelativeLayout
android:id="#+id/view_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="0dp">
<LinearLayout
android:id="#+id/list_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
/>
</RelativeLayout>
</NestedScrollView>
<...>//other layout
the list_container could have a few views, one is webView.
What want to do is in the html loaded in the webview, there is a div element, so the android side could locate this div and float a view on top of that div element (the <div> is treat as a location marker).
making it simpler, assuming there is only one webview in the list_container, so the origin of the webview view is same as the relativeLayout's left/top.
and there is some floating View that is child of the RelativeLayout (sibling of the list_container), so the idea is set the floatingView's left/top margin to place the floatingView over of the <div> element in the html.
here use the webview.evaluateJavascript to run a javascript, and get the element's location back through the callback(got from js's document.getElementById(elm.id).getBoundingClientRect(),
after that, just apply the left/top to the floating view's margin.
val strJs = "(function() {\n" +
"var elementId = 'element_div_id';\n" +
"try {\n" +
" var elm = document.getElementById(elementId);\n" +
" var rect = document.getElementById(elm.id).getBoundingClientRect();\n" +
" return {\n" +
" 'elementId': elm.id,\n" +
" 'left': Math.floor(rect.left),\n" +
" 'top': Math.floor(rect.top)\n" +
" };\n" +
"} catch (e) {}\n" +
"return '';})();"
webview.evaluateJavascript(strJs) { data ->
if (!data.isNullOrBlank() && data.isNotEmpty()) {
try {
val obj: JSONObject = JSONObject(data)
val floatViewId = obj.optString("elementId")
val left = obj.optInt("left")
val top = obj.optInt("top")
val theFloatingView = lookupFloatingViewById(floatViewId)
var lp = (theFloatingView.layoutParams)
if (lp != null) {
if (lp.leftMargin != left || lp.topMargin != top) {
lp.setMargins(left, top, 0, 0)
theFloatingView.layoutParams = lp
}
}
} catch (ex: Throwable) {
}
}
}
But the result is that the floating view is not placed at the returned html <div> element's left/top, it is off (on different device the off could be bigger or small) over different part of the html content.
Isn't it the webview and the html hosted in the webview both should have same origin (which actually same as the relativeLayout's lefty/top)? Why can't we use the left/top got from html dom's getBoundingClientRect() directly for the view's margin (for positioning the view)?

found a answer which solved similar problem.
seems the Android view are on different coordinate than the html window. (could someone confirm or anyone has different opinion?)
the location return from getBoundingClientRect() needs to remapped to android's coordinate.
final int px = (int) (x * ctx.getResources().getDisplayMetrics().density);
final int py = (int) (y * ctx.getResources().getDisplayMetrics().density);

Related

Androidplot - creating chart without XML

I need to create some PieCharts on my Activity, and quantity of charts varies.
I try to create PieChart dynamically from code, and it's doesn't work. My Layout correct - Button added successfully. Can you help me? Some code:
Layout:
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:context="example.bros.nik.tabs1.MealsActivity"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/l_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
android:paddingBottom="#dimen/activity_vertical_margin"
android:orientation="vertical" >
</LinearLayout>
</ScrollView>
Java:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_meals);
LinearLayout l_layout = (LinearLayout) findViewById(R.id.l_layout);
meals_params = ResultsActivity.get_meals_params();
LinearLayout.LayoutParams lp_view = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT, 1.0f);
PieChart pie = new PieChart(this, "Chart#1");
Segment s1 = new Segment("Углеводы", meals_params[0].get_carbohydrates_percent());
Segment s2 = new Segment("Жиры", meals_params[0].get_fats_percent());
Segment s3 = new Segment("Белки", meals_params[0].get_proteins_percent());
EmbossMaskFilter emf = new EmbossMaskFilter(new float[]{1, 1, 1}, 0.4f, 10, 8.2f);
SegmentFormatter sf1 = new SegmentFormatter();
sf1.configure(getApplicationContext(), R.xml.pie_segment_formatter1);
sf1.getFillPaint().setMaskFilter(emf);
SegmentFormatter sf2 = new SegmentFormatter();
sf2.configure(getApplicationContext(), R.xml.pie_segment_formatter2);
sf2.getFillPaint().setMaskFilter(emf);
SegmentFormatter sf3 = new SegmentFormatter();
sf3.configure(getApplicationContext(), R.xml.pie_segment_formatter3);
sf3.getFillPaint().setMaskFilter(emf);
pie.addSeries(s1, sf1);
pie.addSeries(s2, sf2);
pie.addSeries(s3, sf3);
pie.getRenderer(PieRenderer.class).setDonutSize(0.1f,PieRenderer.DonutMode.PERCENT);
pie.redraw();
pie.getBorderPaint().setColor(Color.TRANSPARENT);
pie.getBackgroundPaint().setColor(Color.TRANSPARENT);
l_layout.addView(pie, lp_view);
I try this:
pie.setLayoutParams(lp_view);
l_layout.addView(pie);
and it's doesn't help me.
And if I add Button to Layout:
pie.setLayoutParams(lp_view);
l_layout.addView(pie);
Button but = new Button(this);
but.setText("bla");
l_layout.addView(but, lp_view);
then I see this Button (look at screenshot).
Added button
EDIT:
In Android docs written, that in constructor
ViewGroup.LayoutParams (int width, int height)
attributes may be the absolute sizes of view, or may be constants - WRAP_CONTENT (int value -2), MATCH_PARENT (int value -1). Probably, PieChart doesn't recognize these constants, and get it as absolute size with negative values.
I found solution:
private int[] getDisplayParams() {
int[] display_params = new int[2];
Display display = getWindowManager().getDefaultDisplay();
if (Build.VERSION.SDK_INT >= 13) {
Point size = new Point();
display.getSize(size);
display_params[0] = size.x;
display_params[1] = size.y;
}
else {
display_params[0] = display.getWidth();
display_params[1] = display.getHeight();
}
return display_params;
}
and use size of screen for determining size of chart:
int[] display_params = getDisplayParams();
Double pie_size = display_params[0]*0.8;
int pie_size_int = pie_size.intValue();
ViewGroup.LayoutParams lp_view = new LinearLayout.LayoutParams(pie_size_int, pie_size_int);
Looks like you are not providing LayoutParams for pie. Try doing this before adding pie to l_layout:
pie.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.FILL_PARENT,
LinearLayout.LayoutParams.FILL_PARENT));
EDIT:
I went back and noticed that the layout is enclosed in a ScrollView but the Scrollview is set to wrap content and the content is set to match parent so it seems like it might be a chicken and egg problem as described here. I believe buttons, etc. have default non-zero values for these whereas plots do not, which might explain why the button works.
As a sanity check, what happens if you specify an absolute size for your plot like this:
LinearLayout.LayoutParams lp_view = new LinearLayout.LayoutParams(100, 100);
You can also try:
plot.setMinimumHeight(100);
plot.setMinimumWidth(100);
Problem resolved by Nick.
You can also try:
plot.setMinimumHeight(100);
plot.setMinimumWidth(100);

Submitting parent window form from child window

I am displaying a popup (child windows) with some choices. Once user submits them in child windows, i am passing those values to parent window and submitting the form. the values are getting submitted to backend without any issues. however the parent window is not getting refreshed upon submitting the form.
function commonPopup(popup, width, height, e, top, left) {
myWindow=window.open('about:blank', popup, 'directories=0,scrollbars=yes, resizable=yes, location=0, menubar=0, status=0, toolbar=0, width=' + width + ', height=' + height + ', top=' + top + ', left=' + left);
myWindow.document.write('<HEAD>');
myWindow.document.write(' <TITLE>Waiting for results</TITLE>');
myWindow.document.write('</HEAD>');
}
function post_lock_unlock_value(reason,comment) {
document.getElementById('lockReason').value = reason;
document.getElementById('lockComment').value = comment;
document.getElementById('triggerActionId').value='';
document.getElementById('admRepairLock').submit();
}
<s:form name="lock" id="lock" action="my.action">
<s:submit value="Lock Repair Profile" id="lockIndicator" name="lockIndicator"
onclick="commonPopup('lockProfile',450, 200, event,'lockRepairProfile');" cssStyle="width:200px" theme="simple"/>
</s:form>
in child page
function postback() {
window.opener.post_lock_unlock_value("data","data");
self.close();
}
Before closing child window refresh parent like this (add this before your self.close()):
if ( window.opener && !window.opener.closed ) {
window.opener.location.reload();
window.opener.focus();
}
try this:
var newPartyId = $("#desc_textarea").val();
window.opener.$("#desc_textarea").val(newPartyId);

ListView with ViewStub in Item Template - expanded controls not showing for last item

I have the following application layout:
Activity with a LinearLayout hosting a Fragment, which hosts a ListView in a LinearLayout. I've templated (not sure it's the right term - I'm coming from WPF) how the ListView is displayed. There's a 3 line part that is shown at all times, plus a ViewStub which is expanded when the item is clicked (and only one item is to be expanded at all times).
Upon the first click on each ListView item, the stub is inflated (works for all items), and then the details and myButton controls are configured. This works for all ListView items - however, for the last of them, details and myButton never show.
On top of that, if another stub is expanded, the last ListView item becomes invisible - instead of it moving down to make space for the item that is currently expanded.
So, if I click on ListView item on position myListView.Items.Count -1, I don't see anything of the expansion. If I click on any other ListView item, the last ListView item disappears.
Here's the fragment layout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:minWidth="25px"
android:minHeight="25px">
<TextView
android:text="#string/active_calls"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="#+id/Calls_Header" />
<ListView
android:minWidth="25px"
android:minHeight="25px"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="#+id/ActiveCallsList" />
</LinearLayout>
And the layout for each ListView item
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TableLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="#+id/tableLayout1"
android:shrinkColumns="*"
android:stretchColumns="*">
<TableRow
android:id="#+id/tableRow1">
<TextView
android:text="Caller Name"
android:textAppearance="?android:attr/textAppearanceLarge"
android:id="#+id/CallerName"
android:layout_column="0"
android:layout_span="2" />
</TableRow>
<TableRow
android:id="#+id/tableRow2">
<TextView
android:text="+41 12 345 6789"
android:textAppearance="?android:attr/textAppearanceSmall"
android:id="#+id/CallerNumber"
android:layout_column="0" />
<TextView
android:text="00:01:25"
android:textAppearance="?android:attr/textAppearanceSmall"
android:id="#+id/CallDuration"
android:layout_column="1" />
</TableRow>
<TableRow
android:id="#+id/tableRow3">
<TextView
android:text="Ringing"
android:textAppearance="?android:attr/textAppearanceSmall"
android:id="#+id/CallStatus"
android:layout_column="0" />
</TableRow>
</TableLayout>
<ViewStub
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="#+id/viewStub1"
android:inflatedId="#+id/CallDetails"
android:layout="#layout/CallDetails" />
</LinearLayout>
and the part to be expanded
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:text="Text"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="#+id/CallDetailsText" />
<Button
android:text="#string/endCall"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="#+id/EndCallButton" />
</LinearLayout>
And the ItemClick handler associated to my own implementation of an ArrayAdapter deriving from BaseAdapter.
void calls_ItemClick(object sender, AdapterView.ItemClickEventArgs e)
{
if (inflatedView != null)
inflatedView.Visibility = ViewStates.Gone; // make sure only one item is inflated at all times
var obj = e.Parent.GetItemAtPosition(e.Position);
var listView = sender as ListView;
Model.Call t = Model.Calls.MyCalls[e.Position];
string text = t.CallerName + " " + t.CallState;
Log.Debug("SmartAppMobile", "Call " + text + " clicked");
//Toast.MakeText(this.Activity, text, ToastLength.Short).Show();
ViewStub myStub = e.View.FindViewById<ViewStub>(Resource.Id.viewStub1);
bool previouslyFound = false;
if (myStub != null)
{
inflatedView = myStub.Inflate();
}
else
{
Log.Debug("myapp", "View stub not found for " + text);
inflatedView = e.View.FindViewById<View>(Resource.Id.CallDetails);
inflatedView.Visibility = ViewStates.Visible;
previouslyFound = true;
}
TextView details = inflatedView.FindViewById<TextView>(Resource.Id.CallDetailsText);
if (details != null)
details.Text = "Call details go here... " + t.CallerNumber;
else
Log.Debug("myapp", "Call Details Text field not found for " + text);
Button myButton = inflatedView.FindViewById<Button>(Resource.Id.EndCallButton);
if (myButton != null)
{
if (!previouslyFound)
myButton.Click += (x, y) =>
{
Model.Calls.MyCalls.Remove(t);
//((ArrayAdapter)listView.Adapter).NotifyDataSetChanged();
inflatedView.Visibility = ViewStates.Gone;
};
}
}
Also, if I click on myButton, the application reacts as if I had pressed the back button on the phone - and the ListView item is only removed if it is one that I added in code.
So I guess I have to add the way I call the activity hosting the fragment, and how the ListView items are bound as well:
In my fragment hosting the ListView:
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View fragment = inflater.Inflate(Resource.Layout.CallsFragment, null);
calls = fragment.FindViewById<ListView>(Resource.Id.ActiveCallsList);
calls.Adapter = new ActiveCallsAdapter(this.Activity, Model.Calls.MyCalls);
calls.ItemClick += new EventHandler<AdapterView.ItemClickEventArgs>(calls_ItemClick);
return fragment;
}
and my Data model for the calls
public class Calls
{
private static List<Call> myCalls;
private static object myLock = new object();
public static List<Call> MyCalls
{
get
{
lock (myLock)
{
if (myCalls != null)
return myCalls;
myCalls = new List<Call>();
myCalls.Add(new Call { CallerName = "some name", CallerNumber = "some phone number", CallState = Model.CallState.Init });
myCalls.Add(new Call { CallerName = "some other name", CallerNumber = "another number", CallState = CallState.Held });
return myCalls;
}
}
}
}
And the button I use in my main activity to add a new item to the list and display the list:
Button button = FindViewById<Button> (Resource.Id.myButton);
button.Click += delegate
{
button.Text = string.Format ("{0} clicks!", count++);
Model.Calls.MyCalls.Add(new Model.Call { CallerName = "test " + count, CallerNumber = "" + count, CallState = Model.CallState.Active });
StartActivity(new Intent(this, typeof(CallsActivity)));
};
And of course, CallsActivity
[Activity(Label = "My Activity")]
public class CallsActivity : Activity
{
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.CallsActivity);
// Create your application here
}
}
This is a layout bug. When you inflate the selected call, the call layout gets bigger but its parent doesn't, so the bottom one falls off the edge. That's also why it looks like the bottom one is not expanding--the expanded content is falling off the bottom. You can see this in action if you edit CallDetails.axml to set layout_height for the button to 1dp.
You can fix this layout issue by changing activeCallsList/layout_height (in CallsFragment.axml) to match_parent or fill_parent instead of wrap_content. Instead of reserving just enough space to show the calls when first drawn, activeCallsList will reserve as much space as it can. This makes your call Views behave as expected.
-Max

Fancybox Positioning Inside Facebook Canvas iFrame

OK so I have a iframe canvas app with its height set to "Settable" with the facebook javascrip sdk calls to FB.Canvas.setSize(); and FB.Canvas.setAutoGrow();. These are working perfectly, as the iframe gets set to a certain pixel height based on its content.
The problem is that when I make a call to Fancybox, it positions itself based on this height. I know that's exactly what its supposed to do as the fancybox jQuery returns the viewport by:
(line 673 of latest version of jquery.fancybox-1.3.4.js):
_get_viewport = function() {
return [
$(window).width() - (currentOpts.margin * 2),
$(window).height() - (currentOpts.margin * 2),
$(document).scrollTop() + currentOpts.margin
];
},
But the problem is the iframe will, for a lot of viewers, be longer than their browser window. So the Fancybox centers itself in the iframe and ends up only partly visible to the majority of viewers. (i.e. iframe height is 1058px and users browser is say only 650px).
Is there a way to have fancybox just calculate the physical browser height? Or do I need to change some settings in my Facebook canvas app to make it work?
I like how the only scrollbar is the one on Facebook (the parent, if you will).
All suggestions GREATLY appreciated!
For fancybox 2 try:
find:
_start: function(index) {
and replace with:
_start: function(index) {
if ((window.parent != window) && FB && FB.Canvas) {
FB.Canvas.getPageInfo(
function(info) {
window.canvasInfo = info;
F._start_orig(index);
}
);
} else {
F._start_orig(index);
}
},
_start_orig: function (index) {
Then in function getViewport replace return rez; with:
if (window.canvasInfo) {
rez.h = window.canvasInfo.clientHeight;
rez.x = window.canvasInfo.scrollLeft;
rez.y = window.canvasInfo.scrollTop - window.canvasInfo.offsetTop;
}
return rez;
and finally in _getPosition function replace line:
} else if (!current.locked) {
with:
} else if (!current.locked || window.canvasInfo) {
As facebook js api provides page info, then we could use it, so
find
_start = function() {
replace with
_start = function() {
if ((window.parent != window) && FB && FB.Canvas) {
FB.Canvas.getPageInfo(
function(info) {
window.canvasInfo = info;
_start_orig();
}
);
} else {
_start_orig();
}
},
_start_orig = function() {
and also modify _get_viewport function
_get_viewport = function() {
if (window.canvasInfo) {
console.log(window.canvasInfo);
return [
$(window).width() - (currentOpts.margin * 2),
window.canvasInfo.clientHeight - (currentOpts.margin * 2),
$(document).scrollLeft() + currentOpts.margin,
window.canvasInfo.scrollTop - window.canvasInfo.offsetTop + currentOpts.margin
];
} else {
return [
$(window).width() - (currentOpts.margin * 2),
$(window).height() - (currentOpts.margin * 2),
$(document).scrollLeft() + currentOpts.margin,
$(document).scrollTop() + currentOpts.margin
];
}
},
I had the same problem, i used 'centerOnScroll' :true, and now it works fine...
Had the same problem. Thankfully fancybox is accessable through CSS. My solution was to overwrite fancybox's positioning in my CSS file:
#fancybox-wrap {
top: 20px !important;
}
This code places the fancybox always 20px from top of the iframe. Use a different size if you like. The !important sets this positioning even though fancybox sets the position dynamically at runtime.
Here's one way to do it by positioning the Fancybox relative to the position of another element, in my case an Uploadify queue complete div that displays a view link after the user uploads an image.
Have a style block with a set ID like so:
<style id="style-block">
body { background-color: #e7ebf2; overflow: hidden; }
</style>
Then the link to open the Fancybox calls a function with the image name, width, and height to set the content and sizes. The important part is the positioning. By getting the position of the queue complete div, generating a new class declaration (fancy-position), appending it to the style block BEFORE the fancybox loads (!important in class will override positioning from fancybox), then adding the new class using the wrapCSS parameter in the fancybox options, it positions the fancybox exactly where I want it.
function viewImage(image, width, height) {
var complete_pos = $('#image_queue_complete').position();
var css_code = '.fancy-position { top: ' + complete_pos.top.toString() + 'px !important; }';
$('#style-block').append(css_code);
var img_src = '<img src="images/' + image + '" width="' + width.toString() + '" height="' + height.toString() + '" />';
$.fancybox({
content: img_src,
type: 'inline',
autoCenter: false,
wrapCSS: 'fancy-position'
});
}

JavaScript Mouse position over an element (code not working)

I want to display the mouse position when its inside an element.. heres the code:
Mouse Events Example
function GetMousePositionInElement(ev, element)
{
var osx = element.offsetX;
var osy = element.offsetY;
var bottom = osy + element.height();
var x = ev.pageX - osx;
var y = bottom - ev.pageY;
return { x: x, y: y, y_fromTop: element.height() - y };
}
function handleEvent(oEvent) {
var oTextbox = document.getElementById("txt1");
var elem = document.getElementById("div1");
var xp = GetMousePositionInElement(oEvent, elem).x;
var yp = GetMousePositionInElement(oEvent, elem).y;
oTextbox.value += "\n x = " + xp + "y= " + yp;
}
Use your mouse to click and double click the red square.
div style="width: 100px; height: 100px; background-color: red"
onmouseover="handleEvent(event)"
id="div1"> /div
textarea id="txt1" rows="15" cols="50"> /textarea>
There is a problem in the code. Mouse position is not displayed inside the texArea. What changes do i have to make for the code to work and work correctly? (of-cource not all of the code is displayed and i removed some of the < and > inoder to show you some parts of the code that are not displayed otherwise but the code syntax is correct, thats not the problem)
Thank you.
var osy = element.offsetY;
There's no such property as offsetY. You may be thinking of offsetTop. However note the offsetLeft/​Top values are relative to the offsetParent not the page. If you want page-relative co-ordinates you would need to loop through offsetParents, or, since you seem to be including jQuery, call its offset function that does just that:
var offset= $(element).offset();
var osx= offset[0], osy= offset[1];
var bottom = osy + element.height();
element is a DOM HTMLDivElement object, not a jQuery wrapper object, so it doesn't have the height() method. Either add the wrapper:
var bottom= osy+$(element).height();
or use the equivalent DOM method:
var bottom= osy+element.offsetHeight;
var y = bottom - ev.pageY;
Note pageY is not part of the DOM Events standard and also not available in IE. You can calculate it by using the standard clientY property and adjusting for the page's scrollTop. Again, jQuery does this for you, adding the Firefox pageY extension to all browsers, when you use its own methods to bind your event handler:
$('#div1').mouseover(handleEvent);
instead of the inline onmouseover=... event handler attribute.