Friday, March 26, 2010

Skins Question

I have worked in flex 3 for a while and I am trying to wrap my head around skins. I see how they work but my question is, how do you configure the skin in the mxml component that is using it? For example:

%26lt;s:Application xmlns:fx=''http://ns.adobe.com/mxml/2009''
?xmlns:s=''library://ns.adobe.com/flex/spark''
?xmlns:mx=''library://ns.adobe.com/flex/halo''
?skinClass=''com.company.components.skins.GradientSkin''%26gt;

...

How do i pass params to that skin to tell it what color the gradient should be? Also my skin defines the content area group which from what I can tell replaces teh padLeft,padRight etc. How would I specify in the application what the padding is? I don't want to put that in the skin class as hard coded because I cannot reuse it then.

Any thoughts on how I am looking at this incorrectly or what I can do to paramatize my skin cleanly? Thanks.

Skins Question

You can wire up your skins to custom data by:

1. Creating a custom component that inherits from the type of object you are skinning.

2. Adding your custom properties.

3. Binding within the skin.

For example, if I wanted to skin a RadioButton, but pass through a backgroundColor, I might do something like the following:

public class MyRadioButton

{

[Bindable] public var myBackgroundColor:int;

}

Then, in my skin, I would do something like:

%26lt;Rect%26gt;

%26lt;Fill%26gt;

%26lt;SolidColor color=''{hostComponent.myBackgroundColor}''/%26gt;

%26lt;/Fill%26gt;

%26lt;/Rect%26gt;

Especially note the ''hostComponent'', which is the property that grants object access from the skin. Then, in your app, use your component:

%26lt;Application ...%26gt;

%26lt;myns:MyRadioButton myBackgroundColor=''0xFF0000''/%26gt;

%26lt;/Application%26gt;

Skins Question

Sorry for the delayed response, work got busy.

This gets me part way there but I still see a problem. First before that though since I wanted to use this on the application subclassing is probably out right? So i did the following:

%26lt;?xml version=''1.0'' encoding=''utf-8''?%26gt;
%26lt;s:Application xmlns:fx=''http://ns.adobe.com/mxml/2009''
?xmlns:s=''library://ns.adobe.com/flex/spark''
?xmlns:mx=''library://ns.adobe.com/flex/halo''
?xmlns:ec=''components.*''
?xmlns:econ=''com.ericsson.components.containers.*''
?skinClass=''com.ericsson.components.skins.GradientSkin''
?width=''100%'' height=''100%''
?creationComplete=''loginComponent.activated()'' xmlns:components=''com.ericsson.components.*''%26gt;
?%26lt;fx:Style source=''ericsson.css'' /%26gt;
?%26lt;fx:Declarations%26gt;
?%26lt;fx:int id=''gradientAngle''/%26gt;
?%26lt;fx:int id=''gradientStartColor''/%26gt;
?%26lt;fx:int id=''gradientEndColor''/%26gt;
?%26lt;/fx:Declarations%26gt;

To give me 3 bindable variables without having to have an as. Is this ok?

Ok now for the problem. In the examples I can find, when you do:

%26lt;fx:Metadata%26gt;
?[HostComponent(''components.Grade'')]
%26lt;/fx:Metadata%26gt;

You have to specify a class that your skin is for. But that defeats the purpose of having a reusable gradient skin. I was hoping it would be useable on any component, not just instances of class X. Any thoughts on this? I would also like to avoid having to have a subclass of every class that I want to skin, which is why i tried the Decleration thing above.

Also it seems to work if I specify the skinclass as a child rather then an attribute and then pass params to it via its attributes.

So like this:

%26lt;s:Application ..../%26gt;

%26lt;s:layout%26gt;...%26lt;/s:layout%26gt;

%26lt;me:MySkin startColor=''0x000000'' endColor=''0x000000''/%26gt;

%26lt;s:Panel ... rest of content/%26gt;

However , the skin then gets layed out and I assume that means its not really the skin, and just a child. Also to have a custom skin on your application, for example to have a gradient background for your app, is it a good practice to subclass the Application class so that you could specify bindable params if you were to do it the way you show above?

There is a way you can define a skin inline: http://blog.flexexamples.com/2009/03/04/setting-a-custom-skin-on-an-fxbutton-con trol-in-flex-gumbo/ though I don't think that solution scales very well as you end up with lots of stuff in one file.

The better way to do this is through CSS styling, which is what we use in the components in the framework.?In the skin, you can do something like:

%26lt;s:Rect%26gt;

%26lt;s:fill%26gt;

%26lt;s:SolidColor color=''{getStyle('myNewColor')}'' /%26gt;

%26lt;/s:fill%26gt;

%26lt;/s:Rect%26gt;

or you could bind it to your hostComponent's styles:

%26lt;s:Rect%26gt;

%26lt;s:fill%26gt;

%26lt;s:SolidColor color=''{hostComponent.getStyle('myNewColor')}'' /%26gt;

%26lt;/s:fill%26gt;

%26lt;/s:Rect%26gt;

Rather than binding, you could also override updateDisplayList(w,h) to push the style in, like:

%26lt;s:Rect%26gt;

%26lt;s:fill%26gt;

%26lt;s:SolidColor id=''myFill'' color=''{hostComponent.getStyle('myNewColor')}'' /%26gt;

%26lt;/s:fill%26gt;

%26lt;/s:Rect%26gt;

override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
?myFill .color = hostComponent.getStyle(''myNewColor'');
super.updateDisplayList(unscaledWidth, unscaledHeight);
}

Without subclassing, you won't be able to set the styles directly on the component in MXML because you need to add the style metadata to the component, but you can style it through the other ways to set CSS without any problems.

Hope that helps,

Ryan

This was what I was looking for. I am 98% of the way there now. I implemented as you suggested but I still am not getting it right.

I removed almost all the code to make this as simple as possible. I just wanted to get a gradient on the applications background. Here is what I have.

%26lt;?xml version=''1.0'' encoding=''utf-8''?%26gt;
%26lt;s:Application xmlns:fx=''http://ns.adobe.com/mxml/2009''
?xmlns:s=''library://ns.adobe.com/flex/spark''
?skinClass=''com.ericsson.components.skins.GradientSkin''
?width=''100%'' height=''100%''%26gt;
?%26lt;fx:Style%26gt;
?@namespace s ''library://ns.adobe.com/flex/spark'';
?s:Application
?{
?skinGradientRotation:90;
?skinGradientStartColor:#000000;
?skinGradientEndColor:#FFFFFF;
?}
?%26lt;/fx:Style%26gt;
%26lt;/s:Application%26gt;

And the skin

%26lt;?xml version=''1.0'' encoding=''utf-8''?%26gt;
%26lt;s:Skin xmlns:fx=''http://ns.adobe.com/mxml/2009''
?xmlns:s=''library://ns.adobe.com/flex/spark''
?xmlns:mx=''library://ns.adobe.com/flex/halo''%26gt;
?%26lt;fx:Metadata%26gt;
?%26lt;![CDATA[
?[HostComponent(''spark.components.Application'')]
?]]%26gt;
?%26lt;/fx:Metadata%26gt;
?
?%26lt;s:states%26gt;
?%26lt;s:State name=''normal'' /%26gt;
?%26lt;s:State name=''disabled'' /%26gt;
?%26lt;/s:states%26gt;

?%26lt;!-- fill --%26gt;
?%26lt;s:Rect id=''backgroundRect'' left=''0'' right=''0'' top=''0'' bottom=''0''%26gt;
?%26lt;s:fill%26gt;
?%26lt;s:LinearGradient rotation=''{hostComponent.getStyle('skinGradientRotation')}''%26gt;
?%26lt;s:GradientEntry color=''{hostComponent.getStyle('skinGradientStartColor')}'' /%26gt;
?%26lt;s:GradientEntry color=''{hostComponent.getStyle('skinGradientEndColor')}'' /%26gt;
?%26lt;/s:LinearGradient%26gt;
?%26lt;/s:fill%26gt;
?%26lt;/s:Rect%26gt;

?%26lt;s:Group id=''contentGroup''/%26gt;
%26lt;/s:Skin%26gt;

I even added a trace of the rotation value and its comming back as undefined. So I think I'm not quite linking the css right.

Any ideas? Also I plan to use HostComponent(''mx.core.UIComponent'') so its reusable for other classes. Is that legitamite to use the baseclass?

Thanks for all your help.

Everything in your example is right, except for one small thing.?With your code currently, you should be getting a warning, like:

Type 's' in CSS selector 's' must be qualified with a namespace.

The reasons is because you need to use: s|Application instead of s:Application

Also, I mis-spoke a bit about the styling.?You can just use:

%26lt;s:LinearGradient rotation=''{getStyle('skinGradientRotation')}''%26gt;

There's no need to use hostComponent.getStyle() because the Skin's styles are the same as the hostComponent's styles.

-Ryan

Thanks. They are comming through in traces now. The draws as black now(replacing those getStyles with 0x000000 and 0xFFFFFF fixes it so I know everything else is right). I will fiddle with the casting. I added as uint but that didn't help but I am convinced its something along those lines. I will post the working solution when I get it. On a side note can you confirm my understanding of the groups in a skin.

-Drawing done in the main body of the skin applies to the whole component

-Drawing done in a group(example top bar or content group) is drawn wherever that component places its top bar group.

Tom,

Sorry, I don't think I understand your question.?I'm not sure what you mean by ''drawing.''?If you're just talking about where you place your Graphic objects, like Rect or Ellipse, then they are drawn in to their container (such as a Group).?If that Group is moved to be at (100,100) instead of (0,0), then all of the graphics objects inside of them (and all the containers with graphics) move as well.?The ''Skin'' object represents the visuals for the component, so if the component moves, the skin and everything inside of the skin moves as well.

-Ryan

Right thats the drawing i was talking about. I was refering to how your skin can have multiple groups tied to the parent compenent. Like for example application requires content group. I was infering that meant if i had a title bar required group, my parent component probably places its title bar somewhere and all my rects, elipses are offset from that location if they are in the group that matches title bar.

so it turns out this solution doesn't appear to work. During the creation, the host component is not available and its styles are not yet part of the skin's styles. Atleast from what I can tell. The style values are 0 and if I try to get them directly from the host component it throws an error. This makes sense I guess as you probably create the skin, and then attach it to a host. So no styles during creation. So this leads me back to how can I use the host's styles\vars\something to configure the skin. Perhaps skins need to be setup during a later event? I liked the simplicity of building the skin right in the mxml though.. Here is what I did to get to my conclusion:

%26lt;fx:Script%26gt;
?%26lt;![CDATA[
?private function getRotation():uint
?{
?return getStyle(''skinGradientRotation'');
?}
?private function getStartColor():uint
?{
?return getStyle(''skinGradientStartColor'');
?}
?private function getEndColor():uint
?{//**********************************************************************

//Here if you have hostComponent it bombs, if you dont it will be 0, and

//if you check this same style later(Like updateDisplayList) it has the right value
?var val:uint=hostComponent.getStyle(''skinGradientEndColor'');


?if(val is uint)
?{
?trace(''yes'');
?}
?trace(val);
?return val;
?}
?]]%26gt;
?%26lt;/fx:Script%26gt;

?%26lt;!-- fill --%26gt;
?%26lt;s:Rect id=''backgroundRect'' left=''0'' right=''0'' top=''0'' bottom=''0''%26gt;
?%26lt;s:fill%26gt;
?%26lt;s:LinearGradient rotation=''{getRotation()}'' %26gt;
?%26lt;s:GradientEntry color=''{getStartColor()}''?/%26gt;
?%26lt;s:GradientEntry color=''{getEndColor()}'' /%26gt;
?%26lt;/s:LinearGradient%26gt;
?%26lt;/s:fill%26gt;
?%26lt;/s:Rect%26gt;

I think the way I am thinking about skins is the problem. I am looking for a reusable thing so i can stick an any color gradient on an application and never have to make a new skin. I am thinking the way skins are desigend is to give a specific look to everything. Like a ''green frog'' skin makes everything green. And you just make a new skin for every application that you want a different color scheme.

I think your issue is that you need to supply [HostComponent] metadata for the hostComponent property to actually work.

Also, as I said before, you don't need to use hostComponent.getStyle(''myStyle''), but should just be able to use getStyle(''myStyle'').?If you use that in binding expressions, I don't think you should run in to any problems.

-Ryan

Hey. In my above post I mentioned I tried getStyle with and without host component. The host component is null during init which is the root problem. Try the example yourself and see. The styles are all there after init completes so the styles are being set and read correctly.

No comments:

Post a Comment