Smooth scrolling a Spark List with variable height items using the mouse wheel - apache-flex

We have a list of variable height items that we display in a Spark List control. When the user clicks and drags the vertical scrollbar the list scrolls smoothly. When the up/down arrows are used it moves in small and discrete steps. When the mouse wheel is used the list scrolls in very large discrete steps that is problematic for the user.
We would like to enable smooth scrolling for the mouse wheel. The height of our items vary significantly and it is easy to get lost when you scroll with the moouse due to the discrete scrolling.
Our implementation is fairly simple:
<s:List id="chartList"
dataProvider="{pm.charts}"
itemRenderer="charts.ChartItemRenderer"
horizontalScrollPolicy="off"
verticalScrollPolicy="on"
useVirtualLayout="false"
cachePolicy="auto">
</s:List>
<?xml version="1.0" encoding="utf-8"?>
<s:ItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
autoDrawBackground="false"
xmlns:charts="charts.*"
>
<fx:Script>
<![CDATA[
private var _canvas:BitmapData;
public function set canvas(value:BitmapData):void
{
_canvas = value;
}
[Bindable]
public function get canvas():BitmapData
{
return _canvas;
}
public function render(x:int,y:int, data:int):void
{
_canvas.draw(this);
}
]]>
</fx:Script>
<charts:DefaultChartContainer
chart="{data}"
cachePolicy="on"/>
</s:ItemRenderer>
There does not appear to be an out of the box method for implementing smooth scrolling in a Spark List. How would one go about implementing smooth scrolling in a spark list for variable height items?

Edit:
Here is another way to do this:
http://forums.adobe.com/message/3844410#3844410
Ok, so this wont be easy, but it is doable.
1) Create a custom skin for your list
2) In the custom skin, replace the scroller with a custom scroller (MyScroller)
3) Create a new class that extends Scroller, called MyScroller
4) Adobe in their infinite wisdom made skin_mouseWheelHandler private - which they seem to do all over the place, so you would have to override something higher up, maybe attachSkin and detachSkin. Or you could try adding your own skin_mouseWheelHandler with a higher priority in attachSkin and prevent default on it so the default does not get called.
5) Replicate the code in skin_mouseWheelHandler, and modify it to suit your requirements.

Like #Sunil_D. suggested, listening the mousewheel event and manually adjusting the HorizontalScrollPosition is an easy way to handle this. Add the EventListener for your component
chartList.addEventListener(MouseEvent.MOUSE_WHEEL, chartList_mouseWheelHandler);
and increase/decrease the horizontalScrollPosition with, for example, multiples of event.delta
protected function chartList_mouseWheelHandler(event:MouseEvent):void
{
chartList.verticalScrollPosition -= (event.delta * SOME_CORRECT_VALUE);
}
Finding the proper SOME_CORRECT_VALUE might need some experimenting, but it shouldn't be to hard to find.

Related

width of spark label

I need to set the text of a spark label and then position such label along the x axis depending on its width. Unfortunately, it seems that the width of the label does not update right away and thus the positioning will fail. I can listen to updateComplete events on the label and update its position then, but that means repositioning the label a lot more often than I would like (updateComplete fires off a lot more often than upon changing width). Any ideas on how to properly handle what would appear to be a trivial task?
Here's a code snippet that shows what I described above. If you press the button you will see 3 traces: the label width before changing its text, right after setting its text, and when the label is done updating itself. Would love to know if there's a way to get the correct width without having to listen to updateComplete events... The button and the VGroups are just there to run the example
<?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/mx" minWidth="955" minHeight="600">
<fx:Script>
<![CDATA[
import mx.events.FlexEvent;
protected function button1_clickHandler(event:MouseEvent):void
{
trace("label width = ", lb.width);
lb.addEventListener(FlexEvent.UPDATE_COMPLETE, lb_updateCompleteHandler);
lb.text = "hello world!";
trace("label width post change= ", lb.width);
}
protected function lb_updateCompleteHandler(event:FlexEvent):void
{
lb.removeEventListener(FlexEvent.UPDATE_COMPLETE, lb_updateCompleteHandler);
trace("label width post update complete = ", lb.width);
}
]]>
</fx:Script>
<s:VGroup>
<s:Button label="go" click="button1_clickHandler(event)"/>
<s:Label id="lb" text="h" x="10" y="10" />
</s:VGroup>
</s:Application>
The following worked for me. Simple yet works.
var l:Label = new Label();
l.text = "That'll do";
var tlm:TextLineMetrics = l.measureText( l.text);
Alert.show( tlm.width + " is what you are looking for");
VGroup is a Group with VerticalLayout. It doesn't allow to have custom positioning inside group (with x and y). To have this possibility use Group with (default) BasicLayout.
If you use a callLater(), you can "capture" variables in the current scope, but also have it execute after the change in the width has been propagated. For example:
callLater(function():void
{
trace("label width in callLater = ", lb.width);
});
UPDATE: I think I should elaborate a little more to make clear what I meant by "capturing" values. This can be done with the following simple illustration:
callLater(function(prevWidth:Number):void
{
trace("previous label width in callLater = ", prevWidth); // 8
trace("label width in callLater = ", lb.width); // 64
}, [lb.width]);
As the example demonstrates, the parameters that you pass in through the args Array (, i.e., the [lb.width] which becomes prevWidth) take the current value (ie., they pass by value), whereas, the variables you make direct reference to in the function are actually in the scope when the function gets executed (ie., a closure).
You could do this with the event listener also, but with the callLater() it's a little simpler.
looks like listening to resize events does the trick and is not quite as general as updateComplete events, so I'm calling that the "correct answer". There may be a better way to do this when you're trying to position stuff by hand due to performance optimization, although I'm not sure what it is yet.
You can call lb.validateNow() after setting text, this way you will get new width right away. But suggest you to read about components lifecycle not to have such trivial questions.
And yes, as people pointed out already, no repositioning will work with such layout.

Flex 4 UIMovieClip Modify the Width of a Child DisplayObject

I have a collection of UIMovieClip components which reside in an s:HGroup tag. In ActionScript code I am modifying the width of a child clip in one of the UIMovieClips but these changes are not reflected by the s:HGroup.
<s:HGroup id="_buttonGroup">
<uiassets:NavigationTabButtonSWC id="_lobby" />
<uiassets:NavigationTabButtonSWC id="_achievements" />
</s:HGroup>
<fx:Script>
<![CDATA[
protected function init() : void
{
// The HGroup does not pickup this change and so my buttons
// are no longer evenly spaced out and overlap!
_lobby.getChildByName("background").width += 200;
}
]]>
</fx:Script>
Thanks!
There's a few reasons for this. Just changing one child's width doesn't mean it'll change the whole UIMovieClip's width, so you should check that first.
Second, Flex has a very specific way of doing things (called the component lifecycle), which the UIMovieClip doesn't implement so you can't manage the width yourself in the 'measure' function. I'm guessing that you just have other children in your movieclip that doesn't let you resize it all. Try changing the width of the MovieClip itself and it should work. If it doesn't, then there's another problem.

How can I skin or change the default cursor in Flex?

How can I skin, or otherwise change, the default cursor (white arrow) displayed in a Flex application?
Yes, this is possible. You'll need to leverage mx.managers.CursorManager.
There's no way to replace the cursor graphic, but you achieve this by adding a new cursor to the manager with a high priority:
CursorManager.setCursor(myCursor, CursorManagerPriority.HIGH);
In the above example, myCursor can be a JPEG, GIF, PNG, or SVG image, a Sprite object, or a SWF file. Additionally, setCursor accepts two additional parameters, xOffset:Number = 0, yOffset:Number = 0, which you can use to, well, offset the image from the actual pointer position, if you need to.
Re: Your comment:
I believe you're correct. There's no way I know of to override a components hover cursor other than some event foo. Keep in mind that it is the most recently added cursor with the highest priority (to the `CursorMangager, of course) that gets displayed.
If you wat to change the cursur you need to check the mouse when it is currently over the TextField sub-object of the Flex TextInput control:
<?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" minWidth="1024" minHeight="768" mouseMove="application1_mouseMoveHandler(event)">
<fx:Script>
<![CDATA[
protected function application1_mouseMoveHandler(event:MouseEvent):void
{
if(event.target is TextField)
{
if(TextField(event.target).type == TextFieldType.INPUT)
{
Mouse.hide();
}
}
else
{
Mouse.show();
}
}
]]>
</fx:Script>
<mx:TextInput width="300" />
</s:Application>
This is simply making it go away, but you could use the opportunity to make the cursor anything you want by replacing Mouse.hide() with the CursorManager methods described in the other comments. I don't really consider this event "trickery" and overriding the PlayerGlobals.swc class is always going to be more difficult than the open Flex SDK stuff.
Have a look at the following example:
http://blog.flexexamples.com/2007/09/10/changing-the-cursor-in-a-flex-application-using-the-cursormanager-class/

Can't autoscroll a VBox unless height is explicity set(in pixels) in Flex 3

I have a VBox who dynamically adds and removes children programatically. The height is set to 100% and verticalScrollPolicy=auto.
When a user wants to add another child to that Vbox, I want it to autoscroll to the bottom of the VBox since that is where the child is added.
I've tried every solution I could find online, but no matter what, the verticalScrollPosition and maxVerticalScrollPosition are both ALWAYS equal to 0. Even if I manually scroll to the bottom of the VBox and press a button that alerts these numbers.(Even after 'validateNow()' as well).
The only time I can get these numbers to change programmaticaly is when the VBox height is set in pixels, which I don't want since the children all have varying heights.
Plllease tell me that it's possible to set verticalScrollPosition without hard coding the height in pixels? Am I missing something totally obvious here?
You might not be scrolling the VBox, actually; there's a good chance that if your VBox is contained by another container, like a Canvas or the like, and you're adding items to the VBox as you say you are, it's the Canvas that's doing the scrolling, not the VBox -- in which case the VBox would indeed return 0 for its scroll position.
One way or another, you're right -- you have to set the component's height; even constraint-layout settings (e.g., "bottom='10'", etc.) won't work. But if you can manage to set the height of the VBox, either by binding its dimensions somehow to another control or by setting them explicitly as part of a child's appending/creation process, you should be able to accomplish what you're after.
Here's an example of an AIR app I've mocked up to illustrate the example. Basically it just adds randomly sized boxes to a VBox, and scrolls to the bottom of the VBox after each child gets created.
<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" verticalScrollPolicy="off" horizontalScrollPolicy="off" width="250">
<mx:Script>
<![CDATA[
import mx.core.Application;
import mx.containers.Box;
import mx.events.FlexEvent;
private function addItem(h:Number):void
{
var b:Box = new Box();
b.width = 200;
b.setStyle("backgroundColor", 0xFFFFFF);
b.height = h;
// Wait for the component to complete its creation, so you can measure and scroll accordingly later
b.addEventListener(FlexEvent.CREATION_COMPLETE, b_creationComplete);
vb.addChild(b);
}
private function b_creationComplete(event:FlexEvent):void
{
event.currentTarget.removeEventListener(FlexEvent.CREATION_COMPLETE, b_creationComplete);
vb.verticalScrollPosition = vb.getChildAt(vb.numChildren - 1).y;
}
]]>
</mx:Script>
<mx:VBox id="vb" top="10" right="10" left="10" height="{Application.application.height - 80}" verticalScrollPolicy="on" />
<mx:Button label="Add Item" click="addItem(Math.random() * 100)" bottom="10" left="10" />
</mx:WindowedApplication>
In this case, the height of the VBox is being bound to the height of its containing component (here, just the Application). Everything else should be pretty self-explanatory.
Hope that helps! Post back if you have any questions.

Flex: How do I space out items in a HorizontalList control (using a custom ItemRenderer)

I have a HorizontalList control that uses a custom ItemRenderer to represent each item as a toggle-button. The list allows drag and drop, and I used this method to rotate the drop feedback (line) into a vertical position instead of horizontal, but with the buttons mashed together, the drop feedback is pretty subtle. I'd like to space out the buttons somehow, so that the drop feedback is more obvious.
I've looked through the properties and nothing stands out. There are padding and margin properties, but their descriptions say they affect the list control itself, not the items.
Below is the code of my ItemRenderer. I've added padding to it, but that doesn't seem to change anything. If I add padding, that affects the inside of the button, not the space between them, and the button control doesn't have margin properties.
I suppose I could base my ItemRenderer on a canvas in order to get a margin, but then I wouldn't inherit all of the functionality of a button.
<?xml version="1.0" encoding="utf-8"?>
<mx:Button
xmlns:mx="http://www.adobe.com/2006/mxml"
creationComplete="go();"
toggle="true"
>
<mx:Script>
<![CDATA[
private var _val:int = -1;
private function go():void {
this.label = data.title;
_val = data.index;
}
override protected function clickHandler(event:MouseEvent):void{
//todo: bubble an event that causes all other
//buttons in the list to un-toggle
//now do the default clickHandler
super.clickHandler(event);
}
]]>
</mx:Script>
</mx:Button>
How about writing your item renderer as a container (either Canvas or HBox) and placing the Button element inside?
Make a custom skin for your buttons that includes the spacing you need. You may need to combine it with padding styles to ensure that text or icons don't go outside the skin.
It's a bit on the hacky side, but you can also lie about your columnWidth for the actual HorizontalList object. Set it to something larger than your actual itemRenderer width.

Resources