The amazing adventures of Doug Hughes

There are some really fun amazing Flex based music player floating around online. Two music players that have stolen a lot of my time are Seeqpod and JukeFly.

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.

// 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!

Comments on: "Adobe Flex's Tab Navigator and Corner Radius" (8)

  1. i think the easiest way is to combine viewstack and tabbar components. just put tabbar.x = 20, and viewstack.y = tabbar.y + tabbar.height.

    Like

  2. Joe Rinehart said:

    Thanks Mark! I had pretty much gone to square corners because of slivers like this, now I can try out some roundedness again.

    Like

  3. @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!

    Like

  4. http://concealer.mybrute.com
    Check out the cool mini-game

    Like

  5. Man, great work! Thanks so much for sharing! Round and round the corners I go…

    sd

    Like

  6. Great man, it works like a charm!! 😛

    Like

  7. Wow! You saved my day! Thanks!

    Like

  8. Awesome! This has been annoying me for a month, thanks!!

    Fyi, I’m using a borderThickness=1, so I had to set the tabOffset=-1 to align the first tab.

    Like

Comments are closed.

Tag Cloud

%d bloggers like this: