Matt LeGrand is an accomplished user experience specialist with a primary focus on Adobe Flex and Air application development. Matt has been working with Flex since 2005 and Adobe Flash since 1999. His interest are in human computer interaction and interactive media research.
Now here is where my mild OCD comes into play. Both of these applications use Adobe's Tab Navigator component and both rock the rounded corners. If you look closely, in the top left corner where the first tab meets the rounded content container, there is a small gap in the UI. I know I'm a psycho but lets just explore a few ways to fix this.


The first and most obvious way to fix this is just to indent the tab bar to the right a little. So if we have a cornerRadus of 20 we'll need to indent our tab by 20 pixels.
<mx:TabNavigator id="tabNavigator"
tabOffset="20"
cornerRadius="20"
height="100%" width="100%">
Another way to accomplish this would be to override the drawing of that particular corner. We can easily do this by extending the HaloBorder class and overriding the drawRoundRect method. Well use this new border skin instead of the default HaloBorderSkin to style our TabNavigator.
Instead of calling super.drawRoundRect and adding additional functionality to this method, I'm actually going to copy and paste most of this method (originally from the ProgrammaticSkin class) and only alter the section regarding the cornerRadius.
This section looks like this:
// Stroke the rectangle.
if (!cornerRadius)
{
g.drawRect(x, y, width, height);
}
else if (cornerRadius is Number)
{
ellipseSize = Number(cornerRadius) * 2;
g.drawRoundRect(x, y, width, height,
ellipseSize, ellipseSize);
}
else
{
GraphicsUtil.drawRoundRectComplex(g,
x, y, width, height,
cornerRadius.tl, cornerRadius.tr,
cornerRadius.bl, cornerRadius.br);
}
Lets replace this section with code that looks like this:
// Stroke the rectangle.
if (!cornerRadius)
{
g.drawRect(x, y, width, height);
}
else if (cornerRadius is Number)
{
ellipseSize = Number(cornerRadius);
var squareTopLeft:Boolean = this.getStyle('squareTopLeft') as Boolean;
var topLeftCornerRadius:Number = (squareTopLeft)? 0 : ellipseSize;
g.drawRoundRectComplex(x, y, width, height, topLeftCornerRadius, ellipseSize, ellipseSize, ellipseSize);
}
Now we need to add a Boolean style attribute called 'squareTopLeft' to our new class. This is just a flag that enables us to turn on and off our top left corner.
[Style(name="squareTopLeft", type="String", enumeration="true, false", inherit="no" )]
When I'm googling for an answers, I'm often looking to just copy and paste the solution, so here is the full code:
Main app with style:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application layout="vertical"
paddingBottom="20"
paddingLeft="20"
paddingRight="20"
paddingTop="20"
xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Style>
.tabNavigatorSquaredLeftCorner
{
background-color: #FFFFFF;
cornerRadius: 20;
borderSkin: ClassReference('com.alagad.skins.TabNavigatorSquaredTopLeft');
square-top-left: true;
}
</mx:Style>
<mx:TabNavigator id="tabNavigator"
styleName="tabNavigatorSquaredLeftCorner"
height="100%" width="100%">
<mx:Canvas label="Red"
height="100%" width="100%"
backgroundColor="#FF0000"
borderStyle="solid"
cornerRadius="20"/>
<mx:Canvas label="Green"
height="100%" width="100%"
backgroundColor="#00FF00"
borderStyle="solid"
cornerRadius="20"/>
<mx:Canvas label="Blue"
height="100%" width="100%"
backgroundColor="#0000FF"
borderStyle="solid"
cornerRadius="20"/>
</mx:TabNavigator>
</mx:Application>
TabNavigatorSquaredTopLeft.as (BorderSkin):
package com.alagad.skins
{
import flash.display.Graphics;
import flash.geom.Matrix;
import mx.skins.halo.HaloBorder;
import mx.utils.GraphicsUtil;
////////////////////////////////////////////////////////////////////////////
// styles
/////////////////////////////////////////////////////////////////////////////**
* The squareTopLeft style is the boolean value
* to square off the top left corner.
*
* @type String
*/
[Style(name="squareTopLeft", type="String", enumeration="true, false", inherit="no" )]
public class TabNavigatorSquaredTopLeft extends HaloBorder
{
////////////////////////////////////////////////////////////////////////////
// const
////////////////////////////////////////////////////////////////////////////
public function TabNavigatorSquaredTopLeft()
{
super();
}
////////////////////////////////////////////////////////////////////////////
// overridden functions
////////////////////////////////////////////////////////////////////////////
/**
* Overrides the drawRoundRect method from the
* ProgrammaticSkin class and adds the top left
* corner radius option.
*/
override protected function drawRoundRect(x:Number, y:Number,
width:Number, height:Number,
cornerRadius:Object=null,
color:Object=null,
alpha:Object=null,
gradientMatrix:Matrix=null,
gradientType:String="linear",
gradientRatios:Array=null,
hole:Object=null):void
{
var g:Graphics = graphics;
// Quick exit if weight or height is zero.
// This happens when scaling a component to a very small value,
// which then gets rounded to 0.
if (width == 0 || height == 0)
return;// If color is an object then allow for complex fills.
if (color !== null)
{
if (color is uint)
{
g.beginFill(uint(color), Number(alpha));
}
else if (color is Array)
{
var alphas:Array = alpha is Array ?
alpha as Array :
[ alpha, alpha ];if (!gradientRatios)
gradientRatios = [ 0, 0xFF ];g.beginGradientFill(gradientType,
color as Array, alphas,
gradientRatios, gradientMatrix);
}
}var ellipseSize:Number;
// Stroke the rectangle.
if (!cornerRadius)
{
g.drawRect(x, y, width, height);
}
else if (cornerRadius is Number)
{
ellipseSize = Number(cornerRadius);
var squareTopLeft:Boolean = this.getStyle('squareTopLeft') as Boolean;
var topLeftCornerRadius:Number = (squareTopLeft)? 0 : ellipseSize;
g.drawRoundRectComplex(x, y, width, height, topLeftCornerRadius, ellipseSize, ellipseSize, ellipseSize);
}// Carve a rectangular hole out of the middle of the rounded rect.
if (hole)
{
var holeR:Object = hole.r;
if (holeR is Number)
{
ellipseSize = Number(holeR) * 2;
g.drawRoundRect(hole.x, hole.y, hole.w, hole.h,
ellipseSize, ellipseSize);
}
else
{
GraphicsUtil.drawRoundRectComplex(g,
hole.x, hole.y, hole.w, hole.h,
holeR.tl, holeR.tr, holeR.bl, holeR.br);
}
}if (color !== null)
g.endFill();
}
}
}
Seems like a lot of work to do just to avoid a little sliver in your UI.
Hope this helps,
Matt
Thanks Mark! I had pretty much gone to square corners because of slivers like this, now I can try out some roundedness again.
@Irchen,
I've also gone that route. I've found that method really helpful for getting a linear gradient fill to correctly match the viewstck below it.
Thanks for sharing this tip!
http://concealer.mybrute.com
Check out the cool mini-game
Man, great work! Thanks so much for sharing! Round and round the corners I go...
sd
Great man, it works like a charm!! :P
i think the easiest way is to combine viewstack and tabbar components. just put tabbar.x = 20, and viewstack.y = tabbar.y + tabbar.height.
Posted By: lrchen on Apr 16, 2009 at 12:00 AM