Flex - Rounded Corner Component with Tiled Bitmap Background

Lately, I have been getting more involved into working with the visual aspect of Flex. Up to now, I had been able to pass off the styling to Alex after working on the functionality or have just worked on projects where the visuals weren't a concern. Anyway, it is often good to get opportunities to explore.

So, the simple thing that I needed to do was to create a rectangular component based on mx:Canvas with a tiling background and rounded corners. Figuring this ought to be a straightforward thing to do, I blithely set about it.

First snag: Flex doesn't seem to offer a straightforward way of identifying a repeatable background for the Canvas class. Quick searching revealed that one can do this with Degrafa, but I didn't want to go that route. (Not that Degrafa isn't tremendous, it just seems like a lot of overhead just to tile a background image.) There is another solution for setting a tiled image as a background.

That is a really nice solution, but, for my immediate purposes, has distracting elements like loading the image at run-time. Run-time loading could be an incredibly useful feature, and it is worth noting, but if you have a small, single image you want to use, it is nicer to just embed it and not have dependencies at deployment. The core of the solution is to use lower level AS3 functions to draw a rectangled that is filled with the bitmap of choice.

So, a quick bit of code that shows the embedded image written as a tiled background for the component:

<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml"
width="400"
height="55"
creationComplete="init()">


<mx:Script>
<![CDATA[

[Embed(source="/assets/bg.png")]
private const ImgBG:Class;

private function init():void {
graphics.clear();
graphics.beginBitmapFill(new ImgBG().bitmapData );
graphics.drawRect(0, 0, unscaledWidth, unscaledHeight);
graphics.endFill();
}
]]>

</mx:Script>
    
</mx:Canvas>

So the snag happens when you want to apply rounded corners. Since you drew a rectangle, a rectangle is what you get:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" xmlns:local="*">

<local:BackgroundComponent horizontalCenter="0" top="40" borderStyle="solid" cornerRadius="8" />
    
</mx:Application>

That gets us the following (corner radius = 8):

You can see that there is an attempt to get the rounded corner, but the lower call to fill the rectangle with the bitmap trumped it. However, all is not lost. We just need to make a quick adjustment to how we create the shape for the background fill. Admittedly, my first solution was more complicated, but all we need to do is substitute the drawRect with drawRoundRect and then choose an appropriate ellipseWidth and ellipseHeight.

Now choosing the ellipse width and height could be a function of trial and error, but there is no reason to make your life that difficult or to give up a benefit of the design view of Flex Builder. The corner radius for the component is exposed as a style, so what we have to do is to fetch it and pipe it into our round rectangle function. Of course, it seems like is never that easy. The cornerRadius setting is about half the width and height needed for the ellipse. So, once we fetch the value, doubling it does the trick.

The solution looks something like:

<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml"
width="400"
height="55"
creationComplete="init()">


<mx:Script>
<![CDATA[
        

[Embed(source="/assets/bg.png")]
private const ImgBG:Class;

        
private function init():void {
                
var _radius:int = int( this.getStyle("cornerRadius") ) *2;
graphics.clear();
graphics.beginBitmapFill(new ImgBG().bitmapData );
graphics.drawRoundRect(0, 0, unscaledWidth, unscaledHeight,_radius,_radius);
graphics.endFill();
}
]]>

</mx:Script>
    
</mx:Canvas>

This gives us something like the follwing (cornerRadius = 16):

Here is a live demo of a rounded corner component with tiled background.

[Update 23OCT2009] While attempting to come up with an intelligent answer to Tony's comment - and failing to give anything substantive or enlightening - I did an additional round of experimentation to attempt to remove/explain the fudge factor on the _radius value. While getting closer, moving from 2 to 1.2, I still have not eliminated it. At the very least, here is an alternative way to draw a rounded rectangle:

<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml" width="400" height="55" creationComplete="init()">

<mx:Script>
<![CDATA[        

[Embed(source="/assets/bg.png")]
private const ImgBG:Class;

        
private function init():void {
                
var _radius:int = int( this.getStyle("cornerRadius") ) *1.2;
graphics.clear();
graphics.beginBitmapFill(new ImgBG().bitmapData );

//graphics.drawRoundRect(0, 0, unscaledWidth, unscaledHeight,_radius,_radius);
                
this.graphics.lineStyle(0,0,0);            
this.graphics.moveTo(_radius,0);
this.graphics.lineTo(unscaledWidth-_radius,0);
this.graphics.curveTo(unscaledWidth,0,unscaledWidth,_radius);            
this.graphics.lineTo(unscaledWidth,unscaledHeight-_radius);
this.graphics.curveTo(unscaledWidth,unscaledHeight,unscaledWidth-_radius,unscaledHeight);
this.graphics.lineTo(_radius,unscaledHeight);
this.graphics.curveTo(0,unscaledHeight,0,unscaledHeight-_radius);
this.graphics.lineTo(0,_radius);
this.graphics.curveTo(0,0,_radius,0);
this.graphics.endFill();

graphics.endFill();


}

]]>

</mx:Script>    
</mx:Canvas>

Comments (Comment Moderation is enabled. Your comment will not appear until approved.)
hsTed's Gravatar i've been needing to learn how to do this myself. good example!
# Posted By hsTed | 10/22/09 6:45 PM
Tony Fendall's Gravatar Is there a reason why you multiply the cornerRadius style value by 2 in your second example?
# Posted By Tony Fendall | 10/22/09 11:20 PM
jason olmsted's Gravatar @Tony: Yep, there is. As I was writing this post, it occurred to me that I could link the ellipse width and height to the cornerRadius style definition. I found out empirically that a 1:1 correspondence didn't do the trick, so I doubled it and then quickly tested over a range of radius values (8 to 16). It is a bit of a hack but it worked.

I just did another quick experiment that uses a series of lineTo's and curveTo's instead of the drawRoundRect on the thought that I could get better correspondence between the cornerRadius and the corner of the filled shape. Even there, I needed a fudge factor of ~1.2 to keep the background in the border (I guess like an adult version of trying to use crayons to color in a line drawing). I'll update the entry in a moment to include the code.

So, to make a short story long, the corner calculations for drawing produce noticeably different results than the mechanism constrained by cornerRadius.
# Posted By jason olmsted | 10/23/09 9:27 AM
BlogCFC was created by Raymond Camden. This blog is running version 5.9.1.001. Contact Blog Owner