/**
 * 目前只支持单行单列模式。
 *
 */ const { ccclass , property } = cc . _decorator ; const ScrollModel = cc . Enum ( { Horizontal : 0 , Vertical : 1 } ) ; const ScrollType = cc . Enum ( { Single : 0 , Multiple : 1 } ) ; interface viewCell { getSize ( index , data ? ) : number ; } interface tvCell extends cc . Node { tvIndex : number ; _tvIndex : number ; } @ ccclass export default class tableView extends cc . ScrollView { @ property ( { visible : false , override : true } ) horizontal = false ; @ property ( { visible : false , override : true } ) vertical = true ; @ property ( { type : cc . Component . EventHandler , visible : false , override : true } ) scrollEvents = [ ] ; @ property ( cc . Prefab ) _cell : cc . Prefab = null ; @ property ( { type : cc . Prefab , tooltip : '渲染节点' } ) get cell ( ) { return this . _cell ; } set cell ( value ) { if ( ! CC_EDITOR ) { this . clear ( ) ; this . cellPool . clear ( ) ; } this . _cell = value ; } @ property ( { type : ScrollModel , tooltip : '滑动方向' } ) get scrollModel ( ) { return this . horizontal ? ScrollModel . Horizontal : ScrollModel . Vertical ; } set scrollModel ( value ) { if ( ! CC_EDITOR ) { cc . error ( '[tableView] 不允许动态修改scrollModel' ) ; return ; } if ( value === ScrollModel . Horizontal ) { this . horizontal = true ; this . vertical = false ; } else { this . horizontal = false ; this . vertical = true ; } } private cellPool : cc . NodePool = new cc . NodePool ( 'viewCell' ) ; private cellData = null ; private cellCount = 0 ; private childCount = 0 ; private startIndex = 0 ; private maxStartIndex = 0 ; private endIndex = 0 ; private anchorCenterX = 0 ; private anchorCenterY = 0 ; private anchorLeftX = 0 ; private anchorTopY = 0 ; private cellAnchorX = 0 ; private cellAnchorY = 0 ; private customSize = false ; private cellAccumSizes : number [ ] = [ ] ; private cellSizes : number [ ] = [ ] ; private cellSize : number = 0 ; private updateRefreshOnce = false ; private updateRefreshForce = false ; private updateCellsOn = false ; private updateCellsOnce = false ; onDestroy ( ) { while ( this . cellPool . size ( ) ) { this . cellPool . get ( ) . destroy ( ) ; } if ( super . onDestroy ) { super . onDestroy ( ) ; } } onEnable ( ) { this . node . on ( 'scroll-began' , this . onScrollBegin , this ) ; this . node . on ( 'scroll-ended' , this . onScrollEnd , this ) ; if ( super . onEnable ) { super . onEnable ( ) ; } } onDisable ( ) { this . node . off ( 'scroll-began' , this . onScrollBegin , this ) ; this . node . off ( 'scroll-ended' , this . onScrollEnd , this ) ; if ( super . onDisable ) { super . onDisable ( ) ; } } private onScrollBegin ( ) { this . updateCellsOn = true ; } private onScrollEnd ( ) { this . updateCellsOn = false ; this . updateCellsOnce = true ; } getCellCount ( ) { return this . cellCount ; } setCellCount ( num ) { if ( typeof num === 'number' && num >= 0 ) { this . cellCount = num ; } else { cc . error ( '[tableView] setCellCount 参数错误' ) ; } } getCellData ( ) { return this . cellData ; } setCellData ( data ) { if ( typeof data !== 'undefined' ) { this . cellData = data ; } } getCellRange ( ) { const children = < tvCell [ ] > this . content . children ; if ( children . length == 0 ) { return { min : - 1 , max : - 1 } ; } return { min : children [ 0 ] . tvIndex , max : children [ children . length - 1 ] . tvIndex } ; } /**
     * [实时操作] 如果动态修改scrollBar的大小了，则需要调用此方法更新
     */ refreshScrollBar ( ) { this . _updateScrollBar ( this . _getHowMuchOutOfBoundary ( ) ) ; } /**
     * [延时操作] 刷新
     * 1、cell大小改变需要刷新cell位置时调用
     * 2、content大小、锚点发生改变后，需要刷新cell位置时调用
     * 3、cellCount改变，需要刷新cell时调用
     *
     * 调用refresh并不代表会执行cell的init或uninit方法
     * 只有当cell的index因插入、删除、滚屏、初始化等原因需要发生变化时，才会触发init或uninit方法
     */ refresh ( force = true ) { this . updateRefreshOnce = true ; if ( force ) { this . updateRefreshForce = true ; } } /**
     * [实时操作] 刷新
     */ refreshSync ( force = true ) { this . stopRefresh ( ) ; this . initData ( ) ; this . updateCells ( force ) ; } /**
     * 停止refresh
     */ private stopRefresh ( ) { this . updateRefreshOnce = false ; this . updateRefreshForce = false ; } /**
     * [实时操作] 初始化
     */ init ( num , data ? ) { if ( CC_DEBUG ) { if ( ! this . content ) { return cc . error ( '[tableView] 请指定content' ) ; } if ( ! this . cell ) { return cc . error ( '[tableView] 请指定cell' ) ; } if ( ! this . getViewCell ( ) ) { return cc . error ( '[tableView] 请在cell中添加继承自<viewCell>的自定义组件' ) ; } } this . clear ( ) ; this . setCellData ( data || null ) ; this . setCellCount ( num ) ; this . initData ( ) ; this . stopAutoScroll ( ) ; this . scrollToOrigin ( ) ; this . updateCells ( true ) ; } /**
     * [实时操作] 清空
     */ clear ( ) { this . cellCount = 0 ; this . childCount = 0 ; this . startIndex = 0 ; this . endIndex = 0 ; this . maxStartIndex = 0 ; this . content [ this . horizontal ? 'width' : 'height' ] = 0 ; this . stopRefresh ( ) ; this . updateCellCount ( ) ; } /**
     * [实时操作] 重载
     */ reload ( start = 0 , num ? ) { if ( typeof num === 'undefined' ) { this . content . children . forEach ( ( cell ) => { if ( cell . tvIndex >= start ) { this . reloadCell ( cell ) ; } } ) ; } else { if ( num <= 0 ) return ; if ( start < 0 ) start = start + this . cellCount ; if ( start + num < 0 ) return ; this . content . children . forEach ( ( cell ) => { if ( cell . tvIndex >= start && cell . tvIndex < start + num ) { this . reloadCell ( cell ) ; } } ) ; } } /**
     * [延时操作] 插入
     */ insert ( start = - 1 , num = 1 , data ? ) { if ( num <= 0 ) return ; if ( start < 0 ) start = start + this . cellCount + 1 ; if ( start < 0 ) start = 0 ; this . setCellData ( data ) ; const children = this . content . children ; for ( let index = children . length - 1 ; index >= 0 ; index -- ) { const node = < tvCell > children [ index ] ; if ( node . _tvIndex >= start ) { node . _tvIndex += num ; } } this . setCellCount ( this . cellCount + num ) ; this . refresh ( ) ; } /**
     * [延时操作] 删除
     */ remove ( start = - 1 , num = 1 , data ? ) { if ( num <= 0 ) return ; if ( start < 0 ) start = start + this . cellCount ; let end = start + num ; if ( start < 0 ) { start = 0 ; } if ( end > this . cellCount ) { end = this . cellCount ; } num = end - start ; if ( start >= this . cellCount || end <= 0 || num < 0 || start >= end ) return ; this . setCellData ( data ) ; const children = this . content . children ; for ( let index = children . length - 1 ; index >= 0 ; index -- ) { const node = < tvCell > children [ index ] ; if ( node . _tvIndex >= start ) { if ( node . _tvIndex < end ) { node . _tvIndex = - 1 ; } else { node . _tvIndex -= num ; } } } this . setCellCount ( this . cellCount - num ) ; this . refresh ( ) ; } private getCell ( ) { let node = null ; if ( this . cellPool . size ( ) ) { node = < tvCell > this . cellPool . get ( ) ; } else { node = < tvCell > cc . instantiate ( this . cell ) ; } node . tvIndex = - 1 ; node . _tvIndex = - 1 ; return node ; } private putCell ( node ) { this . cellPool . put ( node ) ; } private initCell ( cell , index ) { if ( index >= 0 ) { if ( cell . tvIndex != index || cell . tvIndex != cell . _tvIndex ) { const com = cell . getComponent ( 'viewCell' ) ; if ( cell . tvIndex >= 0 ) com . uninit ( ) ; com . init ( index , this . cellData , this ) ; } cell . tvIndex = index ; cell . _tvIndex = index ; } } private uninitCell ( cell ) { if ( cell . tvIndex >= 0 ) { cell . getComponent ( 'viewCell' ) . uninit ( ) ; cell . tvIndex = - 1 ; cell . _tvIndex = - 1 ; } } private reloadCell ( cell ) { cell . getComponent ( 'viewCell' ) . reload ( this . cellData ) ; } private getViewCell ( ) { if ( this . cell ) { const com = this . cell . data . getComponent ( 'viewCell' ) ; if ( com ) { return com . constructor || cc . js . getClassByName ( cc . js . getClassName ( com ) ) ; } } return null ; } /**
     * 获取默认cell大小
     */ private getDefaultCellSize ( ) { if ( this . cell ) { if ( CC_DEBUG && this . cell . data . getComponent ( cc . Widget ) ) { cc . warn ( '[tableView] cell根节点中存在cc.Widget，可能无法正确获取Size' ) ; } return this . cell . data . getContentSize ( ) ; } return cc . Size . ZERO ; } /**
     * 获取默认cell锚点
     */ private getDefaultCellAnchor ( ) { if ( this . cell ) { return this . cell . data . getAnchorPoint ( ) ; } return cc . Vec2 . ZERO ; } private getScrollLengh ( ) { const offset = this . getScrollOffset ( ) ; const scrollLen = this . horizontal ? - offset . x : offset . y ; if ( scrollLen < 0 ) { return 0 ; } return scrollLen ; } /**
     * 初始化数值
     */ private initData ( ) { const view = this . getViewCell ( ) ; const defaultCellSize = this . getDefaultCellSize ( ) ; const prop = this . horizontal ? 'width' : 'height' ; const viewLen = this . content . parent [ prop ] ; this . customSize = ! ! view && ! ! view . hasOwnProperty ( 'getSize' ) ; this . cellSize = defaultCellSize [ prop ] ; this . cellAccumSizes . length = 0 ; this . cellSizes . length = 0 ; this . maxStartIndex = 0 ; this . startIndex = 0 ; this . endIndex = 0 ; if ( this . customSize ) { /**
             * [性能问题] 这里有可能会出现性能问题, 原因可能有：
             * 1、view.getSize的复杂度过高
             * 2、数据量极大
             */ for ( let index = 0 , accumSize = 0 , size = 0 ; index < this . cellCount ; index ++ ) { size = view . getSize ( index , this . cellData ) ; if ( ! size || size < 0 ) size = this . cellSize ; this . cellSizes . push ( size ) ; accumSize += size ; this . cellAccumSizes . push ( accumSize ) ; } } if ( this . customSize ) { this . childCount = this . cellCount ; const accumIndex = this . cellAccumSizes . length - 1 ; const accumSize = this . cellAccumSizes [ accumIndex ] ; for ( let index = 1 , size = 0 ; index <= accumIndex ; index ++ ) { size = this . cellAccumSizes [ accumIndex - index ] ; if ( accumSize - size >= viewLen ) { this . childCount = index + 1 ; break ; } } } else { this . childCount = Math . ceil ( viewLen / this . cellSize ) + 1 ; } if ( this . cellCount > this . childCount ) { this . maxStartIndex = this . cellCount - this . childCount ; } else { this . maxStartIndex = 0 ; } if ( this . customSize ) { this . content [ prop ] = this . cellAccumSizes [ this . cellAccumSizes . length - 1 ] || 0 ; } else { this . content [ prop ] = this . cellSize * this . cellCount ; } const cellAnchor = this . getDefaultCellAnchor ( ) ; this . anchorCenterX = ( 0.5 - this . content . anchorX ) * this . content . width ; this . anchorCenterY = ( 0.5 - this . content . anchorY ) * this . content . height ; this . anchorLeftX = ( 0 - this . content . anchorX ) * this . content . width ; this . anchorTopY = ( 1 - this . content . anchorY ) * this . content . height ; this . cellAnchorX = cellAnchor . x ; this . cellAnchorY = 1 - cellAnchor . y ; } /**
     * 更新cell的数量，不够添加，多了删除
     */ private updateCellCount ( ) { const children = < tvCell [ ] > this . content . children ; if ( children . length == this . childCount ) { return ; } else if ( children . length > this . childCount ) { let cell = null ; for ( let index = children . length - 1 ; index >= this . childCount ; index -- ) { cell = children [ index ] ; if ( cell . _tvIndex < this . startIndex || cell . _tvIndex > this . endIndex ) { this . uninitCell ( cell ) ; this . putCell ( cell ) ; } } for ( let index = children . length - 1 ; index >= this . childCount ; index -- ) { cell = children [ index ] ; this . uninitCell ( cell ) ; this . putCell ( cell ) ; } } else { for ( let index = children . length ; index < this . childCount ; index ++ ) { this . content . addChild ( this . getCell ( ) ) ; } } } /**
     * 根据滑动距离，获得startIndex
     */ private getStartIndex ( scrollLen ) { let startIndex = 0 ; const maxStartIndex = this . maxStartIndex ; if ( this . customSize ) { const cellAccumSizes = this . cellAccumSizes ; if ( cellAccumSizes . length < 5 ) { for ( ; startIndex < maxStartIndex ; startIndex ++ ) { if ( cellAccumSizes [ startIndex ] > scrollLen ) { break ; } } } else { let min = 0 , max = maxStartIndex , value = 0 ; while ( max >= min ) { startIndex = Math . floor ( ( max + min ) / 2 ) ; value = cellAccumSizes [ startIndex ] ; if ( scrollLen == value ) { if ( startIndex < maxStartIndex ) startIndex += 1 ; break ; } else if ( scrollLen < value && ( startIndex == 0 || scrollLen >= cellAccumSizes [ startIndex - 1 ] ) ) { break ; } else if ( scrollLen > value ) { min = startIndex + 1 ; } else { max = startIndex - 1 ; } } } } else { startIndex = Math . floor ( scrollLen / this . cellSize ) ; if ( startIndex < 0 ) { startIndex = 0 ; } else if ( startIndex > maxStartIndex ) { startIndex = maxStartIndex ; } } return startIndex ; } /**
     * 更新开始下标、结束下标、childNum(只有自定义cell大小才会更新)
     */ private updateCellRange ( ) { const scrollLen = this . getScrollLengh ( ) ; this . startIndex = this . getStartIndex ( scrollLen ) ; if ( this . customSize ) { const viewLen = this . content . parent [ this . horizontal ? 'width' : 'height' ] ; const cellAccumLen = this . cellAccumSizes . length ; if ( this . cellAccumSizes [ cellAccumLen - 1 ] - viewLen <= scrollLen ) { this . childCount = cellAccumLen - this . startIndex ; } else { const startSize = this . cellAccumSizes [ this . startIndex ] ; for ( let endIndex = this . startIndex + 1 , accumSize = 0 ; endIndex < cellAccumLen ; endIndex ++ ) { accumSize = this . cellAccumSizes [ endIndex ] ; if ( accumSize - viewLen >= scrollLen ) { if ( accumSize - startSize >= viewLen ) { this . childCount = endIndex - this . startIndex + 1 ; } else { this . childCount = endIndex - this . startIndex + 2 ; } break ; } } } } this . endIndex = this . startIndex + this . childCount - 1 ; } /**
     * 更新cell状态
     */ private updateCell ( cell , index ? ) { if ( typeof index === 'number' ) { this . initCell ( cell , index ) ; } else { this . initCell ( cell , cell . _tvIndex ) ; index = cell . tvIndex ; } if ( this . horizontal ) { if ( this . customSize ) { cell . x = this . anchorLeftX - this . cellSizes [ index ] * this . cellAnchorX + this . cellAccumSizes [ index ] ; } else { cell . x = this . anchorLeftX - this . cellSize * this . cellAnchorX + this . cellSize * ( index + 1 ) ; } cell . y = this . anchorCenterY ; } else { if ( this . customSize ) { cell . y = this . anchorTopY + this . cellSizes [ index ] * this . cellAnchorY - this . cellAccumSizes [ index ] ; } else { cell . y = this . anchorTopY + this . cellSize * this . cellAnchorY - this . cellSize * ( index + 1 ) ; } cell . x = this . anchorCenterX ; } } /**
     * 更新cells状态
     *
     * 根据startIndex和endIndex，将content下的节点分成keepCells和changeCells
     * 1、统一更新keepCells和changeCells里cell的坐标
     * 2、changeCells里的cell会进行init
     */ private updateCells ( force = false ) { this . updateCellsOnce = false ; this . updateCellRange ( ) ; this . updateCellCount ( ) ; if ( ! this . childCount ) { return ; } const startIndex = this . startIndex ; const endIndex = this . endIndex ; const children = < tvCell [ ] > this . content . children ; if ( ! force && children [ 0 ] . _tvIndex == startIndex && children [ children . length - 1 ] . _tvIndex == endIndex ) { return ; } const keepCells = [ ] ; const changeCells = [ ] ; children . forEach ( ( cell ) => { if ( cell . _tvIndex < startIndex || cell . _tvIndex > endIndex || cell . _tvIndex != cell . tvIndex ) { this . uninitCell ( cell ) ; changeCells . push ( cell ) ; } else { keepCells . push ( cell ) ; } } ) ; if ( changeCells . length == 0 ) { if ( force ) children . forEach ( ( cell ) => this . updateCell ( cell ) ) ; } else if ( keepCells . length == 0 ) { children . forEach ( ( cell , index ) => this . updateCell ( cell , startIndex + index ) ) ; } else { for ( let index = startIndex , keepPoint = 0 , changePoint = 0 , i = 0 ; index <= endIndex ; index ++ , i ++ ) { if ( keepPoint < keepCells . length && index == keepCells [ keepPoint ] . _tvIndex ) { this . updateCell ( keepCells [ keepPoint ++ ] ) ; } else { this . updateCell ( changeCells [ changePoint ++ ] , index ) ; } } } children . forEach ( function ( node ) { node . zIndex = node . tvIndex - startIndex ; } ) ; this . content . sortAllChildren ( ) ; } scrollToIndex ( index ? , timeInSecond ? , attenuated ? ) { console . log ( '[tableView] scrollToIndex' ) ; } getPerScrollOffset ( ) { if ( this . horizontal ) { return cc . v2 ( this . getScrollOffset ( ) . x / this . getMaxScrollOffset ( ) . x , 0 ) ; } else { return cc . v2 ( 0 , this . getScrollOffset ( ) . y / this . getMaxScrollOffset ( ) . y ) ; } } scrollToPerOffset ( offset , timeInSecond ? , attenuated ? ) { if ( this . horizontal ) { offset . x *= this . getMaxScrollOffset ( ) . x ; } else { offset . y *= this . getMaxScrollOffset ( ) . y ; } this . scrollToOffset ( offset , timeInSecond , attenuated ) ; } scrollToOrigin ( timeInSecond ? , attenuated ? ) { if ( this . horizontal ) { this . scrollToLeft ( timeInSecond , attenuated ) ; } else { this . scrollToTop ( timeInSecond , attenuated ) ; } } stopAutoScroll ( ) { if ( ! this . updateCellsOnce && this . updateCellsOn ) { this . updateCellsOnce = true ; } super . stopAutoScroll ( ) ; } scrollToBottom ( timeInSecond ? , attenuated ? ) { if ( timeInSecond ) { this . updateCellsOn = true ; } else { this . updateCellsOnce = true ; } super . scrollToBottom ( timeInSecond , attenuated ) ; } scrollToTop ( timeInSecond ? , attenuated ? ) { if ( timeInSecond ) { this . updateCellsOn = true ; } else { this . updateCellsOnce = true ; } super . scrollToTop ( timeInSecond , attenuated ) ; } scrollToLeft ( timeInSecond ? , attenuated ? ) { if ( timeInSecond ) { this . updateCellsOn = true ; } else { this . updateCellsOnce = true ; } super . scrollToLeft ( timeInSecond , attenuated ) ; } scrollToRight ( timeInSecond ? , attenuated ? ) { if ( timeInSecond ) { this . updateCellsOn = true ; } else { this . updateCellsOnce = true ; } super . scrollToRight ( timeInSecond , attenuated ) ; } scrollToOffset ( offset , timeInSecond ? , attenuated ? ) { if ( timeInSecond ) { this . updateCellsOn = true ; } else { this . updateCellsOnce = true ; } super . scrollToOffset ( offset , timeInSecond , attenuated ) ; } update ( dt ) { super . update ( dt ) ; if ( this . updateRefreshOnce ) this . refreshSync ( this . updateRefreshForce ) ; if ( this . updateCellsOn || this . updateCellsOnce ) this . updateCells ( ) ; } }