flex45, rotation
Many ways to do the same thing…but which is right? It’s an issue developers face every single day.
Here’s one of those issues that I faced this past week: rotating an object in Flex. I was playing with the camera in Flex Mobile, and I quickly learned that it is landscape only even if the device is being held in portrait. Really, Adobe?
“…a Video object attached to the camera will only show upright video in a landscape-aspect orientation. Thus, mobile apps should use a landscape orientation when displaying video and should not auto-rotate.”
– official Adobe docs for flash.media.Camera
Sad but true. So, if you wanted to build a video chat app and use the front camera you either always hold the iPhone in landscape or you hold it portrait and rotate the video in code. (Why did I say iPhone?…Alas, Flex Mobile does not provide front camera support for Android…period…none!)
rotation
AttributeThe first thing to try is to rotate the easy way, like this:
camera.rotation = 90;
Using actionscript, I just set the rotation to 90°. Of course, this rotates about (0,0), the upper left corner in Flex, and not what we need.
rotation
, Plus transformX
and transformY
To rotate an object about its center, you can set transformX
and transformY
(and transformZ
too), like this:
<s:Button id="btn" label="My Button" rotation="90" transformX="{btn.width * 0.5}" transformY="{btn2.height * 0.5}" />
Using MXML, I set rotation to 90°, plus transformX and transformY to half the width and height of the object (in this case, a button). By moving the rotation point from the top left to the center of the button, I get what I want: an object rotating about its center. [I had forgotten how this works...thanks go to Bindu for reminding me!]
Option #2 works because every component in Flex has a full affine transformation matrix behind it (as of Flash Player v9, I think, but only exposed to MXML in Flex 4). In MXML, when you set attributes like rotation
, transformX
, and transformY
, you are just setting the underlying transformation matrix.
You can also set the matrix directly, like this:
var mat:Matrix = new Matrix(); mat.translate(-W/2, -H/2); mat.rotate(Math.PI/2); mat.translate(+W/2, +H/2); mat.concat(myObj.transform.matrix); myObj.transform.matrix = mat;
To get a rotation about the center of our object, we first translate it, then rotate, then translate it back. Note the concat()
operation, which just “adds” the current matrix to our new rotation matrix, and thus preserves any previous transforms (maybe our object had scaleX
or scaleY
applied).
Lastly, if we want to watch the rotation happen, we can use a Rotate
animation, like this:
var rot:Rotate = new Rotate(); rot.angleBy = 90; rot.duration = 1000; rot.autoCenterTransform = true; rot.target = myObj; rot.play();
Here we just instantiate a new spark Rotate effect, set its various properties, and start it with a call to play(). The key property to force rotation about the center is autoCenterTransform
, just set it to true
.
When I need total control, I find myself doing the full matrix solution. Otherwise for anything simple, the MXML attributes are best.
Here’s an example project showing all options (view source enabled):
custom component, design, flex45
Did a little bit of vanilla Flex work recently, and I needed a Tree component to display an object hierarchy. Everyone, by now, hopefully knows that mx:DataGrid
and mx:Tree
are two of the crappiest, bug ridden, worst performing components from Flex 3. And, everyone by now, has left the buggy world of Flex 3 behind and entered the world Flex 4 and the vastly improved Spark components. With the arrival of Flex 4.5 this summer, Adobe finally gave us a rewritten Spark-based DataGrid
. Alas, no updated Tree
yet, so I had to write my own. So once again, I turned to the trusty combo of List
plus custom ItemRenderer
to make pure-Spark custom TreeList
component that doesn’t suck.
Alex Harui is the guru of turning a Flex 4 Spark List
into a look-alike for the old Flex 3 component using skins and a custom ItemRenderer
. Alex has used List
+ ItemRenderer
to make a DataGrid, DateField, ColorPicker, Menu + MenuBar, and even an XML-based TreeList. Unfortunately, Alex’s TreeList
assumes incoming XML, and I needed a TreeList
that could display a simple object hierarchy (root node with children, and those children have children, etc.). Since I couldn’t find exactly what I wanted, I decided to build it myself.
The key step to getting a hierarchy of objects to display as a list is: flatten the list, duh! Or at least flatten the part of the tree you wish to display. So, I built a simple adapter class that turns an object hierarchy into an ArrayList
that can be given directly to a List
‘s dataProvider
.
Here’s the actual flattener, but with just the comments not the code:
public class MyObjFlattenedList extends ArrayList { //the root object private var _root:MyObj; //list of open nodes private var _openList:Array; public function MyObjFlattenedList(root:MyObj) { _root = root; _openList = []; reset(); ... } public function reset(openList:Array = null):void { //init the flattened list, starting at root _openList = (openList == null ? [] : openList); var a:Array = []; addItemToList(_root, a); this.source = a; } private function addItemToList(obj:MyObj, a:Array):void { //recursively walk obj and all of its "open" children to build //a flattened list that is returned in array a } public function isItemOpen(obj:MyObj):Boolean { //true if obj has children and is "open" } public function openItem(obj:MyObj):void { //add all of obj's children (if any) to the list } public function closeItem(obj:MyObj):void { //remove all of obj's children (if any) from the list } ... }
There’s really not much to it. When instantiated with a root object, the object hierarchy is walked recursively to build a flattened list of all the open nodes. Once the initial list is built, openItem()
can be called to open a node, and add all its children to the list. Alternately, closeItem()
can be called to close a node, and remove all its children from the list.
I used some basic styling and skinning, but the ItemRenderer
does the majority of the work. Here’s the abbreviated version of MyObjRenderer.mxml
with all the boring stuff edited out:
<?xml version="1.0" encoding="utf-8"?> <s:ItemRenderer xmlns:fx="ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" "100%" autoDrawBackground="false"> <fx:Script> <![CDATA[ [Bindable] private var _obj:MyObj; [Bindable] private var _hasChildren:Boolean = false; private var _list:MyObjFlattenedList; override public function set data(val:Object):void { super.data = val; if (val != null) { _obj = val as MyObj; var ownerList:List = owner as List; _list = ownerList.dataProvider as MyObjFlattenedList; btn.selected = _list.isItemOpen(_obj); _hasChildren = (_obj.children != null && _obj.children.length > 0); } } private function toggleHandler():void { if (btn.selected) { _list.openItem(_obj); } else { _list.closeItem(_obj); } } ]]> </fx:Script> <s:states> <s:State name="normal" /> <s:State name="hovered" /> <s:State name="selected" /> </s:states> <s:Rect ...> <s:HGroup ...> <s:ToggleButton id="btn" click="toggleHandler()" visible="{_hasChildren}" ... /> <s:Group id="dot" visible="{!_hasChildren}" ... /> <s:Label ... /> </s:HGroup> </s:ItemRenderer>
Each rendered item has a background Rect
and a Label
. But most importantly, each row has either a ToggleButton (if the object has children) or some non-interactive visuals (if the object doesn’t have children it just gets a dot). The toggle button is the key interactive element used to open or close the node, everything else is part of the visual gravy added to make the list look good.
Focusing on the functional stuff, if you look in the script block, you’ll see two functions: a data()
setter and a toggleHandler()
to handle toggle button clicks. As expected, clicking the toggle button calls openItem()
or closeItem()
on the flattener adapter which adds or removes children from the flattened list, respectively. The setter mostly sets up the local variables, but it notably computes if the object has children or not, and also sets the initial state of the toggle button.
With just a little effort, we can have a nice usable Spark TreeList component that looks decent. More importantly, we have total control and can make the TreeList look like anything our designer can throw at us. As is always the case, the combo of List
+ ItemRenderer
proves to be awesome. I tried to cover all the interesting pieces, but for the details, you’ll need to check out the source code.
Here’s the finished product (view source enabled):
Use it and enjoy.
iOS, testing
Sometimes I feel like I write more test code than real code. For unit tests on iOS our stack is OCHamcrest, OCMock, and GHUnit. For functional tests, there’s nothing better than FoneMonkey. For this post, I’m going to focus on OCHamcrest.
Hamcrest was born in the Java world as the matcher framework in jMock. It was quickly extracted into its own framework and has become somewhat of a monster in the testing world. It’s now included directly in JUnit (since v4.4), and has been ported to many languages (OCHamcrest in Objective-C, Hamcrest-AS3 in Actionscript, PyHamcrest in Python, etc.). Additionally, the matcher concept is generally useful, and Hamcrest is used is lots of different places (my favorite is collection filtering with Hamcrest in LambdaJ).
When writing unit tests, OCHamcrest offers lots of advantages over the vanilla SenTest assertions. First, there’s a ton of matchers that really make life easy, especially when testing collections like NSArray
. Second, OCHamcrest matchers are very readable in code, almost self-documenting. Lastly, OCHamcrest automatically provides excellent failure messages when actual is not equal to expected.
Some string matching examples:
NSString *s = @"FooBar"; assertThat(s, is(@"FooBar")); assertThat(s, startsWith(@"Foo")); assertThat(s, endsWith(@"Bar")); assertThat(s, containsString(@"oo")); assertThat(s, equalToIgnoringCase(@"foobar")); assertThat(@" X \n Y \t\t Z \n", equalToIgnoringWhiteSpace(@"X Y Z"));
NOTE: Technically,
is
isn’t really a matcher, it’s a matcher decorator that implicity converts to theequalTo
matcher. [thanks Jon!]
You can combine multiple matchers with:
NSString *s = @"FooBar"; assertThat(s, allOf(startsWith(@"Foo"), endsWith(@"Bar"), nil)); assertThat(s, anyOf(startsWith(@"Foo"), startsWith(@"Bar"), nil)); assertThat(s, anyOf(endsWith(@"Foo"), endsWith(@"Bar"), nil));
NOTE: The list of matchers must be
nil
terminated.
You can invert a matcher, or multiple matchers, with:
NSString *s = @"FooBar"; assertThat(s, isNot(@"foo")); assertThat(s, isNot(endsWith(@"Baz"))); assertThat(s, isNot(allOf(startsWith(@"Baz"), endsWith(@"Baz"), nil))); assertThat(s, isNot(anyOf(startsWith(@"Baz"), startsWith(@"Baz"), nil)));
nil
You can match nil
with:
nil
!nil
NSObject *o = nil; assertThat(o, nilValue()); NSString *s = @"FooBar"; assertThat(s, notNilValue());
You can match an instance’s class with:
NSString *s = @"FooBar"; assertThat(s, instanceOf([NSString class]));
One of the great pains of Objective-C is typing numbers from primitive types to objects and back again. OCHamcrest has a variety of matchers the help make life easy.
int
(other types too: assertThatFloat
, assertThatDouble
, etc.)int
(other types too: equalToFloat
, equalToDouble
, equalToBool
, etc.)double
)NSNumber
), also lessThanOrEqualTo
NSNumber
), also greaterThanOrEqualTo
assertThatInt(5, equalToInt(5)); assertThatFloat(3.14, equalToFloat(3.14f)); assertThatBool( false, equalToBool(NO) );