Our thoughts in real time
January 05, 2010
Maintain Image Aspect Ratio Programmatically
The aspect ratio of an image is the ratio of its width to height, and all images have an inherent aspect ratio. By default, the <mx:Image> tag property maintainAspectRatio is set to true. This setting preserves the aspect ratio so that an image does not appear distorted.
<mx:Image maintainAspectRatio="true" />
So, if an <mx:Image> tag has defined an explicit width and/or height, the control will preserve the aspect ratio accordingly.
ie. <mx:Image width="100" height="100" />
However, there is a catch to the maintainAspectRatio property. Documented in the Adobe Livedocs, it states that when maintaining aspect ratio, there is a possibility there there will be "empty" pixels. For example, if an image is 100x100 pixels, and the <mx:Image> tag is defined as:
<mx:Image source="example.jpg" height="150" width="200" />
Then Flex sizes the image to 150 by 150 pxiels, the largest posible image that maintains the aspect ratio and conforms to the size constraints. The other 50 by 150 pixels remain empty. However, the <mx:Image> tag reserves the empty pixels and makes them unavailable to other controls and layout elements.
This can be an issue when when the <mx:Image> tag is wrapped in a defined "center" horizontal-aligned, "middle" vertically-aligned container, and the images can be of varying dimensions. In the example below, the original image is 380x169 pixels:
<mx:Box width="400" height="400" backgroundColor="#FFFFFF" horizontalAlign="center" verticalAlign="middle">
<mx:Image source="example1.jpg" height="25" maintainAspectRatio="true" />
</mx:Box>
Here, the <mx:Image> tag preserves the aspect ratio where the height is 25 pixels. However, the image is now not centered horizontally and vertically.
Image using the maintainAspectRatio
To go about this, one solution is to not rely on the <mx:Image> tag maintainAspectRatio property, and to programmatically determined the explicit width and height.
<mx:Box width="400" height="400" backgroundColor="#FFFFFF" horizontalAlign="center" verticalAlign="middle">
<mx:Image source="example1.jpg" height="25" maintainAspectRatio="false" complete="handleComplete(event)" />
</mx:Box>
Here, set the maintainAspectRatio to false so the <mx:Image> tag will not be handling the aspect ratio maintenance. Add a complete event handler so that when the image is loaded, we can calculate the aspect ratio:
private function handleComplete(event:Event):void
{
var target:Image = event.target as Image;
// Formula: The product of ratio and a known dimension.
target.width = (target.contentWidth * target.height) / target.contentHeight;
}
The width is defined as the product of the ratio and the known height. And as you can see, the image preserved its aspect ratio and is horizontally and vertically centered.
Image's dimensions determined programmatically
This is a simple example. There may be occasions where an <mx:Image> tag has both a defined explicit width and height. If that is the case, then we will need to add logic inside the complete event handler to determine what the explicit width and height is suppose to be in order to maintain aspect ratio.
Happy coding!
Posted at 11:52 AM in AIR/FLex | Permalink | Comments (0) | TrackBack (0)
December 04, 2009
Building a Better TileList: A Custom Scrollable TileList using Flex
I use a TileList component quite often when building Flex applications. Recently, I was asked to create a nice, smooth, horizontal scrolling effect for a TileList containing a 3x3 page grid of 40 or so images. After many unsuccessful attempts to cajole the TileList into the proper behavior, I decided to roll my own.
It seems the fundamental problem with the TileList, which extends from the ListBase class, is its management of item renderers. It gets confused when attempting an animation to change the horizontal scroll position using an AnimateProperty effect. It seems that item renderers get confused when moving items into view using an effect.
After futzing around with the offscreenExtraColumns and other properties, I still could not get the behavior I desired out of the standard TileList. So I built my own.
Now, I must state that the ScrollableTileList class that I created does have some limitations; it pre-loads an item renderer for each element in the dataProvider. This means that it isn't suited for very large lists or lists of unknown size which could grow large. But it works very well for lists that are of a known, somewhat small (~100 items) size. It is also left up to the item renderer to implement a selected property, which is used to highlight the selected item in the list. The current implementation does not have a default item renderer; you must supply one.
The ScrollableTileList is geared to swap directly in for a 'normal' TileList, so you'll see familiar properties like columnCount and rowCount. Another nice feature is more precise control over horizontal and vertical gaps between tiles, which is not well supported by the native TileList component.
I've provided a sample application that uses the ScrollableTileList here. The source code is included: Just right click on the demo and choose 'View Source' from the Flash menu.
Enjoy!
Posted at 10:59 AM in AIR/FLex | Permalink | Comments (2) | TrackBack (0)
November 21, 2009
Grio partners with Clientshow
Grio is helping to develop applications for ClientShow that will allow creative professionals to manage their clients and pitch their work. Launch of the system is targeted for early 2010.
Posted at 09:25 AM in News | Permalink | Comments (0) | TrackBack (0)
November 19, 2009
Grio to develop systems for Teachscape
Grio has signed an agreement with Teachscape to develop education-related software that enhances the learning experience. Teachscape was founded in 1999 and is the leader in providing solutions that drive increasing levels of student achievement.
Posted at 09:53 AM in News | Permalink | Comments (0) | TrackBack (0)
October 27, 2009
Riot Games launches League of Legends
Grio helped to develop many aspects of the platform for this MMO - initial reviews point to LOL becoming a huge success.
Posted at 09:58 AM in News | Permalink | Comments (0) | TrackBack (0)
October 23, 2009
Wrangling Flex Labels: Auto-sizing Text
I am currently working on a project where we have a fixed-width dialog box, and need to fit the title text in a label. The problem is, the text is too long. What to do? Well, the basic Flex Label provides you a couple of options: show an ellipsis (…) at the end of the label or cut the text off. Neither of these solutions was viable.
This is a common problem that occurs in UI development: fitting text in a limited space. My solution to this was to create a LabelUtil class that automatically adjusts the font size of the text to fit the width of the label.
Here is the meat of the code:
import
flash.text.AntiAliasType;
import
flash.text.Font;
import
flash.text.GridFitType;
import
flash.text.TextLineMetrics;
import
mx.controls.Label;
import
mx.core.Application;
import
mx.core.UITextFormat;
import
mx.styles.CSSStyleDeclaration;
import
mx.styles.StyleManager;
public class LabelUtil
{
public function LabelUtil()
{
}
static public function constrainTextToWidth( label : Label, htmlText : String ) : void
{
var style : CSSStyleDeclaration =
StyleManager.getStyleDeclaration("." +
label.styleName);
var fontSize : Number = style.getStyle( "fontSize" ) as Number;
label.setStyle( "fontSize", fontSize );
label.htmlText = htmlText;
label.invalidateSize();
label.validateNow();
while (
getTextWidth( label.text, fontSize, style ) > label.width )
{
fontSize =
fontSize - 0.5;
label.setStyle(
"fontSize", fontSize );
}
}
static public function getTextWidth( text : String, fontSize
: Number, style : CSSStyleDeclaration ) : Number
{
var textFormat
: UITextFormat =
new
UITextFormat(
Application.application.systemManager,
style.getStyle(
"fontFamily" ),
fontSize,
null,
style.getStyle(
"fontWeight" ) == "bold",
style.getStyle(
"fontStyle" ) == "italic",
null,
null,
null,
null,
style.getStyle(
"paddingLeft" ),
style.getStyle(
"paddingRight" ),
style.getStyle(
"textIndent" ) );
textFormat.antiAliasType
= flash.text.AntiAliasType.ADVANCED;
textFormat.gridFitType
= flash.text.GridFitType.PIXEL;
var textMetrics
: TextLineMetrics = textFormat.measureText( text );
return
textMetrics.width;
}
}
OK, so what’s going on here? First we pass in the label in question and the html text to be displayed (note the function will also work for plain text). Then, we measure the width of the text and compare it to the width of the label. If the text is narrower that the label, we’re done. Otherwise, we reduce the font size by a half-point until the text width is less than the label width.
There are a couple of things to consider using this method: First, you must specify the width of your label in pixels (this shouldn’t be a problem as this solution applies to a fixed width label). Also, you’ll need to make sure to define a CSS style for the label (you could modify the method to use the label’s inherent style properties, but I didn’t need to).
Notice that I am using a UITextFormat object to measure the text width. Why is that, when the label provides a textWidth property? The answer is that textWidth is unreliable in the label control. This method is rock solid.
Hope this helps your labeling needs. Happy Coding!
Posted at 01:45 PM in AIR/FLex | Permalink | Comments (1) | TrackBack (0)
October 01, 2009
Going with the Flow: Tips and Tricks for Using the Adobe Flex TextFlow Component
We've been busily developing using Flex 4 (aka “Gumbo”) here at the Grio offices and are impressed with many of its new features. One of the more interesting but difficult to decipher features is the TextFlow component.
The TextFlow component is used to layout text in a highly controlled way. It uses an XML-based markup language, Text Layout Format (TLF), to define the content of the TextFlow. TLF uses some similar tags to HTML (<div>, <p>, <span>), but it is definitely not HTML. This may trip you up a bit as you learn TLF; I recommend reading the Adobe Labs info before diving too deep into development (http://labs.adobe.com/technologies/textlayout/).
Adobe Labs also provides a nice text editor that can read and write TLF (http://labs.adobe.com/technologies/textlayout/demos/ ; a note of caution: the editor is somewhat out of date, so the TLF generated may not work perfectly with the latest text layout package).
One important thing to note is that The HTML to TLF conversion capabilities built into the Text Layout package are crap. They do not support much of anything, except paragraphs. Lists are not supported, and CSS is not either. For our last project, we basically built our own translator to generate TLF from HTML.
Which leads me to reveal my trick to render bulleted lists in TLF. The keys are: 1) The Unicode bullet character (\u2022); 2) A negative paragraph textIndent format attribute; and 3) The paragraphStartIndent attribute.
Here’s the format definition for each list item:
<format id='list_item' paragraphStartIndent='15' textIndent='-9' paragraphSpaceAfter='10'/>
Note that we have a negative textIndent and a positive paragraphStartIndent. This gives the desired list effect. You will need to adjust these numbers based on the size and face of the font you are using.
And here’s the function that converts the HTML list to a TLF list:
private static function appendList(htmlListNode:XML, tlfXml:XML, isOrdered:Boolean):void {
//used for ordered lists
var count:int = 1;
var listEl : String = isOrdered ? "ol" : "ul";
//get the li tags
for each (var child:XML in htmlListNode.*) {
var lineItemXml:XML;
var listItemContent:String = child.children().toXMLString();
// Ensure content for list item
if (listItemContent.length == 0) continue;
listItemContent = listItemContent.toLowerCase();
listItemContent = listItemContent.replace("<strong>","<span fontWeight='bold'>");
listItemContent = listItemContent.replace("</strong>","</span>");
if (listItemContent.indexOf("<ul>") > -1) {var preNestedListText:String = listItemContent.substring(0,listItemContent.indexOf(listEl));
var nestedList:String = listItemContent.substring(listItemContent.indexOf(listEl), listItemContent.length);
var nestedListXml:XML = new XML(nestedList);
if(preNestedListText.length > 0) {if(isOrdered) {
tlfXml.appendChild(new XML("<p listitem='true' format='list_item'>" + count + ". " + listItemContent +"</p>"));
} else {
tlfXml.appendChild(new XML("<p listitem='true' format='list_item'>\u2022 " + listItemContent + "</p>"));
}
}
appendList(nestedListXml, tlfXml, false);} else {
if(isOrdered) {
tlfXml.appendChild(new XML("<p listitem='true' format='list_item'>" + count + ". " + listItemContent +"</p>"));
} else {
tlfXml.appendChild(new XML("<p listitem='true' format='list_item'>\u2022 " + listItemContent + "</p>"));
}
count++;}
}
}
In summary, TLF is a very powerful text formatting language, but it currently lacks good HTML conversion tools and simple tags for things such as lists. We hope Adobe provides support for these in the future, but until then we’ll just have to code around the problem.
Posted at 02:14 PM in AIR/FLex | Permalink | Comments (2) | TrackBack (0)
Vook is Live Today
Grio developed the vook platform and Flex-based reader. Look for new vooks to come out on a regular basis.
Posted at 10:03 AM in News | Permalink | Comments (0) | TrackBack (0)
September 23, 2009
Uploading Files from iPhone
We recently completed an iPhone application that allows user to, among other things, perform voice-recording and upload the recorded file to a server. For uploading, it uses HTTP multipart POST against an Apache webserver. This type of arrangement is a very common way of sending a file to a webserver. I am certain that similar implementations can be found in several different programming languages. Here is my Objective C implementation...I tried to focus on brevity and encapsulation.
The code:
* Upload a file to a webserver thru http post.
* uid is the http request id
* requestData is list of key/value pairs to be sent along with the file
* url is the destination url
* filePath is absolute file path of the file to be uploaded
*/
- (void)postUpload:(NSString *)url
:(NSString *)uid
:(NSDictionary *)requestData
:(NSString *)filePath
{
NSURL *theURL = [NSURL URLWithString:url];
NSMutableURLRequest *theRequest = [NSMutableURLRequest requestWithURL:theURL
cachePolicy:NSURLRequestReloadIgnoringCacheData
timeoutInterval:20.0f];
[theRequest setHTTPMethod:@"POST"];
[theRequest setValue:uid forHTTPHeaderField:@"uid"];
// define post boundary...
NSString *boundary = [[NSProcessInfo processInfo] globallyUniqueString];
NSString *boundaryString = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary];
[theRequest addValue:boundaryString forHTTPHeaderField:@"Content-Type"];
// define boundary separator...
NSString *boundarySeparator = [NSString stringWithFormat:@"--%@\r\n", boundary];
//adding the body...
NSMutableData *postBody = [NSMutableData data];
// adding params...
for (id key in requestData) {
NSString *formDataName = [NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n", key];
NSString *formDataValue = [NSString stringWithFormat:@"%@\r\n", [requestData objectForKey:key]];
[postBody appendData:[boundarySeparator dataUsingEncoding:NSUTF8StringEncoding]];
[postBody appendData:[formDataName dataUsingEncoding:NSUTF8StringEncoding]];
[postBody appendData:[formDataValue dataUsingEncoding:NSUTF8StringEncoding]];
}
// if file is defined, upload it...
if (filePath) {
NSArray *split = [filePath componentsSeparatedByString:@"/"];
NSString *fileName = (NSString*)[split lastObject];
NSData *fileContent = [NSData dataWithContentsOfFile:filePath options:0 error:nil];
[postBody appendData:[boundarySeparator dataUsingEncoding:NSUTF8StringEncoding]];
[postBody appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"audioBody\"; filename=\"%@\"\r\n", fileName] dataUsingEncoding:NSUTF8StringEncoding]];
[postBody appendData:[@"Content-Type: application/octet-stream\r\n\r\n"
dataUsingEncoding:NSUTF8StringEncoding]];
[postBody appendData:fileContent];
}
[postBody appendData:[[NSString stringWithFormat:@"\r\n--%@--\r \n",boundary]
dataUsingEncoding:NSUTF8StringEncoding]];
[theRequest setHTTPBody:postBody];
NSURLResponse *theResponse = NULL;
NSError *theError = NULL;
NSData *reqResults = [NSURLConnection sendSynchronousRequest:theRequest
returningResponse:&theResponse
error:&theError];
if (!reqResults) {
NSLog(@"Connection error for URL: %@", [url description]);
UIAlertView *alert = [UIAlertView alloc];
[[alert initWithTitle:@"Error in connection"
message:[theError localizedDescription]
delegate:self
cancelButtonTitle:nil
otherButtonTitles:@"Quit", nil] autorelease];
[alert show];
}
}
NSArray *keys = [NSArray arrayWithObjects:@"recipients", @"title", @"textBody", nil];
NSArray *objects = [NSArray arrayWithObjects:uids, inputSubject.text, inputMessage.text, nil];
NSDictionary* postData = [NSDictionary dictionaryWithObjects:objects forKeys:keys];
// post now...
[[WebService alloc] postUpload:URL_ENDPOINT_SEND_MESSAGE
:appDelegate.uid
:postData
:[recorder recordFilePath]];
<?php
define('FILE_SAVE_DIRECTORY','/folder/to/save/uploaded/files/');
error_log("Requesting upload file....");
error_log(print_r($_SERVER, true));
error_log("\n\n");
error_log(print_r($_FILES, true));
if($_POST && isset($_FILES['file'])) {
error_log(" the file:...." . $_FILES['file']['name']);
$file = $_FILES['file'];
$dst = FILE_SAVE_DIRECTORY . basename($file['name']);
if (move_uploaded_file($file['tmp_name'], $dst)){
echo 'Upload Successfully: '.$dst;
}
else {
echo 'Error uploading the file.';
}
}
?>
Posted at 03:53 PM in iPhone | Permalink | Comments (1) | TrackBack (0)
September 21, 2009
Grio Completes Flex App for Compiere, Inc.
Grio has recently completed a custom Adobe Flex application for Compiere, Inc. Compiere delivers the most widely used open source enterprise resource planning (ERP) and customer relationship management (CRM) solution with more than 1.8 million software downloads.
Grio was employed by Compiere to help create a new Flex-based module that will be delivered in Compiere's upcoming release.
Posted at 02:53 PM in News | Permalink | Comments (0) | TrackBack (0)