Skinning a Flex Slider

[a part II of this post is available here]
It’s been a long long time since my last post but I recently move to my new apartment and it ISP forgot to move my internet access…It was a bit annoying at the beginning since I could not work on the version of NURVE, but I had a lot of time to explore new Flex features and especially customizing/creating UI components. I had so much fun doing this that I gonna write few posts on it.

The first component I worked on was the Slider (actually HSlider), because I think it’s the component that can be used in numerous cases and also because most of the time it doesn’t fit our my UI needs. Sorry to all the GREAT developers that worked on Flex UI components but the default Slider is just ugly…

Fortunately they add a cool feature that allow everyone to  customize the look and feel. (I think it’s important to understand that some of the time it may be interesting to change the look but changing the feel is also really important.) I won’t talk about the styles we can apply via CSS, but instead, how to use the sliderThumbClass and the trackSkin from the Slider Object.

Before jumping into the code, lets have a look to the result :

As you can see, no more default UI and a ‘Tooltip’ more useful. I used this Slider in one of my project where I had to navigate thru archives.

Ok so as I said I used sliderThumbClass to modify the ThumbSlider UI and the trackSkin in order to change the track of the slider.

The track skin:

This part is really easy, we just have to create a UIComponent with the desired shape. So just Create a new Class that extends UIComponent and override updateDisplayList function with your code that draws the shape you want.

package com.GuN.UI.customUIComponent.slider
{
	import mx.core.UIComponent;

	public class SliderTrack extends UIComponent
	{
			override public function get height():Number{
	            return 20;
	        }

        override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void{
            super.updateDisplayList(unscaledWidth, unscaledHeight);

            //create 2 circle that will act like round corners
            this.graphics.beginFill(0xFFFFFF,1);
            this.graphics.drawCircle(0,0,5);
            this.graphics.drawCircle(unscaledWidth,0,5);
            this.graphics.endFill();
            //create the line that represents the track
            this.graphics.moveTo(0,0);
            this.graphics.lineStyle(10,0xFFFFFF);
            this.graphics.lineTo(unscaledWidth,0);

        }

	}
}

I could use a drawRoundRect() aswell, but I will explain that on an other component customization post.
Ok now the track is now done, let’s have a look to the Thumb.

The slider Thumb

My Slider have a really simple thumb, it’s actually just a black circle. Like what we’ve done for the the track, we’ll do exactly the same for the Thumb. So create a class extends SliderThumb to inherit the behavior of a slider and override the updateDisplayList function.

package com.GuN.UI.customUIComponent.slider
{
	import mx.controls.sliderClasses.SliderThumb;

	public class CSpSliderThumb extends SliderThumb
	{

public function CSpSliderThumb()
		{
			super();
}

           override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void{
            super.updateDisplayList(unscaledWidth,unscaledHeight);
            this.graphics.beginFill(0x000000,1);
            this.graphics.drawCircle(2,-8,4);
           	this.graphics.endFill();
        }
     }
}

At this point you have a new slider without any cool effect. You can still continue using the default datatooltip but you can also create your very own datatooltip UI. On my slider I added a Sprite beneath the Thumb to display the data while sliding.

Here is the code for the datatip container :

package com.GuN.UI.customUIComponent.slider.sprites
{
	import flash.display.Sprite;
	import flash.text.TextField;
	import flash.text.TextFieldAutoSize;
	import flash.text.TextFormat;

	public class CSpSprite extends Sprite
	{
		var lbl:TextField;
		var bkgColor:uint = 0x000000;
		var bkgAlpha:Number = .5;

		var textColor:uint = 0xFFFFFF;

		public function CSpSprite()
		{
			super();
			lbl = new TextField();
			drawShape();
			drawText();

		}

		private function drawText():void{
			lbl.x = -10;
			lbl.y = 3;
			lbl.autoSize = TextFieldAutoSize.CENTER;
            lbl.background = false;
            lbl.border = false;

            var format:TextFormat = new TextFormat();
            format.font = "Verdana";
            format.color = textColor;
            format.size = 9;
            format.underline = false;

            lbl.defaultTextFormat = format;
            addChild(lbl);
		}

		private function drawShape():void
		{

			this.graphics.beginFill(bkgColor,bkgAlpha);
			this.graphics.lineTo(35,0);
			this.graphics.lineTo(40,-9);
			this.graphics.lineTo(45,0);
			this.graphics.lineTo(80,0);
			this.graphics.lineTo(80,25);
			this.graphics.lineTo(0,25);
			this.graphics.lineTo(0,0);
			this.graphics.endFill();

			//Create a white border
			this.graphics.lineStyle(1, 0xFFFFFF, 1);
			this.graphics.moveTo(-1,-1);
			this.graphics.lineTo(34,-1);
			this.graphics.lineTo(39,-10);
			this.graphics.lineTo(44,-1);
			this.graphics.lineTo(79,-1);
			this.graphics.lineTo(79,24);
			this.graphics.lineTo(-1,24);
			this.graphics.lineTo(-1,-1);

		}

		public function setValue(v:String):void{
			lbl.text = v;
		}

	}
}

We can add our datatipcontainer to the SliderThumbClass we created before

package com.GuN.UI.customUIComponent.slider
{
	import mx.controls.sliderClasses.SliderThumb;

	public class CSpSliderThumb extends SliderThumb
	{

public function CSpSliderThumb()
		{
			super();
			initSprite();
			addChild(spr);

		}

private function initSprite():void{
			spr = new CSpSprite();
			spr.x = -(spr.width/2)+4;
			spr.y = 9;
		}

           override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void{
            super.updateDisplayList(unscaledWidth,unscaledHeight);
            this.graphics.beginFill(0x000000,1);
            this.graphics.drawCircle(2,-8,4);
           	this.graphics.endFill();
        }
     }
}

Now that our Slider is almost done, we can create the mxml that will house the custom slider. Like I said I used this slider to navigate thru archives so used an array of values that contains all the months that had to work with.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" backgroundGradientAlphas="&#91;1.0, 1.0&#93;" backgroundGradientColors="&#91;#353535, #353535&#93;">
	<mx:Script>
		<!&#91;CDATA&#91;
			import com.GuN.UI.customUIComponent.slider.SliderTrack;
			import com.GuN.UI.customUIComponent.slider.CSpSliderThumb;

			var arrayValues:Array = &#91;"null","January '08", "February '08", "March '08", "April '08", "May '08", "June '08", "July '08", "August '08",
										"September '08", "October '08", "November '08", "December '08"&#93;;

		&#93;&#93;>
	</mx:Script>

		<mx:HSlider x="10" y="107"
		id="s"
		showDataTip="false"
		values="{arrayValues}"
		creationComplete="{s.value=1}"
		snapInterval="1"
		minimum="1"
		maximum="{arrayValues.length-1}"
		liveDragging="true"
		trackSkin="{SliderTrack}"
		sliderThumbClass="{CSpSliderThumb}"
		 width="502"/>

</mx:Application>

For more details you can have look to the sources my work, as usual you can freely reuse/hack my code. 
By the way, if you wanna see cool effects check Chet Haase’s ‘Codedependent’ Blog and especially the Spring is in the AIR? post which is really interesting.

25 Responses

  1. Nice example, that’s exactly what i was looking for.

  2. Glad it helped 🙂

  3. First of all thanks for this great article, it helped me a bunch styling my components.. Anyhow I’m having an issue with the original “ugly” thumb that is still showing up though I followed all your steps. I now see both thumbs (the circle and the small “original” arrow)

    Any idea what could be wrong?

    Thanks
    Tiago

  4. Hi Tiago,
    Not sure I really understand your problem. Do you have an example of your component I can see ?

    G

  5. Im having the same issue as Tiago.
    I see the original triangle thumb.

  6. […] in Bloggin’ This post is a quick follow up on my last skinning post, since I realized  that I should add some details about the sliderThumb […]

  7. Very informative article. Helped me a lot in getting a custom look for my video player.

    What is the best way to incorporate track highlighting? Would I have to somehow get the thumb position and draw a line on the track?

    Thanks for your help.

    -Raj

    • Submitted on 2009/04/12 at 5:49pm
      Hi Raj,
      regarding the track highlighting, I would simply go that way:

      Create a new skin for the highlight:

      package com.GuN.UI.customUIComponent.slider  
      {  
          import mx.skins.Border;  
        
          public class TestHighLight extends Border  
          {  
              public function TestHighLight()  
              {  
                  super();  
              }  
        
              override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void{  
                  this.graphics.clear();//clean previous graphics  
                  super.updateDisplayList(unscaledWidth,unscaledHeight);  
        
                  //draw the round border of the highlight  
                  this.graphics.beginFill(0xFF0000,1);//highlight Color  
                  this.graphics.drawCircle(0,-1,unscaledHeight/2);  
                  this.graphics.drawCircle(unscaledWidth,-1,unscaledHeight/2);  
                  this.graphics.endFill();  
        
                  //draw th body of the highlight  
                  this.graphics.moveTo(0,-1);  
                  this.graphics.lineStyle(10,0xFF0000);//highlight Color  
                  this.graphics.lineTo(unscaledWidth,-1);  
              }  
        
          }  
      }  
      

      and in your component creation, set your new class as the track highlghtskin:

      <mx:HSlider x="10" y="107"
      		...		
      showTrackHighlight="true"		trackHighlightSkin="com.GuN.UI.customUIComponent.slider.TestHighLight"
      .../>
      

      Hope it helped.

      G

  8. hi there!!
    thanks for sharing this topic

    best wishes
    avn.rocky

  9. Very nice example! the best skin is always the programmatic one 😉

  10. sweet…thanks for this posting. I was able to get this up and going in a matter of minutes though never having implemented a skin before.

  11. Nice example

  12. Nice, it’s strange that it’s working for you w/o clearing the graphics when you render the track skin. For me I need to add graphics.clear() before…

    • Strange indeed… What happens if you don’t clear the track skin ? Is your thumb still visible or is it just the track graphics that’s somehow dirty ?

      btw thanks for your feedback.

  13. In your CSpSliderThumb class above you have:

    spr = new CSpSprite();

    but you’ve never defined spr.

    I’m trying to run your example but flex builder is complaining about this. Have you forgot to define it or am I doing something wrong?

    Thanks,
    Patrick

  14. Hi Guillaume,

    In your example the datatip or “tool tip” strings are stored in an array. For my application I want to retrieve my tool tip strings from a web service. Therefore I populate an array on the fly as follows:

    var myArray:Array = new Array();

    for(all tool tips)
    myArray.push(tooltip)

    If I create the array in this way then the HSlider only sees the first item in the array.

    Do you know why?

    Thanks,
    Patrick

    • Hi Patrick,
      That’s weird… I made a quick test in order to populate tooltip data into an array (that should be quite the same behavior than what you are try to have)
      Here I used ‘anotherArray’ as tooltip dataprovider

      <?xml version="1.0" encoding="utf-8"?>
      <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" 
      	layout="absolute" backgroundGradientAlphas="&#91;1.0, 1.0&#93;" backgroundGradientColors="&#91;#353535, #353535&#93;" 
      	viewSourceURL="srcview/index.html" xmlns:textInput="com.GuN.UI.customUIComponent.textInput.*" 
      	creationComplete="init()"
      	>
      [...]
      var arrayValues:Array = ["null","January '08", "February '08", "March '08", "April '08", "May '08", "June '08", "July '08", "August '08",
      										"September '08", "October '08", "November '08", "December '08"];
      
      [Bindable] public var anotherArray:Array = new Array();
              
              public function init():void{
              	anotherArray = new Array();
              	for each(var s:String in arrayValues){
              		anotherArray.push(s);
              	}
              }
      
      <mx:HSlider x="10" y="107"
      		id="s"
      		showDataTip="false"
      		values="{anotherArray}"
      		creationComplete="{s.value=1}"
      		snapInterval="1"
      		minimum="1"
      		maximum="{arrayValues.length-1}"
      		liveDragging="true"
      		trackSkin="{SliderTrack}"
      		
      		sliderThumbClass="{CSpSliderThumb}"
      	
      		 width="502"/>
      
      

      And everything works fine, I can see all the tooltips.

  15. Thanks for this information.

    You may be interested in another approach I found which uses a custom image to replace the slider thumb:

    http://blog.flexexamples.com/2007/09/12/changing-a-slider-controls-thumb-skin/

  16. I am attempting to use your “skin” for two thumbs, but I am having trouble getting the proper new datatip. It seems to think there is only one thumb and will pick up where the last thumb left off, rather than incrementing based on the location on the track (the default datatip works properly). Can you offer any pointers straight away for working with two thumbs?

    Also, I am trying to induce the new datatip to display on default and remain displayed without any user interaction, but i am having some issues with that, too.

    Any help would be much appreciated.

    Thanks,
    Nick

  17. very good

    I wanted to make a custom HSlider component derivating from Adobe’s one but I faced a silly issue

    private function createBackgroundTrack():void
    private function createHighlightTrack():void
    private function createThumbs():void

    Everything is private, that’s really stupid in a “flexible” framework, it should be protected in order to let you users inherit and access properties by inheritance.
    What do they smoke at Adobe ?
    Now I have to copy the whole class and modify the visibility of those methods just to change those 3 functions and set the height value to the BackgroundTrack component.

    Silly, no perfectly stupid…

    • I dug, quite a lot, in Adobe’s classes and found weird things regarding some methods scope…
      But my understanding of the Flex component skinning is that Adobe only want you to use your own methods/behaviour and not override the default ones.
      That’s the point of:
      trackSkin
      sliderThumbClass
      …etc.

Leave a reply to Patrick Kiernan Cancel reply