As currently developing an mobile application with Flex Mobile, got to the point of finalising things and make some fine visual layout adjustments. As my Application is built in sections using TabbedViewNavigatorApplication i wanted to add and fine tune icons that appear in the TabbedViewNavigator #tabBar tabs. Well, that was one of those tasks that seems to take minutes, ending in searching Adobe api docs and googling around for hours, eventually to find the solution in days. Said so i want to post my solution, hoping that someone will need it and use it in their own specific situations.
The first thing to mention here is that Adobe per default, has an extensive help library for skinning spark components, but eventually and on the end - those examples often only scratch the surface of what is needed in specific situation and / or implementation.
At first i want to say that I avoid using mxml skins and always do my spark skinning job in AS3 classes using ClassReference. Seems to me that this approach is much more flexible, elegant and cleaner than over-spamming things in mxml. Although sometimes more harder to implement.
So your typical minimal TabbedViewNavigator Application looks like this:
<?xml version="1.0" encoding="utf-8"?>
<s:TabbedViewNavigatorApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark" applicationDPI="160" applicationComplete="onApplicationComplete(event)">
<fx:Style source="MyStyles.css" />
<fx:Script>
<![CDATA[
import spark.components.ViewNavigator;
private function onApplicationComplete(e:Event):void {
for each(var vn : ViewNavigator in this.navigators){
/// do something with your VN's ...
vn.icon = "./assets/myFaboulousIcon.png";
}
}
]]>
</fx:Script>
<s:ViewNavigator label="one" width="100%" height="100%" firstView="views.oneView"/>
<s:ViewNavigator label="two" width="100%" height="100%" firstView="views.twoView"/>
<s:ViewNavigator label="three" width="100%" height="100%" firstView="views.threeView"/>
</s:TabbedViewNavigatorApplication>
In this typical configuration you can access the ViewNavigator icon property and set it either in your mxml or from AS3 (eg. in the for loop above) eventually adding a switch statement to select different icons for each tab ... But that is where your story ends. ViewNavigator icon property is used when certain ViewNavigator is represented by a visual component. Which means that in your #tabBar the ViewNavigator is represented visually as a part of navigators stack, and is logical - in this situation it uses the icon specified.
Now let's suppose now you want to change the alpha or colorise your icons programatically, meaning you always use one set of icons but in different situations - you give them different visual properties, and one of those situations can be - their visual appeal in the application #tabBar.
The icon property is a path to the icon image file, and as a type is an generic Object. So you can't change alpha or do anything else with it. Neither icon property will give you any reference to the display object containing the icon itself or anything else you want for your juicy AS3 jonglery pokery ...
For such task we must do some nice spark skinning (in AS3) ;) So read the answer bellow
The first step is to add your css file from example above MyStyles.css (i always do skin class references from within css, found it easy to manage and change if needed) ...
/* MyStyle.css example */
.
.
s|TabbedViewNavigator #tabBar {
skinClass: ClassReference("com.myapp.skins.TabBarSkin");
}
.
.
You must now define your custom TabBarSkin class that can look something like this:
/* TabBarSkin Class */
package com.myapp.skins
{
import spark.skins.mobile.TabbedViewNavigatorTabBarSkin;
import spark.skins.mobile.supportClasses.ButtonBarButtonClassFactory;
import spark.components.ButtonBarButton;
public class TabBarSkin extends TabbedViewNavigatorTabBarSkin
{
public function TabBarSkin() {
super();
}
override protected function createChildren():void
{
if (!firstButton) {
firstButton = new ButtonBarButtonClassFactory(ButtonBarButton);
firstButton.skinClass = TabBarFirstTabSkin;
}
if (!lastButton) {
lastButton = new ButtonBarButtonClassFactory(ButtonBarButton);
lastButton.skinClass = TabBarLastTabSkin;
}
if (!middleButton) {
middleButton = new ButtonBarButtonClassFactory(ButtonBarButton);
middleButton.skinClass = TabBarLastTabSkin;
}
super.createChildren();
}
}
}
Not getting so much in details, you must know that this custom class TabBarSkin inherits from TabbedViewNavigatorTabBarSkin which has 3 skin classes for each of the significant tab positions / first / mid / last / in your #tabBar. In the most simple situation we must implement (extend) two of them / first / and / last -> as the / mid / position also uses the / last / skin, and in this case we don't need it separately implemented.
/* TabBarFirstTabSkin Class */
package com.myapp.skins
{
import spark.components.Group;
import spark.skins.mobile.TabbedViewNavigatorTabBarFirstTabSkin;
public class TabBarFirstTabSkin extends TabbedViewNavigatorTabBarFirstTabSkin
{
private var __iconGroup : Group = null;
public function TabBarFirstTabSkin() {
super();
}
override protected function layoutContents(unscaledWidth : Number, unscaledHeight : Number) : void {
super.layoutContents(unscaledWidth, unscaledHeight);
if(!__iconGroup) {
__iconGroup = getIconDisplay() as Group;
}
}
}
}
Same as for the / last / one:
/* TabBarLastTabSkin Class */
package com.myapp.skins
{
import spark.components.Group;
import spark.skins.mobile.TabbedViewNavigatorTabBarLastTabSkin;
public class TabBarLastTabSkin extends TabbedViewNavigatorTabBarLastTabSkin
{
private var __iconGroup : Group = null;
public function TabBarLastTabSkin() {
super();
}
override protected function layoutContents(unscaledWidth : Number, unscaledHeight : Number) : void {
super.layoutContents(unscaledWidth, unscaledHeight);
if(!__iconGroup) {
__iconGroup = getIconDisplay() as Group;
}
}
}
}
Finally, the __iconGroup member will now have reference to the Group visual component containing your icon! -> and which you defined in your ViewNavigator instance through mxml / or in AS3 code. Now we can go dirty ;) and do things like this for example:
.
.
.
var colorTransform : ColorTransform = new ColorTransform();
colorTransform.color = 0xFF3300;
colorTransform.alphaMultiplier = 0.85;
__iconGroup.transform.colorTransform = colorTransform;
.
.
.
Which will colorise your icon in red and give alpha 0.85. etc ... This is really the basics you can do with spark #tabBar skinning in Flex Mobile. Hope will help someone. Cheers.
Related
I am working in actionscript3, and since I'm self-taught, I think I've developed some bad habits, including coding on the timeline and using multiple scenes.
I am hoping to rectify this now that I'm working on a larger project.
Based on what I've read, linking multiple .fla files together is a better practice, each with their own document class. Is that correct?
If so, how do I load one .fla with its document class and then link that into the subsequent .fla file (instead of using scenes)? Or am I misinterpreting what was recommended?
Thanks!
There's no point to split your application in several loadable modules unless you have any of the following preconditions:
you have smart resource management to load and unload content
if you put everything into one file it gets just too big and hard to work with in design time or it takes far too long to compile
Regular AS3 alternative to working with scenes is creating/destroying content instances and using the main document class as their manager. You design content in the library and create behavior AS3 classes for them. Lets say, you have two content classes A and B. At the start the manager should show one of them and wait for the signal to show next one:
private var APage:A;
private var BPage:B;
gotoA();
function gotoA():void
{
if (BPage)
{
BPage.destroy();
removeChild(BPage);
BPage.removeEventListener(Event.CLOSE, gotoA);
}
APage = new A;
APage.addEventListener(Event.CLOSE, gotoB);
addChild(APage);
}
function gotoB():void
{
if (APage)
{
APage.destroy();
removeChild(APage);
APage.removeEventListener(Event.CLOSE, gotoB);
}
BPage = new B;
BPage.addEventListener(Event.CLOSE, gotoA);
addChild(BPage);
}
So, both A and B should have respective methods .destroy() that release used resources, unsubscribes methods from events, remove display objects, and so on, and they both should fire Event.CLOSE when they're done.
If you have many pages like that, you need to go for more algorithmic approach. For example, to create class BasicPage which will interact with manager and have the methods needed in all pages already declared:
package
{
import flash.display.Sprite;
class BasicPage extends Sprite
{
// A reference to the page manager instance.
public var Manager:PageManager;
public function destroy():void
{
while (numChildren > 0) removeChildAt(0);
Manager = null;
}
// Subclasses will have an access to this method to tell manager to show another page.
protected function showOtherPage(pageClass:Class):void
{
Manager.showPage(pageClass);
}
// A method that is called by manager when everything is ready.
// If page should take any actions on start it is a good idea to override this method.
public function startEngine():void
{
}
}
}
Then, example page A:
package
{
import flash.events.MouseEvent;
public class A extends BasicPage
{
// Lets say, class A in library have a designed button named Click.
public var Click:SimpleButton;
// We have things to undo here.
override public function destroy():void
{
Click.removeEventListener(MouseEvent.CLICK, onClick);
Click = null;
// Pass the destruction to superclass so it wraps its existence either.
super.destroy();
}
override public function startEngine():void
{
Click.addEventListener(MouseEvent.CLICK, onClick);
}
private function onClick(e:MouseEvent):void
{
// Lets use inherited method to show other page.
showOtherPage(B);
}
}
}
So, PageManager will be like:
package
{
public class PageManager extends Sprite
{
private var Page:BasicPage;
// constructor
function PageManager()
{
super();
showPage(A);
}
function showPage(pageClass:Class):void
{
if (Page)
{
Page.destroy();
removeChild(Page);
Page = null;
}
Page = new pageClass;
Page.Manager = this;
addChild(Page);
Page.startEngine();
}
}
}
This all could look scary at first, but it really isn't. PageManager will always have a current page, once there's a need to show another page, the current will be destroyed on a regular basis. Each page class will tend to its own content, which makes coding simpler, for you don't need to see the whole picture. If you need any persistent data, keep it in the PageManager so each page will have access to the data with no need for the pages to communicate with each other.
I'm dabbling with game design and trying to create some characters for the game. Right now I've just created a single MovieClip that contains a rectangle. The MovieClip symbol extends a class that I've created in Flash Builder that implements the logic of a monster. I can then drag an instance of this monster symbol from the library to the stage and the code works when I run the simulation. So far, so good.
Now I want to create several monsters, all slightly different:
public class Monster extends MovieClip
{
public var isFriendly:Boolean = true;
public var strength:int = 10;
public var catchPhrase:String = "Booyah!";
public function Monster()
{
}
}
One way to do this is to write a new class for each monster that extends Monster and sets the properties I want in the constructor (I'd also have to create a unique symbol in the library for each of these variations too). However, this seems to be overkill if my monsters only differ by their property values.
Looking at the Flash Professional use interface, I see that at the very bottom of the Properties panel is a section that looks like a small table headed by 'Properties/Value'. Can I use this to somehow set the properties of my classes from within the Flash Professional UI? I can't find any info on how this is used.
Okay, I figured it out. The key is converting my symbol into a flash Component.
First I edited my ActionScript class to export the properties I wanted to set (including the Inspectable tag):
public class Monster
{
private var _catchPhrase:String;
public function Monster()
{
}
public function get catchPhrase():String
{
return _catchPhrase;
}
[Inspectable(name = "catchPhrase", type = String, defaultValue = "Booyah!")]
public function set catchPhrase(value:String):void
{
_catchPhrase = value;
}
}
Then I right clicked on the Monster symbol in my library and selected 'Component Definition...'. This brought up the Component Definition dialog. I then entered the name of my ActionScript class in the Class field and clicked the checkmark to validate it. Flash then automatically generated the properties I needed.
I also found this tutorial helpful:
http://redbjarne.wordpress.com/actionscript-3-0-custom-components-from-hell/
I currently store embedded images in a flex library project (swc) like so:
public class BitmapAssets
{
[Embed(source="/assets/icon_160.png")]
public static const icon_160:Class;
[Embed(source="/assets/icon_240.png")]
public static const icon_240:Class;
[Embed(source="/assets/icon_240.png")]
public static const icon_240:Class;
}
And then i reference the images in other projects. For example:
<s:Button>
<s:icon>
<s:MultiDPIBitmapSource source160dpi="{BitmapAssets.icon160}"
source240dpi="{BitmapAssets.icon240}"
source320dpi="{BitmapAssets.icon320}" />
</s:icon>
</s:Button>
What i would like to do is embed the MultiDPIBitmapSource in the library project; then i could reduce the code to this:
<s:Button icon="{MultiDPIBitmapAssets.icon}" />
However, i can't figure out how to embed a MultiDPIBitmapSource object with source160/240/320 values filled.
Any solutions gratefully received ;)
Edit:
As an alternative to 'true' embedding, i'm wondering whether mxml compiling could provide an answer. For instance, if i have an mxml declaration:
<fx:declarations>
<s:MultiDPIBitmapSource id="icon"
source160dpi="{BitmapAssets.icon160}"
source240dpi="{BitmapAssets.icon240}"
source320dpi="{BitmapAssets.icon320}" />
</fx:declarations>
Then the mxml compiler will turn it into:
var icon:MultiDPIBitmapSource = new MultiDPIBitmapSource();
icon.source160dpi = BitmapAssets.icon160;
icon.source240dpi = BitmapAssets.icon240;
icon.source320dpi = BitmapAssets.icon320;
And i can then reference it as i'd like:
<s:Button icon="{MultiDPIBitmapAssets.icon}" />
Technically, its not embedded. But for practical purposes, it is (at least in my mind). The question is, where do i put the declarations tag? Can i define a non-visual class in mxml?
MultiDPIBitmapSource is an object created at runtime, not at compile-time. You won't be able to embed an instance of it in the app.
However, you could create a static reference to an object. It would require a bit more code, but it would be less code to write every time you need to use it.
public class Assets {
private static var _asset:MultiDPIBitmapSource;
public static function get asset():MultiDPIBitmapSource {
if ( !_assets ) {
_assets = new MultiDPIBitmapSource();
_assets.source160 = "url";
_assets.source240 = "url";
_assets.source320 = "url";
_assets.source480 = "url";
}
return _assets;
}
}
And then to use it:
<s:Button icon="{Assets.asset}"/>
So it basically treats the source as a mini-Singleton. I'm personally not a fan of this method. The only gain you get is slightly less code in each class, but you lose flexibility and it goes against general OOP practices. But it should work.
I'm writing a Flex application using Flash Builder 4 and I'm having a bit of trouble with an AS3 object. Essentially, it is a BorderContainer, with a few buttons and images, and programming logic that determines how these interact with eachother and a database.
What I want to be able to do is configure the layout/style of the inner components using MXML and CSS. I can configure the inherited objects, but not ones that I have defined...
For example, in my MXML. I can modify the (inherited) borderstroke variable of myContainer like so;
<IE:MyContainer>
<IE:borderStroke>
<s:LinearGradientStroke weight="10" rotation="270">
<s:GradientEntry color="0xF655E5"/>
<s:GradientEntry color="0x6600CC"/>
</s:LinearGradientStroke>
</IE:borderStroke>
</IE:MyContainer>
However, I can't edit the nextButton variable (which is of type Button) like this;
<IE:MyContainer>
<IE:nextButton width="100" height="30" left="10%" bottom="10%"/>
</IE:MyContainer>
If I try, I get the compile error "Could not resolve to a component implementation".
What do I need to do to make this work?!
Thanks in advance,
Aidan
EDIT:
Here's the main method of MyContainer (actually named InvestigativeEnvironment).
The call to defineTestInvestigativeEnvironment() is what takes care of setting up the objects and action listeners and such. What I want to do is change the layout and appearance of these visual components in MXML (nextButton, prevButton, toolbox, displayArea). I want to be able to set their height, width, background, x, y, horizontalCenter, etc like I can to a button that I add to a container via MXML.
public class InvestigativeEnvironment extends BorderContainer
{
private var toolbox:Toolbox;
private var bodySystem:BodySystem;
public var nextButton:Button;
public var prevButton:Button;
private var displayArea:Group;
private var image:Image;
private var toolDisplayArea:Group;
public function InvestigativeEnvironment()
{
super();
//create 'Next' button and event listener
nextButton = new Button();
nextButton.addEventListener(MouseEvent.CLICK, nextViewAngle);
nextButton.label = "Next";
this.addElement(nextButton);
//create 'Prev' button and event listener
prevButton = new Button();
prevButton.addEventListener(MouseEvent.CLICK, prevViewAngle);
prevButton.label = "Prev";
this.addElement(prevButton);
//define investigative environment by creating models.
defineTestInvestigativeEnvironment();
//Instantiate the Group that contains the model image and tool overlays
displayArea=new Group();
//Instantiate the image that is used to display the model
image = new Image();
image.source=bodySystem.getImage();
image.horizontalCenter=0;
image.verticalCenter=0;
displayArea.addElement(image);
//add toolOverlayContainer to the display area ABOVE the model image
toolDisplayArea = new Group();
toolDisplayArea.verticalCenter=0;
toolDisplayArea.horizontalCenter=0;
displayArea.addElement(toolDisplayArea);
this.addElement(displayArea);
//add toolbox to display
toolbox = new Toolbox(toolDisplayArea);
toolbox.replaceTools(bodySystem.getToolGroup());
this.addElement(toolbox);
}
I can't understand what is your problem with editing button in particular, sorry for that. But I have a lot of notices about your InvestigativeEnvironment which code you've attached.
First, you haven't follow Flex components livecycle (see here or here). So in your code you should add and configure children in createChildren() method.
But anyway you can't use your container to add children both with MXML and from code. If your adding custom components code will be executed first MXML (in your implementation with adding them in constructor it is so) all the MXML tags just remove all your added content (anyway result will be unpredictable). In other case it will be very hard to control instance's order.
I can suggest you to declare your nextButton etc as skin parts and perform their positioning in skin. This way these internal controls will be a part of border and you can add MXML children without any problem. And you can configure them within partAdded() method.
It turns out that I wasn't quite asking the right question. I wanted to edit the components, but specifically the layout and color type attributes of them.
What I need to do is set the id of the components, and then target them using CSS.
For my nextButton, I add the ID like this;
nextButton.id="nextButton";
Then I can lay it out in the MXML file (or external stylesheet) like this;
<fx:Style>
#namespace s "library://ns.adobe.com/flex/spark";
#namespace IE "InvestigativeEnvironment.*";
IE|InvestigativeEnvironment s|Button {
chromeColor: #336666;
}
#nextButton {
bottom: 100;
right: 5;
}
</fx:Style>
am creating some Advanced Datagrid with actionscript.
I have created an actionscript class where I extend the VBox object:
package core
{
import mx.containers.VBox;
import mx.controls.TextInput;
public class customItemRender extends VBox
{
public function customItemRender(_TextInput:TextInput, _TextInput2:TextInput)
{
//TODO: implement function
super.addChild(_TextInput);
super.addChild(_TextInput2);
}
}
}
The problem comes up when I declare de itemrender property on the data grid:
AdvancedDataGridColumn.itemRenderer = new ClassFactory(customItemRender(_TextInput1,_TextInput2));
The compiler wont let me instanciate my customItemRender.
Does any one know if there is an alternative solution to solve the problem?
Thanks in advance for you helps,
Regards Javier
private var _ItemRendere:ClassFactory;
private function get MyItemRendere():ClassFactory
{
if (_ItemRendere == null)
{
_ItemRendere = new ClassFactory();
_ItemRendere.generator = customItemRender;
_ItemRendere.properties = {
_TextInput1:MY_TextInput1_OBJECT,
_TextInput2:MY_TextInput2_OBJECT
};
}
return _ItemRendere;
}
then you can use
AdvancedDataGridColumn.itemRenderer = MyItemRendere;
I've only tried to do this using MXML. In that case, i usually have to wrap the IListItemRenderer instance in mx:Component tags. I'm not exactly sure what is going on programmatically when I do this, but it works. The reason is that the itemRender is actually looking for an instance of IFactory rather than an instance so I suppose to do this strictly using AS you would need to create your own IFactory implementation.
e.g.
<mx:List>
<mx:itemRenderer>
<mx:Component>
<mx:Text />
</mx:Component>
</mx:itemRenderer>
</mx:List>
ClassFactory's constructor has a Class as a parameter, not an instance. You need to call:
new ClassFactory(customItemRender);
and not:
new ClassFactory(new customItemRender(_TextInput1,_TextInput2));
or:
new ClassFactory(customItemRender(_TextInput1,_TextInput2));
Now, since the constructor will not be called with reference to TextInput1 and TextInput2, you'll need to instantiate your own TextInputs in the custom renderer itself. (But this is a good thing, if you continue to call new customItemRender(_TextInput1, _TextInput2), then the two TextInputs will only be added to the LAST instance of customItemRender, and all of the others will not have these two objects ).