mirror of
				https://github.com/568071718/creator-collection-view
				synced 2025-11-04 13:25:29 +00:00 
			
		
		
		
	2x 新版本
This commit is contained in:
		@@ -73,18 +73,24 @@
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "__id__": 5
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "__id__": 8
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "__id__": 12
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    "_active": true,
 | 
			
		||||
    "_components": [
 | 
			
		||||
      {
 | 
			
		||||
        "__id__": 8
 | 
			
		||||
        "__id__": 16
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "__id__": 9
 | 
			
		||||
        "__id__": 17
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "__id__": 10
 | 
			
		||||
        "__id__": 18
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    "_prefab": null,
 | 
			
		||||
@@ -235,7 +241,7 @@
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "__type__": "cc.Node",
 | 
			
		||||
    "_name": "list",
 | 
			
		||||
    "_name": "list1",
 | 
			
		||||
    "_objFlags": 0,
 | 
			
		||||
    "_parent": {
 | 
			
		||||
      "__id__": 2
 | 
			
		||||
@@ -258,8 +264,102 @@
 | 
			
		||||
    },
 | 
			
		||||
    "_contentSize": {
 | 
			
		||||
      "__type__": "cc.Size",
 | 
			
		||||
      "width": 400,
 | 
			
		||||
      "height": 600
 | 
			
		||||
      "width": 310,
 | 
			
		||||
      "height": 640
 | 
			
		||||
    },
 | 
			
		||||
    "_anchorPoint": {
 | 
			
		||||
      "__type__": "cc.Vec2",
 | 
			
		||||
      "x": 0.5,
 | 
			
		||||
      "y": 0.5
 | 
			
		||||
    },
 | 
			
		||||
    "_trs": {
 | 
			
		||||
      "__type__": "TypedArray",
 | 
			
		||||
      "ctor": "Float64Array",
 | 
			
		||||
      "array": [
 | 
			
		||||
        -325,
 | 
			
		||||
        0,
 | 
			
		||||
        0,
 | 
			
		||||
        0,
 | 
			
		||||
        0,
 | 
			
		||||
        0,
 | 
			
		||||
        1,
 | 
			
		||||
        1,
 | 
			
		||||
        1,
 | 
			
		||||
        1
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    "_eulerAngles": {
 | 
			
		||||
      "__type__": "cc.Vec3",
 | 
			
		||||
      "x": 0,
 | 
			
		||||
      "y": 0,
 | 
			
		||||
      "z": 0
 | 
			
		||||
    },
 | 
			
		||||
    "_skewX": 0,
 | 
			
		||||
    "_skewY": 0,
 | 
			
		||||
    "_is3DNode": false,
 | 
			
		||||
    "_groupIndex": 0,
 | 
			
		||||
    "groupIndex": 0,
 | 
			
		||||
    "_id": "37KB4+AFFHxrAaBW5L4Ycp"
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "__type__": "62dc08kS7dIiIYJEawwSV8U",
 | 
			
		||||
    "_name": "",
 | 
			
		||||
    "_objFlags": 0,
 | 
			
		||||
    "node": {
 | 
			
		||||
      "__id__": 5
 | 
			
		||||
    },
 | 
			
		||||
    "_enabled": true,
 | 
			
		||||
    "mask": true,
 | 
			
		||||
    "scrollEnabled": true,
 | 
			
		||||
    "wheelScrollEnabled": true,
 | 
			
		||||
    "scrollDirection": 1,
 | 
			
		||||
    "mode": 0,
 | 
			
		||||
    "preloadNodesLimitPerFrame": 2,
 | 
			
		||||
    "frameInterval": 1,
 | 
			
		||||
    "recycleInterval": 1,
 | 
			
		||||
    "registerCellForEditor": [
 | 
			
		||||
      {
 | 
			
		||||
        "__id__": 7
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    "registerSupplementaryForEditor": [],
 | 
			
		||||
    "_id": "d7ZzsT1NNNuKRUYTA6KIhf"
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "__type__": "_yx_editor_register_element_info",
 | 
			
		||||
    "prefab": {
 | 
			
		||||
      "__uuid__": "8fa9c17d-2552-411c-9848-7b16bf0208a8"
 | 
			
		||||
    },
 | 
			
		||||
    "identifier": "cell",
 | 
			
		||||
    "comp": ""
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "__type__": "cc.Node",
 | 
			
		||||
    "_name": "list2",
 | 
			
		||||
    "_objFlags": 0,
 | 
			
		||||
    "_parent": {
 | 
			
		||||
      "__id__": 2
 | 
			
		||||
    },
 | 
			
		||||
    "_children": [],
 | 
			
		||||
    "_active": true,
 | 
			
		||||
    "_components": [
 | 
			
		||||
      {
 | 
			
		||||
        "__id__": 9
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    "_prefab": null,
 | 
			
		||||
    "_opacity": 255,
 | 
			
		||||
    "_color": {
 | 
			
		||||
      "__type__": "cc.Color",
 | 
			
		||||
      "r": 255,
 | 
			
		||||
      "g": 255,
 | 
			
		||||
      "b": 255,
 | 
			
		||||
      "a": 255
 | 
			
		||||
    },
 | 
			
		||||
    "_contentSize": {
 | 
			
		||||
      "__type__": "cc.Size",
 | 
			
		||||
      "width": 310,
 | 
			
		||||
      "height": 640
 | 
			
		||||
    },
 | 
			
		||||
    "_anchorPoint": {
 | 
			
		||||
      "__type__": "cc.Vec2",
 | 
			
		||||
@@ -293,14 +393,14 @@
 | 
			
		||||
    "_is3DNode": false,
 | 
			
		||||
    "_groupIndex": 0,
 | 
			
		||||
    "groupIndex": 0,
 | 
			
		||||
    "_id": "0783nLyzRLIb74nyA86bgq"
 | 
			
		||||
    "_id": "316iCgSztKvpYfUuRpjZpB"
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "__type__": "62dc08kS7dIiIYJEawwSV8U",
 | 
			
		||||
    "_name": "",
 | 
			
		||||
    "_objFlags": 0,
 | 
			
		||||
    "node": {
 | 
			
		||||
      "__id__": 5
 | 
			
		||||
      "__id__": 8
 | 
			
		||||
    },
 | 
			
		||||
    "_enabled": true,
 | 
			
		||||
    "mask": true,
 | 
			
		||||
@@ -313,19 +413,138 @@
 | 
			
		||||
    "recycleInterval": 1,
 | 
			
		||||
    "registerCellForEditor": [
 | 
			
		||||
      {
 | 
			
		||||
        "__id__": 7
 | 
			
		||||
        "__id__": 10
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    "_id": "c6OeMxzjlE/okCBJxHaOEk"
 | 
			
		||||
    "registerSupplementaryForEditor": [
 | 
			
		||||
      {
 | 
			
		||||
        "__id__": 11
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    "_id": "6168pMrAdHy5egsiy1RWAl"
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "__type__": "_yx_editor_register_cell_info",
 | 
			
		||||
    "__type__": "_yx_editor_register_element_info",
 | 
			
		||||
    "prefab": {
 | 
			
		||||
      "__uuid__": "8fa9c17d-2552-411c-9848-7b16bf0208a8"
 | 
			
		||||
    },
 | 
			
		||||
    "identifier": "cell",
 | 
			
		||||
    "comp": ""
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "__type__": "_yx_editor_register_element_info",
 | 
			
		||||
    "prefab": {
 | 
			
		||||
      "__uuid__": "8fa9c17d-2552-411c-9848-7b16bf0208a8"
 | 
			
		||||
    },
 | 
			
		||||
    "identifier": "supplementary",
 | 
			
		||||
    "comp": ""
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "__type__": "cc.Node",
 | 
			
		||||
    "_name": "list3",
 | 
			
		||||
    "_objFlags": 0,
 | 
			
		||||
    "_parent": {
 | 
			
		||||
      "__id__": 2
 | 
			
		||||
    },
 | 
			
		||||
    "_children": [],
 | 
			
		||||
    "_active": true,
 | 
			
		||||
    "_components": [
 | 
			
		||||
      {
 | 
			
		||||
        "__id__": 13
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    "_prefab": null,
 | 
			
		||||
    "_opacity": 255,
 | 
			
		||||
    "_color": {
 | 
			
		||||
      "__type__": "cc.Color",
 | 
			
		||||
      "r": 255,
 | 
			
		||||
      "g": 255,
 | 
			
		||||
      "b": 255,
 | 
			
		||||
      "a": 255
 | 
			
		||||
    },
 | 
			
		||||
    "_contentSize": {
 | 
			
		||||
      "__type__": "cc.Size",
 | 
			
		||||
      "width": 310,
 | 
			
		||||
      "height": 640
 | 
			
		||||
    },
 | 
			
		||||
    "_anchorPoint": {
 | 
			
		||||
      "__type__": "cc.Vec2",
 | 
			
		||||
      "x": 0.5,
 | 
			
		||||
      "y": 0.5
 | 
			
		||||
    },
 | 
			
		||||
    "_trs": {
 | 
			
		||||
      "__type__": "TypedArray",
 | 
			
		||||
      "ctor": "Float64Array",
 | 
			
		||||
      "array": [
 | 
			
		||||
        325,
 | 
			
		||||
        0,
 | 
			
		||||
        0,
 | 
			
		||||
        0,
 | 
			
		||||
        0,
 | 
			
		||||
        0,
 | 
			
		||||
        1,
 | 
			
		||||
        1,
 | 
			
		||||
        1,
 | 
			
		||||
        1
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    "_eulerAngles": {
 | 
			
		||||
      "__type__": "cc.Vec3",
 | 
			
		||||
      "x": 0,
 | 
			
		||||
      "y": 0,
 | 
			
		||||
      "z": 0
 | 
			
		||||
    },
 | 
			
		||||
    "_skewX": 0,
 | 
			
		||||
    "_skewY": 0,
 | 
			
		||||
    "_is3DNode": false,
 | 
			
		||||
    "_groupIndex": 0,
 | 
			
		||||
    "groupIndex": 0,
 | 
			
		||||
    "_id": "99jY+AojdA0YZq4ysmw84S"
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "__type__": "62dc08kS7dIiIYJEawwSV8U",
 | 
			
		||||
    "_name": "",
 | 
			
		||||
    "_objFlags": 0,
 | 
			
		||||
    "node": {
 | 
			
		||||
      "__id__": 12
 | 
			
		||||
    },
 | 
			
		||||
    "_enabled": true,
 | 
			
		||||
    "mask": true,
 | 
			
		||||
    "scrollEnabled": true,
 | 
			
		||||
    "wheelScrollEnabled": true,
 | 
			
		||||
    "scrollDirection": 1,
 | 
			
		||||
    "mode": 0,
 | 
			
		||||
    "preloadNodesLimitPerFrame": 2,
 | 
			
		||||
    "frameInterval": 1,
 | 
			
		||||
    "recycleInterval": 1,
 | 
			
		||||
    "registerCellForEditor": [
 | 
			
		||||
      {
 | 
			
		||||
        "__id__": 14
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    "registerSupplementaryForEditor": [
 | 
			
		||||
      {
 | 
			
		||||
        "__id__": 15
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    "_id": "4bts8zampKvaCz5ILMmZJA"
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "__type__": "_yx_editor_register_element_info",
 | 
			
		||||
    "prefab": {
 | 
			
		||||
      "__uuid__": "8fa9c17d-2552-411c-9848-7b16bf0208a8"
 | 
			
		||||
    },
 | 
			
		||||
    "identifier": "cell",
 | 
			
		||||
    "comp": ""
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "__type__": "_yx_editor_register_element_info",
 | 
			
		||||
    "prefab": {
 | 
			
		||||
      "__uuid__": "8fa9c17d-2552-411c-9848-7b16bf0208a8"
 | 
			
		||||
    },
 | 
			
		||||
    "identifier": "supplementary",
 | 
			
		||||
    "comp": ""
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "__type__": "cc.Canvas",
 | 
			
		||||
    "_name": "",
 | 
			
		||||
@@ -378,9 +597,6 @@
 | 
			
		||||
      "__id__": 2
 | 
			
		||||
    },
 | 
			
		||||
    "_enabled": true,
 | 
			
		||||
    "listComp": {
 | 
			
		||||
      "__id__": 6
 | 
			
		||||
    },
 | 
			
		||||
    "_id": "560cKL+1lFIZFZSgoBz8At"
 | 
			
		||||
  }
 | 
			
		||||
]
 | 
			
		||||
@@ -10,50 +10,115 @@ import { YXTableLayout } from "../lib/yx-table-layout";
 | 
			
		||||
 | 
			
		||||
const { ccclass, property } = cc._decorator;
 | 
			
		||||
 | 
			
		||||
class Data {
 | 
			
		||||
    id: number
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ccclass
 | 
			
		||||
export default class NewClass extends cc.Component {
 | 
			
		||||
 | 
			
		||||
    @property(YXCollectionView)
 | 
			
		||||
    listComp: YXCollectionView = null
 | 
			
		||||
 | 
			
		||||
    testData: Data[] = []
 | 
			
		||||
 | 
			
		||||
    protected start(): void {
 | 
			
		||||
        this.setup_list1()
 | 
			
		||||
        this.setup_list2()
 | 
			
		||||
        this.setup_list3()
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    setup_list1() {
 | 
			
		||||
        const listComp = this.node.getChildByName('list1').getComponent(YXCollectionView)
 | 
			
		||||
 | 
			
		||||
        // 绑定数据源
 | 
			
		||||
        this.listComp.numberOfItems = () => this.testData.length
 | 
			
		||||
        this.listComp.cellForItemAt = (indexPath, collectionView) => {
 | 
			
		||||
            const rowData = this.testData[indexPath.item]
 | 
			
		||||
        listComp.numberOfItems = () => 10000
 | 
			
		||||
        listComp.cellForItemAt = (indexPath, collectionView) => {
 | 
			
		||||
            const cell = collectionView.dequeueReusableCell(`cell`)
 | 
			
		||||
            cell.getChildByName('label').getComponent(cc.Label).string = `${indexPath}`
 | 
			
		||||
            return cell
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 确定布局方案  
 | 
			
		||||
        let layout = new YXTableLayout()
 | 
			
		||||
        layout.spacing = 20
 | 
			
		||||
        layout.itemSize = new cc.Size(400, 100)
 | 
			
		||||
        this.listComp.layout = layout
 | 
			
		||||
        layout.rowHeight = 100
 | 
			
		||||
        listComp.layout = layout
 | 
			
		||||
 | 
			
		||||
        this.receivedData()
 | 
			
		||||
        listComp.reloadData()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 模拟收到数据  
 | 
			
		||||
     */
 | 
			
		||||
    receivedData() {
 | 
			
		||||
        this.testData = []
 | 
			
		||||
        for (let index = 0; index < 1000; index++) {
 | 
			
		||||
            let data = new Data()
 | 
			
		||||
            data.id = index
 | 
			
		||||
            this.testData.push(data)
 | 
			
		||||
    setup_list2() {
 | 
			
		||||
        const listComp = this.node.getChildByName('list2').getComponent(YXCollectionView)
 | 
			
		||||
 | 
			
		||||
        listComp.numberOfSections = () => 100
 | 
			
		||||
        listComp.supplementaryForItemAt = (indexPath, collectionView, kinds) => {
 | 
			
		||||
            if (kinds === YXTableLayout.SupplementaryKinds.HEADER) {
 | 
			
		||||
                const supplementary = collectionView.dequeueReusableSupplementary('supplementary')
 | 
			
		||||
                supplementary.getChildByName('label').getComponent(cc.Label).string = `header  ${indexPath}`
 | 
			
		||||
                const shape = supplementary.getChildByName('shape')
 | 
			
		||||
                shape.color = new cc.Color(100, 100, 150)
 | 
			
		||||
                return supplementary
 | 
			
		||||
            }
 | 
			
		||||
            if (kinds === YXTableLayout.SupplementaryKinds.FOOTER) {
 | 
			
		||||
                const supplementary = collectionView.dequeueReusableSupplementary('supplementary')
 | 
			
		||||
                supplementary.getChildByName('label').getComponent(cc.Label).string = `footer  ${indexPath}`
 | 
			
		||||
                const shape = supplementary.getChildByName('shape')
 | 
			
		||||
                shape.color = new cc.Color(150, 100, 100)
 | 
			
		||||
                return supplementary
 | 
			
		||||
            }
 | 
			
		||||
            return null
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 刷新列表
 | 
			
		||||
        this.listComp.reloadData()
 | 
			
		||||
        listComp.numberOfItems = () => 20
 | 
			
		||||
        listComp.cellForItemAt = (indexPath, collectionView) => {
 | 
			
		||||
            const cell = collectionView.dequeueReusableCell(`cell`)
 | 
			
		||||
            cell.getChildByName('label').getComponent(cc.Label).string = `${indexPath}`
 | 
			
		||||
            return cell
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let layout = new YXTableLayout()
 | 
			
		||||
        layout.spacing = 20
 | 
			
		||||
        layout.top = 20
 | 
			
		||||
        layout.bottom = 20
 | 
			
		||||
        layout.rowHeight = 100
 | 
			
		||||
        layout.sectionHeaderHeight = 120
 | 
			
		||||
        layout.sectionFooterHeight = 120
 | 
			
		||||
        listComp.layout = layout
 | 
			
		||||
 | 
			
		||||
        listComp.reloadData()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setup_list3() {
 | 
			
		||||
        const listComp = this.node.getChildByName('list3').getComponent(YXCollectionView)
 | 
			
		||||
 | 
			
		||||
        listComp.numberOfSections = () => 100
 | 
			
		||||
        listComp.supplementaryForItemAt = (indexPath, collectionView, kinds) => {
 | 
			
		||||
            if (kinds === YXTableLayout.SupplementaryKinds.HEADER) {
 | 
			
		||||
                const supplementary = collectionView.dequeueReusableSupplementary('supplementary')
 | 
			
		||||
                supplementary.getChildByName('label').getComponent(cc.Label).string = `header  ${indexPath}`
 | 
			
		||||
                const shape = supplementary.getChildByName('shape')
 | 
			
		||||
                shape.color = new cc.Color(100, 100, 150)
 | 
			
		||||
                return supplementary
 | 
			
		||||
            }
 | 
			
		||||
            if (kinds === YXTableLayout.SupplementaryKinds.FOOTER) {
 | 
			
		||||
                const supplementary = collectionView.dequeueReusableSupplementary('supplementary')
 | 
			
		||||
                supplementary.getChildByName('label').getComponent(cc.Label).string = `footer  ${indexPath}`
 | 
			
		||||
                const shape = supplementary.getChildByName('shape')
 | 
			
		||||
                shape.color = new cc.Color(150, 100, 100)
 | 
			
		||||
                return supplementary
 | 
			
		||||
            }
 | 
			
		||||
            return null
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        listComp.numberOfItems = () => 20
 | 
			
		||||
        listComp.cellForItemAt = (indexPath, collectionView) => {
 | 
			
		||||
            const cell = collectionView.dequeueReusableCell(`cell`)
 | 
			
		||||
            cell.getChildByName('label').getComponent(cc.Label).string = `${indexPath}`
 | 
			
		||||
            return cell
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let layout = new YXTableLayout()
 | 
			
		||||
        layout.spacing = 20
 | 
			
		||||
        layout.top = 20
 | 
			
		||||
        layout.bottom = 20
 | 
			
		||||
        layout.rowHeight = 100
 | 
			
		||||
        layout.sectionHeaderHeight = 120
 | 
			
		||||
        layout.sectionFooterHeight = 120
 | 
			
		||||
        layout.sectionHeadersPinToVisibleBounds = true
 | 
			
		||||
        layout.sectionFootersPinToVisibleBounds = true
 | 
			
		||||
        listComp.layout = layout
 | 
			
		||||
 | 
			
		||||
        listComp.reloadData()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -1,114 +1,349 @@
 | 
			
		||||
import { YXBinaryLayout, YXCollectionView, YXIndexPath, YXLayoutAttributes } from "./yx-collection-view";
 | 
			
		||||
import { YXCollectionView, YXIndexPath, YXLayout, YXLayoutAttributes } from "./yx-collection-view";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
enum _yx_table_layout_alignment {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 单元节点相对列表居中  
 | 
			
		||||
     */
 | 
			
		||||
    CENTER,
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 单元节点紧靠列表左侧  
 | 
			
		||||
     */
 | 
			
		||||
    LEFT,
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 单元节点紧靠列表右侧  
 | 
			
		||||
     */
 | 
			
		||||
    RIGHT,
 | 
			
		||||
enum _yx_table_layout_supplementary_kinds {
 | 
			
		||||
    HEADER = 'header',
 | 
			
		||||
    FOOTER = 'footer',
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 想要了解自定义布局的,可以看看这个类,这个类实现了一个基础的 table view 的布局样式,相对来说比较简单  
 | 
			
		||||
 * 
 | 
			
		||||
 * - 支持不同的节点大小  
 | 
			
		||||
 * - 支持调整对齐方式  
 | 
			
		||||
 * - 不支持分区布局  
 | 
			
		||||
 * - 不支持水平方向滚动,仅支持垂直方向滚动  
 | 
			
		||||
 * - 不支持多列布局,仅支持单列布局 
 | 
			
		||||
 * - 支持调整上下边距/间距  
 | 
			
		||||
 */
 | 
			
		||||
export class YXTableLayout extends YXBinaryLayout {
 | 
			
		||||
export class YXTableLayout extends YXLayout {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 单元格大小   
 | 
			
		||||
     * 行高  
 | 
			
		||||
     */
 | 
			
		||||
    itemSize: cc.Size | ((indexPath: YXIndexPath, layout: YXTableLayout, collectionView: YXCollectionView) => cc.Size) = new cc.Size(100, 100)
 | 
			
		||||
    rowHeight: number | ((indexPath: YXIndexPath) => number) = 100
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 间距  
 | 
			
		||||
     */
 | 
			
		||||
    spacing: number = 0
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 上边距
 | 
			
		||||
     * 内容上边距
 | 
			
		||||
     */
 | 
			
		||||
    top: number = 0
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 下边距
 | 
			
		||||
     * 内容下边距
 | 
			
		||||
     */
 | 
			
		||||
    bottom: number = 0
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 对齐方式  
 | 
			
		||||
     * 节点之间间距  
 | 
			
		||||
     */
 | 
			
		||||
    alignment: _yx_table_layout_alignment = YXTableLayout.Alignment.CENTER
 | 
			
		||||
    static Alignment = _yx_table_layout_alignment
 | 
			
		||||
    spacing: number = 0
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 区头高度
 | 
			
		||||
     */
 | 
			
		||||
    sectionHeaderHeight: number | ((section: number) => number) = null
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 区尾高度
 | 
			
		||||
     */
 | 
			
		||||
    sectionFooterHeight: number | ((section: number) => number) = null
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 钉住 header 的位置 ( header 吸附在列表可见范围内 )  
 | 
			
		||||
     */
 | 
			
		||||
    sectionHeadersPinToVisibleBounds: boolean = false
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 钉住 footer 的位置 ( footer 吸附在列表可见范围内 )  
 | 
			
		||||
     */
 | 
			
		||||
    sectionFootersPinToVisibleBounds: boolean = false
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 区头/区尾标识  
 | 
			
		||||
     */
 | 
			
		||||
    static SupplementaryKinds = _yx_table_layout_supplementary_kinds
 | 
			
		||||
 | 
			
		||||
    protected originalHeaderRect: Map<number, cc.Rect> = new Map() // 保存所有 header 的原始位置
 | 
			
		||||
    protected originalFooterRect: Map<number, cc.Rect> = new Map() // 保存所有 footer 的原始位置
 | 
			
		||||
 | 
			
		||||
    // 为了优化查找,额外维护几个数组按类别管理所有的布局属性,空间换时间  
 | 
			
		||||
    protected allCellAttributes: YXLayoutAttributes[] = []
 | 
			
		||||
    protected allHeaderAttributes: YXLayoutAttributes[] = []
 | 
			
		||||
    protected allFooterAttributes: YXLayoutAttributes[] = []
 | 
			
		||||
 | 
			
		||||
    prepare(collectionView: YXCollectionView): void {
 | 
			
		||||
        // 设置列表的滚动方向  
 | 
			
		||||
        if (collectionView.scrollDirection == YXCollectionView.ScrollDirection.HORIZONTAL) {
 | 
			
		||||
            cc.warn(`YXTableLayout 只支持垂直方向排列`)
 | 
			
		||||
        }
 | 
			
		||||
        // 设置列表的滚动方向(这套布局固定为垂直方向滚动)  
 | 
			
		||||
        collectionView.scrollView.horizontal = false
 | 
			
		||||
        collectionView.scrollView.vertical = true
 | 
			
		||||
 | 
			
		||||
        let contentSize = collectionView.node.getContentSize()
 | 
			
		||||
        let attrs = []
 | 
			
		||||
 | 
			
		||||
        let maxY = this.top
 | 
			
		||||
 | 
			
		||||
        // 获取列表内一共需要展示多少数据  
 | 
			
		||||
        let numberOfItems = collectionView.getNumberOfItems(0)
 | 
			
		||||
        for (let row = 0; row < numberOfItems; row++) {
 | 
			
		||||
 | 
			
		||||
            // 生成对应的 indexPath,并通过 indexPath 获取节点大小  
 | 
			
		||||
            // 这里是不支持分区所以不考虑 section 的情况,section 默认就是 0,支持的分区的情况可以回头看 flow-layout 的实现  
 | 
			
		||||
            let indexPath = new YXIndexPath(0, row)
 | 
			
		||||
            let itemSize = this.itemSize instanceof Function ? this.itemSize(indexPath, this, collectionView) : this.itemSize
 | 
			
		||||
 | 
			
		||||
            // 生成布局属性对象,并按照 table view 的规则确定好节点的位置    
 | 
			
		||||
            let attributes = new YXLayoutAttributes(indexPath)
 | 
			
		||||
            attributes.frame = new cc.Rect()
 | 
			
		||||
            attributes.frame.size = itemSize
 | 
			
		||||
            attributes.frame.y = maxY + (row > 0 ? this.spacing : 0)
 | 
			
		||||
            attributes.frame.x = 0
 | 
			
		||||
            if (this.alignment == _yx_table_layout_alignment.RIGHT) {
 | 
			
		||||
                attributes.frame.x = (contentSize.width - attributes.frame.width)
 | 
			
		||||
            }
 | 
			
		||||
            if (this.alignment == _yx_table_layout_alignment.CENTER) {
 | 
			
		||||
                attributes.frame.x = (contentSize.width - attributes.frame.width) * 0.5
 | 
			
		||||
            }
 | 
			
		||||
            attrs.push(attributes)
 | 
			
		||||
            maxY = attributes.frame.yMax
 | 
			
		||||
        if (collectionView.scrollDirection === YXCollectionView.ScrollDirection.HORIZONTAL) {
 | 
			
		||||
            // 由于这套布局规则只支持垂直方向布局,当外部配置了水平方向滚动时这里可以给个警告  
 | 
			
		||||
            cc.warn(`YXTableLayout 仅支持垂直方向排列`)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        maxY += this.bottom
 | 
			
		||||
        // 清空一下布局属性数组
 | 
			
		||||
        this.attributes = []
 | 
			
		||||
        this.allCellAttributes = []
 | 
			
		||||
        this.allHeaderAttributes = []
 | 
			
		||||
        this.allFooterAttributes = []
 | 
			
		||||
        this.originalHeaderRect.clear()
 | 
			
		||||
        this.originalFooterRect.clear()
 | 
			
		||||
 | 
			
		||||
        // 保存起来给列表组件使用  
 | 
			
		||||
        this.attributes = attrs
 | 
			
		||||
        // 获取列表宽度  
 | 
			
		||||
        const contentWidth = collectionView.node.width
 | 
			
		||||
 | 
			
		||||
        // 确定滚动范围的总大小  
 | 
			
		||||
        contentSize.height = Math.max(contentSize.height, maxY)
 | 
			
		||||
        this.contentSize = contentSize
 | 
			
		||||
        // 声明一个临时变量,用来记录当前所有内容的总高度  
 | 
			
		||||
        let contentHeight = 0
 | 
			
		||||
 | 
			
		||||
        // 获取列表一共分多少个区
 | 
			
		||||
        let numberOfSections = collectionView.getNumberOfSections()
 | 
			
		||||
 | 
			
		||||
        // 为每条数据对应的生成一个布局属性
 | 
			
		||||
        for (let section = 0; section < numberOfSections; section++) {
 | 
			
		||||
 | 
			
		||||
            // 创建一个区索引
 | 
			
		||||
            let sectionIndexPath = new YXIndexPath(section, 0)
 | 
			
		||||
 | 
			
		||||
            // 通过区索引创建一个区头节点布局属性
 | 
			
		||||
            let sectionHeaderHeight = 0
 | 
			
		||||
            if (this.sectionHeaderHeight) {
 | 
			
		||||
                sectionHeaderHeight = this.sectionHeaderHeight instanceof Function ? this.sectionHeaderHeight(section) : this.sectionHeaderHeight
 | 
			
		||||
            }
 | 
			
		||||
            if (sectionHeaderHeight > 0) {
 | 
			
		||||
                let headerAttr = YXLayoutAttributes.layoutAttributesForSupplementary(sectionIndexPath, YXTableLayout.SupplementaryKinds.HEADER)
 | 
			
		||||
 | 
			
		||||
                // 确定这个节点的位置  
 | 
			
		||||
                headerAttr.frame.x = 0
 | 
			
		||||
                headerAttr.frame.width = contentWidth
 | 
			
		||||
                headerAttr.frame.height = sectionHeaderHeight
 | 
			
		||||
                headerAttr.frame.y = contentHeight
 | 
			
		||||
 | 
			
		||||
                // 调整层级
 | 
			
		||||
                headerAttr.zIndex = 1
 | 
			
		||||
 | 
			
		||||
                // 重要: 保存布局属性
 | 
			
		||||
                this.attributes.push(headerAttr)
 | 
			
		||||
                this.originalHeaderRect.set(section, headerAttr.frame.clone())
 | 
			
		||||
                this.allHeaderAttributes.push(headerAttr)
 | 
			
		||||
 | 
			
		||||
                // 更新整体内容高度
 | 
			
		||||
                contentHeight = headerAttr.frame.yMax
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // 将 top 配置应用到每个区  
 | 
			
		||||
            contentHeight = contentHeight + this.top
 | 
			
		||||
 | 
			
		||||
            // 获取这个区内的内容数量,注意这里传入的是 section  
 | 
			
		||||
            let numberOfItems = collectionView.getNumberOfItems(section)
 | 
			
		||||
 | 
			
		||||
            for (let item = 0; item < numberOfItems; item++) {
 | 
			
		||||
 | 
			
		||||
                // 创建索引,注意这里的 section 已经改为正确的 section 了  
 | 
			
		||||
                let indexPath = new YXIndexPath(section, item)
 | 
			
		||||
 | 
			
		||||
                // 通过索引创建一个 cell 节点的布局属性
 | 
			
		||||
                let attr = YXLayoutAttributes.layoutAttributesForCell(indexPath)
 | 
			
		||||
 | 
			
		||||
                // 通过索引获取这个节点的高度
 | 
			
		||||
                let rowHeight = this.rowHeight instanceof Function ? this.rowHeight(indexPath) : this.rowHeight
 | 
			
		||||
 | 
			
		||||
                // 确定这个节点的位置  
 | 
			
		||||
                attr.frame.x = 0
 | 
			
		||||
                attr.frame.width = contentWidth
 | 
			
		||||
                attr.frame.height = rowHeight
 | 
			
		||||
                attr.frame.y = contentHeight + (item > 0 ? this.spacing : 0)
 | 
			
		||||
 | 
			
		||||
                // 重要: 保存布局属性
 | 
			
		||||
                this.attributes.push(attr)
 | 
			
		||||
                this.allCellAttributes.push(attr)
 | 
			
		||||
 | 
			
		||||
                // 更新当前内容高度
 | 
			
		||||
                contentHeight = attr.frame.yMax
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // 高度补一个底部间距,跟 top 一样,也是应用到每个区  
 | 
			
		||||
            contentHeight = contentHeight + this.bottom
 | 
			
		||||
 | 
			
		||||
            // 通过区索引创建一个区尾节点布局属性
 | 
			
		||||
            let sectionFooterHeight = 0
 | 
			
		||||
            if (this.sectionFooterHeight) {
 | 
			
		||||
                sectionFooterHeight = this.sectionFooterHeight instanceof Function ? this.sectionFooterHeight(section) : this.sectionFooterHeight
 | 
			
		||||
            }
 | 
			
		||||
            if (sectionFooterHeight > 0) {
 | 
			
		||||
                let footerAttr = YXLayoutAttributes.layoutAttributesForSupplementary(sectionIndexPath, YXTableLayout.SupplementaryKinds.FOOTER)
 | 
			
		||||
 | 
			
		||||
                // 确定这个节点的位置  
 | 
			
		||||
                footerAttr.frame.x = 0
 | 
			
		||||
                footerAttr.frame.width = contentWidth
 | 
			
		||||
                footerAttr.frame.height = sectionFooterHeight
 | 
			
		||||
                footerAttr.frame.y = contentHeight
 | 
			
		||||
 | 
			
		||||
                // 调整层级
 | 
			
		||||
                footerAttr.zIndex = 1
 | 
			
		||||
 | 
			
		||||
                // 重要: 保存布局属性
 | 
			
		||||
                this.attributes.push(footerAttr)
 | 
			
		||||
                this.originalFooterRect.set(section, footerAttr.frame.clone())
 | 
			
		||||
                this.allFooterAttributes.push(footerAttr)
 | 
			
		||||
 | 
			
		||||
                // 更新整体内容高度
 | 
			
		||||
                contentHeight = footerAttr.frame.yMax
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 重要: 设置内容区域总大小,只有确定了滚动区域的大小列表才能滚动  
 | 
			
		||||
        this.contentSize = new cc.Size(contentWidth, contentHeight)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    initOffset(collectionView: YXCollectionView): void {
 | 
			
		||||
        // 首次更新数据,滚动至列表顶部  
 | 
			
		||||
        // 列表首次刷新时,调整一下列表的偏移位置  
 | 
			
		||||
        collectionView.scrollView.scrollToTop()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    layoutAttributesForElementsInRect(rect: cc.Rect, collectionView: YXCollectionView): YXLayoutAttributes[] {
 | 
			
		||||
        let result = this.visibleElementsInRect(rect, collectionView)
 | 
			
		||||
        if (this.sectionHeadersPinToVisibleBounds == false && this.sectionFootersPinToVisibleBounds == false) {
 | 
			
		||||
            return result // 不需要调整节点位置,直接返回就好
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let numberOfSections = collectionView.getNumberOfSections()
 | 
			
		||||
        let scrollOffset = collectionView.scrollView.getScrollOffset()
 | 
			
		||||
        for (let index = 0; index < result.length; index++) {
 | 
			
		||||
            const element = result[index];
 | 
			
		||||
            if (element.elementCategory === 'Supplementary') {
 | 
			
		||||
 | 
			
		||||
                if (this.sectionHeadersPinToVisibleBounds && element.supplementaryKinds === YXTableLayout.SupplementaryKinds.HEADER) {
 | 
			
		||||
                    const originalFrame = this.originalHeaderRect.get(element.indexPath.section)
 | 
			
		||||
                    element.frame.y = originalFrame.y
 | 
			
		||||
                    if (scrollOffset.y > originalFrame.y) {
 | 
			
		||||
                        element.frame.y = scrollOffset.y
 | 
			
		||||
                    }
 | 
			
		||||
                    const nextOriginalFrame = this.getNextOriginalFrame(element.indexPath.section, YXTableLayout.SupplementaryKinds.FOOTER, numberOfSections)
 | 
			
		||||
                    if (nextOriginalFrame) {
 | 
			
		||||
                        if (element.frame.yMax > nextOriginalFrame.y) {
 | 
			
		||||
                            element.frame.y = nextOriginalFrame.y - element.frame.height
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (this.sectionFootersPinToVisibleBounds && element.supplementaryKinds === YXTableLayout.SupplementaryKinds.FOOTER) {
 | 
			
		||||
                    let bottom = scrollOffset.y + collectionView.scrollView.node.height
 | 
			
		||||
                    const originalFrame = this.originalFooterRect.get(element.indexPath.section)
 | 
			
		||||
                    const previousOriginalFrame = this.getPreviousOriginalFrame(element.indexPath.section, YXTableLayout.SupplementaryKinds.HEADER)
 | 
			
		||||
                    element.frame.y = originalFrame.y
 | 
			
		||||
                    if (bottom < originalFrame.yMax) {
 | 
			
		||||
                        element.frame.y = bottom - element.frame.height
 | 
			
		||||
                        if (previousOriginalFrame) {
 | 
			
		||||
                            if (element.frame.y < previousOriginalFrame.yMax) {
 | 
			
		||||
                                element.frame.y = previousOriginalFrame.yMax
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return result
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    shouldUpdateAttributesZIndex(): boolean {
 | 
			
		||||
        return this.sectionHeadersPinToVisibleBounds || this.sectionFootersPinToVisibleBounds
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    shouldUpdateAttributesForBoundsChange(): boolean {
 | 
			
		||||
        return this.sectionHeadersPinToVisibleBounds || this.sectionFootersPinToVisibleBounds
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取 `section` 下一个 header 或者 footer 的位置  
 | 
			
		||||
     */
 | 
			
		||||
    protected getNextOriginalFrame(section: number, kinds: _yx_table_layout_supplementary_kinds, total: number) {
 | 
			
		||||
        if (section >= total) { return null }
 | 
			
		||||
        if (kinds === YXTableLayout.SupplementaryKinds.HEADER) {
 | 
			
		||||
            let result = this.originalHeaderRect.get(section)
 | 
			
		||||
            if (result) { return result }
 | 
			
		||||
            return this.getNextOriginalFrame(section, YXTableLayout.SupplementaryKinds.FOOTER, total)
 | 
			
		||||
        }
 | 
			
		||||
        if (kinds === YXTableLayout.SupplementaryKinds.FOOTER) {
 | 
			
		||||
            let result = this.originalFooterRect.get(section)
 | 
			
		||||
            if (result) { return result }
 | 
			
		||||
            return this.getNextOriginalFrame(section + 1, YXTableLayout.SupplementaryKinds.HEADER, total)
 | 
			
		||||
        }
 | 
			
		||||
        return null
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取 `section` 前一个 header 或者 footer 的位置  
 | 
			
		||||
     */
 | 
			
		||||
    protected getPreviousOriginalFrame(section: number, kinds: _yx_table_layout_supplementary_kinds) {
 | 
			
		||||
        if (section < 0) { return null }
 | 
			
		||||
        if (kinds === YXTableLayout.SupplementaryKinds.HEADER) {
 | 
			
		||||
            let result = this.originalHeaderRect.get(section)
 | 
			
		||||
            if (result) { return result }
 | 
			
		||||
            return this.getPreviousOriginalFrame(section - 1, YXTableLayout.SupplementaryKinds.FOOTER)
 | 
			
		||||
        }
 | 
			
		||||
        if (kinds === YXTableLayout.SupplementaryKinds.FOOTER) {
 | 
			
		||||
            let result = this.originalFooterRect.get(section)
 | 
			
		||||
            if (result) { return result }
 | 
			
		||||
            return this.getPreviousOriginalFrame(section, YXTableLayout.SupplementaryKinds.HEADER)
 | 
			
		||||
        }
 | 
			
		||||
        return null
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 抽出来一个方法用来优化列表性能  
 | 
			
		||||
     * 在优化之前,可以先看一下 @see YXLayout.layoutAttributesForElementsInRect 关于返回值的说明  
 | 
			
		||||
     * 对于有序列表来说,一般都是可以通过二分查找来进行优化  
 | 
			
		||||
     */
 | 
			
		||||
    protected visibleElementsInRect(rect: cc.Rect, collectionView: YXCollectionView) {
 | 
			
		||||
        if (this.attributes.length <= 100) { return this.attributes } // 少量数据就不查了,直接返回全部  
 | 
			
		||||
 | 
			
		||||
        let result: YXLayoutAttributes[] = []
 | 
			
		||||
 | 
			
		||||
        // header 跟 footer 暂时不考虑,数据相对来说不算很多,直接全部返回  
 | 
			
		||||
        result.push(...this.allHeaderAttributes)
 | 
			
		||||
        result.push(...this.allFooterAttributes)
 | 
			
		||||
 | 
			
		||||
        // 关于 cell,这里用二分查找来优化一下  
 | 
			
		||||
        // 首先通过二分先查出个大概位置  
 | 
			
		||||
        let midIdx = -1
 | 
			
		||||
        let left = 0
 | 
			
		||||
        let right = this.allCellAttributes.length - 1
 | 
			
		||||
 | 
			
		||||
        while (left <= right && right >= 0) {
 | 
			
		||||
            let mid = left + (right - left) / 2
 | 
			
		||||
            mid = Math.floor(mid)
 | 
			
		||||
            let attr = this.allCellAttributes[mid]
 | 
			
		||||
            if (rect.intersects(attr.frame)) {
 | 
			
		||||
                midIdx = mid
 | 
			
		||||
                break
 | 
			
		||||
            }
 | 
			
		||||
            if (rect.yMax < attr.frame.yMin || rect.xMax < attr.frame.xMin) {
 | 
			
		||||
                right = mid - 1
 | 
			
		||||
            } else {
 | 
			
		||||
                left = mid + 1
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 二分查找出错了,返回全部的布局属性  
 | 
			
		||||
        if (midIdx < 0) {
 | 
			
		||||
            return this.attributes
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 把模糊查到这个先加进来
 | 
			
		||||
        result.push(this.allCellAttributes[midIdx])
 | 
			
		||||
 | 
			
		||||
        // 然后依次往前检查,直到超出当前的显示范围  
 | 
			
		||||
        let startIdx = midIdx
 | 
			
		||||
        while (startIdx > 0) {
 | 
			
		||||
            let idx = startIdx - 1
 | 
			
		||||
            let attr = this.allCellAttributes[idx]
 | 
			
		||||
            if (rect.intersects(attr.frame) == false) {
 | 
			
		||||
                break
 | 
			
		||||
            }
 | 
			
		||||
            result.push(attr)
 | 
			
		||||
            startIdx = idx
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 依次往后检查,直到超出当前的显示范围  
 | 
			
		||||
        let endIdx = midIdx
 | 
			
		||||
        while (endIdx < this.allCellAttributes.length - 1) {
 | 
			
		||||
            let idx = endIdx + 1
 | 
			
		||||
            let attr = this.allCellAttributes[idx]
 | 
			
		||||
            if (rect.intersects(attr.frame) == false) {
 | 
			
		||||
                break
 | 
			
		||||
            }
 | 
			
		||||
            result.push(attr)
 | 
			
		||||
            endIdx = idx
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return result
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user