mirror of
https://github.com/568071718/creator-collection-view
synced 2025-04-12 04:11:03 +00:00
3x 新版本
This commit is contained in:
parent
aecd893457
commit
1c825731dc
@ -23,7 +23,7 @@
|
||||
"_active": true,
|
||||
"_components": [],
|
||||
"_prefab": {
|
||||
"__id__": 13
|
||||
"__id__": 23
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
@ -54,7 +54,7 @@
|
||||
},
|
||||
"autoReleaseAssets": false,
|
||||
"_globals": {
|
||||
"__id__": 14
|
||||
"__id__": 24
|
||||
},
|
||||
"_id": "5d2f3c26-c7d2-48bd-bd13-ca5aa20a93c4"
|
||||
},
|
||||
@ -72,21 +72,27 @@
|
||||
},
|
||||
{
|
||||
"__id__": 5
|
||||
},
|
||||
{
|
||||
"__id__": 9
|
||||
},
|
||||
{
|
||||
"__id__": 14
|
||||
}
|
||||
],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 9
|
||||
"__id__": 19
|
||||
},
|
||||
{
|
||||
"__id__": 10
|
||||
"__id__": 20
|
||||
},
|
||||
{
|
||||
"__id__": 11
|
||||
"__id__": 21
|
||||
},
|
||||
{
|
||||
"__id__": 12
|
||||
"__id__": 22
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@ -178,7 +184,7 @@
|
||||
"_priority": 0,
|
||||
"_fov": 45,
|
||||
"_fovAxis": 0,
|
||||
"_orthoHeight": 481.3008130081301,
|
||||
"_orthoHeight": 415.3929539295393,
|
||||
"_near": 0,
|
||||
"_far": 1000,
|
||||
"_color": {
|
||||
@ -212,7 +218,7 @@
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "list",
|
||||
"_name": "list1",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_parent": {
|
||||
@ -231,7 +237,7 @@
|
||||
"_prefab": null,
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"x": -420,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
@ -303,10 +309,11 @@
|
||||
"__id__": 8
|
||||
}
|
||||
],
|
||||
"registerSupplementaryForEditor": [],
|
||||
"_id": "540sP7RuVMr66Fyji3wiJs"
|
||||
},
|
||||
{
|
||||
"__type__": "_yx_editor_register_cell_info",
|
||||
"__type__": "_yx_editor_register_element_info",
|
||||
"prefab": {
|
||||
"__uuid__": "c8b689a4-ae47-4041-8c9c-7298939e76be",
|
||||
"__expectedType__": "cc.Prefab"
|
||||
@ -314,6 +321,242 @@
|
||||
"identifier": "cell",
|
||||
"comp": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "list2",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_parent": {
|
||||
"__id__": 2
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 10
|
||||
},
|
||||
{
|
||||
"__id__": 11
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
"__type__": "cc.Quat",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"w": 1
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
"_layer": 33554432,
|
||||
"_euler": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_id": "15UUoyfNhFtISYnWUZ/NMT"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.UITransform",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 9
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": null,
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 400,
|
||||
"height": 700
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
},
|
||||
"_id": "61HeNMUnxBJ4RjTTIkj7jX"
|
||||
},
|
||||
{
|
||||
"__type__": "f94cbxum3xAsomhVNW1piY0",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 9
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": null,
|
||||
"mask": true,
|
||||
"scrollEnabled": true,
|
||||
"wheelScrollEnabled": true,
|
||||
"scrollDirection": 1,
|
||||
"mode": 0,
|
||||
"preloadNodesLimitPerFrame": 2,
|
||||
"frameInterval": 1,
|
||||
"recycleInterval": 1,
|
||||
"registerCellForEditor": [
|
||||
{
|
||||
"__id__": 12
|
||||
}
|
||||
],
|
||||
"registerSupplementaryForEditor": [
|
||||
{
|
||||
"__id__": 13
|
||||
}
|
||||
],
|
||||
"_id": "557IDOUVFMgaXUVLj9KNOh"
|
||||
},
|
||||
{
|
||||
"__type__": "_yx_editor_register_element_info",
|
||||
"prefab": {
|
||||
"__uuid__": "c8b689a4-ae47-4041-8c9c-7298939e76be",
|
||||
"__expectedType__": "cc.Prefab"
|
||||
},
|
||||
"identifier": "cell",
|
||||
"comp": ""
|
||||
},
|
||||
{
|
||||
"__type__": "_yx_editor_register_element_info",
|
||||
"prefab": {
|
||||
"__uuid__": "c8b689a4-ae47-4041-8c9c-7298939e76be",
|
||||
"__expectedType__": "cc.Prefab"
|
||||
},
|
||||
"identifier": "supplementary",
|
||||
"comp": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "list3",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_parent": {
|
||||
"__id__": 2
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 15
|
||||
},
|
||||
{
|
||||
"__id__": 16
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 420,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
"__type__": "cc.Quat",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"w": 1
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
"_layer": 33554432,
|
||||
"_euler": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_id": "fdY0keATpIKb8Mxuy2Wwhd"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.UITransform",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 14
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": null,
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 400,
|
||||
"height": 700
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
},
|
||||
"_id": "6agiejTfZKQIWskmHSuyHv"
|
||||
},
|
||||
{
|
||||
"__type__": "f94cbxum3xAsomhVNW1piY0",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 14
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": null,
|
||||
"mask": true,
|
||||
"scrollEnabled": true,
|
||||
"wheelScrollEnabled": true,
|
||||
"scrollDirection": 1,
|
||||
"mode": 0,
|
||||
"preloadNodesLimitPerFrame": 2,
|
||||
"frameInterval": 1,
|
||||
"recycleInterval": 1,
|
||||
"registerCellForEditor": [
|
||||
{
|
||||
"__id__": 17
|
||||
}
|
||||
],
|
||||
"registerSupplementaryForEditor": [
|
||||
{
|
||||
"__id__": 18
|
||||
}
|
||||
],
|
||||
"_id": "e75snCyRxN9KrcOvFeGEnP"
|
||||
},
|
||||
{
|
||||
"__type__": "_yx_editor_register_element_info",
|
||||
"prefab": {
|
||||
"__uuid__": "c8b689a4-ae47-4041-8c9c-7298939e76be",
|
||||
"__expectedType__": "cc.Prefab"
|
||||
},
|
||||
"identifier": "cell",
|
||||
"comp": ""
|
||||
},
|
||||
{
|
||||
"__type__": "_yx_editor_register_element_info",
|
||||
"prefab": {
|
||||
"__uuid__": "c8b689a4-ae47-4041-8c9c-7298939e76be",
|
||||
"__expectedType__": "cc.Prefab"
|
||||
},
|
||||
"identifier": "supplementary",
|
||||
"comp": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.UITransform",
|
||||
"_name": "",
|
||||
@ -392,9 +635,6 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": null,
|
||||
"listComp": {
|
||||
"__id__": 7
|
||||
},
|
||||
"_id": "acPDbfi09KmLOkYo+Irf3Y"
|
||||
},
|
||||
{
|
||||
@ -408,25 +648,25 @@
|
||||
{
|
||||
"__type__": "cc.SceneGlobals",
|
||||
"ambient": {
|
||||
"__id__": 15
|
||||
"__id__": 25
|
||||
},
|
||||
"shadows": {
|
||||
"__id__": 16
|
||||
"__id__": 26
|
||||
},
|
||||
"_skybox": {
|
||||
"__id__": 17
|
||||
"__id__": 27
|
||||
},
|
||||
"fog": {
|
||||
"__id__": 18
|
||||
"__id__": 28
|
||||
},
|
||||
"octree": {
|
||||
"__id__": 19
|
||||
"__id__": 29
|
||||
},
|
||||
"skin": {
|
||||
"__id__": 20
|
||||
"__id__": 30
|
||||
},
|
||||
"lightProbeInfo": {
|
||||
"__id__": 21
|
||||
"__id__": 31
|
||||
},
|
||||
"bakedWithStationaryMainLight": false,
|
||||
"bakedWithHighpLightmap": false
|
||||
|
@ -1,53 +1,118 @@
|
||||
import { _decorator, Component, Label, math, Node } from 'cc';
|
||||
import { _decorator, Component, Label, math, Node, Sprite } from 'cc';
|
||||
import { YXCollectionView } from '../lib/yx-collection-view';
|
||||
import { YXTableLayout } from '../lib/yx-table-layout';
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
class Data {
|
||||
id: number
|
||||
}
|
||||
|
||||
@ccclass('home')
|
||||
export class home extends Component {
|
||||
|
||||
@property(YXCollectionView)
|
||||
listComp: YXCollectionView = null
|
||||
|
||||
testData: Data[] = []
|
||||
|
||||
protected start(): void {
|
||||
this.setup_list1()
|
||||
this.setup_list2()
|
||||
this.setup_list3()
|
||||
}
|
||||
|
||||
// 绑定数据源
|
||||
this.listComp.numberOfItems = () => this.testData.length
|
||||
this.listComp.cellForItemAt = (indexPath, collectionView) => {
|
||||
const rowData = this.testData[indexPath.item]
|
||||
setup_list1() {
|
||||
const listComp = this.node.getChildByName('list1').getComponent(YXCollectionView)
|
||||
|
||||
listComp.numberOfItems = () => 10000
|
||||
listComp.cellForItemAt = (indexPath, collectionView) => {
|
||||
const cell = collectionView.dequeueReusableCell(`cell`)
|
||||
cell.getChildByName('label').getComponent(Label).string = `${indexPath}`
|
||||
return cell
|
||||
}
|
||||
|
||||
// 确定布局方案
|
||||
let layout = new YXTableLayout()
|
||||
layout.spacing = 20
|
||||
layout.itemSize = new math.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(Label).string = `header ${indexPath}`
|
||||
const shape = supplementary.getChildByName('shape')
|
||||
shape.getComponent(Sprite).color = new math.Color(100, 100, 150)
|
||||
return supplementary
|
||||
}
|
||||
if (kinds === YXTableLayout.SupplementaryKinds.FOOTER) {
|
||||
const supplementary = collectionView.dequeueReusableSupplementary('supplementary')
|
||||
supplementary.getChildByName('label').getComponent(Label).string = `footer ${indexPath}`
|
||||
const shape = supplementary.getChildByName('shape')
|
||||
shape.getComponent(Sprite).color = new math.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(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(Label).string = `header ${indexPath}`
|
||||
const shape = supplementary.getChildByName('shape')
|
||||
shape.getComponent(Sprite).color = new math.Color(100, 100, 150)
|
||||
return supplementary
|
||||
}
|
||||
if (kinds === YXTableLayout.SupplementaryKinds.FOOTER) {
|
||||
const supplementary = collectionView.dequeueReusableSupplementary('supplementary')
|
||||
supplementary.getChildByName('label').getComponent(Label).string = `footer ${indexPath}`
|
||||
const shape = supplementary.getChildByName('shape')
|
||||
shape.getComponent(Sprite).color = new math.Color(150, 100, 100)
|
||||
return supplementary
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
listComp.numberOfItems = () => 20
|
||||
listComp.cellForItemAt = (indexPath, collectionView) => {
|
||||
const cell = collectionView.dequeueReusableCell(`cell`)
|
||||
cell.getChildByName('label').getComponent(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,115 +1,352 @@
|
||||
import { math, UITransform, warn } from "cc";
|
||||
import { YXBinaryLayout, YXCollectionView, YXIndexPath, YXLayoutAttributes } from "./yx-collection-view";
|
||||
import { log, math, UITransform, warn } from "cc";
|
||||
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: math.Size | ((indexPath: YXIndexPath, layout: YXTableLayout, collectionView: YXCollectionView) => math.Size) = new math.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, math.Rect> = new Map() // 保存所有 header 的原始位置
|
||||
protected originalFooterRect: Map<number, math.Rect> = new Map() // 保存所有 footer 的原始位置
|
||||
|
||||
// 为了优化查找,额外维护几个数组按类别管理所有的布局属性,空间换时间
|
||||
protected allCellAttributes: YXLayoutAttributes[] = []
|
||||
protected allHeaderAttributes: YXLayoutAttributes[] = []
|
||||
protected allFooterAttributes: YXLayoutAttributes[] = []
|
||||
|
||||
prepare(collectionView: YXCollectionView): void {
|
||||
// 设置列表的滚动方向
|
||||
if (collectionView.scrollDirection == YXCollectionView.ScrollDirection.HORIZONTAL) {
|
||||
warn(`YXTableLayout 只支持垂直方向排列`)
|
||||
}
|
||||
// 设置列表的滚动方向(这套布局固定为垂直方向滚动)
|
||||
collectionView.scrollView.horizontal = false
|
||||
collectionView.scrollView.vertical = true
|
||||
|
||||
let contentSize = collectionView.node.getComponent(UITransform).contentSize.clone()
|
||||
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 math.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) {
|
||||
// 由于这套布局规则只支持垂直方向布局,当外部配置了水平方向滚动时这里可以给个警告
|
||||
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.getComponent(UITransform).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 math.Size(contentWidth, contentHeight)
|
||||
}
|
||||
|
||||
initOffset(collectionView: YXCollectionView): void {
|
||||
// 首次更新数据,滚动至列表顶部
|
||||
// 列表首次刷新时,调整一下列表的偏移位置
|
||||
collectionView.scrollView.scrollToTop()
|
||||
}
|
||||
|
||||
layoutAttributesForElementsInRect(rect: math.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.view.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: math.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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user