Problem Description
There is a bug in the new Composite Layout engine. When a view of a certain size is placed inside a second view, and the second view's width is set to Ti.UI.SIZE, the second view's toimage() size is exactly 100 instead of being the same size as the first view.
Actual Results
Run the testcase below. The console will show
--> TheOrange width:160dp
--> GreenView.swidth:0
--> GreenView.iwidth:100
--> GreenView.swidth2:0
But the GreenView.iwidth should be 160 (on a medium-resolution emulator). Also, you can see that GreenView.size.width (GreenView.swidth2) returns 0, but it should be 160.
Expected results
The SIZE component working as the docs says.
Test case
1. Create a new mobile project
2. Add this code to app.js
var win1 = Ti.UI.createWindow({
title:'Win 1',
layout:'vertical',
backgroundColor:'#fff'
});
var vw1 = Ti.UI.createView({
layout:'vertical',
backgroundColor:'red',
width:'100%',
height:75
});
win1.add(vw1);
//
var TheOrange=Ti.UI.createView({height:'160dp',
width:'160dp',
backgroundColor:'orange',
top:0
});
var GreenView=Ti.UI.createView({
height:Ti.UI.SIZE,
width:Ti.UI.SIZE,
focusable:false,
touchEnabled:false,
backgroundColor:'green'
});
Ti.API.info('--> TheOrange width:'+TheOrange.width);
GreenView.add(TheOrange);
Ti.API.info('--> GreenView.swidth:'+GreenView.size.width);
Ti.API.info('--> GreenView.iwidth:'+GreenView.toImage().width);
//
vw1.add(GreenView);
vw1.layout='horizontal';
var BlueView=Ti.UI.createView({
backgroundColor:'blue',
width:50,
height:50
});
vw1.add(BlueView);
win1.open();
Ti.API.info('--> GreenView.swidth2:'+GreenView.size.width);
The other (main) expected result is that GreenView.toImage().width should return 160 instead of 100. I have no idea where the 100 came from. It even returns 100 if TheOrange.width is set to '120dp' instead of '160dp'.
There are a couple of issues with the test case. 1. view.size.width will return the system units which is in pixels. So 160dp will be converted to pixels, which won't match 160. 2. The test case calls toImage() before the layout pass, so the view is essentially 0. In the logs, you'll see "width property is 0 for view, display view before calling toImage()" when running that test. I have modified the test so it will return the correct values:
Going to mark this as invalid for now, please feel free to reopen if you find other issues.
Allen, regarding your #1 point, GreenView.size.width should indeed return 160, because, as this bug "environment" states, we are running this on the "Android SDK 2.2 (emulator, medium size)". Since it's in medium resolution, '160dp' is equal to 160 pixels. You changed TheOrange.height from '160dp' to 160, which just hides the problem. In #2, you state that toImage() is being called "before the layout pass". Are you talking about the vw1.add(GreenView) line? Is that the earliest time that toImage() can be used? We need to be able to contstruct complex views and determine resulting size *before* displaying them on the screen, to determine if they will fit on the screen and make adjustments if necessary.
In regards to #2, I just tested calling GreenView.toImage().width right after vw1.add(GreenView) and it still returns the bogus 100 number. Also notice that the last GreenView.size.width call is after the win1.open() call, but it still returns 0.
More fuel: on iOS, using the 2.0.0.v20120322231759 SDK, the output of my original testcase is this: --> TheOrange width:160dp --> GreenView.swidth:0 --> GreenView.iwidth:160 --> GreenView.swidth2:160 This is what I would expect, so there is a parity issue here.
@Shawn Layout occurs after the window and views have been realized, which happens asynchronously after
is called. Allen added a click listener to illustrate that the values are correct once the UI has been realized, laid out, and finally you've clicked the button (because everything is now visible)
Marshall, please see the comment I added right before yours. In iOS, it works correctly without having to wait for the window to appear visually. Maybe the toImage() call forces it to "render"? There's definitely a parity issue here. We need to be able to contstruct complex views and determine resulting size *before* displaying them on the screen, to determine if they will fit on the screen and make adjustments if necessary.
#1: I just tried on a medium density emulator, and it does return 160, if you specify '160dp'. #2: 100 is actually the default value we set for the image if it has not been rendered yet. That's why you see 100. As Marshall mentioned, toImage() needs to be called AFTER the views have been realized and laid out. Since open is an asynchronous call, printing the value right after open may not return the correct values. I would try to listen for the 'postlayout' event on the window if you want to check for the measurements after the layout pass.
Allen, thanks for following up. I understand what you're saying about #2, but in iOS, it works correctly without having to wait for the window to appear visually. Maybe the toImage() call forces it to "render"? There's definitely a parity issue here. So the question is, what's the difference between the iOS implementation and the Android implementation? The iOS code can resolve the toImage().width without waiting for 'postlayout', which is what we need in Android also. I know that one of the goals of Composite Layouts was to bring parity to the two platforms. This issue demonstrates a differnce between the two, where the Android version prevents functionality that exists in the iOS version.
This continues to be a problem (2.1.2). Please reopen.
@Shawn - We have taken another look at this issue. iOS platform performs size calculations when the view has not been laid out by default. We don't have an efficient way to do so on Android platform and the size calculations are performed as part of the layout pass. It is a significant effort to refactor TiCompositeLayout class to accomplish this. You can work around this issue using "postlayout" event.
Neeraj, thanks for looking at this. I understand what you're saying. Is there more to the "postlayout" workaround that will allow me to keep a good user experience by not visually showing the messed-up screen, then fix the positioning, then show the good screen?
Closing ticket, please open a new ticket if any more problems persist.