📄 simplerandomwalk.as
字号:
package
{
import mx.core.UIComponent;
import mx.core.ClassFactory;
import mx.controls.Label;
import flash.utils.Dictionary;
import mx.core.IDataRenderer;
import flash.events.MouseEvent;
import mx.utils.UIDUtil;
import mx.core.IFactory;
import randomWalkClasses.RandomWalkEvent;
import mx.core.IFlexDisplayObject;
import flash.display.DisplayObject;
import mx.styles.ISimpleStyleClient;
import mx.skins.RectangularBorder;
import randomWalkClasses.RandomWalkRenderer;
import randomWalkClasses.IRandomWalkRenderer;
import flash.display.Sprite;
import flash.events.KeyboardEvent;
import flash.ui.Keyboard;
import mx.managers.IFocusManagerComponent
/* RandomWalk extends UIComponent. The first decision you have to make when building a custom component is what
* base class to use. While a container class is a natural inclination, it is not always the best choice. Use a container
* class if you want its benefits -- built in scroll functionality, and the ability to easily declare and manage children from MXML.
* If instead you are creating a component that will feel more like a control to the developer -- i.e., one that is used to represent
* data, or an actionable task for the user, rather than to group and manage other components -- consider using UIComponent.
* UIComponent can contain other controls (and raw flash display objects) to achieve whatever rendering and interaction behaviors
* it requires
*/
public class SimpleRandomWalk extends UIComponent
{
/* private variables of the component */
private var _dataProvider:XML;
private var _nodesInvalid:Boolean = true;
private var _renderers:Array = [];
private var _selectedPathIndices:Array = [];
private var _rendererDataMap:Object;
private var _highlightedInstance:UIComponent;
protected var _lineSprite:Sprite;
/* private constants. In the process of developing a component, a constant used in the rendering of the component
* is often a good candidate for something that should be a style configurable by the developer via CSS.
*/
private const GAP_FROM_LEFT_RENDERER_EDGE:Number = 2;
private const ITEM_RENDERER_INDEX:Number = 3;
private const GAP_FROM_TOP_EDGE:Number = 2*GAP_FROM_LEFT_RENDERER_EDGE;
private const HORIZONTAL_GAP:Number = 2;
private const VERTICAL_GAP:Number = 2;
/* The constructor. Developer's often create and add internal children in their constructor, but you'll improve the initialization
* performance of your component if you wait to init children in your createChildren() routine.
*/
public function SimpleRandomWalk()
{
super();
_rendererDataMap = {};
}
override protected function createChildren():void
{
super.createChildren();
if(_lineSprite == null)
{
_lineSprite = new Sprite();
}
addChild(_lineSprite);
}
/* ----------------------------------------------------------------------------------------
/ public properties
*/
public function set dataProvider(value:XML):void
{
_dataProvider = value;
_nodesInvalid = true;
invalidateProperties();
}
public function get dataProvider():XML
{
return _dataProvider;
}
/* ----------------------------------------------------------------------------------------
/ property management, plus data -> instance renderers
*/
/* commit properties is where a component should perform any delayed tasks related to changes in either its properties or its underlying data.
* measurement and layout happen elsewhere, but any other compute intensive tasks should be deferred and executed here. For data driven components
* that need to manage a dynamic list of instances to rnederer the data, this is a good place to update that list
*/
override protected function commitProperties():void
{
var root:XML = _dataProvider;
var inst:Label;
var j:int;
var i:int;
if(_nodesInvalid == true)
{
_nodesInvalid = false;
/* For our component, the displayed data, and hence the renderer instances we need, are dictated by the 'selected path' through our data tree. At each level in the
* selected path, we iterate over the children for the open node and create a renderer for them.
*/
/* for each level in the selected path*/
for(i = 0;i<_selectedPathIndices.length+1;i++)
{
// grab its children
var children:XMLList = root.children();
// and any instances we already have defined at that level
var instances:Array = _renderers[i];
// we're going to create renderers for the children of all of our selected nodes.
// but our last selected node might have no children. If that's the case, we'll just stop
// here.
if(children.length() == 0)
break;
if(instances == null)
instances= _renderers[i] = [];
// now for each level N in the selection path, instances[N] should be an array of item renderers to display its children
// here we make sure we have the right number of instances created for that level
if(instances.length < children.length())
{
for(j = instances.length;j < children.length();j++)
{
instances.push(createInstance());
}
}
else if (instances.length > children.length())
{
removeInstances( instances.splice(children.length(),instances.length - children.length()) );
}
// now that we know we have enough renderers, iterate over each renderer, set its data,
// and its selected state
var selectedIndex:Number = _selectedPathIndices[i];
for(j=0;j<children.length();j++)
{
var childNode:XML = children[j];
inst = instances[j];
// when the user clicks on a renderer, we need to know which node it corresponds to. Since AS3 doesn't allow us to decorate
// components with arbitrary properties, we'll use a separate HashMap to map from instances to data nodes.
_rendererDataMap[UIDUtil.getUID(inst)] = childNode;
// set the instances data. Flex's convention is that item renderers that want to know about data implement the IDataRenderer interface.
// if you reasonably think an item renderer that didn't know about its data would be useless in your component, feel free to just assume
// that your renderer implements it.
inst.text = childNode.@label;
}
if(i < _selectedPathIndices.length)
{
root = children[_selectedPathIndices[i]];
}
}
// since we've made changes that affect our size and display, we must invalidate to trigger an update.
invalidateSize();
invalidateDisplayList();
}
}
private function removeInstances(instances:Array):void
{
for(var i:int=0;i<instances.length;i++)
removeChild(instances[i]);
}
private function createInstance():UIComponent
{
var inst:UIComponent = new Label();
inst.addEventListener(MouseEvent.CLICK,itemClickHandler);
inst.addEventListener(MouseEvent.ROLL_OVER,itemRollOverHandler);
inst.addEventListener(MouseEvent.ROLL_OUT,itemRollOutHandler);
addChildAt(inst,Math.min(numChildren,ITEM_RENDERER_INDEX));
return inst;
}
/* ----------------------------------------------------------------------------------------
/ Event handlers
*/
private function itemRollOverHandler(e:MouseEvent):void
{
_highlightedInstance = UIComponent(e.currentTarget);
invalidateDisplayList();
}
private function itemRollOutHandler(e:MouseEvent):void
{
_highlightedInstance = 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(child);
}
private function expandItem(child:UIComponent):void
{
// use our map to get the data associated with this item renderer
var node:XML = _rendererDataMap[UIDUtil.getUID(child)];
// 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.
_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));
// 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]);
}
// 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();
}
/* ----------------------------------------------------------------------------------------
* measurement and layout
*/
// 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
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -