Commit 69c33daa authored by haiyoucuv's avatar haiyoucuv

init

parent 340e9683
import { _decorator, log, math, UITransform } from 'cc';
import { YXCollectionView, YXIndexPath, YXLayout, YXLayoutAttributes } from '../lib/yx-collection-view';
const { ccclass, property } = _decorator;
/**
* 需求: 垂直多列布局,当最后一排节点排列不满一行的时候居中排列
* @deprecated 使用 YXFlowLayout 实现
*/
export class CustomGridFlowLayout extends YXLayout {
itemSize: math.Size = new math.Size(100, 100)
verticalSpacing: number = 0
horizontalSpacing: number = 0
alignment: number = 1 // 0靠左 1居中 2靠右
prepare(collectionView: YXCollectionView): void {
this._vertical(collectionView)
}
protected _vertical(collectionView: YXCollectionView) {
collectionView.scrollView.horizontal = false
collectionView.scrollView.vertical = true
let attrs: YXLayoutAttributes[] = []
let contentSize = collectionView.node.getComponent(UITransform).contentSize.clone()
// 计算每行最多可以放多少个节点
const top = 0 // 上边距
const width = collectionView.node.getComponent(UITransform).width
let num = 1
while ((num * this.itemSize.width + (num - 1) * this.horizontalSpacing) <= width) { num++ }
num = Math.max(1, num - 1)
// 根据设置的对齐方式计算左边距
let left = 0
if (this.alignment == 1) {
let maxWidth = (num * this.itemSize.width + (num - 1) * this.horizontalSpacing) // 每行节点宽度
left = (width - maxWidth) * 0.5
}
if (this.alignment == 2) {
let maxWidth = (num * this.itemSize.width + (num - 1) * this.horizontalSpacing) // 每行节点宽度
left = width - maxWidth
}
let rowAttrs: YXLayoutAttributes[][] = [] // 保存每行的节点布局属性
const total = collectionView.numberOfItems instanceof Function ? collectionView.numberOfItems(0, collectionView) : collectionView.numberOfItems
for (let index = 0; index < total; index++) {
// 计算这个节点是第几行
let row = Math.floor(index / num)
// 计算这个节点是第几列
let column = index % num
// 计算节点 origin
let x = left + (this.itemSize.width + this.horizontalSpacing) * column
let y = top + (this.itemSize.height + this.verticalSpacing) * row
let attr = new YXLayoutAttributes()
attr.indexPath = new YXIndexPath(0, index)
attr.frame = new math.Rect()
attr.frame.x = x
attr.frame.y = y
attr.frame.width = this.itemSize.width
attr.frame.height = this.itemSize.height
attrs.push(attr)
let tmpArray = rowAttrs[row]
if (tmpArray == null) {
tmpArray = []
rowAttrs[row] = tmpArray
}
tmpArray.push(attr)
contentSize.height = Math.max(contentSize.height, attr.frame.yMax)
}
if (rowAttrs.length > 0) {
// 检查最后一行节点数量,调整对齐逻辑
const lastRowAttrs = rowAttrs[rowAttrs.length - 1]
if (lastRowAttrs.length < num) {
let left = 0
if (this.alignment == 1) {
let maxWidth = (lastRowAttrs.length * this.itemSize.width + (lastRowAttrs.length - 1) * this.horizontalSpacing) // 最后这行节点宽度
left = (width - maxWidth) * 0.5
}
if (this.alignment == 2) {
let maxWidth = (lastRowAttrs.length * this.itemSize.width + (lastRowAttrs.length - 1) * this.horizontalSpacing) // 最后这行节点宽度
left = width - maxWidth
}
for (let index = 0; index < lastRowAttrs.length; index++) {
const element = lastRowAttrs[index];
element.frame.x = left + (this.itemSize.width + this.horizontalSpacing) * index
}
}
}
this.attributes = attrs
this.contentSize = contentSize
rowAttrs = []
}
initOffset(collectionView: YXCollectionView): void {
collectionView.scrollView.scrollToTop()
}
}
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "f24212c8-cb09-46b0-bf5d-8011eb1e7fcd",
"files": [],
"subMetas": {},
"userData": {}
}
import { _decorator, math, UITransform } from 'cc';
import { YXCollectionView, YXEdgeInsets, YXIndexPath, YXLayout, YXLayoutAttributes } from './yx-collection-view';
const { ccclass, property } = _decorator;
const _rectOut = new math.Rect()
/**
* 流式布局 (紧凑排列)
* 支持水平/垂直方向排列滚动
*/
export class YXCompactFlowLayout extends YXLayout {
/**
* 是否开启分页滚动效果
*/
pagingEnabled: boolean = false
/**
* @bug 如果节点大小差距很大,可能会导致计算屏幕内节点时不准确,出现节点不被正确添加到滚动视图上的问题 (使用瀑布流布局时问题明显)
* @fix 可以通过此属性,追加屏幕显示的节点数量
* 设置这个值会在检查是否可见的节点时,尝试检查更多的可能处于屏幕外的节点,具体设置多少要根据实际情况调试,一般如果都是正常大小的节点,不需要考虑这个配置
* 设置负值会检查所有的节点
*/
extraVisibleCount: number = 0
/**
* 元素大小
*/
itemSize: math.Size | ((indexPath: YXIndexPath, layout: YXCompactFlowLayout, collectionView: YXCollectionView) => math.Size) = new math.Size(100, 100)
getItemSize(): math.Size {
if (this.itemSize instanceof Function == false) {
return this.itemSize
}
throw new Error("YXFlowLayout: 动态配置的布局参数不支持直接获取,请检查自己的布局逻辑并谨慎的通过动态配置自己获取,注意避免死循环");
}
/**
* 元素之间垂直间距
*/
verticalSpacing: number | ((section: number, layout: YXCompactFlowLayout, collectionView: YXCollectionView) => number) = 0
getVerticalSpacing(): number {
if (this.verticalSpacing instanceof Function == false) {
return this.verticalSpacing
}
throw new Error("YXFlowLayout: 动态配置的布局参数不支持直接获取,请检查自己的布局逻辑并谨慎的通过动态配置自己获取,注意避免死循环");
}
/**
* 元素之间水平间距
*/
horizontalSpacing: number | ((section: number, layout: YXCompactFlowLayout, collectionView: YXCollectionView) => number) = 0
getHorizontalSpacing(): number {
if (this.horizontalSpacing instanceof Function == false) {
return this.horizontalSpacing
}
throw new Error("YXFlowLayout: 动态配置的布局参数不支持直接获取,请检查自己的布局逻辑并谨慎的通过动态配置自己获取,注意避免死循环");
}
/**
* 边距
*/
sectionInset: YXEdgeInsets | ((section: number, layout: YXCompactFlowLayout, collectionView: YXCollectionView) => YXEdgeInsets) = YXEdgeInsets.ZERO
getSectionInset(): YXEdgeInsets {
if (this.sectionInset instanceof Function == false) {
return this.sectionInset
}
throw new Error("YXFlowLayout: 动态配置的布局参数不支持直接获取,请检查自己的布局逻辑并谨慎的通过动态配置自己获取,注意避免死循环");
}
prepare(collectionView: YXCollectionView): void {
if (collectionView.scrollDirection == YXCollectionView.ScrollDirection.HORIZONTAL) {
this._horizontal(collectionView)
return
}
if (collectionView.scrollDirection == YXCollectionView.ScrollDirection.VERTICAL) {
this._vertical(collectionView)
return
}
}
initOffset(collectionView: YXCollectionView): void {
if (collectionView.scrollDirection == YXCollectionView.ScrollDirection.HORIZONTAL) {
collectionView.scrollView.scrollToLeft(0)
return
}
if (collectionView.scrollDirection == YXCollectionView.ScrollDirection.VERTICAL) {
collectionView.scrollView.scrollToTop(0)
return
}
}
targetOffset(collectionView: YXCollectionView, touchMoveVelocity: math.Vec3, startOffset: math.Vec2): { offset: math.Vec2; time: number; } {
if (this.pagingEnabled == false) {
return null
}
let offset = collectionView.scrollView.getScrollOffset()
offset.x = - offset.x
let threshold = 0.2
if (collectionView.scrollDirection == YXCollectionView.ScrollDirection.HORIZONTAL) {
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
}
if (collectionView.scrollDirection == YXCollectionView.ScrollDirection.VERTICAL) {
let idx = Math.round(offset.y / collectionView.scrollView.view.height)
let r = touchMoveVelocity.y / collectionView.scrollView.view.height
if (startOffset && Math.abs(r) >= threshold) {
idx = Math.round(startOffset.y / collectionView.scrollView.view.height) + (r > 0 ? 1 : -1)
}
offset.y = idx * collectionView.scrollView.view.height
}
return { offset: offset, time: 0.25 }
}
layoutAttributesForElementsInRect(rect: math.Rect, collectionView: YXCollectionView): YXLayoutAttributes[] {
if (this.extraVisibleCount < 0) {
return super.layoutAttributesForElementsInRect(rect, collectionView)
}
// 二分先查出大概位置
let midIdx = -1
let left = 0
let right = this.attributes.length - 1
while (left <= right && right >= 0) {
let mid = left + (right - left) / 2
mid = Math.floor(mid)
let attr = this.attributes[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 super.layoutAttributesForElementsInRect(rect, collectionView)
}
let result = []
result.push(this.attributes[midIdx])
// 往前检查
let startIdx = midIdx
while (startIdx > 0) {
let idx = startIdx - 1
let attr = this.attributes[idx]
if (rect.intersects(attr.frame) == false) {
break
}
result.push(attr)
startIdx = idx
}
// 追加检查
let extra_left = this.extraVisibleCount
while (extra_left > 0) {
let idx = startIdx - 1
if (idx < 0) { break }
let attr = this.attributes[idx]
if (rect.intersects(attr.frame)) { result.push(attr) }
startIdx = idx
extra_left--
}
// 往后检查
let endIdx = midIdx
while (endIdx < this.attributes.length - 1) {
let idx = endIdx + 1
let attr = this.attributes[idx]
if (rect.intersects(attr.frame) == false) {
break
}
result.push(attr)
endIdx = idx
}
// 追加检查
let extra_right = this.extraVisibleCount
while (extra_right > 0) {
let idx = endIdx + 1
if (idx >= this.attributes.length) { break }
let attr = this.attributes[idx]
if (rect.intersects(attr.frame)) { result.push(attr) }
endIdx = idx
extra_right--
}
return result
}
layoutAttributesForItemAtIndexPath(indexPath: YXIndexPath, collectionView: YXCollectionView): YXLayoutAttributes {
let left = 0
let right = this.attributes.length - 1
while (left <= right && right >= 0) {
let mid = left + (right - left) / 2
mid = Math.floor(mid)
let attr = this.attributes[mid]
if (attr.indexPath.equals(indexPath)) {
return attr
}
if (attr.indexPath.section < indexPath.section || (attr.indexPath.section == indexPath.section && attr.indexPath.item < indexPath.item)) {
left = mid + 1
} else {
right = mid - 1
}
}
return super.layoutAttributesForItemAtIndexPath(indexPath, collectionView)
}
private _horizontal(collectionView: YXCollectionView) {
collectionView.scrollView.horizontal = true
collectionView.scrollView.vertical = false
let contentSize = collectionView.node.getComponent(UITransform).contentSize.clone()
let allAttributes: YXLayoutAttributes[] = []
let numberOfSections = collectionView.numberOfSections instanceof Function ? collectionView.numberOfSections(collectionView) : collectionView.numberOfSections
let sectionMaxX = 0
for (let section = 0; section < numberOfSections; section++) {
let numberOfItems = collectionView.numberOfItems instanceof Function ? collectionView.numberOfItems(section, collectionView) : collectionView.numberOfItems
let verticalSpacing = this.verticalSpacing instanceof Function ? this.verticalSpacing(section, this, collectionView) : this.verticalSpacing
let horizontalSpacing = this.horizontalSpacing instanceof Function ? this.horizontalSpacing(section, this, collectionView) : this.horizontalSpacing
let sectionInset = this.sectionInset instanceof Function ? this.sectionInset(section, this, collectionView) : this.sectionInset
sectionMaxX += sectionInset.left
let whole = new _yx_flow_layout_whole()
whole.verticalSpacing = verticalSpacing
whole.horizontalSpacing = horizontalSpacing
whole.sectionInset = sectionInset
whole.offset = sectionMaxX
whole.attrs = []
whole.containerWidth = 0
whole.containerHeight = contentSize.height
for (let item = 0; item < numberOfItems; item++) {
let indexPath = new YXIndexPath(section, item)
let itemSize = this.itemSize instanceof Function ? this.itemSize(indexPath, this, collectionView) : this.itemSize
let attr = whole.layout_horizontal_item(indexPath, itemSize)
if (attr == null) {
// 返回 null 表示摆不下了,需要换列排列
whole.offset = whole.offset + whole.containerWidth + horizontalSpacing
whole.containerWidth = 0
whole.attrs = []
attr = whole.layout_horizontal_item(indexPath, itemSize)
}
if (attr) {
allAttributes.push(attr)
}
sectionMaxX = Math.max(sectionMaxX, whole.offset + whole.containerWidth)
}
sectionMaxX += sectionInset.right
}
this.attributes = allAttributes
contentSize.width = Math.max(contentSize.width, sectionMaxX)
this.contentSize = contentSize
}
private _vertical(collectionView: YXCollectionView) {
collectionView.scrollView.horizontal = false
collectionView.scrollView.vertical = true
let contentSize = collectionView.node.getComponent(UITransform).contentSize.clone()
let allAttributes: YXLayoutAttributes[] = []
let numberOfSections = collectionView.numberOfSections instanceof Function ? collectionView.numberOfSections(collectionView) : collectionView.numberOfSections
let sectionMaxY = 0
for (let section = 0; section < numberOfSections; section++) {
let numberOfItems = collectionView.numberOfItems instanceof Function ? collectionView.numberOfItems(section, collectionView) : collectionView.numberOfItems
let verticalSpacing = this.verticalSpacing instanceof Function ? this.verticalSpacing(section, this, collectionView) : this.verticalSpacing
let horizontalSpacing = this.horizontalSpacing instanceof Function ? this.horizontalSpacing(section, this, collectionView) : this.horizontalSpacing
let sectionInset = this.sectionInset instanceof Function ? this.sectionInset(section, this, collectionView) : this.sectionInset
sectionMaxY += sectionInset.top
let whole = new _yx_flow_layout_whole()
whole.verticalSpacing = verticalSpacing
whole.horizontalSpacing = horizontalSpacing
whole.sectionInset = sectionInset
whole.offset = sectionMaxY
whole.attrs = []
whole.containerWidth = contentSize.width
whole.containerHeight = 0
for (let item = 0; item < numberOfItems; item++) {
let indexPath = new YXIndexPath(section, item)
let itemSize = this.itemSize instanceof Function ? this.itemSize(indexPath, this, collectionView) : this.itemSize
let attr = whole.layout_vertical_item(indexPath, itemSize)
if (attr == null) {
// 返回 null 表示摆不下了,需要换行排列
whole.offset = whole.offset + whole.containerHeight + verticalSpacing
whole.containerHeight = 0
whole.attrs = []
attr = whole.layout_vertical_item(indexPath, itemSize)
}
if (attr) {
allAttributes.push(attr)
}
sectionMaxY = Math.max(sectionMaxY, whole.offset + whole.containerHeight)
}
sectionMaxY += sectionInset.bottom
}
this.attributes = allAttributes
contentSize.height = Math.max(contentSize.height, sectionMaxY)
this.contentSize = contentSize
}
}
/**
* 这个类用来实现紧凑流式布局
* 目前版本使用的策略: 贪心算法,遍历取当前整体内最优的位置
* GPT: 水平/垂直扫描 和 贪心算法 是解决这种矩形排列问题的简单有效方法。它们通过从当前矩形的位置出发,逐步扫描周围空白区域来寻找新矩形的放置位置。虽然这种方法比较直观且易于实现,但在复杂情况下可能不是最优解。对于更高效的排列策略,可能需要结合更多的启发式方法或优化算法。
* @todo 优化排列算法 (如果能想到更好的排列算法的话)
*/
class _yx_flow_layout_whole {
/**
* 当前这块内容的起始位置
*/
offset: number
/**
* flow layout 部分参数
*/
verticalSpacing: number
horizontalSpacing: number
sectionInset: YXEdgeInsets
/**
* 容器相关的参数
*/
containerWidth: number
containerHeight: number
/**
* 这块内容区域目前已经摆放的节点
*/
attrs: YXLayoutAttributes[] = []
/**
* 检查传进来的位置是否跟当前这块内容的其他节点重叠
*/
intersects(rect: math.Rect) {
for (let index = 0; index < this.attrs.length; index++) {
const element = this.attrs[index];
math.Rect.intersection(_rectOut, element.frame, rect)
if (_rectOut.width > 0 && _rectOut.height > 0) {
return true
}
}
return false
}
/**
* 垂直方向列表的节点的排列规则
*/
layout_vertical_item(indexPath: YXIndexPath, itemSize: math.Size) {
if (this.attrs.length <= 0) {
let attributes = new YXLayoutAttributes()
attributes.indexPath = indexPath
attributes.frame = new math.Rect(this.sectionInset.left, this.offset, itemSize.width, itemSize.height)
this.attrs.push(attributes)
this.containerHeight = Math.max(this.containerHeight, attributes.frame.height)
return attributes
}
const frame = new math.Rect()
frame.size = itemSize
// 检查所有节点的右边
for (let index = 0; index < this.attrs.length; index++) {
const element = this.attrs[index];
frame.x = element.frame.xMax + this.horizontalSpacing
frame.y = element.frame.y
if (frame.xMax <= (this.containerWidth - this.sectionInset.right)) {
if (this.intersects(frame) == false) {
let attributes = new YXLayoutAttributes()
attributes.indexPath = indexPath
attributes.frame = frame
this.attrs.push(attributes)
// this.containerHeight = Math.max(this.containerHeight, attributes.frame.height)
this.containerHeight = Math.max(this.containerHeight, attributes.frame.yMax - this.offset)
return attributes
}
}
}
// 检测所有节点的下边
for (let index = 0; index < this.attrs.length; index++) {
const element = this.attrs[index];
frame.x = element.frame.x
frame.y = element.frame.yMax + this.verticalSpacing
const check_xMax = frame.xMax <= (this.containerWidth - this.sectionInset.right)
const check_yMax = frame.yMax <= (this.offset + this.containerHeight)
if (check_xMax && check_yMax) {
if (this.intersects(frame) == false) {
let attributes = new YXLayoutAttributes()
attributes.indexPath = indexPath
attributes.frame = frame
this.attrs.push(attributes)
this.containerHeight = Math.max(this.containerHeight, attributes.frame.height)
return attributes
}
}
}
// 走到这里表示这块内容区域已经摆不下了,需要换行处理
return null
}
/**
* 水平方向列表的节点的排列规则
*/
layout_horizontal_item(indexPath: YXIndexPath, itemSize: math.Size) {
if (this.attrs.length <= 0) {
let attributes = new YXLayoutAttributes()
attributes.indexPath = indexPath
attributes.frame = new math.Rect(this.offset, this.sectionInset.top, itemSize.width, itemSize.height)
this.attrs.push(attributes)
this.containerWidth = Math.max(this.containerWidth, attributes.frame.width)
return attributes
}
const frame = new math.Rect()
frame.size = itemSize
// 检测所有节点的下边
for (let index = 0; index < this.attrs.length; index++) {
const element = this.attrs[index];
frame.x = element.frame.x
frame.y = element.frame.yMax + this.verticalSpacing
if (frame.yMax <= (this.containerHeight - this.sectionInset.bottom)) {
if (this.intersects(frame) == false) {
let attributes = new YXLayoutAttributes()
attributes.indexPath = indexPath
attributes.frame = frame
this.attrs.push(attributes)
// this.containerWidth = Math.max(this.containerWidth, attributes.frame.width)
this.containerWidth = Math.max(this.containerWidth, attributes.frame.xMax - this.offset)
return attributes
}
}
}
// 检查所有节点的右边
for (let index = 0; index < this.attrs.length; index++) {
const element = this.attrs[index];
frame.x = element.frame.xMax + this.horizontalSpacing
frame.y = element.frame.y
const check_xMax = frame.xMax <= (this.offset + this.containerWidth)
const check_yMax = frame.yMax <= (this.containerHeight - this.sectionInset.bottom)
if (check_xMax && check_yMax) {
if (this.intersects(frame) == false) {
let attributes = new YXLayoutAttributes()
attributes.indexPath = indexPath
attributes.frame = frame
this.attrs.push(attributes)
this.containerWidth = Math.max(this.containerWidth, attributes.frame.width)
return attributes
}
}
}
// 走到这里表示这块内容区域已经摆不下了
return null
}
}
\ No newline at end of file
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "9f598932-bd6f-4ad7-8ee7-b073d3689bb7",
"files": [],
"subMetas": {},
"userData": {}
}
{"ver":"4.0.24","importer":"typescript","imported":true,"uuid":"9e43992e-f80d-44fd-953f-230a4d2b8162","files":[],"subMetas":{},"userData":{}}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment