      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