1 
  2 /**
  3  * @class Container是所有容器类的基类。每个Container都可以添加其他可视对象为子级。
  4  * @augments View
  5  * @param {Object} properties 创建对象的属性参数。可包含此类所有可写属性。
  6  * @module hilo/view/Container
  7  * @requires hilo/core/Hilo
  8  * @requires hilo/core/Class
  9  * @requires hilo/view/View
 10  * @property {Array} children 容器的子元素列表。只读。
 11  * @property {Number} numChildren 容器的子元素数量。只读。
 12  * @property {Boolean} pointerChildren 指示容器的子元素是否能响应用户交互事件。默认为true。
 13  * @property {Boolean} clipChildren 指示是否裁剪超出容器范围的子元素。默认为false。
 14  */
 15 var Container = Class.create(/** @lends Container.prototype */{
 16     Extends: View,
 17     constructor: function Container(properties){
 18         properties = properties || {};
 19         this.id = this.id || properties.id || Hilo.getUid("Container");
 20         Container.superclass.constructor.call(this, properties);
 21 
 22         if(this.children) this._updateChildren();
 23         else this.children = [];
 24     },
 25 
 26     children: null,
 27     pointerChildren: true,
 28     clipChildren: false,
 29     numChildren: {
 30         get: function(){
 31             return this.children.length;
 32         }
 33     },
 34 
 35     /**
 36      * 在指定索引位置添加子元素。
 37      * @param {View} child 要添加的子元素。
 38      * @param {Number} index 指定的索引位置,从0开始。
 39      */
 40     addChildAt: function(child, index){
 41         var children = this.children, 
 42             len = children.length, 
 43             parent = child.parent;
 44         
 45         index = index < 0 ? 0 : index > len ? len : index;
 46         var childIndex = this.getChildIndex(child);
 47         if(childIndex == index){
 48             return this;
 49         }else if(childIndex >= 0){
 50             children.splice(childIndex, 1);
 51         }else if(parent){
 52             parent.removeChild(child);
 53         }
 54 
 55         children.splice(index, 0, child);
 56         this._updateChildren(index);
 57 
 58         return this;
 59     },
 60 
 61     /**
 62      * 在最上面添加子元素。
 63      * @param {View} child 要添加的子元素。
 64      */
 65     addChild: function(child){
 66         var total = this.children.length, 
 67             args = arguments;
 68 
 69         for(var i = 0, len = args.length; i < len; i++){
 70             this.addChildAt(args[i], total + i);
 71         }
 72         return this;
 73     },
 74 
 75     /**
 76      * 在指定索引位置删除子元素。
 77      * @param {Int} index 指定删除元素的索引位置,从0开始。
 78      * @returns 被删除的对象。
 79      */
 80     removeChildAt: function(index){
 81         var children = this.children;
 82         if(index < 0 || index >= children.length) return null;
 83 
 84         var child = children[index];
 85         if(child){
 86             var stage = this.stage;
 87             if(stage) stage.renderer.remove(child);
 88             child.parent = null;
 89             child.depth = -1;
 90         }
 91 
 92         children.splice(index, 1);
 93         this._updateChildren(index);
 94 
 95         return child;
 96     },
 97 
 98     /**
 99      * 删除指定的子元素。
100      * @param {View} child 指定要删除的子元素。
101      * @returns 被删除的对象。
102      */
103     removeChild: function(child){
104         return this.removeChildAt(this.getChildIndex(child));
105     },
106 
107     /**
108      * 删除指定id的子元素。
109      * @param {String} id 指定要删除的子元素的id。
110      * @returns 被删除的对象。
111      */
112     removeChildById: function(id){
113         var children = this.children, child;
114         for(var i = 0, len = children.length; i < len; i++){
115             child = children[i];
116             if(child.id === id){
117                 this.removeChildAt(i);
118                 return child;
119             }
120         }
121         return null;
122     },
123 
124     /**
125      * 删除所有的子元素。
126      */
127     removeAllChildren: function(){
128         while(this.children.length) this.removeChildAt(0);
129         return this;
130     },
131 
132     /**
133      * 返回指定索引位置的子元素。
134      * @param {Number} index 指定要返回的子元素的索引值,从0开始。
135      */
136     getChildAt: function(index){
137         var children = this.children;
138         if(index < 0 || index >= children.length) return null;
139         return children[index];
140     },
141 
142     /**
143      * 返回指定id的子元素。
144      * @param {String} id 指定要返回的子元素的id。
145      */
146     getChildById: function(id){
147         var children = this.children, child;
148         for(var i = 0, len = children.length; i < len; i++){
149             child = children[i];
150             if(child.id === id) return child;
151         }
152         return null;
153     },
154 
155     /**
156      * 返回指定子元素的索引值。
157      * @param {View} child 指定要返回索引值的子元素。
158      */
159     getChildIndex: function(child){
160         return this.children.indexOf(child);
161     },
162 
163     /**
164      * 设置子元素的索引位置。
165      * @param {View} child 指定要设置的子元素。
166      * @param {Number} index 指定要设置的索引值。
167      */
168     setChildIndex: function(child, index){
169         var children = this.children, 
170             oldIndex = children.indexOf(child);
171 
172         if(oldIndex >= 0 && oldIndex != index){
173             var len = children.length;
174             index = index < 0 ? 0 : index >= len ? len - 1 : index;
175             children.splice(oldIndex, 1);
176             children.splice(index, 0, child);
177             this._updateChildren();
178         }
179         return this;
180     },
181 
182     /**
183      * 交换两个子元素的索引位置。
184      * @param {View} child1 指定要交换的子元素A。
185      * @param {View} child2 指定要交换的子元素B。
186      */
187     swapChildren: function(child1, child2){
188         var children = this.children,
189             index1 = this.getChildIndex(child1), 
190             index2 = this.getChildIndex(child2);
191 
192         child1.depth = index2;
193         children[index2] = child1;
194         child2.depth = index1;
195         children[index1] = child2;
196     },
197 
198     /**
199      * 交换两个指定索引位置的子元素。
200      * @param {Number} index1 指定要交换的索引位置A。
201      * @param {Number} index2 指定要交换的索引位置B。
202      */
203     swapChildrenAt: function(index1, index2){
204         var children = this.children,
205             child1 = this.getChildAt(index1), 
206             child2 = this.getChildAt(index2);
207 
208         child1.depth = index2;
209         children[index2] = child1;
210         child2.depth = index1;
211         children[index1] = child2;
212     },
213 
214     /**
215      * 根据指定键值或函数对子元素进行排序。
216      * @param {Object} keyOrFunction 如果此参数为String时,则根据子元素的某个属性值进行排序;如果此参数为Function时,则根据此函数进行排序。
217      */
218     sortChildren: function(keyOrFunction){
219         var fn = keyOrFunction,
220             children = this.children;
221         if(typeof fn == "string"){
222             var key = fn;
223             fn = function(a, b){
224                 return b[key] - a[key];
225             };
226         }
227         children.sort(fn);
228         this._updateChildren();
229     },
230 
231     /**
232      * 更新子元素。
233      * @private
234      */
235     _updateChildren: function(start, end){
236         var children = this.children, child,
237             start = start || 0,
238             end = end || children.length;
239         for(var i = start; i < end; i++){
240             child = children[i];
241             child.depth = i + 1;
242             child.parent = this;
243         }
244     },
245 
246     /**
247      * 返回是否包含参数指定的子元素。
248      * @param {View} child 指定要测试的子元素。
249      */
250     contains: function(child){
251         return this.getChildIndex(child) >= 0;
252     },
253 
254     /** 
255      * 返回由x和y指定的点下的对象。
256      * @param {Number} x 指定点的x轴坐标。
257      * @param {Number} y 指定点的y轴坐标。
258      * @param {Boolean} usePolyCollision 指定是否使用多边形碰撞检测。默认为false。
259      * @param {Boolean} global 使用此标志表明将查找所有符合的对象,而不仅仅是第一个,即全局匹配。默认为false。
260      * @param {Boolean} eventMode 使用此标志表明将在事件模式下查找对象。默认为false。
261      */
262     getViewAtPoint: function(x, y, usePolyCollision, global, eventMode){
263         var result = global ? [] : null,
264             children = this.children, child, obj;
265         
266         for(var i = children.length - 1; i >= 0; i--){
267             child = children[i];
268             //skip child which is not shown or pointer enabled
269             if(!child || !child.visible || child.alpha <= 0 || (eventMode && !child.pointerEnabled)) continue;
270             //find child recursively
271             if(child.children && child.numChildren && !(eventMode && !child.pointerChildren)){
272                 obj = child.getViewAtPoint(x, y, usePolyCollision, global, eventMode);
273             }
274 
275             if(obj){
276                 if(!global) return obj;
277                 else if(obj.length) result = result.concat(obj);
278             }else if(child.hitTestPoint(x, y, usePolyCollision)){
279                 if(!global) return child;
280                 else result.push(child);
281             }
282         }
283 
284         return global && result.length ? result : null;
285     },
286 
287     /**
288      * 覆盖渲染方法。
289      * @private
290      */
291     render: function(renderer, delta){
292         Container.superclass.render.call(this, renderer, delta);
293         
294         var children = this.children.slice(0), i, len, child;
295         for(i = 0, len = children.length; i < len; i++){
296             child = children[i];
297             //NOTE: the child could remove or change it's parent
298             if(child.parent === this) child._render(renderer, delta);
299         }
300     }
301 
302 });