mirror of
https://github.com/hiloteam/Hilo.git
synced 2025-12-08 20:35:59 +00:00
348 lines
11 KiB
JavaScript
348 lines
11 KiB
JavaScript
/**
|
||
* Hilo
|
||
* Copyright 2015 alibaba.com
|
||
* Licensed under the MIT License
|
||
*/
|
||
|
||
/**
|
||
* @class Container是所有容器类的基类。每个Container都可以添加其他可视对象为子级。
|
||
* @augments View
|
||
* @param {Object} properties 创建对象的属性参数。可包含此类所有可写属性。
|
||
* @module hilo/view/Container
|
||
* @requires hilo/core/Hilo
|
||
* @requires hilo/core/Class
|
||
* @requires hilo/view/View
|
||
* @property {Array} children 容器的子元素列表。只读。
|
||
* @property {Boolean} pointerChildren 指示容器的子元素是否能响应用户交互事件。默认为true。
|
||
* @property {Boolean} clipChildren 指示是否裁剪超出容器范围的子元素。默认为false。
|
||
*/
|
||
var Container = Class.create(/** @lends Container.prototype */{
|
||
Extends: View,
|
||
constructor: function(properties){
|
||
properties = properties || {};
|
||
this.id = this.id || properties.id || Hilo.getUid("Container");
|
||
Container.superclass.constructor.call(this, properties);
|
||
|
||
if(this.children) this._updateChildren();
|
||
else this.children = [];
|
||
},
|
||
|
||
children: null,
|
||
pointerChildren: true,
|
||
clipChildren: false,
|
||
|
||
/**
|
||
* 返回容器的子元素的数量。
|
||
* @returns {Uint} 容器的子元素的数量。
|
||
*/
|
||
getNumChildren: function(){
|
||
return this.children.length;
|
||
},
|
||
|
||
/**
|
||
* 在指定索引位置添加子元素。
|
||
* @param {View} child 要添加的子元素。
|
||
* @param {Number} index 指定的索引位置,从0开始。
|
||
*/
|
||
addChildAt: function(child, index){
|
||
var children = this.children,
|
||
len = children.length,
|
||
parent = child.parent;
|
||
|
||
index = index < 0 ? 0 : index > len ? len : index;
|
||
var childIndex = this.getChildIndex(child);
|
||
if(childIndex == index){
|
||
return this;
|
||
}else if(childIndex >= 0){
|
||
children.splice(childIndex, 1);
|
||
index = index == len ? len - 1 : index;
|
||
}else if(parent){
|
||
parent.removeChild(child);
|
||
}
|
||
|
||
children.splice(index, 0, child);
|
||
|
||
//直接插入,影响插入位置之后的深度
|
||
//Insert directly, this will affect depth of elements after the index.
|
||
if(childIndex < 0){
|
||
this._updateChildren(index);
|
||
}
|
||
//只是移动时影响中间段的深度
|
||
//Will affect depth of elements in the middle during moving
|
||
else{
|
||
var startIndex = childIndex < index ? childIndex : index;
|
||
var endIndex = childIndex < index ? index : childIndex;
|
||
this._updateChildren(startIndex, endIndex + 1);
|
||
}
|
||
|
||
return this;
|
||
},
|
||
|
||
/**
|
||
* 在最上面添加子元素。
|
||
* @param {View} child 要添加的子元素。
|
||
*/
|
||
addChild: function(child){
|
||
var total = this.children.length,
|
||
args = arguments;
|
||
|
||
for(var i = 0, len = args.length; i < len; i++){
|
||
this.addChildAt(args[i], total + i);
|
||
}
|
||
return this;
|
||
},
|
||
|
||
/**
|
||
* 在指定索引位置删除子元素。
|
||
* @param {Int} index 指定删除元素的索引位置,从0开始。
|
||
* @returns {View} 被删除的对象。
|
||
*/
|
||
removeChildAt: function(index){
|
||
var children = this.children;
|
||
if(index < 0 || index >= children.length) return null;
|
||
|
||
var child = children[index];
|
||
if(child){
|
||
//NOTE: use `__renderer` for fixing child removal (DOMRenderer and FlashRenderer only).
|
||
//Do `not` use it in any other case.
|
||
if(!child.__renderer){
|
||
var obj = child;
|
||
while(obj = obj.parent){
|
||
//obj is stage
|
||
if(obj.renderer){
|
||
child.__renderer = obj.renderer;
|
||
break;
|
||
}
|
||
else if(obj.__renderer){
|
||
child.__renderer = obj.__renderer;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
if(child.__renderer){
|
||
child.__renderer.remove(child);
|
||
}
|
||
|
||
child.parent = null;
|
||
child.depth = -1;
|
||
}
|
||
|
||
children.splice(index, 1);
|
||
this._updateChildren(index);
|
||
|
||
return child;
|
||
},
|
||
|
||
/**
|
||
* 删除指定的子元素。
|
||
* @param {View} child 指定要删除的子元素。
|
||
* @returns {View} 被删除的对象。
|
||
*/
|
||
removeChild: function(child){
|
||
return this.removeChildAt(this.getChildIndex(child));
|
||
},
|
||
|
||
/**
|
||
* 删除指定id的子元素。
|
||
* @param {String} id 指定要删除的子元素的id。
|
||
* @returns {View} 被删除的对象。
|
||
*/
|
||
removeChildById: function(id){
|
||
var children = this.children, child;
|
||
for(var i = 0, len = children.length; i < len; i++){
|
||
child = children[i];
|
||
if(child.id === id){
|
||
this.removeChildAt(i);
|
||
return child;
|
||
}
|
||
}
|
||
return null;
|
||
},
|
||
|
||
/**
|
||
* 删除所有的子元素。
|
||
* @returns {Container} 容器本身。
|
||
*/
|
||
removeAllChildren: function(){
|
||
while(this.children.length) this.removeChildAt(0);
|
||
return this;
|
||
},
|
||
|
||
/**
|
||
* 返回指定索引位置的子元素。
|
||
* @param {Number} index 指定要返回的子元素的索引值,从0开始。
|
||
*/
|
||
getChildAt: function(index){
|
||
var children = this.children;
|
||
if(index < 0 || index >= children.length) return null;
|
||
return children[index];
|
||
},
|
||
|
||
/**
|
||
* 返回指定id的子元素。
|
||
* @param {String} id 指定要返回的子元素的id。
|
||
*/
|
||
getChildById: function(id){
|
||
var children = this.children, child;
|
||
for(var i = 0, len = children.length; i < len; i++){
|
||
child = children[i];
|
||
if(child.id === id) return child;
|
||
}
|
||
return null;
|
||
},
|
||
|
||
/**
|
||
* 返回指定子元素的索引值。
|
||
* @param {View} child 指定要返回索引值的子元素。
|
||
*/
|
||
getChildIndex: function(child){
|
||
return this.children.indexOf(child);
|
||
},
|
||
|
||
/**
|
||
* 设置子元素的索引位置。
|
||
* @param {View} child 指定要设置的子元素。
|
||
* @param {Number} index 指定要设置的索引值。
|
||
*/
|
||
setChildIndex: function(child, index){
|
||
var children = this.children,
|
||
oldIndex = children.indexOf(child);
|
||
|
||
if(oldIndex >= 0 && oldIndex != index){
|
||
var len = children.length;
|
||
index = index < 0 ? 0 : index >= len ? len - 1 : index;
|
||
children.splice(oldIndex, 1);
|
||
children.splice(index, 0, child);
|
||
this._updateChildren();
|
||
}
|
||
return this;
|
||
},
|
||
|
||
/**
|
||
* 交换两个子元素的索引位置。
|
||
* @param {View} child1 指定要交换的子元素A。
|
||
* @param {View} child2 指定要交换的子元素B。
|
||
*/
|
||
swapChildren: function(child1, child2){
|
||
var children = this.children,
|
||
index1 = this.getChildIndex(child1),
|
||
index2 = this.getChildIndex(child2);
|
||
|
||
child1.depth = index2;
|
||
children[index2] = child1;
|
||
child2.depth = index1;
|
||
children[index1] = child2;
|
||
},
|
||
|
||
/**
|
||
* 交换两个指定索引位置的子元素。
|
||
* @param {Number} index1 指定要交换的索引位置A。
|
||
* @param {Number} index2 指定要交换的索引位置B。
|
||
*/
|
||
swapChildrenAt: function(index1, index2){
|
||
var children = this.children,
|
||
child1 = this.getChildAt(index1),
|
||
child2 = this.getChildAt(index2);
|
||
|
||
child1.depth = index2;
|
||
children[index2] = child1;
|
||
child2.depth = index1;
|
||
children[index1] = child2;
|
||
},
|
||
|
||
/**
|
||
* 根据指定键值或函数对子元素进行排序。
|
||
* @param {Object} keyOrFunction 如果此参数为String时,则根据子元素的某个属性值进行排序;如果此参数为Function时,则根据此函数进行排序。
|
||
*/
|
||
sortChildren: function(keyOrFunction){
|
||
var fn = keyOrFunction,
|
||
children = this.children;
|
||
if(typeof fn == "string"){
|
||
var key = fn;
|
||
fn = function(a, b){
|
||
return b[key] - a[key];
|
||
};
|
||
}
|
||
children.sort(fn);
|
||
this._updateChildren();
|
||
},
|
||
|
||
/**
|
||
* 更新子元素。
|
||
* @private
|
||
*/
|
||
_updateChildren: function(start, end){
|
||
var children = this.children, child;
|
||
start = start || 0;
|
||
end = end || children.length;
|
||
for(var i = start; i < end; i++){
|
||
child = children[i];
|
||
child.depth = i + 1;
|
||
child.parent = this;
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 返回是否包含参数指定的子元素。
|
||
* @param {View} child 指定要测试的子元素。
|
||
*/
|
||
contains: function(child){
|
||
while(child = child.parent){
|
||
if(child === this){
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
},
|
||
|
||
/**
|
||
* 返回由x和y指定的点下的对象。
|
||
* @param {Number} x 指定点的x轴坐标。
|
||
* @param {Number} y 指定点的y轴坐标。
|
||
* @param {Boolean} usePolyCollision 指定是否使用多边形碰撞检测。默认为false。
|
||
* @param {Boolean} global 使用此标志表明将查找所有符合的对象,而不仅仅是第一个,即全局匹配。默认为false。
|
||
* @param {Boolean} eventMode 使用此标志表明将在事件模式下查找对象。默认为false。
|
||
*/
|
||
getViewAtPoint: function(x, y, usePolyCollision, global, eventMode){
|
||
var result = global ? [] : null,
|
||
children = this.children, child, obj;
|
||
|
||
for(var i = children.length - 1; i >= 0; i--){
|
||
child = children[i];
|
||
//skip child which is not shown or pointer enabled
|
||
if(!child || !child.visible || child.alpha <= 0 || (eventMode && !child.pointerEnabled)) continue;
|
||
//find child recursively
|
||
if(child.children && child.children.length && !(eventMode && !child.pointerChildren)){
|
||
obj = child.getViewAtPoint(x, y, usePolyCollision, global, eventMode);
|
||
}
|
||
|
||
if(obj){
|
||
if(!global) return obj;
|
||
else if(obj.length) result = result.concat(obj);
|
||
}else if(child.hitTestPoint(x, y, usePolyCollision)){
|
||
if(!global) return child;
|
||
else result.push(child);
|
||
}
|
||
}
|
||
|
||
return global && result.length ? result : null;
|
||
},
|
||
|
||
/**
|
||
* 覆盖渲染方法。
|
||
* @private
|
||
*/
|
||
render: function(renderer, delta){
|
||
Container.superclass.render.call(this, renderer, delta);
|
||
|
||
var children = this.children.slice(0), i, len, child;
|
||
for(i = 0, len = children.length; i < len; i++){
|
||
child = children[i];
|
||
//NOTE: the child could remove or change it's parent
|
||
if(child.parent === this) child._render(renderer, delta);
|
||
}
|
||
}
|
||
|
||
});
|