ScrollViewCtrl:
import NodePool from "./NodePool";
const { ccclass, property } = cc._decorator;
@ccclass
export default class ScrollViewCtrl extends cc.Component {
@property(cc.Prefab)
itemPrefab: cc.Prefab = null;
bindIndexList: {};
scrollView: cc.ScrollView = null;
content: cc.Node = null;
view: cc.Node = null;
layout: cc.Layout = null;
itemName: string = null;
mat4: cc.Mat4 = null;
isInit: boolean = null;
data: any[] = null;
callbackList: any[] = null;
extData: any = null;
firstX: number = null;
firstY: number = null;
itemCache: any[] = null;
itemBuffer: any = null;
_tmpV2: any = null;
viewRect: any = null;
start() {
this.initOnce();
}
initOnce() {
this.bindIndexList = {};
this.scrollView = this.node.getComponent(cc.ScrollView);
this.content = this.scrollView.content
this.view = this.content.parent;
this.layout = this.content.getComponent(cc.Layout);
this.itemName = this.itemPrefab.name;
this.mat4 = cc.mat4();
this.initOnce = function () { }
}
onDestroy() {
this.content.off(cc.Node.EventType.POSITION_CHANGED, this.scrollEvent, this);
this.recycle();
}
registerScrollEvent(handler, target) {
if (!handler.name) {
return;
}
var scrollView = this.node.getComponent(cc.ScrollView);
var eventHandler = new cc.Component.EventHandler();
eventHandler.target = target.node;
eventHandler.component = cc.js.getClassName(target);
eventHandler.handler = handler.name;
var index = scrollView.scrollEvents.length;
scrollView.scrollEvents[index] = eventHandler;
}
init(data, param?) {
this.initOnce();
if (!Array.isArray(data)) {
console.error("传进来的数据不为数组!");
return;
}
if (!data.length) {
this.recycle();
return;
}
param = param || {};
this.isInit = true;
this.data = data;
this.callbackList = [];
this.extData = param.extData;
if (data.length == 0) {
this.content.off(cc.Node.EventType.POSITION_CHANGED, this.scrollEvent, this);
this.recycle();
return;
}
if (param.onChanged) {
this.onItemChanged(param.onChanged);
}
this.layout.enabled = false;
this.scrollView.stopAutoScroll();
if (!NodePool.Instance.hasPool(this.itemName)) {
NodePool.Instance.initPool(this.itemPrefab);
}
var template = this.itemPrefab.data;
var paddingLeft = this.layout.paddingLeft;
var paddingRight = this.layout.paddingRight;
var paddingTop = this.layout.paddingTop;
var paddingBottom = this.layout.paddingBottom;
var spacingX = this.layout.spacingX;
var spacingY = this.layout.spacingY;
let firstX = template.x;
let firstY = template.y;
if (this.scrollView.horizontal) {
firstX = -template.width / 2
firstX -= paddingLeft;
}
if (this.scrollView.vertical) {
firstY = -template.height / 2;
firstY -= paddingTop;
}
this.firstX = firstX;
this.firstY = firstY;
this.itemCache = [];
this.itemBuffer = this.itemBuffer || [];
this.itemBuffer.forEach(buffer => {
buffer.index = -1;
buffer.item.x = -9999999;
buffer.item.y = -9999999;
buffer.item.opacity = 0;
});
var initCache = (i) => {
this.itemCache[i] = this.itemCache[i] || {};
this.itemCache[i].x = firstX;
this.itemCache[i].y = firstY;
this.itemCache[i].width = template.width;
this.itemCache[i].height = template.height;
this.itemCache[i].scaleX = template.scaleX;
this.itemCache[i].scaleY = template.scaleY;
this.itemCache[i].visible = false;
}
initCache(0);
for (let i = 1; i < this.data.length; i++) {
initCache(i);
if (this.scrollView.horizontal) {
this.itemCache[i].x = this.itemCache[i - 1].x - (this.itemCache[i - 1].width / 2 + this.itemCache[i].width / 2 + spacingX);
}
if (this.scrollView.vertical) {
this.itemCache[i].y = this.itemCache[i - 1].y - (this.itemCache[i - 1].height / 2 + this.itemCache[i].height / 2 + spacingY);
}
}
let lastItem = this.itemCache[this.itemCache.length - 1];
if (this.scrollView.horizontal) {
this.content.width = Math.abs(lastItem.x + lastItem.width / 2 + paddingRight);
}
if (this.scrollView.vertical) {
this.content.height = Math.abs(lastItem.y - lastItem.height / 2 - paddingBottom);
}
this.content.on(cc.Node.EventType.POSITION_CHANGED, this.scrollEvent, this);
this.scheduleOnce(this.updateListView);
}
updateItemView(item, index) {
var js = this.getScript(item);
if (js && js.initData) {
js.initData(this.data[index], index, this.extData);
js.updateView && js.updateView();
}
}
getItem() {
let item = NodePool.Instance.getNode(this.itemName);
item.x = this.firstX;
item.y = this.firstY;
let data = {
item: item,
index: -1,
}
this.itemBuffer.push(data);
item.on(cc.Node.EventType.SIZE_CHANGED, this.onItemSizeChanged.bind(this, item), this);
item.on(cc.Node.EventType.SCALE_CHANGED, this.onItemSizeChanged.bind(this, item), this);
return data;
}
scrollEvent() {
if (!this.content || !this.isInit) return;
this.updateListView();
}
updateListView() {
if (!this.itemCache) {
return;
}
var attach = (buffer, i) => {
buffer.index = i;
buffer.item.x = this.itemCache[i].x;
buffer.item.y = this.itemCache[i].y;
buffer.item.scaleX = this.itemCache[i].scaleX;
buffer.item.scaleY = this.itemCache[i].scaleY;
buffer.item.opacity = 255;
if (this.scrollView.horizontal) {
buffer.item.width = this.itemCache[i].width;
}
if (this.scrollView.vertical) {
buffer.item.height = this.itemCache[i].height;
}
buffer.item.parent = this.content;
this.updateItemView(buffer.item, i);
}
for (var i = 0; i < this.itemCache.length; i++) {
var cache = this.itemCache[i];
var visible = this.isItemInView(i);
var buffer = this.itemBuffer.find((v) => {
return v.index == i;
});
if (visible) {
if (this.bindIndexList[i]) {
buffer = this.itemBuffer.find((v) => {
return v.bindIndex == i;
});
}
if (!buffer) {
buffer = this.itemBuffer.find((v) => {
return v.index == -1 && v.bindIndex == undefined;
});
buffer = buffer || this.getItem();
}
if (buffer.index != i) {
attach(buffer, i);
}
} else if (buffer) {
buffer.index = -1;
buffer.item.x = -9999999;
buffer.item.y = -9999999;
buffer.item.opacity = 0;
}
if (cache.visible != visible) {
this.runItemChangedCallback(i, visible);
}
cache.visible = visible;
}
this.itemBuffer.sort((a, b) => {
if (a.index < 0 || b.index < 0) {
return 1;
}
return a.index - b.index;
});
for (var i = 0; i < this.itemBuffer.length; i++) {
this.itemBuffer[i].item.setSiblingIndex(i);
}
}
isItemInView(index) {
this._tmpV2 = this._tmpV2 || cc.v2(0, 0);
this.view.getWorldMatrix(this.mat4);
let scale = this.mat4.m[0];
let wposx = this.mat4.m[12];
let wposy = this.mat4.m[13];
let width1 = this.view.width * scale;
let height1 = this.view.height * scale;
let wpos = this.view.convertToWorldSpaceAR(cc.Vec2.ZERO, this._tmpV2);
if (!this.viewRect || scale != 1 || (this.viewRect.x + width1 / 2) != wposx || (this.viewRect.y + height1 / 2) != wposy) {
this.viewRect = new cc.Rect(wpos.x - width1 / 2, wpos.y - height1 / 2, width1, height1);
}
let data = this.itemCache[index];
let wpos2 = this.content.convertToWorldSpaceAR(cc.v2(data.x, data.y));
let width2 = data.width * data.scaleX;
let height2 = data.height * data.scaleY;
let rect = new cc.Rect(wpos2.x - width2 / 2, wpos2.y - height2 / 2, width2, height2);
let ret = this.viewRect.intersects(rect);
return ret;
}
onItemSizeChanged(item) {
if (!this.itemCache) {
return;
}
let data = this.itemBuffer.find((v) => {
return v.item == item;
});
if (data && data.index >= 0) {
var itemData = this.itemCache[data.index];
if (this.scrollView.horizontal && itemData.width == item.width && itemData.scaleX == item.scaleX) {
return;
}
if (this.scrollView.vertical && itemData.height == item.height && itemData.scaleY == item.scaleY) {
return;
}
itemData.width = item.width;
itemData.scaleX = item.scaleX;
itemData.height = item.height;
itemData.scaleY = item.scaleY;
this.updateBuffer();
this.scheduleOnce(this.updateListView);
}
}
setItemProperty(index, property, value) {
if (!this.itemCache || !this.itemBuffer) {
return;
}
var buffer = this.itemBuffer.find(v => {
return v.index == index;
});
if (buffer) {
buffer.item[property] = value;
} else {
var itemData = this.itemCache[index];
itemData[property] = value;
}
this.updateBuffer();
this.scheduleOnce(this.updateListView);
}
updateBuffer() {
let lastItem = this.itemCache[this.itemCache.length - 1];
if (this.scrollView.vertical) {
this.itemCache[0].y = -this.itemCache[0].height / 2 - this.layout.paddingTop;
if (this.itemCache[0].scaleY != 1) {
this.itemCache[0].y = -Math.abs(this.itemCache[0].scaleY * this.itemCache[0].height) / 2 - this.layout.paddingTop;
}
this.itemBuffer.find((v) => {
if (v.index == 0) {
v.item.y = this.itemCache[0].y;
}
});
for (var i = 1; i < this.data.length; i++) {
var data1 = this.itemCache[i - 1];
var data2 = this.itemCache[i];
var h1 = data1.height;
var h2 = data2.height;
if (data1.scaleY != 1) {
h1 = Math.abs(data1.scaleY * data1.height);
}
if (data2.scaleY != 1) {
h2 = Math.abs(data2.scaleY * data2.height);
}
data2.y = data1.y - (h1 / 2 + h2 / 2 + this.layout.spacingY);
this.itemBuffer.find((v) => {
if (v.index == i) {
v.item.y = data2.y;
}
});
}
var lastRealHeight = lastItem.height / 2;
if (lastItem.scaleY != 1) {
lastRealHeight = Math.abs(lastItem.scaleY * lastItem.height) / 2;
}
this.content.height = Math.abs(lastItem.y - lastRealHeight - this.layout.paddingBottom);
}
}
scrollToItem(index, t?, extParams?) {
if (!this.itemCache || !this.itemCache.length) {
return;
}
if (index < 0) index = 0;
if (index >= this.itemCache.length) index = this.itemCache.length - 1;
var cache = this.itemCache[index];
if (!cache) {
return;
}
extParams = extParams || {};
t = t || 0;
if (this.scrollView) {
var toY;
if (extParams.customTween) {
} else {
toY = -(cache.y + Math.abs(cache.height * cache.scaleY) / 2);
this.scrollView.scrollToOffset(cc.v2(0, toY), t)
}
}
}
getItemByIndex(index) {
if (!this.itemBuffer) {
return;
}
var buffer = this.itemBuffer.find(v => {
return v.index == index;
}) || {};
return buffer.item;
}
removeItemByIndex(index) {
if (!this.itemBuffer) {
return;
}
var idx = this.itemBuffer.findIndex(v => {
return v.index == index;
});
if (idx >= 0) {
var buffer = this.itemBuffer.splice(idx, 1);
this.destroyNode(buffer.item);
}
}
bindItemWithIndex(item, bindIndex, isBind) {
if (!this.itemBuffer) {
return;
}
isBind = isBind || true;
var buffer = this.itemBuffer.find(v => {
return v.item == item;
});
if (buffer) {
buffer.bindIndex = bindIndex;
if (isBind) {
this.bindIndexList[bindIndex] = isBind;
} else {
delete this.bindIndexList[bindIndex];
delete buffer.bindIndex;
}
}
}
onItemChanged(callback) {
if (typeof (callback) == "function") {
this.callbackList.push(callback);
}
}
runItemChangedCallback(index, visible) {
try {
for (let i = 0; i < this.callbackList.length; i++) {
this.callbackList[i](index, visible);
}
} catch (e) {
console.error(e);
}
}
recycle() {
if (this.itemBuffer) {
this.itemBuffer.forEach(element => {
if (element && cc.isValid(element.item)) {
element.item.off(cc.Node.EventType.SIZE_CHANGED, this.onItemSizeChanged.bind(this, element.item), this);
element.item.off(cc.Node.EventType.SCALE_CHANGED, this.onItemSizeChanged.bind(this, element.item), this);
NodePool.Instance.putNode(this.itemName, element.item);
}
});
}
this.itemCache = null;
this.itemBuffer = null;
}
destroyNode(node) {
if (!cc.isValid(node)) {
console.error("Tools: destroyNode error, param is invalid");
return;
}
node.removeFromParent(false);
node.destroy();
}
getScript(node: cc.Node) {
if (!node) return null;
let arr = node._components;
for (let i = 0; i < arr.length; i++) {
const element = arr[i];
if (element && arr[i].hasOwnProperty("_super")) {
return arr[i];
}
}
return null;
}
}
NodePool:
import EngineUtil from "../Utils/EngineUtil";
var _nodePool = {};
var _prefabList = {};
export default class NodePool {
protected static _instance: NodePool = null;
public static get Instance(): NodePool {
if (NodePool._instance == null) {
NodePool._instance = new NodePool();
}
return NodePool._instance;
}
private _pathList: {};
addPath(path) {
this._pathList = this._pathList || {};
if (!Array.isArray(path)) {
path = [path];
}
for (let i = 0; i < path.length; i++) {
let name = cc.path.basename(path[i]);
this._pathList[name] = this._pathList[name] || path[i];
}
console.log(this._pathList);
}
loadPool(name, callback) {
if (!Array.isArray(name)) {
name = [name];
}
var paths = [];
for (let i = 0; i < name.length; i++) {
paths.push(this._pathList[name[i]]);
}
cc.resources.load(paths, cc.Prefab, (res: any) => {
if (!Array.isArray(res)) {
res = [res];
}
for (let i = 0; i < res.length; i++) {
let poolName = res[i].name;
if (!this.hasPool(poolName)) {
this.initPool(poolName, res[i]);
}
}
if (callback) callback();
});
}
initPool(object, count = 1, name = "") {
if (!name) name = object.name;
if (!this.hasPool(name)) {
if (_nodePool[name]) {
for (const n of _nodePool[name]) {
EngineUtil.destroyNode(n);
}
}
_nodePool[name] = [];
_prefabList[name] = object;
for (var i = 0; i < count; i++) {
var node = cc.instantiate(object);
node.active = false;
_nodePool[name].push(node);
}
}
}
hasPool(name) {
return _prefabList[name] && _prefabList[name].isValid;
}
putNode(name, node) {
if (!cc.isValid(node)) {
console.error("putNode: node param is invalid");
return;
}
var pool = _nodePool[name];
if (!pool) {
console.error("putNode: pool %s not found", name);
return;
}
if (pool.findIndex((item) => {
return item == node
}) >= 0) {
return;
}
node.stopAllActions();
node.removeFromParent(true);
node.x = 0;
node.y = 0;
node.scale = 1;
node.opacity = 255;
node.active = false;
pool.push(node);
}
getNode(name) {
var pool = _nodePool[name];
if (!pool) {
console.error("getNode: pool %s not found", name);
return null;
}
var node = pool.length > 0 ? pool.pop() : cc.instantiate(_prefabList[name]);
node = cc.isValid(node) ? node : cc.instantiate(_prefabList[name]);
node.active = true;
node.x = 0;
node.y = 0;
return node;
}
reset() {
if (!_nodePool) {
return;
}
var list = _nodePool;
for (var key in list) {
var pool = list[key];
while (pool.length > 0) {
EngineUtil.destroyNode(pool.pop());
}
}
}
getPool() {
return _nodePool;
}
_test() {
console.log(_nodePool);
}
}
|