mirror of
https://github.com/568071718/creator-collection-view
synced 2025-12-08 21:58:51 +00:00
添加一些演示场景
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
144
list-3x/assets/lib/page-layout.ts
Normal file
144
list-3x/assets/lib/page-layout.ts
Normal 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)
|
||||
}
|
||||
}
|
||||
9
list-3x/assets/lib/page-layout.ts.meta
Normal file
9
list-3x/assets/lib/page-layout.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.23",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "0b60b98b-e96f-4ced-8e6c-87910ed7b6d1",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user