From 845ef9c801dfdbc6dcef466693b6f8cb273b0391 Mon Sep 17 00:00:00 2001 From: caochao Date: Mon, 15 Oct 2018 16:40:45 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0scrollview.ts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .DS_Store | Bin 22532 -> 24580 bytes components/scrollview.ts | 437 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 437 insertions(+) create mode 100644 components/scrollview.ts diff --git a/.DS_Store b/.DS_Store index 8c403fbfba80529da98dee89172e3e8c8ab016b2..543045130fc0f911ae7de010740c77d4dbc98fd7 100644 GIT binary patch delta 779 zcmZqKz}RwtQJR5)fzhcn#gKs^0?6b5Vh}CB!@v+QQQCx2Z)3o2en#%e^1hEY^9abY z%6R7FCnx3PCxH|JwTc7jW&go|fnl?v=t_1?c7{@h5{68M90olgU5uuo6`^5cp%9Zc zFObj5kO;S> j!spDB`9lI(fFZ*%IU{81; + private node_pools:Map; + + private dir:number; + private width:number; + private height:number; + private gap_x:number; + private gap_y:number; + private cb_host:any; + private item_setter:(item:cc.Node, key:string, data:any, index:number)=>[number, number]; + private recycle_cb:(item:cc.Node, key:string)=>void; + private scroll_to_end_cb:()=>void; + private auto_scrolling:boolean; + private items:ScrollItem[]; + private start_index:number; + private stop_index:number; + + constructor(params:ScrollViewParams) + { + this.scrollview = params.scrollview; + this.mask = params.mask; + this.content = params.content; + this.node_pools = new Map(); + this.item_templates = new Map(); + params.item_templates.forEach((tpl) => { + tpl.node.active = false; + this.item_templates.set(tpl.key, tpl.node); + }); + + this.dir = params.direction || ScrollDirection.Vertical; + this.width = params.width || this.mask.width; + this.height = params.height || this.mask.height; + this.gap_x = params.gap_x || 0; + this.gap_y = params.gap_y || 0; + this.cb_host = params.cb_host; + this.item_setter = params.item_setter; + this.recycle_cb = params.recycle_cb; + this.scroll_to_end_cb = params.scroll_to_end_cb; + this.auto_scrolling = params.auto_scrolling || false; + + if(this.dir == ScrollDirection.Vertical) + { + this.content.width = this.width; + } + else + { + this.content.height = this.height; + } + this.mask.setContentSize(this.width, this.height); + this.mask.addComponent(cc.Mask); + this.scrollview.node.setContentSize(this.width, this.height); + this.scrollview.vertical = this.dir == ScrollDirection.Vertical; + this.scrollview.horizontal = this.dir == ScrollDirection.Horizontal; + this.scrollview.inertia = true; + this.scrollview.node.on("scrolling", this.on_scrolling, this); + this.scrollview.node.on("scroll-to-bottom", this.on_scroll_to_end, this); + this.scrollview.node.on("scroll-to-right", this.on_scroll_to_end, this); + // cc.info("constructor", this.mask.width, this.mask.height, this.scrollview.node.width, this.scrollview.node.height, this.content.width, this.content.height); + } + + private on_scroll_to_end() + { + if(this.scroll_to_end_cb) + { + this.scroll_to_end_cb.call(this.cb_host); + } + } + + private on_scrolling() + { + if(!this.items || !this.items.length) + { + return; + } + if(this.dir == ScrollDirection.Vertical) + { + let posy:number = this.content.y; + // cc.info("onscrolling, content posy=", posy); + if(posy < 0) + { + posy = 0; + } + if(posy > this.content.height - this.height) + { + posy = this.content.height - this.height; + } + let start:number = 0; + let stop:number = this.items.length - 1; + let viewport_start:number = -posy; + let viewport_stop:number = viewport_start - this.height; + while(this.items[start].y - this.items[start].height > viewport_start) + { + start++; + } + while(this.items[stop].y < viewport_stop) + { + stop--; + } + if(start != this.start_index && stop != this.stop_index) + { + this.start_index = start; + this.stop_index = stop; + cc.info("render_from:", start, stop); + this.render_items(); + } + } + else + { + let posx:number = this.content.x; + // cc.info("onscrolling, content posx=", posx); + if(posx > 0) + { + posx = 0; + } + if(posx < this.width - this.content.width) + { + posx = this.width - this.content.width; + } + let start:number = 0; + let stop:number = this.items.length - 1; + let viewport_start:number = -posx; + let viewport_stop:number = viewport_start + this.width; + while(this.items[start].x + this.items[start].width < viewport_start) + { + start++; + } + while(this.items[stop].x > viewport_stop) + { + stop--; + } + if(start != this.start_index && stop != this.stop_index) + { + this.start_index = start; + this.stop_index = stop; + cc.info("render_from:", start, stop); + this.render_items(); + } + } + } + + private spawn_node(key:string):cc.Node + { + let node:cc.Node; + let pools:cc.Node[] = this.node_pools.get(key); + if(pools && pools.length > 0) + { + node = pools.pop(); + } + else + { + node = cc.instantiate(this.item_templates.get(key)); + node.active = true; + cc.info("spawn_node, key=", key); + } + node.parent = this.content; + return node; + } + + private recycle_item(item:ScrollItem) + { + if(item.node && cc.isValid(item.node)) + { + let pools:cc.Node[] = this.node_pools.get(item.data.key); + if(!pools) + { + pools = []; + this.node_pools.set(item.data.key, pools); + } + pools.push(item.node); + if(this.recycle_cb) + { + this.recycle_cb.call(this.cb_host, item.node, item.data.key); + } + item.node.removeFromParent(); + item.node = null; + } + } + + private clear_items() + { + if(this.items) + { + this.items.forEach((item) => { + this.recycle_item(item); + }); + } + } + + private render_items() + { + let item:ScrollItem; + for(let i:number = 0; i < this.start_index; i++) + { + item = this.items[i]; + if(item.node) + { + cc.info("recycle_item", i); + this.recycle_item(item); + } + } + for(let i:number = this.items.length - 1; i > this.stop_index; i--) + { + item = this.items[i]; + if(item.node) + { + cc.info("recycle_item", i); + this.recycle_item(item); + } + } + for(let i:number = this.start_index; i <= this.stop_index; i++) + { + item = this.items[i]; + if(!item.node) + { + cc.info("render_item", i); + item.node = this.spawn_node(item.data.key); + this.item_setter.call(this.cb_host, item.node, item.data.key, item.data.data, i); + } + item.node.setPosition(item.x, item.y); + } + } + + private pack_item(index:number, data:ScrollItemData):ScrollItem + { + let node:cc.Node = this.spawn_node(data.key); + let [width, height]:[number, number] = this.item_setter.call(this.cb_host, node, data.key, data.data, index); + let item:ScrollItem = {x:0, y:0, width:width, height:height, data:data, node:node}; + this.recycle_item(item); + return item; + } + + private layout_items(start:number) + { + // cc.info("layout_items, start=", start); + if(this.items.length <= 0) + { + return; + } + let start_pos:number = 0; + if(start > 0) + { + let prev_item:ScrollItem = this.items[start - 1]; + if(this.dir == ScrollDirection.Vertical) + { + start_pos = prev_item.y - prev_item.height - this.gap_y; + } + else + { + start_pos = prev_item.x + prev_item.width + this.gap_x; + } + } + for(let index:number = start, stop:number = this.items.length; index < stop; index++) + { + let item:ScrollItem = this.items[index]; + if(this.dir == ScrollDirection.Vertical) + { + item.x = 0; + item.y = start_pos; + start_pos -= item.height + this.gap_y; + } + else + { + item.y = 0; + item.x = start_pos; + start_pos += item.width + this.gap_x; + } + } + } + + private resize_content() + { + if(this.items.length <= 0) + { + this.content.width = 0; + this.content.height = 0; + return; + } + let last_item:ScrollItem = this.items[this.items.length - 1]; + if(this.dir == ScrollDirection.Vertical) + { + this.content.height = Math.max(this.height, last_item.height - last_item.y); + } + else + { + this.content.width = Math.max(this.width, last_item.x + last_item.width); + } + // cc.info("resize_content", this.mask.width, this.mask.height, this.scrollview.node.width, this.scrollview.node.height, this.content.width, this.content.height); + } + + set_data(datas:ScrollItemData[]) + { + this.clear_items(); + this.items = []; + datas.forEach((data, index) => { + let item:ScrollItem = this.pack_item(index, data); + this.items.push(item); + }); + this.layout_items(0); + this.resize_content(); + this.start_index = -1; + this.stop_index = -1; + if(this.dir == ScrollDirection.Vertical) + { + this.content.y = 0; + } + else + { + this.content.x = 0; + } + if(this.items.length > 0) + { + this.on_scrolling(); + } + } + + insert_data(index:number, ...datas:ScrollItemData[]) + { + if(datas.length == 0 ) + { + cc.info("nothing to insert"); + return; + } + if(!this.items) + { + this.items = []; + } + if(index < 0 || index > this.items.length) + { + cc.warn("invalid index", index); + return; + } + let is_append:boolean = index == this.items.length; + let items:ScrollItem[] = []; + datas.forEach((data, index) => { + let item:ScrollItem = this.pack_item(index, data); + items.push(item); + }); + this.items.splice(index, 0, ...items); + this.layout_items(index); + this.resize_content(); + this.start_index = -1; + this.stop_index = -1; + + if(this.auto_scrolling && is_append) + { + this.scroll_to_end(); + } + this.on_scrolling(); + } + + append_data(...datas:ScrollItemData[]) + { + if(!this.items) + { + this.items = []; + } + this.insert_data(this.items.length, ...datas); + } + + scroll_to_end() + { + if(this.dir == ScrollDirection.Vertical) + { + this.scrollview.scrollToBottom(); + } + else + { + this.scrollview.scrollToRight(); + } + } + + destroy() + { + this.clear_items(); + this.node_pools.forEach((pools, key) => { + pools.forEach((node) => { + node.destroy(); + }); + }); + this.node_pools = null; + this.items = null; + + if(cc.isValid(this.scrollview.node)) + { + this.scrollview.node.off("scrolling", this.on_scrolling, this); + this.scrollview.node.off("scroll-to-bottom", this.on_scroll_to_end, this); + this.scrollview.node.off("scroll-to-right", this.on_scroll_to_end, this); + } + } +} + +export type ScrollItemTemplate = { + key:string; + node:cc.Node; +} + +export type ScrollItemData = { + key:string; + data:any; +} + +export enum ScrollDirection +{ + Vertical = 1, + Horizontal = 2, +} + +type ScrollViewParams = { + scrollview:cc.ScrollView; + mask:cc.Node; + content:cc.Node; + item_templates:ScrollItemTemplate[]; + direction?:ScrollDirection; + width?:number; + height?:number; + gap_x?:number; + gap_y?:number; + cb_host?:any; //回调函数host + item_setter:(item:cc.Node, key:string, data:any, index:number)=>[number, number]; //item更新setter + recycle_cb?:(item:cc.Node, key:string)=>void; //回收时的回调 + scroll_to_end_cb?:()=>void; //滚动到尽头的回调 + auto_scrolling?:boolean; //append时自动滚动到尽头 +} + +type ScrollItem = { + x:number; + y:number; + width:number; + height:number; + data:ScrollItemData; + node:cc.Node; +} \ No newline at end of file