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