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