Optimize ItemRenderer For Flex Mobile Application - actionscript-3

I made this class, which is an ItemRenderer class, used in a DataGroup ( mobile application ),
and I am not entirely sure if I did the right thing or not, my issues are :
Is there a better way to show the image, which is 80x80 and directly loaded from the server;
How to make the height of the row dynamic, I mean, depending on the height of the 3 StyleableTextFeild
Is this the right way to add the listener on the image, that will trigger a simple HTTPService,
Here is the functions from the class, Any help would be much appreciated !!
Image
Declared it as a simple image :
var logo:Image;
On override createChildren
logo = new Image();
addChild(logo);
And I added on set Data
logo.source = "http://192.168.0.15:3000/"+value.logo_thumb_url;
Size
override protected function measure():void {
measuredWidth = measuredMinWidth = stage.fullScreenWidth;
measuredHeight = measuredMinHeight = 100;
}
Listener
override public function set data(value:Object):void {
tel.text = String(value.Tel);
description.text = String(value.Descricao);
nome.text = String(value.Nome);
logo.addEventListener(MouseEvent.CLICK, function():void{
var service:HTTPService = new HTTPService();
service.url = value.targer;
service.method = "GET";
// setting headers and other variables ...
service.send();
});
}

You can use URLLoader or Loader for loading the image if you are planning to cache the image on the client side, if you cache the image, it wil help you not load the image again when the users scrolls through the list. (What you have done is Ok, but you will hit performance issues)
For variable row height, if Datagroup does not work, use List. find it here Flex 4: Setting Spark List height to its content height
There should be a buttonMode property for some items, make it buttonMode for the logo, for variable row height, find something related to wordWrap and variableRowHeight properties on the datagroup.
There are a few suggestions, what you have coded is good, but, instead of adding the listeners on set data, add it in creation complete, as it is more appropriate. Also, the event listeners has to be weak referenced, http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/events/EventDispatcher.html#addEventListener()

Related

In Flex, what is a better way to build UI component whose height is based on width and content?

I need to build a new UI control based on UIComponent. The control's height should have a certain ratio to the width and also depends on what content is shown (amount of text inside). Could anybody recommend how I should do this?
I have tried googling around, but it seems all articles just try to avoid talking about the case. My personal feeling is that the way Flex renders, i.e. invalidate and update display list, is natively inefficient to deal with the situation.
Below are my codes. It works all fine, but it seems this is not the most efficient way because the height is calculated inside updateDisplayList(), which triggers another updateDisplayList().
The control is used inside a mobile project, so when the device is rotated, the parent's size will be changed and the control needs to be resized as well.
So, a few questions:
What should I do in measure()? Since the height is based on actual width, I cannot get the final width until updateDisplayList(), because the parent of the control has width defined as percentage.
Is it correct to calculate the height at the very beginning of updateDisplayList()? If not, how should I do it?
Below is an example what I mean.
import flash.text.TextField;
import flash.text.TextFieldAutoSize;
import mx.core.UIComponent;
public class MyControl extends UIComponent
{
public function MyControl()
{
super();
}
public var textPadding:Number = 15;
public var heightRatio:Number = 1.61803398875;
private var _label:String;
private var labelChanged:Boolean = false;
public function get label():String
{
return _label;
}
public function set label(value:String):void //can be long text with about 20-30 lines
{
if (_label != value)
{
_label = value;
labelChanged = true;
invalidateProperties();
invalidateSize();
invalidateDisplayList();
}
}
private var text:TextField;
override protected function createChildren():void
{
super.createChildren();
if (!text)
{
text = new TextField();
text.autoSize = TextFieldAutoSize.LEFT;
text.multiline = true;
text.wordWrap = true;
addChild(text);
}
}
override protected function commitProperties():void
{
super.commitProperties();
if (labelChanged)
{
text.text = label;
labelChanged = false;
}
}
override protected function measure():void
{
super.measure();
//What should I do here?
}
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
text.width = unscaledWidth;
if (text.height < unscaledWidth / heightRatio)
{
height = unscaledWidth / heightRatio + textPadding * 2;
}
else
{
height = text.height + textPadding * 2;
}
super.updateDisplayList(unscaledWidth, unscaledHeight);
text.x = textPadding;
text.y = textPadding;
}
}
I need to build a new UI control based on UIComponent. The control's
height should have a certain ratio to the width and also depends on
what content is shown (amount of text inside). Could anybody recommend
how I should do this?
Technically you can't.
Your whole approach / understanding of how Flex works is fundamentally flawed.
A Flex component will never size itself. It is always sized by its parent. The measure() method will set ideal values: measuredWidth, measuredHeight, measuredMinWidth, and measuredMinHeight. But, they are just suggestions to be provided to the parent container; which may or may not use them.
MXML masks this concept and your misunderstanding is very common.
In most cases, the default Flex layout containers will do their best to honor these values, but they have logic to handle situations where their is not enough space. For example; a VGroup with two children that are both set to 100% height won't size both children to 100% of the VGroup.
Without seeing your code that uses and sizes your component it is tough to say what is going on. Most likely the 'height' that you are setting is not getting overwritten by the parent container; and that is the only reason I can believe this approach works.
I'm making some assumptions here; but I think your measure method should look something like this:
override protected function measure():void
{
super.measure();
this.measuredwidth = text.getMeasuredOrExplicitWidth();
this.measuredHeight = text.getMeasuredOrExplicitHeight()/heightRatio + textPadding * 2;
}
updateDisplayList(), should be something like this:
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
text.width = unscaledWidth;
text.height = unscaledWidth / heightRatio;
super.updateDisplayList(unscaledWidth, unscaledHeight);
text.x = textPadding;
text.y = textPadding;
}
The updateDisplayList() in the component's parent container should do something like this:
myControlInstance.height = myControlInstance.measuredHeight;
myControlInstance.width = myControlInstance.measuredWidth;
The Spark framework uses all the same methods; but sometimes shuffles them around a bit. So, the updateDisplayList() and measure() are usually in a skin class; or a layout class.
According to Adobe Documentation http://livedocs.adobe.com/flex/3/html/help.html?content=ascomponents_advanced_2.html, measure() is used to measure default size and updateDisplayList() is used to size and place child components.
To answer your questions:
1) I'd say to do nothing, since you're not interested in default size.
2) I think, it doesn't matter since the value change doesn't have immediate effect (as you write updateDisplayList will be called again).
My suggestion is to have an listener for widthChanged event and use it calculate proper height. And to do height calculation when label is changed.
There is no way around the extra update cycle, that is just the nature of layout with text reflow. Usually how it's done (or at least how it's done in the flex components) is to measure based on it's preferred width first, which may be quite wide if measuring text with no explicit bounds. This measurement is important however as it notifies parent containers if and when it should try and expand out the width as much as possible, before the uper limit is set in layoutBoundsWidth or layoutBoundsHeight. Once your bounds are set in the first call to updateDisplayList, you should resize your text and invalidateSize() so you can measure the height on the second pass. Sorry I have no code examples, im on the mobile. If you need them, I can post some when I'm in the office.

ScrollViewer ChangeView - AccessViolationException one out of the thousand times

I have FlipView (it's virtualized with Mode="Standard") with ScrollViewer, Image (inside ScrollViewer as DataTemplate). I set ImageOpened event handler on Image with such code:
private void Image_ImageOpened(object sender, RoutedEventArgs e)
{
var image = sender as Image;
double width = image.ActualWidth;
double height = image.ActualHeight;
var sv = image.Parent as ScrollViewer;
if (sv != null && width != 0 && height != 0)
{
var properZoom = ZoomUtilities.CalculateZoomFactor(width, height);
sv.MinZoomFactor = 0.3f;
sv.ChangeView(null, null, properZoom);
sv.MinZoomFactor = properZoom;
}
}
properZoom is always correct value. One out of the thousand times when I change item (swipe) or load page with this FlipView application crash with breakpoint on sv.ChangeView(..) and AccessViolationException exception is thrown. Does anyone know what could be the reason of such behaviour? Are there any restriction when I can call ChangeView method?
EDIT:
Forgot to mention - there is also DoubleTapped event handler on ScrollViewer which also calls ChangeView
I had the same AccessViolationException using .ChangeView(..). I noticed the exception was thrown when the ScrollViewer I wanted to manipulate had focus. For example when a user was dragging through the list or controlling the scrollbar with his mouse.
Disabling animations solved my problems. If you want to keep the animations you should look for a way to remove focus on the controls before changing the view. I guess the animated ChangeView tries to change a property that is locked because of the user input.
Hope this helps.
I had the same problem.
Solution to me: call inverse, from image object to parent got error.
Call normal path: objectscroolviewer.changeview, works no error.
Then:
var sv = image.Parent as ScrollViewer;
change to
var sv = ScrollViewerObject;
Get object to variable externally, not by internal reference of parent, and ignores sender parameter.

How to add width property to the canvas in flex 3

I need to add the width property to the canvas .By traceing the value iam geting its value as zero though it contain images and has a width.
//code of adding 5 canvases to the main canvas
public function addcanvas(canarr:Array):void
{
var can_arr:Array=new Array();
can_arr=canarr;
for(var c:int=0;c<can_arr.length;c++)
{
canvasA=new Canvas();
canvasA.id="canvasA";
trace("ccc:"+c);
canvasA.x=(c*10)+10;
canvasA.y=0;2
this.addChild(canvasA);
}
trace("canvasA.width: "+canvasA.width);
}
and next i will add images into each canvas
//enter code here
public function ClubCards(imgarr:Array):void
{
var Gimg_arr:Array = new Array();
Gimg_arr =imgarr;
for(var j:int=0;j < Gimg_arr.length;j++)
{
myimage1 = new Image();
myimage1.x=0+(j*20);
myimage1.y=40;
myimage1.source=gameModule.getStyle(Gimg_arr[j].toString());
GroupingImageListeners(myimage1);
canvasA.addChild(myimage1);
trace("canvasA.width: "+canvasA.width );
}
iam unable to know the width of the canvas which has images in it . can u please help me out.how can i get that canvas width
Thank you in advance
I can't add a comment, so I will add an answer saying this code should be triggered by a call to the updateDisplayList method (during the appropriate phase of the flex component life-cycle). I would suggest reading and understanding this update cycle before doing any further Flex development (it will save you a lot of time in the long-run).
More info can be found here: http://livedocs.adobe.com/flex/3/html/help.html?content=ascomponents_advanced_2.html
UPDATE
Since you mentioned Canvas, I updated the link to point to the 3.x version of the article
You did not make your canvas invalidate its size. Without an order to redraw, I don't think you'll get the correct size. Without Event.RENDER dispatched your component will not calculate its size.
Maybe you should try:
canvas.invalidateSize();
before accessing its width and see the result. This should make your canvas call its method measure() and calculate its size.

A function that deletes an instance by removing it from stage and nulling it does not remove it from memory

I have an issue with a function I use to delete an instance and replace it with another. Basically, it keeps the item in memory no matter what. Inside the object I have weak listeners and I null everything after it gets removed, but the function I run to check if it is still active tells me that it is (just an Event.ENTER_FRAME tracing some text, with a weak link).
Even when I removed everything from the instances I am loading, it still seems to stay in memory, according to my trace it still is. How do I completely delete something from memory more thoroughly than nulling it out after removing it from the stage? Am I not seeing something?
This is the function:
private function loadArea(inputArea:String)
{
//This is for a checker to make sure that areas only get loaded once.
currentRoom = inputArea;
//If the area currently loaded is not null, make it null now.
if(selectedArea != null) selectedArea = null;
//Null any data inside of the reference used to create the name of the new area.
areaReference = null;
//Grab the class using the input.
areaReference = getDefinitionByName(inputArea + "Area") as Class;
//Null the sprite used to house the class
areaSprite = null;
//Set the holder as a new instance of the desired class.
areaSprite = new areaReference() as Sprite;
//If the selected area is still not null for some reason,
if(selectedArea != null)
{
//Remove the area from the container...
areaContainer.removeChild(selectedArea);
//...and nullify it.
selectedArea = null;
}
//Set the current area as the newly created instance.
selectedArea = areaSprite;
//If the area is not the "Game", load in the assets one way,
if(inputArea != "Game") selectedArea.construct(areaAssets);
//otherwise do it another way.
else selectedArea.construct(newScreenData,apiServer,cdnServer,areaAssets);
//This is for a checker that fades out the screen, which it needs to fade back in soon.
newScreenData = null;
//While the container for areas has any areas inside of it, remove them.
while(areaContainer.numChildren) areaContainer.removeChildAt(0);
//...then add the new instance area to the container.
areaContainer.addChild(selectedArea);
//...then let all the parts of the game know that a new area has been laoded in.
Global.echoEvent.echo("gameBootUp","playAreaIn");
}
The memory is actually released when Garbage Collector will find and erase an orphaned instance of yours. Before that, your memory usage will state there is an instance in memory. There is no way to force garbage collection, calling System.gc() only "instructs" Flash to run it, it might not obey. So, you have done what you had to, let it be.
Removing all references including stage and nulling an object is all it takes to free up memory.
If the object is not being released then you are missing something or doing something incorrectly or out of sequence.
Carefully go through your code, making sure you identify where objects are being referenced so you can remove them.
Looking at your example code:
if(selectedArea != null) selectedArea = null;
Here you are making sure that the selectedArea is null.
But immediately after you are testing selectedArea again
(you know it is null so this block is never used)
if(selectedArea != null){
//Remove the area from the container...
areaContainer.removeChild(selectedArea);
//...and nullify it.
selectedArea = null;
}
In every language its VERY DIFFICULT to "clear" memory... even HTML. That being said... try to reduce your memory footprint.
Correct Null:
1. remove all event listeners
2. remove all children
3. remove all mapped/referenced methods/parameters
4. set the class object to null
In most cases this is all you need to do, the WAY you do it will determine if the object gets cleared. Consider the following situation.
You have a custom sprite class (MEMORY FOOTPRINT #1) that has a mapped property (mapping happen when one class object references another). Once you map/reference one object to another = MEMORY FOOTPRINT #2. Adding events = MEMORY FOOTPRINT #3, etc and so on.
Custom Sprite
import flash.display.Sprite;
class CustomSprite extends Sprite{
private var _mappedProperty:Object;
public function addMapping(map:Object):void{
_mappedProperty = map;
}
public function finalize():void{
_mappedProperty = null;
}
}
Assuming we're using CustomSprite in many other methods, lets look at some common ways of removing the ojbect.
INCORRECT - in this situation [objToRemove] was not set to null to free its memory:
var objToRemove:CustomSprite = new CustomSprite;
function doSomething(referenceObj:CustomSprite):void{
var methodObj:CustomSprite = referenceObj;
//CRAZY LINES OF CODE
methodObj = null; //frees memory from [doSomething::methodObj]
referenceObj = null; //frees memory from [doSomething::referenceObj]
//objToRemove is not cleared and will remain in memory
}
INCORRECT - in this situation [objToRemove] has a reference object so it will not clean until the reference is removed:
var objToRemove:CustomSprite = new CustomSprite;
var mappedObject:Sprite = new Sprite;
objToRemove.addMapping(mappedObject);
objToRemove.addEventListener(Event.ENTER_FRAME,onEnterFrame);
//CRAZY LINES OF CODE
//remove all children
while(objToRemove.numChildren > 0){
objToRemove.removeChildAt(0);
}
//remove all event listeners
objToRemove.removeEventListener(Event.ENTER_FRAME,onEnterFrame);
//this will NOT work
objToRemove = null;
//reason is objToRemove has a reference object of [mappedObject]
//[mappedObject] is not a child so it needs to be removed manually
//from WHITIN the CustomSprite class using [CustomSprite::finalize()]
Ok... breath... the correct way is actually simple.
CORRECT - here we are using [Dynamic] objects rather than [Static] class objects, this is considered Object Mapping:
//think of this as a global list of objects
var objPool:Dictionary = new Dictionary;
//create a pool reference
objPool['poolObj'] = new CustomSprite;
//CRAZY LINES OF CODE;
//do the normal [null] process
//both of these will work
objPool['poolObj'] = null;
//or
delete objPool['poolObj'];
SUPER ADVANCED CORRECT - no example provided, I have to get back to work lol...
1. Take a ByteArray clone of the class
2. User a Loader to construct the ByteArray as the class, rather than using "new"
3. When finished... unload/remove the loader
4. EVERYTHING will clear... thats how a Loader works!
(Not getting into why and how... too long of an explanation)
Although this works flawlessly... its not generally accepted or suggested in a work environment.

flash as3 children of Image or UIComponent not displayed

For instance the mx.controls.Image objects are only displayed when i add them directly to the main application object. If i add a "subimage" to the previously created Image object it simply doesnt show. Why ? What concept did i miss ?
What I want to do:
var img : Image = new Image;
var subimg : Image = new Image;
img.source = "images/panel.png";
subimg.source = "images/panel.png";
subimg.x = 10;
subimg.y = 10;
addChild (img);
img.addChild(subimg); // img is displayed, but not the overlapping subimg
OK, and here the code how it by directly adding the subimg to the Application just like img - this one works ofcourse:
var img : Image = new Image;
var subimg : Image = new Image;
img.source = "images/panel.png";
subimg.source = "images/panel.png";
subimg.x = 10;
subimg.y = 10;
addChild (img);
addChild(subimg); // img & subimg is displayed correctly
What exactly is it that you want to do, that the second example isn't doing for you? Generally speaking UIComponents are things with complicated internals, being that they're skinnable and styleable and so on, and they manage their own contents (as with Image, which populates itself with loaded assets).
I'm not familiar enough with Image to say precisely what the problem is - whether the subimg object is being hidden or whether the load is failing, or what. But what you should probably do is to make your own Sprite and add both Images inside it, or make two sprites, add an image to each, and parent them the way you like, so you can have a similar parent-child relationship without mucking around in the internals of a component.
For example:
// ... make img and subimg
var imgContainer:Sprite = new Sprite();
imgContainer.addChild(img);
var subimgContainer:Sprite = new Sprite();
subimgContainer.addChild(subimg);
imgContainer.addChild(subimgContainer);
addChild(imgContainer);