📄 randomwalk.as
字号:
private function instanceToData(value:UIComponent):XML
{
return _rendererDataMap[UIDUtil.getUID(value)];
}
/* ----------------------------------------------------------------------------------------
/ Event handlers
*/
private function itemRollOverHandler(e:MouseEvent):void
{
_highlightedNode = instanceToData(UIComponent(e.currentTarget));
invalidateDisplayList();
}
private function itemRollOutHandler(e:MouseEvent):void
{
_highlightedNode = null;
invalidateDisplayList();
}
/* called when the user clicks on an item */
private function itemClickHandler(e:MouseEvent):void
{
// currentTarget is the item we actually assigned the handler to. Since some components have sub-pieces, target can sometimes point to
// a display object we know or care nothing about.
var child:UIComponent = UIComponent(e.currentTarget);
expandItem(instanceToData(child));
}
private function expandItem(node:XML):void
{
// first determine the node's depth
var depth:int = 0;
var parent:XML = node.parent();
while(parent != null)
{
parent= parent.parent();
depth++;
}
// now determine its index in its parent
var idx:int = node.childIndex();
// those two pieces of information allow us to update our selected path information.
if(_selectedPathIndices[depth-1] != idx)
{
_selectedPathIndices[depth-1] = idx;
// when the user clicks in the middle of the tree, we want to throw away any selected path
// below the one they clicked on
_selectedPathIndices.splice(depth,_selectedPathIndices.length - (depth-1));
// this is how we generate a custom event for the developer to listen for.
dispatchEvent(new RandomWalkEvent(RandomWalkEvent.ITEM_CLICK,node));
// since we're potentially throwing away a part of the selected path, we want to throw away any renderers
// we were using for that portion.
if(_selectedPathIndices.length < _renderers.length)
{
var deadInstances:Array = _renderers.splice(_selectedPathIndices.length,(_renderers.length - _selectedPathIndices.length));
for(var j:int=0;j<deadInstances.length;j++)
removeInstances(deadInstances[j]);
}
}
else
{
// when the user clicks in the middle of the tree, we want to throw away any selected path
// below the one they clicked on
_selectedPathIndices.splice(depth,_selectedPathIndices.length - (depth));
// this is how we generate a custom event for the developer to listen for.
dispatchEvent(new RandomWalkEvent(RandomWalkEvent.ITEM_CLICK,node));
// since we're potentially throwing away a part of the selected path, we want to throw away any renderers
// we were using for that portion.
if(_selectedPathIndices.length+1 < _renderers.length)
{
deadInstances = _renderers.splice(_selectedPathIndices.length+1,(_renderers.length - (_selectedPathIndices.length+1)));
for(j=0;j<deadInstances.length;j++)
removeInstances(deadInstances[j]);
}
}
// since our selected path changed, we'll need to update our renderers. That's expensive, so we'll defer it until our next
// commitProperties call.
_nodesInvalid = true;
invalidateProperties();
HistoryManager.save();
}
/* ----------------------------------------------------------------------------------------
* private style accessors. As a 3rd party component developer, you probably will want to distribute a default stylesheet for your component both as a set of default
* values, and as a starting point for developers to customize its appearance. To be safe, though, it's best to make sure your component doesn't assume any selectors
* in the application by coding the default values directly into the component. An easy way to do this is to access all your styles via private properties that return
* either a specified value, or a hard coded default.
*/
private function get horizontalGapWithDefault():Number
{
var result:Number = getStyle("horizontalGap");
if(isNaN(result))
result = 0;
return result;
}
private function get verticalGapWithDefault():Number
{
var result:Number = getStyle("verticalGap");
if(isNaN(result))
result = 0;
return result;
}
/* ----------------------------------------------------------------------------------------
* measurement and layout
*/
// this is the measurement function for this component. This function is responsible for examining the content of the component, combinging that
// with its knowledge about how it lays out that content, and coming up with a reasonable approximation for what it thinks its size should be
// if the developer doesn't explicitly assign one.
// writing measurement functions for data driven components whose content scales with the amount of data is tricky. It's even more tricky when
// the content changes as the user interacts with the component. In this case, we've chosen to just always measure the component to
// the minimum size necessary to show its content in its current state, whatever that may be. Other components might choose to simply measure
// to a reasonable default.
// keep in mind that you may choose not to implement this function at all. For one-off components, where you know that size at runtime will be either
// percentage based or explicitly assigned, there's no reason to fill out a measurement function.
override protected function measure():void
{
var horizontalGap:Number = horizontalGapWithDefault;
var verticalGap:Number = verticalGapWithDefault;
var left:Number = 4*GAP_FROM_LEFT_RENDERER_EDGE;
var maxHeight:Number = 0;
for(var i:int = 0;i<_renderers.length;i++)
{
var maxWidth:Number = 0;
var instances:Array = _renderers[i];
var stackHeight:Number = 0;
for(var j:int = 0;j<instances.length;j++)
{
var inst:UIComponent = instances[j];
maxWidth = Math.max(inst.getExplicitOrMeasuredWidth(), maxWidth);
stackHeight += inst.getExplicitOrMeasuredHeight() + verticalGap;
}
stackHeight -= verticalGap;
maxHeight = Math.max(maxHeight, stackHeight);
left += maxWidth + horizontalGap;
}
left -= horizontalGap;
measuredWidth = left + 4*GAP_FROM_LEFT_RENDERER_EDGE;
measuredHeight = maxHeight;
invalidateDisplayList();
}
// this is the main rendering function for the component. This function is responsible for positioning and sizing any children
// of the component (either user specified or component generated), and doing any programmatic rendering using the drawing API.
// the method takes two parameters, unscaledWidth and unscaledHeight. These are the assigned width and height of the component
// in its own coordinate system (i.e., absent any scaling or rotation of the component or its parents). Generally, you want
// to render your component relative to these values, and assume the system will take care of any other adjustments.
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
// If we're doing any drawing at all, we typically want to clear the graphics first thing. All drawing happens through
// the Component's graphics objects. Forgetting to clear is a very common mistake. Because Flash is a 'retained mode' renderer, it remembers
// (retains) all the drawing you do until you explicitly tell it to clear it. That means that even if you draw the same rectangle, flash remembers
// the previous rectangle, and now draws both of them on screen. Do this enough, and you'll see your application slow down trying to render all of those shapes.
// so always remember to clear before redrawing.
// note that in this case we're not drawing directly into our component, but rather into a sprite we've added as a child. In many cases it's fine to draw directly into the
// component. But any children added to the component will obscure the drawing. So if you need to make sure your drawing is visible (i.e., in this case, we want our line to show up
// even when a background is specified), it's best to create a child for drawing into that you can explicitly insert into the layering of your children.
// This also points out that it's perfectly legal to create raw flash display objects...in this case a Sprite...and use them as children of your UIComponent. While Containers
// have the restriction that they can only contain UIComponents, a UIComponent can contain any display object you like.
// One more thing to note...It's a common mistake to create a Sprite, add it as a child, and then in your updateDisplayList routine, try and set its width and height to match the
// size of the component so you can draw into it. But to Flash, assigning the width and height to a Sprite or other Display Object has a very different meaning. Instead of telling the component
// where it can draw, what you're actually doing is telling it to scale its content to match that size. In this case, that's not what we want. Sprites and other raw display Objects will just expand
// automatically to fill whatever space their contents (drawing, other display objecs) fill. So for our sprite we're going to draw into, we won't bother trying to tell it to be a particular size;
// we just start drawing into it at the size we want, and expect Flash to take care of the rest.
_lineSprite.graphics.clear();
// firs thing we'll do is set the size of our border skin. It's a component's responsibility to position and size all of its children; Flex does no sizing or positioning
// by default. When an external developer tries to set the size of a component, the framework needs to react accordingly; namely, it remembers that size as an explicit request for a width
// and height, and lets the component's parent know that it may need to re-measure itself and possibly update its display list, in case those details depend on the size of the sub-component
// that has been modified. However, when a parent component sets the size of a child in the course of its rendering code, the framework wants to take a different path. It doesn't want to notify
// the parent that a child has changed -- clearly the parent knows that (being the one doing the setting). Additionally, there is often a difference between an 'explicit' size set by an external developer,
// and the 'actual' size the component's parent decides to assign to it based on its explicit size and other considerations.
// In order to differentiate between these two different code paths, components support the 'setActualSize' function. This sets the 'current' width and height of the component, without recording any
// change to its explicitly requested size, and without triggering any additional measurement or layout in the component's parent.
// For these reasons, when writing a custom component, you should generally use the 'setActualSize' function on your subcomponents during your updateDisplayList call.
_background.setActualSize(unscaledWidth, unscaledHeight);
// we're going to lay out our stacks of renderers, left to right. We'll initialize this variable to point to where our first stack will go.
var left:Number = 4*GAP_FROM_LEFT_RENDERER_EDGE;
var details:StackDetails;
// this is going to be a two pass layout process. First, we'll lay out each stack left to right, top to bottom. Then we'll iterate through our stacks in
// reverse order, and try and align the selected item in each stack, from right to left. In the first pass, we're going to calculate some information that
// will prove useful in the second pass. So we'll store off that information in a temporary array for later use.
var detailsList:Array = [];
// the value of our horizontalGap style. Since calling getStyle (and guarding against bad values) is a mildly expensive operation, it's a good idea to store off
// style values into a local variable if you're going to accessing them repeatedly in a function.
var horizontalGap:Number = horizontalGapWithDefault;
// OK, time for our first pass. We're going to iterate over our stacks of renderers, laying them out left to right, top to bottom, and calculating critical
// size details as we go.
for(var i:int = 0;i<_renderers.length;i++)
{
// lay out the Nth stack
details = renderStack(_renderers[i], Number(_selectedPathIndices[i]), left);
// store off the details we calculated to use in our second pass
detailsList.push(details);
// advance our marker for where the next stack should be positioned horizontally.
left = details.right + horizontalGap;
}
// Ok, we're going to perform our second layout pass here. The idea is this: We want to adjust the vertical positioning of our selected items so we get a nice straight line.
// To get the effect we're going for, we want to lay out the last selected item first, and then work our way backwards. The one catch is that our last stack or renderers
// don't have anything selected yet. So we'll start with the second to last one.
var selectionBaselineTarget:Number;
if (_selectedPathIndices.length == 0)
{
// we've only got one open stack, with nothing selected, so just align that one and call it a day.
alignStack(_renderers[0],detailsList[0],selectionBaselineTarget);
}
else if (_selectedPathIndices.length > 0)
{
// ok, we've got at least one with a selected item. So start
// with the last selected item and align it.
i = _selectedPathIndices.length-1;
selectionBaselineTarget = alignStack(_renderers[i],detailsList[i],selectionBaselineTarget);
// now let's adjust the last stack, the unselected one. He'll just try and be centered on the previous selection.
if(i+1 < _renderers.length)
alignStack(_renderers[i+1],detailsList[i+1],selectionBaselineTarget);
for(i--;i>=0;i--)
{
// now, working backwards, try and align the selection to the baseline of the previous
// selection.
selectionBaselineTarget = alignStack(_renderers[i],detailsList[i], selectionBaselineTarget);
}
}
// Now we want to draw the line that underscores the individual items. Here we're going to use the
// graphics API. If we were making a truly configurable component, we might package up the line drawing into
// a sub-component and allow developers to swap in a different implementation using skinning/CSS. There's a grey
// area between skinning and itemRenderers here, which this definitely falls into.
if(_renderers.length > 0)
{
// start at the baseline of the selected item in the first stack
// HEY! Look! hardcoded graphical properties. That's a red flag that these values (the line color, weight, and alpha)
// are probably something we should move into styles for an easy extra bit of configuration.
_lineSprite.graphics.lineStyle(1,0xAAAAAA);
details = detailsList[0];
_lineSprite.graphics.moveTo(details.left - GAP_FROM_LEFT_RENDERER_EDGE,details.selectionBaseline);
// now, for each stack,
for(i=0;i<_renderers.length;i++)
{
// grab those layout details we stored off earlier
details = detailsList[i];
if(i < _selectedPathIndices.length)
{
// first draw from the end of the previous selection vertically to the beginning of the
// baseline of this selection
_lineSprite.graphics.lineTo(details.left - GAP_FROM_LEFT_RENDERER_EDGE,details.selectionBaseline);
// now draw horizontally across the selection's baseline.
_lineSprite.graphics.lineTo(details.right + horizontalGap - GAP_FROM_LEFT_RENDERER_EDGE,details.selectionBaseline);
}
else if (_renderers[i].length > 0)
{
// there's no selection for this stack, so we'll just draw a vertical line
// down the entire stack.
_lineSprite.graphics.moveTo(details.left-GAP_FROM_LEFT_RENDERER_EDGE,details.top);
_lineSprite.graphics.lineTo(details.left-GAP_FROM_LEFT_RENDERER_EDGE,details.bottom);
}
}
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -