Generic Background Gradient for Containers

A customer submitted a bug this week requesting an easy way to use background gradients in our containers like VBox, HBox, etc. This sounded like a great request and I was surprised that we didn’t already offer this in the framework. I logged the bug as bug SDK-111200 and sought the help of our developer Glenn who is the master of all things styles, skins and themes. Glenn offered the simple solution of using a style we already have called applicationControlBar. Here is an example where you can control the hightlights, colors and alphas of the gradient:

<?xml version=”1.0″ encoding=”utf-8″?>
<mx:Application xmlns:mx=”http://www.adobe.com/2006/mxml&#8221; backgroundColor=”white”>
<mx:Style>
.gradientBackground { borderStyle: applicationControlBar; fillColors: #EEEEFF, #8888AA; fillAlphas: 0.7, 0.7; highlightAlphas: 0, 0; }
</mx:Style>
<mx:VBox styleName=”gradientBackground” width=”300″ height=”300″ /> </mx:Application>

This worked great. However, the customer came back to tell me that this style shifted all of the components in the container by one pixel. Apparently, this is because the applicationControlBar style has a default 1 pixel border. Oh well. So, if someone wants a simple gradient, and doesn’t care about that one pixel border, use the code above.

However, Glenn came up with an even better solution. I have to repeat, all of these solutions were kindly provided by Glenn Ruehle. I was just the messenger in this transaction with the customer. Glenn went ahead and created a custom gradient class that you can use on any container and control the gradientType, fillColors, fillAlphas, angle, and focalPointRatio of the style. Here is what it looks like:

gradient

Here is the code for this simple Application:

<?xml version=”1.0″ encoding=”utf-8″?>
<mx:Application xmlns:mx=”http://www.adobe.com/2006/mxml&#8221; backgroundColor=”white” width=”200″ height=”170″ >
<mx:Style>
.gradientBackground1 {
backgroundImage: ClassReference(“GradientBackground”);
backgroundSize: “100%”;
fillColors: #EEEEEE, #999999;
fillAlphas: 0.5, 0.5;
}

.gradientBackground2 {
backgroundImage: ClassReference(“GradientBackground”);
backgroundSize: “100%”;
gradientType: radial;
fillColors: #FFCC33, #999999;
fillAlphas: 0.2, 0.5;
angle: 210;
focalPointRatio: 0.75;
}
</mx:Style>

<mx:VBox styleName=”gradientBackground1″ width=”100″ height=”50″ />
<mx:VBox styleName=”gradientBackground2″ width=”100″ height=”50″ />
</mx:Application>

And, here is Glenn’s code for the gradient background style:

package
{
import mx.skins.ProgrammaticSkin;
import flash.geom.Matrix;

public class GradientBackground extends ProgrammaticSkin
{
override public function get measuredWidth():Number
{
return 20;
}

override public function get measuredHeight():Number
{
return 20;
}

override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
var fillColors:Array = getStyle(“fillColors”);
var fillAlphas:Array = getStyle(“fillAlphas”);
var gradientType:String = getStyle(“gradientType”);
var angle:Number = getStyle(“angle”);
var focalPointRatio:Number = getStyle(“focalPointRatio”);

// Default values, if styles aren’t defined
if (fillColors == null)
fillColors = [0xEEEEEE, 0x999999];

if (fillAlphas == null)
fillAlphas = [1, 1];

if (gradientType == “” || gradientType == null)
gradientType = “linear”;

if (isNaN(angle))
angle = 90;

if (isNaN(focalPointRatio))
focalPointRatio = 0.5;

var matrix:Matrix = new Matrix();
matrix.createGradientBox(unscaledWidth, unscaledHeight, angle * Math.PI / 180);

graphics.beginGradientFill(gradientType, fillColors, fillAlphas, [0, 255] , matrix, “pad”, “rgb”, focalPointRatio);
graphics.drawRect(0, 0, unscaledWidth, unscaledHeight);
graphics.endFill();
}
}
}

Hopefully, this same example will show up in the Flex Cookbook where Glenn might post it himself.

52 responses

  1. great to see your blog, Joan! Looking fwd to more such tips! Rock On!

  2. great gradientBackground class, but you cannot have rounded corners

  3. I’ve modified the gradientBackgroundClass. Try this:

    http://butterfliesandbugs.bravehost.com/AS/GradientBackground.as

    If you set cornerRadius on your Container, it should be reflected by this gradient class.

  4. Thanks for a useful class… Is there any particular reason that the measuredHeight and measuredWidth are overridden to return 20? If you don’t do that then unscaled width and height come back as zero so I see that it’s necessary but I was wondering why 20 was chosen.

    I started looking into this because I have a requirement for a gradient background which is offset – so it only begins from a given x. I was going to pass another style property into the skin with the x offset but I can’t do this as the skin doesn’t actually know how wide it is so my offset is meaningless to it…

    Any ideas?

    Thanks πŸ™‚

  5. The measured width and height values of 20 were chosen arbitrarily. These can be safely changed to any non-zero values.

    If you need to offset the gradient drawing, I’d recommend writing a new borderSkin class instead of just a background image class. The updateDisplayList() function of the border skin will be called with the actual, unscaled size of the border/background, so you can draw wherever you’d like.

    A sample borderSkin can be found here:
    http://www.adobe.com/devnet/flex/quickstart/skinning_components/#programmatic

  6. Thanks, Glenn!

  7. Thanks Glenn – that’s exactly what I ended up doing yesterday πŸ™‚ I had a few problems because the border skin wasn’t being applied on my HBox which had me stumped for a while but I fixed it by explicitly setting a borderStyle: inset; as well.

    Thanks for the reply πŸ™‚

  8. Hi!
    That’s funny, I was doing some research on Google about the background gradients for Flex 3, and I found this blog entry… and in fact, I am the customer you talked with about the 1 pixel shift ! πŸ™‚

    I’m just disappointed to see that this background gradient functionnality has not been implemented in Flex 3 :/

    Keep the good work !

  9. I found in the Flex bug database :
    “Deferred for the future especially since there is a workaround.”

    I hope the future is not Flex 8!

  10. I just looked up a discussion that we had about this enhancement request from back in June, and the reason it was deferred was because the best practice for creating these gradients in the future will be “Graphic Tags” which I wrote about briefly in my blog. If you saw the early “Thermo” demos online or at one of the MAX conferences, you would have seen these Graphic tags in the code that was generated for the Flex app built. So, I was pushing for this bug, but, since there will be a better way to do it in the release after Moxie, then, Glenn didn’t want to put a workaround in there for Moxie. πŸ™‚ I hope that is a decent explanation. Isn’t our transparency great? You can find out what happens to all of your bug requests.

  11. very interesting, but I don’t agree with you
    Idetrorce

  12. What do you not agree with? Please be more specific. We appreciate your comments and suggestions and they do impact our decision making. While Flex 3 (Moxie) is wrapping up soon, your opinions can definitely still impact the future releases.

    Thanks, Joan

  13. Hi,

    Thanks a lot for your kind answer.
    I’ve just had a look at these Graphic tags, and they looks really great.

    It’s a real pleasure to see how you and your team are so responsive. We don’t feel alone as Flex developers!

  14. DarK RΓ©mi oF DooM | Reply

    Hi,

    First of all; I just discovered this Blog and it’s great.

    I just found out that the verification for the fillAlphas is incorrect. I tried not to define the fillAlphas in my CSS (as I assumed that the fillAlphas would be automatically set to 1,1) but the gradient didn’t appear. Therefore, I traced the fillAlphas array directly under “var fillAlphas:Array = getStyle(‘fillAlphas’);” and obtained “0.6,0.4,0.75,0.65”.

    After so researchs, I found out that the framework contains a default value for the fillAlphas (and for almost every other possible style declaration). Maybe I’m stuck with these because some properties of my project include the framework (even if it’s probably included in the default settings) but I thought it could be interesting to share this “discovery”. (The same problem would happen with fillColors too if you do not set them in the CSS.)

  15. Hi

    it doesn’t work here 😦
    i get several errors
    on the backgroundsize it says: invalid character : %

  16. Hi,

    Is there any way of applying 3 shades color gradient. e.g fillColors = [0xEEEEEE, 0x999999, 0x663300];

    Thanks Joan

    Daniel Moore

  17. In a somewhat related note, if you set an application’s “controlBar.visible” property to true then a 40px or so gap will be left at the top of your application before your content begins… yet Flex Builder gives neither a compile or run-time error if you have a direct child object of Application named “controlBar”.

    Doh.

  18. Great Post! I was wondering if you knew how to fix the gap issue involved with using the applicationControlBar. When I use your method to a apply a VBox gradient, it produces a gap and I am not sure how to get rid of it. Thank you!

  19. Great post. Thanks for helping other!

    Would it be a quick change to make the gradient fade from left to right, rather than bottom to top?

  20. I’ll answer my own question. Change 180 to 90 on the following line

    matrix.createGradientBox(unscaledWidth, unscaledHeight, angle * Math.PI / 90);

  21. Thanks a lot, very useful !
    What about clicking through gradient box (alpha > 0) ? πŸ™‚

  22. as jlafferty pointed out, if a component has a cornerRadius, this needs to be taken into consideration by the GradientBackground class. However because the gradientBackground gets resized, this needs to be taken into consideration when defining the cornerRadius. the cornerRadius also has to be doubled to arrive at the diameter as required by the Graphics.drawRoundRect function. Meaning that the line should read:
    graphics.drawRoundRect(0, 0, unscaledWidth, unscaledHeight,cornerRadius*2/this.scaleX, cornerRadius*2/this.scaleY);

  23. I want multiple colours in my grad! Gimmee gimmee gimmee!

  24. I also get errors on the style sheet

  25. does anyone know what measuredHeight() and measuredWidth() is for? My gradient only looks normal if I hardcode it to the element size (800 by 20) … but does that mean I need a separate instance of this class each time I want to use the gradient?

  26. Hey, Craig Grummitt made an important correction (4 comments above). The cornerRadius makes corners but they are not scaled properly and looked distorted. Replacing that one line with what Craig suggested will solve this problem. This was a real problem for me – thanks Craig!! Here’s the full code:

    package
    {
    import mx.skins.ProgrammaticSkin;
    import flash.geom.Matrix;

    public class GradientBackground extends ProgrammaticSkin
    {
    override public function get measuredWidth():Number
    {
    return 20;
    }

    override public function get measuredHeight():Number
    {
    return 20;
    }

    override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
    {
    var fillColors:Array = getStyle(“fillColors”);
    var fillAlphas:Array = getStyle(“fillAlphas”);
    var cornerRadius:int = getStyle(“cornerRadius”);
    var gradientType:String = getStyle(“gradientType”);
    var angle:Number = getStyle(“angle”);
    var focalPointRatio:Number = getStyle(“focalPointRatio”);

    // Default values, if styles aren’t defined
    if (fillColors == null)
    fillColors = [0xEEEEEE, 0x999999];

    if (fillAlphas == null)
    fillAlphas = [1, 1];

    if (gradientType == “” || gradientType == null)
    gradientType = “linear”;

    if (isNaN(angle))
    angle = 90;

    if (isNaN(focalPointRatio))
    focalPointRatio = 0.5;

    var matrix:Matrix = new Matrix();
    matrix.createGradientBox(unscaledWidth, unscaledHeight, angle * Math.PI / 180);

    graphics.beginGradientFill(gradientType, fillColors, fillAlphas, [0, 255] , matrix, “pad”, “rgb”, focalPointRatio);
    //graphics.drawRect(0, 0, unscaledWidth, unscaledHeight);
    graphics.drawRoundRect(0, 0, unscaledWidth, unscaledHeight,cornerRadius*2/this.scaleX, cornerRadius*2/this.scaleY);
    graphics.endFill();

    }
    }
    }

  27. Is there any way to apply a gradient fill to arbitrarily-drawn graphic shapes? for instance, I try this:

    g.beginGradientFill(/* params here*/ );
    g.moveTo(11, 7);
    g.lineTo(0, 13);
    g.lineTo(11, 19);
    g.moveTo(11, 7);
    g.endFill();

    , where g is a Graphics object. All I get is a solid color. Here’s the gradient matrix I’m trying:

    var matrix:Matrix = new Matrix();
    var boxWidth:Number = 12;
    var boxHeight:Number = 11;
    var boxRotation:Number = Math.PI/2; // 90οΏ½?
    var tx:Number = 0;
    var ty:Number = 0;
    matrix.createGradientBox(boxWidth, boxHeight, boxRotation, tx, ty);

    This is the little ‘triangle’ in the error tooltip that I’m trying to apply a gradient to.
    I’m I doing anything immediately obvious that’s wrong?

  28. Create an external style sheet then:
    This removes the annoying white fade at the top too.
    I set the swf to wmode to transparent in the HTML, and the div has a background image. So you get a nice navigation bar πŸ˜‰

    ApplicationControlBar{
    border: 0;
    borderStyle: none;
    fillAlphas: 0.0, 0.0;
    highlightAlphas: 0, 0;
    fillColors: #931F1F, #931F1F;
    cornerRadius: 0;
    paddingLeft: 8;
    dropShadowEnabled: false;

    }

  29. Thanks, this was useful. However, for some bizarre reason, this didn’t work in my application until I removed the ClassReference() in my CSS style declaration like so:

    background-image: “com.myapp.cairngorm.view.skins.GradientBackgroundSkin”;

    No idea why. Anyone have an idea?

  30. Ah, turns out I had a custom Canvas that had an overriden updateDisplayList() that didn’t call super.updateDisplayList(). Why this would cause that symptom, I haven’t a clue. Anyway, it seems to work either way now.

  31. I was able to use the original example presented (with applicationControLBar) and remove the added scrollbar/offset simply by adding paddingRight:-2; to the container element.

  32. Hi! I was surfing and found your blog post… nice! I love your blog. πŸ™‚ Cheers! Sandra. R.

  33. Are there any existing additions for CSS rounded bottom corners to the GradientBackground class?

  34. Hi there – thanks for this great little class!

    I found one error – if you resize the object, the skin doesn’t get redrawn correctly. I just added a graphics.clear(); before things start to get drawn and all is well. Here is the code:

    package
    {
    import mx.skins.ProgrammaticSkin;
    import flash.geom.Matrix;

    public class GradientBackground extends ProgrammaticSkin
    {
    override public function get measuredWidth():Number
    {
    // return any non-zero num
    return 1;
    }

    override public function get measuredHeight():Number
    {
    // return any non-zero num
    return 1;
    }

    override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
    {
    var fillColors:Array = getStyle(“fillColors”);
    var fillAlphas:Array = getStyle(“fillAlphas”);
    var cornerRadius:int = getStyle(“cornerRadius”);
    var gradientType:String = getStyle(“gradientType”);
    var angle:Number = getStyle(“angle”);
    var focalPointRatio:Number = getStyle(“focalPointRatio”);

    // Default values, if styles aren’t defined
    if (fillColors == null)
    fillColors = [0xEEEEEE, 0x999999];

    if (fillAlphas == null)
    fillAlphas = [1, 1];

    if (gradientType == “” || gradientType == null)
    gradientType = “linear”;

    if (isNaN(angle))
    angle = 90;

    if (isNaN(focalPointRatio))
    focalPointRatio = 0.5;

    var matrix:Matrix = new Matrix();
    matrix.createGradientBox(unscaledWidth, unscaledHeight, angle * Math.PI / 180);

    graphics.clear();

    graphics.beginGradientFill(gradientType, fillColors, fillAlphas, [0, 255] , matrix, “pad”, “rgb”, focalPointRatio);
    //graphics.drawRect(0, 0, unscaledWidth, unscaledHeight);
    graphics.drawRoundRect(0, 0, unscaledWidth, unscaledHeight,cornerRadius*2/this.scaleX, cornerRadius*2/this.scaleY);
    graphics.endFill();
    }
    }
    }

  35. Smart Quotes strike again! Why aren’t they called stupid quotes?

    Nice work!

  36. Great article! Helped me out a lot! However I found another bug too. If properties scaleX or scaleY are equal to 0, then an error is thrown. To solve this I added the following lines of code after the last conditional statement:

    if( this.scaleX == 0 )
    this.scaleX = 1.0;

    if( this.scaleY == 0 )
    this.scaleY = 1.0;

  37. Thanks, now I am an “expert” in background gradient πŸ™‚

  38. Cool, but it only works for SDKs prior to 4.0.

    1. How can I make it work for mx components in flex 4?

      1. @Umesh: This should continue to work for mx components in Flex 4. Does it not? Also, if you want a container with a background gradient in Flex 4, its really easy to use the Spark BorderContainer instead of any of the MX components. You can try that. Good luck!

  39. As most things. 4.0 mucks up a lot of stuff to make other stuff better.

  40. Thanks, When I try to use the above solution for a tab navigator, the gradient is drawn from the top of the tab bar and leaves an equal amount of space at the bottom. I tried changing a few param to start gradient from the bottom of the tab bar. but could not get the gradient fill till the bottom of the tab navigator viewstack. Any inputs in regard?

  41. When I try to implement this, the background image starts from the top of the tab bar and leaves a space equal to the tab bar height at the bottom of the container. I am able to adjust the starting position of the gradient, but the bottom space always remains. same is the case with any other image I use as BackgroundImage. Any idea how I can get the gradient to fill the entire container(excluding the tab bar).

  42. […] for Spark Containers One of my very first posts on this blog was about how to create a “Generic Background Gradient for Containers“. In Flex 3, you could only use styles to create solid backgrounds for your various MX […]

  43. A few people asked about getting this to work in Flex 4. I don’t have a solution, however I do know the problem. The validateDisplayList() of the ProgrammaticSkin class is never called, and therefore updateDisplayList() of this class is never called, never creating the actual gradient.

    I’m not sure why, but I assume it relates to changes with the LayoutManager.

    This is a great class despite this limtiation, though. Thanks very much.

  44. I know in Flex4, we should use BorderContainer to create the gradient background. But I just wonder why this code does not work in Flex 4. I tested it in View in a mobile project, and it really did NOT work.

    Could anyone give some explanation?

    1. @rocksoccer: I believe this doesn’t work in Flex 4 because it uses some styles that are not available in the Spark theme. The Spark theme is default in Flex 4. If you change your project to use the Halo theme which was default for Flex 3, I think this should work.

      1. Thanks for the reply. I am just a little curious what makes it not working.

      2. In Flex 4 backgroundImage is not a valid style for Container (VBox, HBox, etc). Change backgroundImage to borderSkin and everything else will fall in place.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: