📄 randomwalk.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
import mx.managers.HistoryManager;
import mx.managers.IHistoryManagerClient;
import flash.events.Event;
/* The styles this component will support. By declaring them here, in metadata, the MXML compiler will allow developers to define inline styles on
* MXML tags, and perform property type checking. Styles should be declared using the camelCase notation
* In general, you shouldn't declare styles as inheriting. Inheriting styles are applied globally in Flex, so if you declare a style
* as inheriting, it may accidentally cascade down to a subcomponent that uses the same style name for a different meaning. Use
* other techniques to intentionally propogate style values from components to internal sub-components */
[Style(name="horizontalGap", type="Number", format="Length", inherit="no")]
[Style(name="verticalGap", type="Number", format="Length", inherit="no")]
/* A skin is really just another style as far as the Flex compiler is concerned. by defining the style as type Class, you can use either the Embed()
* or ClassReference() CSS functions to name either a bitmap or programmatic skin via CSS.
*/
[Style(name="itemHighlightSkin", type="Class", inherit="no")]
/* Styles used by the default programmatic highlightSkin
*/
[Style(name="highlightRadius", type="Number", inherit="no")]
[Style(name="highlightColors", type="Array", inherit="no")]
/* The events this component will dispatch. By declaring them here, in metadata, the MXML compiler will allow developers to attach an event handler in MXML.
* otherwise it would see the handler as an attempt to set a non-existant property */
[Event(name="itemClick", type="randomWalkClasses.RandomWalkEvent")]
/* This component's default property. Declaring a default property allows the developer to specify the value of the property
* as the content of its tag in MXML without having to explicitly wrap it in a property tag. Be judicious with your use of this
* feature...only use it in scenarios where a developer might reasonably consider the value of this property to be the 'content'
* or intrinsic value of the component. We consider it best practice not to declare scalar values properties (numbers, strings)
* as default properties as these are generally best specified as attributes
*/
[DefaultProperty("dataProvider")]
/* 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 RandomWalk extends UIComponent implements IFocusManagerComponent,
IHistoryManagerClient
{
/* private variables of the component */
private var _dataProvider:XML;
private var _nodesInvalid:Boolean = true;
private var _renderers:Array = [];
private var _selectedPathIndices:Array = [];
private var _itemRenderer:IFactory = new ClassFactory(RandomWalkRenderer);
private var _rendererDataMap:Object;
private var _dataRendererMap:Object;
private var _highlightedNode:XML;
private var _highlight:IFlexDisplayObject;
private var _background:IFlexDisplayObject;
private var _lineSprite:Sprite;
private var _historyManagementEnabled:Boolean = false;
/* 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;
/* 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 RandomWalk()
{
super();
_rendererDataMap = {};
_dataRendererMap = {};
addEventListener(Event.ADDED, addedHandler);
addEventListener(Event.REMOVED, removedHandler);
}
override protected function createChildren():void
{
super.createChildren();
var backgroundClass:Class = getStyle("backgroundSkin");
if (backgroundClass != null)
{
_background = new backgroundClass()
if(_background is ISimpleStyleClient)
ISimpleStyleClient(_background).styleName = this;
addChild(DisplayObject(_background));
}
var highlightClass:Class = getStyle("itemHighlightSkin");
if (highlightClass != null)
{
_highlight = new highlightClass();
if(_highlight is ISimpleStyleClient)
ISimpleStyleClient(_highlight).styleName = this;
addChild(DisplayObject(_highlight));
}
_lineSprite = new Sprite();
addChild(_lineSprite);
}
/* ----------------------------------------------------------------------------------------
/ public properties
*/
public function set dataProvider(value:XML):void
{
_dataProvider = value;
_nodesInvalid = true;
invalidateProperties();
}
[Bindable] public function get dataProvider():XML
{
return _dataProvider;
}
public function set itemRenderer(value:IFactory):void
{
_itemRenderer = value;
for (var i:int = 0;i<_renderers.length;i++)
removeInstances(_renderers[i]);
_renderers = [];
invalidateProperties();
}
[Bindable] public function get itemRenderer():IFactory
{
return _itemRenderer;
}
public function set historyManagementEnabled(value:Boolean):void
{
if (_historyManagementEnabled != value)
{
_historyManagementEnabled = value;
if (_historyManagementEnabled)
HistoryManager.register(this);
else
HistoryManager.unregister(this);
}
}
[Bindable] public function get historyManagementEnabled():Boolean
{
return _historyManagementEnabled;
}
/* ----------------------------------------------------------------------------------------
/ 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:UIComponent;
var j:int;
var i:int;
if(_nodesInvalid)
{
_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;
_dataRendererMap[UIDUtil.getUID(childNode)] = inst;
// 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.
IDataRenderer(inst).data = childNode;
// it's often the case that a component will have more information its item renderers might be interested in than just the data. It's the practice
// in Flex to define a separate interface to allow define the additional information your component might pass to an item renderer. Try not to require
// that your item renderers implement this interface...it's nice to provide customers with the option to invest less effort but still get a gracefully
// degrading experience.
// in this case, we think our item renderers might want to render differently based on whether they're selected or not. While in controlled situations, you
// could just explicitly set the 'currentState' property of your renderers, If you want to allow developers to swap in different item renderers, that's not a great
// idea. It essentially hijacks the view states of the item renderer, and doesn't allow the developer to add additional states or behavior. Better to let them
// manage their own currentState.
if (inst is IRandomWalkRenderer)
IRandomWalkRenderer(inst).selectedState = (isNaN(selectedIndex)? NaN:
(selectedIndex == j)? 1:
0);
}
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 = _itemRenderer.newInstance();
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;
}
private function dataToInstance(value:XML):UIComponent
{
return _dataRendererMap[UIDUtil.getUID(value)];
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -