添加一些演示场景

This commit is contained in:
o.o.c.
2025-12-07 20:00:23 +08:00
parent d42bf02f6e
commit 493a02d981
36 changed files with 4754 additions and 366 deletions

View File

@@ -1,8 +1,10 @@
import { math, UITransform, warn } from "cc";
import { YXCollectionView, YXIndexPath, YXLayout, YXLayoutAttributes } from "./yx-collection-view";
/**
* 网格布局
*/
export class GridLayout extends YXLayout {
/**
* 节点大小
*/
@@ -18,11 +20,24 @@ export class GridLayout extends YXLayout {
*/
verticalSpacing: number = 0
/**
* @deprecated contentAlignment
*/
set alignment(value: number) {
this.contentAlignment = value
}
/**
* 整体对齐方式
* 0靠左 1居中 2靠右
*/
alignment: number = 1
contentAlignment: number = 1
/**
* 最后一行元素的对齐方式
* 0靠左 1居中 2靠右 (默认 null靠左)
*/
lastRowAlignment?: number = null
/**
* 获取每行最多可以容纳多少个节点
@@ -64,15 +79,14 @@ export class GridLayout extends YXLayout {
// 计算每行最多可以放多少个节点
this._maxItemsPerRow = null
let num = this.getMaxItemsPerRow(collectionView)
let maxWidth = (num * this.itemSize.width + (num - 1) * this.horizontalSpacing) // 每行节点需要占用的总宽度
// 根据设置的对齐方式计算左边距
let left = 0
if (this.alignment == 1) {
let maxWidth = (num * this.itemSize.width + (num - 1) * this.horizontalSpacing) // 每行节点总宽度
if (this.contentAlignment == 1) {
left = (width - maxWidth) * 0.5
}
if (this.alignment == 2) {
let maxWidth = (num * this.itemSize.width + (num - 1) * this.horizontalSpacing) // 每行节点总宽度
if (this.contentAlignment == 2) {
left = width - maxWidth
}
@@ -80,6 +94,10 @@ export class GridLayout extends YXLayout {
if (numberOfSections > 1) { warn(`GridLayout 暂时不支持分区模式`) }
const numberOfItems = collectionView.getNumberOfItems(0)
// 计算元素一共有多少行
let total = Math.ceil(numberOfItems / num)
// 临时记录最后一行的元素
let lastAttrs: YXLayoutAttributes[] = []
for (let index = 0; index < numberOfItems; index++) {
// 计算这个节点是第几行
@@ -101,6 +119,26 @@ export class GridLayout extends YXLayout {
// 更新内容高度
contentSize.height = Math.max(contentSize.height, attr.frame.yMax)
if (row == total - 1) {
lastAttrs.push(attr)
}
}
// 单独调整最后一行元素的对齐方式
if (this.lastRowAlignment != null) {
let tempLeft = left
if (this.lastRowAlignment == 1) {
tempLeft = left + (maxWidth - this.itemSize.width * lastAttrs.length - this.horizontalSpacing * (lastAttrs.length - 1)) * 0.5
}
if (this.lastRowAlignment == 2) {
tempLeft = left + (maxWidth - this.itemSize.width * lastAttrs.length - this.horizontalSpacing * (lastAttrs.length - 1))
}
for (let index = 0; index < lastAttrs.length; index++) {
const attr = lastAttrs[index]
attr.frame.x = tempLeft
tempLeft = attr.frame.xMax + this.horizontalSpacing
}
}
this.attributes = attrs
@@ -137,4 +175,3 @@ export class GridLayout extends YXLayout {
return this.attributes.slice(startIdx, endIdx)
}
}

View File

@@ -0,0 +1,144 @@
import { log, math, warn } from "cc";
import { YXCollectionView, YXIndexPath, YXLayout, YXLayoutAttributes } from "./yx-collection-view";
/**
* PageView 布局
*/
export class PageLayout extends YXLayout {
/**
* 是否开启分页效果
*/
private pagingEnabled: boolean = true
/**
* 分页效果开启时,自动吸附动画时间
*/
pagingAnimationDuration: number = 0.5
/**
* 循环滚动,默认关闭
* 注意: 当开启循环滚动时YXCollectionView 需要额外设置 `ignoreScrollEndedDuringAutoScroll = true`
* 注意: 开启循环滚动会生成较大范围的 `indexPath`,在使用索引的时候需要进行取余处理
*
* @example
* listComp.ignoreScrollEndedDuringAutoScroll = true
* listComp.numberOfItems = () => {
* return <data-length>
* }
* listComp.cellForItemAt = (indexPath, collectionView) => {
* let index = indexPath.row % <data-length> // 通过取余获取真实数据索引
* const cell = collectionView.dequeueReusableCell(`cell`)
* return cell
* }
*/
loop: boolean = false
/**
* 仅开启循环滚动时生效,由于循环滚动是伪循环,如果不间断的朝着某一个方向一直滑是会滑到头的 (就像苹果的闹钟时间可以滑到尽头...)
* 调整这个属性可以放大滚动范围,避免滑动到头穿帮
* 会生成额外的布局属性,建议范围 1 ~ 10默认: 5 (实际情况可以看数据压力,数据不多的话可以设置更大)
*/
scrollRangeMultiplier: number = 5
private get safeScrollRangeMultiplier(): number {
let value = Math.floor(this.scrollRangeMultiplier)
return Math.max(1, value)
}
prepare(collectionView: YXCollectionView): void {
collectionView.scrollView.horizontal = true
collectionView.scrollView.vertical = false
if (collectionView.scrollDirection === YXCollectionView.ScrollDirection.VERTICAL) {
warn(`PageLayout 仅支持水平方向排列`)
}
const numberOfSections = collectionView.getNumberOfSections()
if (numberOfSections > 1) { warn(`GridLayout 暂时不支持分区模式`) }
let contentSize = collectionView.scrollView.view.contentSize.clone()
let attrs = []
let itemSize = collectionView.scrollView.view.contentSize
let numberOfItems = collectionView.getNumberOfItems(0)
if (this.loop) {
numberOfItems = numberOfItems * 3 * this.safeScrollRangeMultiplier
if (collectionView.ignoreScrollEndedDuringAutoScroll == false) {
warn(`PageLayout: 开启循环滚动时建议将 YXCollectionView.ignoreScrollEndedDuringAutoScroll 设置为 true`)
}
}
for (let index = 0; index < numberOfItems; index++) {
let attr = YXLayoutAttributes.layoutAttributesForCell(new YXIndexPath(0, index))
attr.frame.x = itemSize.width * index
attr.frame.y = 0
attr.frame.width = itemSize.width
attr.frame.height = itemSize.height
attrs.push(attr)
contentSize.width = Math.max(contentSize.width, attr.frame.xMax)
}
this.attributes = attrs
this.contentSize = contentSize
}
initOffset(collectionView: YXCollectionView): void {
if (this.loop) {
let numberOfItems = collectionView.getNumberOfItems(0)
let offset = new math.Vec2()
offset.x = numberOfItems * this.safeScrollRangeMultiplier * collectionView.scrollView.view.width
offset.y = 0
collectionView.scrollView.scrollToOffset(offset, 0)
} else {
collectionView.scrollView.scrollToLeft()
}
}
targetOffset(collectionView: YXCollectionView, touchMoveVelocity: math.Vec3, startOffset: math.Vec2, originTargetOffset: math.Vec2, originScrollDuration: number): { offset: math.Vec2; time?: number; attenuated?: boolean; } | null {
if (this.pagingEnabled == false) {
return null
}
let offset = collectionView.scrollView.getScrollOffset()
offset.x = - offset.x
let threshold = 0.2
let idx = Math.round(offset.x / collectionView.scrollView.view.width)
let r = touchMoveVelocity.x / collectionView.scrollView.view.width
if (startOffset && Math.abs(r) >= threshold) {
idx = Math.round(startOffset.x / collectionView.scrollView.view.width) + (r > 0 ? -1 : 1)
}
offset.x = idx * collectionView.scrollView.view.width
return { offset: offset, time: this.pagingAnimationDuration }
}
layoutAttributesForElementsInRect(rect: math.Rect, collectionView: YXCollectionView): YXLayoutAttributes[] {
if (collectionView.scrollView.view.width <= 0 || this.attributes.length <= 0) {
return super.layoutAttributesForElementsInRect(rect, collectionView)
}
// 直接计算出当前元素位置,另外额外返回左右两边的元素
let result = []
let idx = Math.round(rect.x / collectionView.scrollView.view.width)
let previousIdx = idx - 1
let latterIdx = idx + 1
if (idx >= 0 && idx < this.attributes.length) {
result.push(this.attributes[idx])
}
if (previousIdx >= 0 && previousIdx < this.attributes.length && previousIdx != idx) {
result.push(this.attributes[previousIdx])
}
if (latterIdx >= 0 && latterIdx < this.attributes.length && latterIdx != idx) {
result.push(this.attributes[latterIdx])
}
return result
}
onScrollEnded(collectionView: YXCollectionView): void {
if (this.loop == false) {
return
}
let numberOfItems = collectionView.getNumberOfItems(0)
let offset = collectionView.scrollView.getScrollOffset()
offset.x = - offset.x
let idx = Math.round(offset.x / collectionView.scrollView.view.width) % numberOfItems
offset.x = collectionView.scrollView.view.width * (numberOfItems * this.safeScrollRangeMultiplier + idx)
collectionView.scrollView.scrollToOffset(offset)
// 直接设置滚动位置不会触发刷新,这里强制刷新一下
collectionView.markForUpdateVisibleData(true)
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "0b60b98b-e96f-4ced-8e6c-87910ed7b6d1",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -645,6 +645,7 @@ export class YXCollectionView extends Component {
/**
* 滚动过程中每多少帧回收一次不可见节点1表示每帧都回收0表示不在滚动过程中回收不可见节点
* @bug 滚动过程中如果实时的回收不可见节点,有时候会收不到 scroll view 的 cancel 事件,导致 scroll view 的滚动状态不会更新 (且收不到滚动结束事件)
* @bug 列表滚动时卡住不会正常回弹
* @fix 当这个属性设置为 0 时,只会在 `touch-up` 和 `scroll-ended` 里面回收不可见节点
*/
@property({ tooltip: `滚动过程中每多少帧回收一次不可见节点1表示每帧都回收0表示不在滚动过程中回收不可见节点` })
@@ -755,6 +756,10 @@ export class YXCollectionView extends Component {
let cell = result.getComponent(_yx_node_element_comp) || result.addComponent(_yx_node_element_comp)
cell.identifier = identifier
/**
* @todo 滑动很快的时候似乎偶尔会触发 `Error occurs in an event listener: touch-start`
* 复现条件: 列表嵌套 && 快速滑动 && 刷新列表,像是刷新后把 cell node 给回收了导致的
*/
result.on(NodeEventType.TOUCH_END, this.onTouchElement, this)
}
return result

View File

@@ -6,6 +6,10 @@ enum _yx_table_layout_supplementary_kinds {
FOOTER = 'footer',
}
/**
* TableView 布局
* 这个布局实现了 YXCollectionView 约定的大部分的概念,有想深入了解自定义布局的可以拿这套布局当作参考
*/
export class YXTableLayout extends YXLayout {
/**
@@ -181,7 +185,7 @@ export class YXTableLayout extends YXLayout {
}
// 重要: 设置内容区域总大小,只有确定了滚动区域的大小列表才能滚动
this.contentSize = new math.Size(contentWidth, contentHeight)
this.contentSize = new math.Size(contentWidth, Math.max(contentHeight, collectionView.scrollView.view.height))
}
initOffset(collectionView: YXCollectionView): void {