11 Replies Latest reply on Apr 15, 2011 2:31 PM by CleanCoder

    Lack of Skinning Ability

    jamedo1234

      I will attempt to lay out an example of the problems I am running into.

       

      I intend to have a video Fullscreen button. I extend the spark Button, and create a MXML Component. The component should have two states, 'fullscreen' & 'normal'. Based on the display state of player, I toggle the states. All is well.

       

      Now I want to add in the UI for the button. If I add the UI into the Component directly, I can easily make use of the two custom states I have created to edit the UI based on these two states. However, I can't take advantage of the Button's over,disabled, etc states. So I attempt to follow Adobe's advice, and make a Skin to house these UI components instead.

       

      Now that the UI graphics are in a Skin, I get to take advantage of the Button skin states, which is great. However, I lose out on the component states I created earlier. This becomes a pretty obvious predicament. Even if I go through the hassle of adding skin states to the Button skin I am using, I can still only be in one state at a time. So even if I add a 'normal' state to the Button skin, I now am missing out on the Button states, because the skin can't be in 'over && normal', just one or the other.

       

      I suppose I could double the amount of states to have a 'over_normal' state, but this is extremely inconvenient, especially when I have a lot of custom states.

       

      The other solution I suppose is to have two skins, for each state. This is also extremely inconvenient. I don't want duplicate code everywhere.

       

      I'm confused as to how there is not an easy way to handle this. It seems like it should be a common issue.

       

      Please help.

        • 1. Re: Lack of Skinning Ability
          kevinklin Adobe Employee

          Hi,

           

          For our video player, we actually swap the button's skin between the "fullscreen" and "normal" states. I think that's probably the easiest and simplest way to go. In your VideoPlayer's skin, you can just have something like:

           

          <s:Button id="fullScreenButton"
                    skinClass="skins.NormalButtonSkin"
                    skinClass.fullscreen="skins.FullScreenButtonSkin" />
          

           

          That should give you the flexbility you need.

          -Kevin

          • 2. Re: Lack of Skinning Ability
            jamedo1234 Level 1

            Thanks for the response.

             

            This was something I was trying to avoid, only because it would seem silly to duplicate my UI into two different skins, just for a few changes related to states, that I can't connect to with on skin.

             

            Let's say my Skin is 100 lines of code. I have to duplicate it in another skin, and now maintain two of them. Meanwhile, I am only changing a few variables in the skin.

             

            I am really hoping this is not the only way, but I am starting to think this might be it in order to utilize a Skin, and utilize button states.

            • 3. Re: Lack of Skinning Ability
              CleanCoder Level 2

              Just to clarify, it sounds like you want a Button that changes its appearance based on a "normal" or "fullscreen" state, while still maintaining the normal button state behavior ("up", "over", "down", etc.). If that is your intent, I would suggest creating a "fullscreen" button state based on the "normal" button state, ("upAndFullScreen", "overAndFullScreen", "downAndFullScreen", "disabledAndFullScreen"). By using the "basedOn" property you should be able to avoid duplicating view state changes that are required in both "normal" and "fullscreen" states. This would also require you to extend the Button class method "getCurrentSkinState()" and append the "WithFullScreen" to the state being returned when in the "fullscreen" mode.

               

              Not sure if any of that helps, so take it for what its worth.  

              • 4. Re: Lack of Skinning Ability
                jamedo1234 Level 1

                Yeah this was the second solution I was also thinking of doing.

                 

                And yes, you are correct, I want to be able to take advantage of the button states, as well as my own custom states.

                 

                The problem here, is that I did not want to have 'x' number of custom states, plus the 4 button states, and have to combine them all, and have '4 * x' states to worry about. But I may be missing something based on how you explained it. I'm not quite sure how the 'basedOn' property works. Any way you could elaborate on your suggestion for using this?

                 

                What would be great, is instead of haveing stateGroups, there were stateCombinations. Meaning, I could be in multiple states at once. Although I suppose this may not agree with the theory of states, that is basically what I'm trying to do. For example, the 'over' state of a button, would never interfere with a custom state that I may need to use. I could be in a button state, and I could be in a custom state, both at the same time. This would be very helpful.

                • 5. Re: Lack of Skinning Ability
                  CleanCoder Level 2

                  I primarily do my skinning in pure Actionscript files not MXML, but I think stateGroups in MXML will work for you. I think the compiler ends up creating the necessary states with the "basedOn" properties already configured for you, I am just used to doing it manually in Actionscript. What changes do you want to make to the Button if it is in a "fullscreen" state?

                  • 6. Re: Lack of Skinning Ability
                    jamedo1234 Level 1

                    The only reason I am using MXML skins is that I am trying to become a model Flex developer and try to follow the standards for once. If you had a more ideal way of doing what I want to do, I am all ears. I am just starting to get into Flex (4), while previously using pure AS3.

                     

                    - I have a ButtonComponent that inherits from spark Button. (example : Fullscreen video button)

                    - I could either add my UI (graphics) to the component or to the skin. I am trying to make use of good practice, so a skin would be nice. Plus, I don't believe you can add anything to the Button component. I would have to change it to a container, and then implement Button characteristics on my own (who knows, this may be a better option).

                     

                    - My Component has two states, 'fullscreen' and 'normal'. This is fine. I switch between states based on the display state of the stage.

                    - My skin gets built, and uses the button Component as the Host. It adds the inherited Button skin states to the skin. All is well.

                     

                    Now, at this point, I can take advantage of the up, over, down, disabled, states. Specifically, I can edit the color (color.over='0x000000') of a few graphics based on the button skin state. This is good. This is one of the main reasons I am using this Button from spark, so I can use it's built in features.

                     

                    But there are other properties in the graphics that I would also like to edit based on certain circumstances. But those circumstances do not include up, down, over, disabled. They include 'x' number of other states, if you will. What I would love to do, is say something like :

                     

                    rotation.customState='180'

                     

                    &

                     

                    color.over='0x000000'

                     

                    But this customState would have NO relation to the button states this skin inherited. However, I can't just add this customState to the list of skin states, because they would interfere with the inherited button states (or so i assume). And while I am not sure if this would be true for all cases, the Component states I made in the HostComponent, are the same as the states I would like to add in the Skin.

                     

                    I don't want to duplicate code, and have multiple skins, each with the same UI code inside, with some minor tweaks. It seems I should be able to have 1 skin, that adjusts to different states.

                     

                    I also don't want a million different states in the Skin.

                     

                    I am hoping that perhaps stateGroups, or basedOn, or anything would allow me to do what I want.

                     

                    Sorry for the long description. Trying to make it as clear as possible. Again, I am open for other paths of doing this, but am trying to understand how to make this work following the suggested tools by Adobe.

                     

                    Thanks much.

                    • 7. Re: Lack of Skinning Ability
                      CleanCoder Level 2

                      Ok, couple things...

                       

                      Personally I was never a big fan of MXML skins (or MXML in general), but they definetely have become common because they seem to allow designers to develop skins without large amounts of code. They definetely are not the most efficient way of doing things however, and this is why the new mobile skins have gone back to pure AS3 skins.

                       

                      You are on the right path creating the states in your skins. You seemed to indicate that you are also creating the states in your host component, which should not be necessary, just create them once in your skin. The Button component basically just tells the skin what state it is in through "getCurrentSkinState()". The "up, over, down" states are required for the Button to properly display itself as a Button, so you will have to build off of those states to add functionality. You will need a state for each circumstance where you want to make view state changes ("upAndCustomState, "overAndCustomState", etc.). If you use stateGroups (or basedOn) the cost of having mulitple states should be minimal. For example, if a "customState" change occurs without user interaction, then the Button would still be in its "up" state. So you would create a "upStateGroup" and a "upAndCustomState". Assign the "upStateGroup" to both the "up" state and the "upAndCustomState".By overriding "getCurrentSkinState()" you can determine if your are currently in the "up" state and if you should be in the "upAndCustomState".

                       

                      This would make more sense if I posted an example. I'll try to put something up when I get some time.

                      • 8. Re: Lack of Skinning Ability
                        jamedo1234 Level 1

                        CleanCoder,

                         

                        Thanks for the explanation. After playing around with this idea, I got it to work...

                         

                        <s:State name="disabled" stateGroups="disabledGroup" />

                        <s:State name="down" stateGroups="downGroup"/>

                        <s:State name="over" stateGroups="overGroup" />

                        <s:State name="up" stateGroups="upGroup"/>

                        <s:State name="upFullscreen" stateGroups="upGroup, fullscreenGroup" />

                        <s:State name="downFullscreen" stateGroups="downGroup, fullscreenGroup" />

                        <s:State name="overFullscreen" stateGroups="overGroup, fullscreenGroup" />

                        <s:State name="disabledFullscreen" stateGroups="disabledGroup, fullscreenGroup" />

                         

                        - I override the getCurrentSkinState() in the hostComponent, and set it to the state I want.

                        - When I want a button related change to my graphics, I use the disabledGroup, downGroup, overGroup, upGroup on my properties.

                        - When I want a fullscreen related change to my graphics, I use the fullscreenGroup.

                         

                        This worked well. Thanks for helping me grasp the idea of groups and states.

                         

                        Now that you have answered my question, I would be interested to know what you would think is a better solution to using skins, or building components in general. I myself would prefer AS3 over MXML, but since I have not used Flex until recently, I was unsure how to take advantage of this in AS3. I decided to start with MXML. But I am always seeking to use the best possible method for accomplishing my needs.

                         

                        If you wouldn't mind, if you could speak to how you would go about accomplishing what I had wanted to do in this post, in your own prefered way, I'd love to hear it.

                         

                        Thanks again. Much appreciated for answering my question.

                        • 9. Re: Lack of Skinning Ability
                          CleanCoder Level 2

                          Glad to hear it worked for you, looks like you nailed the MXML version perfectly.

                           

                          As far as an AS3 implementation, here is a basic skin that you can use with a Button or a ToggleButton. After thinking about your use case a little more, I figured a ToggleButton would incorporate most of the features that you required. The ToggleButton being in the "selected" state is basically the equivalent of your "fullscreen" state (you can easily change the "AndSelected" back to "AndFullscreen" if you need. I included some simple SetStyle overrides just to demostrate, but you will definetely want to read up on SetProperty and AddItems if you intend on manipulating properties or children on view state changes.

                           

                          package
                          {
                          import mx.core.IStateClient2;
                          import mx.states.SetStyle;
                          import mx.states.State;
                          
                          import spark.components.Label;
                          import spark.components.supportClasses.ButtonBase;
                          import spark.skins.SparkSkin;
                          
                          public class SimpleButtonSkin extends SparkSkin implements IStateClient2
                          {
                               //---------------------------------------------------------------------------
                               //
                               //  Constants
                               //
                               //---------------------------------------------------------------------------
                               
                               /**
                                * 
                                */
                               private static const EXCLUSIONS:Array = ["labelDisplay"];
                               
                               //---------------------------------------------------------------------------
                               //
                               //  Constructor
                               //
                               //---------------------------------------------------------------------------
                               
                               /**
                                * 
                                */
                               public function SimpleButtonSkin()
                               {
                                    super();
                               }
                               
                               //---------------------------------------------------------------------------
                               //
                               //  Properties
                               //
                               //---------------------------------------------------------------------------
                               
                               //-----------------------------------
                               //  exclusions
                               //-----------------------------------
                               
                               /**
                                * 
                                */
                               override public function get colorizeExclusions():Array
                               {
                                    return EXCLUSIONS;
                               }
                               
                               //-----------------------------------
                               //  hostComponent
                               //-----------------------------------
                               
                               private var _hostComponent:ButtonBase
                               
                               /**
                                * 
                                */
                               public function get hostComponent():ButtonBase
                               {
                                    return _hostComponent;
                               }
                               
                               public function set hostComponent( obj:ButtonBase ):void
                               {
                                    _hostComponent = obj;
                               }
                               
                               //-----------------------------------
                               //  labelDisplay
                               //-----------------------------------
                               
                               private var _labelDisplay:Label;
                               
                               /**
                                * 
                                */
                               [Bindable]
                               
                               public function get labelDisplay():Label 
                               {
                                    return _labelDisplay;
                               }
                               
                               public function set labelDisplay( value:Label ):void
                               {
                                    if (_labelDisplay == value)
                                    {
                                         return;
                                    }
                                    _labelDisplay = value;
                               }
                               
                               //--------------------------------------------------------------------------
                               //
                               //  Methods public [override]
                               //
                               //--------------------------------------------------------------------------
                               
                               /**
                                * 
                                */
                               override public function initialize():void
                               {
                                    super.initialize();
                                    
                                    initStates();
                               }
                               
                               //---------------------------------------------------------------------------
                               //
                               //  Methods protected [override]
                               //
                               //---------------------------------------------------------------------------
                               
                               /**
                                * 
                                */
                               override protected function createChildren():void
                               {
                                    super.createChildren();
                                    
                                    var labelDisplay:Label = new Label();
                                    labelDisplay.id = "labelDisplay";
                                    this.labelDisplay = labelDisplay;
                                    addElement(labelDisplay);
                               }
                               
                               //---------------------------------------------------------------------------
                               //
                               //  Methods protected
                               //
                               //---------------------------------------------------------------------------
                               
                               /**
                                * 
                                */
                               protected function initStates():void
                               {
                                    var states:Array = [
                                         new State({
                                              name:"up",
                                              overrides:[
                                                   new SetStyle(this.labelDisplay, "color", 0x000000)
                                              ]
                                         }),     
                                         new State({
                                              name:"over",
                                              overrides:[
                                                   new SetStyle(this.labelDisplay, "color", 0xEC66CC)
                                              ]
                                         }),     
                                         new State({
                                              name:"down",
                                              overrides:[
                                                   new SetStyle(this.labelDisplay, "color", 0xEC6B23)
                                              ]
                                         }),
                                         new State({
                                              name:"disabled",
                                              overrides:[
                                                   new SetStyle(this.labelDisplay, "color", 0xCCCCCC)
                                              ]
                                         }),
                                         new State({
                                              name:"upAndSelected",
                                              basedOn:"up",
                                              overrides:[
                                                   new SetStyle(this.labelDisplay, "color", 0x00FF00)
                                              ]
                                         }),
                                         new State({
                                              name:"overAndSelected", 
                                              basedOn:"over",
                                              overrides:[
                                                   new SetStyle(this.labelDisplay, "color", 0xFF0000)
                                              ]
                                         }),
                                         new State({
                                              name:"downAndSelected", 
                                              basedOn:"down",
                                              overrides:[
                                                   new SetStyle(this.labelDisplay, "color", 0x0066FF)
                                              ]
                                         }),
                                         new State({
                                              name:"disabledAndSelected",
                                              basedOn:"disabled",
                                              overrides:[
                                                   new SetStyle(this.labelDisplay, "color", 0xCCCCCC)
                                              ]
                                         })
                                    ];
                                    
                                    this.states = states;
                               }
                          }
                          
                          }
                          
                          • 10. Re: Lack of Skinning Ability
                            jamedo1234 Level 1

                            CleanCoder,

                             

                            Thanks for the example.

                             

                            Your time has been much appreciated.

                            • 11. Re: Lack of Skinning Ability
                              CleanCoder Level 2

                              Glad to help.