4 Replies Latest reply on Nov 8, 2009 5:22 PM by David_F57

    Skins that effect the host component behaviour.

    David_F57 Level 5

      There was an interesting question that was raised in the pre-release forums about what is the appropriate way to handle animations between the skin and its host. Basically the issue was if there is an animation in the host and another in the skin what would be the best way to code it so that both animations ran in parallel, My thoughts are why not do it all in the skin. this example animates a container by resizing it and centering it in the application.

       

      I figured it would be an interesting topic for those that are trying adding extra component functionality into the skin.

       

      @PD - Maybe you could apply a little of your magic to something like this and add it your blog.

       

      David

       

       

       

      The App
      =============================================================
      <?xml version="1.0" encoding="utf-8"?>
      <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"
        creationComplete="application1_creationCompleteHandler(event)" width="100%" height="100%">
      <s:layout>
      <s:BasicLayout/>
      </s:layout>

       

      <fx:Script>
      <![CDATA[
      import mx.collections.ArrayCollection;
      import mx.events.FlexEvent;

       

      protected function application1_creationCompleteHandler(event:FlexEvent):void
      {
      menu1.verticalCenter=height/2*-1 + 35; 
      menu1.horizontalCenter=width/2*-1 + 110;
      }

       

      ]]>
      </fx:Script>
      <s:SkinnableContainer id="menu1" left="10" top="10" width="200" height="50"
      skinClass="SkinnableContainerSkin2" backgroundColor="#A16969">
      </s:SkinnableContainer>
      </s:Application>

       

      =============================================================
      The Skin
      =============================================================
      <?xml version="1.0" encoding="utf-8"?>
      <s:Skin xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark"
          xmlns:fb="http://ns.adobe.com/flashbuilder/2009" alpha.disabled="0.5" creationComplete="skin1_creationCompleteHandler(event)">

       

      <fx:Declarations>
      <s:Parallel id="sizer">
      <s:Animate target="{hostComponent}" duration="2000" repeatCount="1">
      <s:SimpleMotionPath id="setheight" property="height" valueTo="500"/>
      </s:Animate>
      <s:Animate target="{hostComponent}" duration="2000" repeatCount="1">
      <s:SimpleMotionPath id="setvertical" property="verticalCenter" valueTo="0"/>
      </s:Animate>
      <s:Animate target="{hostComponent}" duration="2000" repeatCount="1">
      <s:SimpleMotionPath id="sethorizontal" property="horizontalCenter" valueTo="0"/>
      </s:Animate>
      </s:Parallel>

       

      </fx:Declarations>

       

      <fx:Metadata>
          <![CDATA[
              [HostComponent("spark.components.SkinnableContainer")]
          ]]>
          </fx:Metadata>
         
          <fx:Script fb:purpose="styling">
              <![CDATA[       
      import mx.events.FlexEvent;
      import mx.core.FlexGlobals;
      private var Vert:int;
      private var Horz:int;

       

                  override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number) : void
                  {
                      bgFill.color = getStyle("backgroundColor");
                      bgFill.alpha = getStyle("backgroundAlpha");
                     
                      super.updateDisplayList(unscaledWidth, unscaledHeight);
                  }

       

      protected function resizeMe(e:MouseEvent): void
      {
      Vert = int(FlexGlobals.topLevelApplication.contentGroup.height/2*-1)+35;
      Horz = int(FlexGlobals.topLevelApplication.contentGroup.width/2*-1)+110;
      if (hostComponent.height < 51)
      {
      setheight.valueTo=500;
      setvertical.valueTo=0;
      sethorizontal.valueTo=0;
      }
      else
      {     
      setheight.valueTo=50;
      setvertical.valueTo=Vert;
      sethorizontal.valueTo=Horz;
      }
      sizer.play();
      }

       

      protected function skin1_creationCompleteHandler(event:FlexEvent):void
      {
      Vert = int(FlexGlobals.topLevelApplication.contentGroup.height/2*-1);
      Horz = int(FlexGlobals.topLevelApplication.contentGroup.width/2*-1);
      }

       

              ]]>       
          </fx:Script>
         
          <s:states>
              <s:State name="normal" />
              <s:State name="disabled" />
          </s:states>
         
          <s:Rect left="0" right="0" top="0" bottom="0">
              <s:fill>
                  <s:SolidColor id="bgFill" color="0x00DDDD"/>
              </s:fill>
          </s:Rect>
         
          <s:Group id="contentGroup" left="0" right="0" top="0" bottom="0" minWidth="0" minHeight="0" click="resizeMe(event)">
              <s:layout>
                  <s:BasicLayout/>
              </s:layout>
          </s:Group>

       

      </s:Skin>

        • 1. Re: Skins that effect the host component behaviour.
          mewk Level 3

          David,

           

          Thank god for original thinking! I would never have thought to code it this way. And now, with that said, let me just say: absolutely no. No way, never!!

           

          This essentially creates one source for the component class and undoes a major feature of gumbo, which is a separation of view and logic. There are a few problems I see (and trust me, I'll be thinking about this more over the weekend) and the one I'll mention first is coding a click event inside the skin class. Now, what if I want to code more behavior on a click event? -- say to change colors, or display text, or bounce the component up and down? I could easily encapsulate these new methods on the component class and have the click use whatever method I choose, but in your code the solution would be much less flexible.

           

          There's a lot more to say on the subject, but I'll save it for tomorrow (sleep awaits! ). I would like to just add that I was looking at the Border class in the SDK today and was surprised to see how much logic coding they had crammed into the skin, so maybe I'm wrong (impossible of course).

           

          Hopefully, this post won't get lost in the weekend lull; it's a great example and it would be great if some of the more seasoned vets could share their thoughts. Well, until tomorrow,

           

          - e

          • 2. Re: Skins that effect the host component behaviour.
            David_F57 Level 5

            Hi Mewk,

             

            Love the purist attitude .....

             

            Me i'm the guy that used exit in loops in the old gwbasic days...they wouldn't have put it there if they didn't want me to use it(my professor from the old college days still hates me 3 decades later for converting his whole class to exit (ab)users).

             

            The click was there just to run the effect(I agree not something that would get used in production). It would more likely be triggered by changing the skins state from "not_so_big" to "a_lot_ bigger".

             

            I just thought now is a good time to provoke a bit of thought into just where the line should be drawn between good practise and bad practise on the use of the skin class.

             

            David.

            • 3. Re: Skins that effect the host component behaviour.This is
              rfrishbe Level 3

              This is a good question.

               

              There's no hard and fast rule to apply which says "this belongs in the skin" vs. "this belongs in the component".  Similarly, there are also no hard and fast rules around when to use a the new skinning architecture vs. just creating a custom component.  Just do whatever you feel comfortable with and makes your job easier.  At the end of the day, it's about productivity and not living up to ideals.  That said, there are probably some easier and more logical ways to do some things.

               

              On the skinning architecture vs. custom component debate, with a SkinnableComponent we have a clear separation of the component properties and behavior on one side and the look and feel of the component on the Skin side.  Also, there's a clear contract we use to talk back and forth to one another.  The reason for the separation between the Skin and the SkinnableComponent is so that we can have one Button SkinnableComponent and multiple Skins for that Button which all tweak the visual appearance of it.

               

              It doesn't make sense for every component to be skinnable.  If you know what your component is going to be and look like and don't need the extra flexibility skinning provides, then you can get rid of the extra overhead that skinning requires (like having 2 classes).  An example custom component is:

               

              <s:Group>

              <s:Rect>

              ...

              </s:Rect>

               

              <mx:Image src="..." />

              <s:Panel skinClass="myCustomSkinClass">

              ....

              </s:Panel>

              </s:Group>

               

              If you want more flexibility and want the ability to easily change the look and feel of the component (i.e. skin it), then you'd extend SkinnableComponent, fill out the skinning lifecycle methods, and create a default Skin for its appearance.

               

              Now, when you're building a SkinnableComponent, there's always a question of what to put in the component vs. what to put in the skin.  In general, we try to put the core properties and behaviors in the component and anything visual in the skin.  However, another guideline to consider is whether all skins would want this behavior.  If so, then it makes sense (and makes your life easier) to put it in the SkinnableComponent rather than the Skin.  We do this in the framework for components like VSlider, where the logic for positioning the y-axis of the thumb is in the component and not the skin, even though it's a "visual" thing.  We also have discussed how we would build up a ColorPicker component, and I think the way we would go about it is by putting a lot of the "visual" logic in the component because otherwise we'd have to duplicate it across all skins.

               

              Now, the other question you guys are asking here are "when do I bake effects (or any behavior) in to the component (either in the skin or in the SkinnableComponent AS class) vs. when do I declare effects alongside the component".  Again, I think the answer to that is whether you want all your components to have this behavior.  If that was the case, then I'd lose no sleep baking it in to the component.  However, if it's not the case, then I'd make the end-developer delcare it when they use your component, like:

               

              <s:MyCustomComponent id="myComponent" />

              <s:Resize id="resizer" widthTo="100" heightTo="50" target="{myComponent}"/>

               

              I would think most of the time, you probably wouldn't want to bake an effect like that in to the component, especially because it has some sizing information on it.  However, we have some effects baked in to some of the framework components, like when the thumb of a Slider moves around due to someone clicking on the track.  I think it's fine that it's baked in to the component, but I do think it should probably be stylable so that a user can customize it (that's on our list of lower-priority things to do btw).

               

              The framework has definitely evolved.  I think we started out with a more purist attitude and wanted a clear separation between the skin and the component.  However, as we built out components, we realized it's not always practical to do that.  Similarly, we wanted our skins to be pure MXML; however, for usability reasons, we decided that our skins should be styleable, and that requires a little bit of ActionScript code.  Border is a great example where it doesn't really follow a lot of these guidelines, but it's just a styleable component; however, this component makes other people's jobs easier.  At the end of the day, it's about productivity and usability, and hopefully the Spark architecture is a step in the right direction.

               

              Anyways, I hope that helps some.  These are just some guidelines.  As people play around with the architecture more, I'm sure some other people will have some good advice to share as well.

               

              -Ryan

              • 4. Re: Skins that effect the host component behaviour.This is
                David_F57 Level 5

                Hi Ryan,

                 

                Thanks for the input, my first thoughts when I saw the new skins(apart from the 'lets make it pretty' factor)  was that this is a really great way to expand a components functionality without having to create a custom component and all the associated grief that can come with that decision.

                 

                some ideas...(just a couple as I have about 2 dozen in the play stage at the moment)

                 

                You want a collapsible panel - put the code in a skin then not only do you have a collapsable skin component but any existing projects can be updated quickly, simply by apply the skin no new code to worry about, reduced risk of breaking functionality or introducing new bugs(something testers like to hear).

                 

                You want a reflector class - again no new code just put the reflector in a skin, now you don't need to define the class for each reflected component  (http://gumbo.flashhub.net/reflect/ my first attempt )

                 

                Scrapbooking - frames around images, skins (ok maybe the new border class would be good here).

                 

                The beauty is once a skin is working it can be used to update existing apps quickly and avoid those wonderous bugs that seem to appear as soon as you decide an existing component should be customised or replaced because you needed to add what started off as a simple function.

                 

                David.