模板工程升级修改
This commit is contained in:
@@ -84,7 +84,21 @@
|
||||
* scrollSensitivity: 5, // 移动5px以上才触发滚动
|
||||
* bounceSpeed: 1 // 回弹速度
|
||||
* });
|
||||
*
|
||||
*
|
||||
* 【横向列表(左右滑动)】
|
||||
* var themeList = new DynamicSpriteList({
|
||||
* containerId: sprites.LIST_CONTAINER,
|
||||
* clipArea: { x: 340, y: 240, width: 600, height: 160 },
|
||||
* orientation: 'horizontal', // 默认 'vertical'
|
||||
* itemWidth: 262, // 主轴步长(项宽 + 项间距),与纵向 rowHeight 对称
|
||||
* renderRow: function (ctx) {
|
||||
* // 横向下 ctx.addSprite 的 x 会叠加该项的主轴基准,y 原样
|
||||
* var sid = ctx.addSprite('preview', TPL_ID, 0, 0);
|
||||
* }
|
||||
* });
|
||||
* // 事件转发:横向传 offmovex(纵向传 offmovey)
|
||||
* themeList.handleMouseMove(event.spriteId, event.offset.x);
|
||||
*
|
||||
* 【设置数据(触发渲染)】
|
||||
* // 数据数组中每个元素对应一行
|
||||
* // setData 会清除旧行、重新创建新行
|
||||
@@ -176,6 +190,26 @@
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
/**
|
||||
* 主轴/交叉轴的引擎操作码与 clipArea 字段映射
|
||||
* 前缀 DSL_ 是必要的:友乐运行时所有 <script> 共享同一全局作用域,
|
||||
* 通用名(如 LIST_AXIS)会与其它文件互相覆盖。
|
||||
* 操作码:18=左右移动 19=上下移动 20=设宽 21=设高
|
||||
* (docs/important/client/精灵操作详细指南.md:38-41)
|
||||
*/
|
||||
var DSL_LIST_AXIS = {
|
||||
vertical: {
|
||||
posOp: 19, sizeOp: 21, crossPosOp: 18, crossSizeOp: 20,
|
||||
clipPos: 'y', clipSize: 'height', crossClipPos: 'x', crossClipSize: 'width',
|
||||
stepField: 'rowHeight'
|
||||
},
|
||||
horizontal: {
|
||||
posOp: 18, sizeOp: 20, crossPosOp: 19, crossSizeOp: 21,
|
||||
clipPos: 'x', clipSize: 'width', crossClipPos: 'y', crossClipSize: 'height',
|
||||
stepField: 'itemWidth'
|
||||
}
|
||||
};
|
||||
|
||||
function DynamicSpriteList(config) {
|
||||
// ========================================================================
|
||||
// 参数验证
|
||||
@@ -189,8 +223,11 @@ function DynamicSpriteList(config) {
|
||||
if (!config.clipArea) {
|
||||
throw new Error('DynamicSpriteList: clipArea is required');
|
||||
}
|
||||
if (typeof config.rowHeight !== 'number' || config.rowHeight <= 0) {
|
||||
throw new Error('DynamicSpriteList: rowHeight must be a positive number');
|
||||
var orientation = (config.orientation === 'horizontal') ? 'horizontal' : 'vertical';
|
||||
var axis = DSL_LIST_AXIS[orientation];
|
||||
var step = config[axis.stepField];
|
||||
if (typeof step !== 'number' || step <= 0) {
|
||||
throw new Error('DynamicSpriteList: ' + axis.stepField + ' must be a positive number');
|
||||
}
|
||||
var hasTemplates = config.templates && typeof config.templates === 'object';
|
||||
var hasRenderRow = typeof config.renderRow === 'function';
|
||||
@@ -220,11 +257,31 @@ function DynamicSpriteList(config) {
|
||||
};
|
||||
|
||||
/**
|
||||
* 每行高度
|
||||
* 排列方向:'vertical'(默认) | 'horizontal'
|
||||
* @type {string}
|
||||
*/
|
||||
this.orientation = orientation;
|
||||
|
||||
/**
|
||||
* 轴映射(操作码 + clipArea 取用字段),内部所有方向相关操作一律经此表
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
this._axis = axis;
|
||||
|
||||
/**
|
||||
* 主轴步长:纵向=行高(rowHeight),横向=项宽含间距(itemWidth)
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this._step = step;
|
||||
|
||||
/**
|
||||
* 每行高度(纵向语义保留字段;横向下等于主轴步长)
|
||||
* @type {number}
|
||||
*/
|
||||
this.rowHeight = config.rowHeight;
|
||||
|
||||
this.rowHeight = step;
|
||||
|
||||
/**
|
||||
* 模板精灵配置
|
||||
* @type {Object}
|
||||
@@ -431,31 +488,31 @@ DynamicSpriteList.prototype.destroy = function() {
|
||||
/**
|
||||
* 滚动到指定位置
|
||||
*
|
||||
* @param {number} y - 目标Y坐标
|
||||
* @param {number} offset - 目标主轴偏移量(纵向=Y方向,横向=X方向)
|
||||
* @param {boolean} [animated=true] - 是否使用动画
|
||||
* @returns {DynamicSpriteList} 返回自身,支持链式调用
|
||||
*/
|
||||
DynamicSpriteList.prototype.scrollTo = function(y, animated) {
|
||||
var currentY = get_self(this.containerId, 19, 0, 0, 0);
|
||||
var targetY = this.clipArea.y - y;
|
||||
|
||||
// 边界限制
|
||||
var contentHeight = this._data.length * this.rowHeight;
|
||||
var minY = this.clipArea.y + this.clipArea.height - contentHeight;
|
||||
if (targetY < minY) {
|
||||
targetY = minY;
|
||||
}
|
||||
if (targetY > this.clipArea.y) {
|
||||
targetY = this.clipArea.y;
|
||||
}
|
||||
|
||||
DynamicSpriteList.prototype.scrollTo = function(offset, animated) {
|
||||
var axis = this._axis;
|
||||
var clipStart = this.clipArea[axis.clipPos];
|
||||
var clipSize = this.clipArea[axis.clipSize];
|
||||
var current = get_self(this.containerId, axis.posOp, 0, 0, 0);
|
||||
var target = clipStart - offset;
|
||||
|
||||
// 边界钳制(双向):先钳末端,再钳起点。
|
||||
// 内容不足一屏时 min > clipStart,两条钳制叠加后恒为起点——这正是期望行为。
|
||||
var contentSize = this._data.length * this._step;
|
||||
var min = clipStart + clipSize - contentSize;
|
||||
if (target < min) { target = min; }
|
||||
if (target > clipStart) { target = clipStart; }
|
||||
|
||||
if (animated !== false) {
|
||||
play_ani(1, this.containerId, 19, currentY, targetY, 0,
|
||||
Math.abs(targetY - currentY), 0, 0, 0, this.bounceSpeed, 0, 0, 0);
|
||||
play_ani(1, this.containerId, axis.posOp, current, target, 0,
|
||||
Math.abs(target - current), 0, 0, 0, this.bounceSpeed, 0, 0, 0);
|
||||
} else {
|
||||
set_self(this.containerId, 19, targetY, 0, 0, 0);
|
||||
set_self(this.containerId, axis.posOp, target, 0, 0, 0);
|
||||
}
|
||||
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
@@ -476,8 +533,8 @@ DynamicSpriteList.prototype.scrollToTop = function(animated) {
|
||||
* @returns {DynamicSpriteList} 返回自身,支持链式调用
|
||||
*/
|
||||
DynamicSpriteList.prototype.scrollToBottom = function(animated) {
|
||||
var contentHeight = this._data.length * this.rowHeight;
|
||||
var maxScroll = contentHeight - this.clipArea.height;
|
||||
var contentSize = this._data.length * this._step;
|
||||
var maxScroll = contentSize - this.clipArea[this._axis.clipSize];
|
||||
if (maxScroll < 0) {
|
||||
maxScroll = 0;
|
||||
}
|
||||
@@ -492,7 +549,7 @@ DynamicSpriteList.prototype.scrollToBottom = function(animated) {
|
||||
* @returns {DynamicSpriteList} 返回自身,支持链式调用
|
||||
*/
|
||||
DynamicSpriteList.prototype.scrollToRow = function(rowIndex, animated) {
|
||||
var y = rowIndex * this.rowHeight;
|
||||
var y = rowIndex * this._step;
|
||||
return this.scrollTo(y, animated);
|
||||
};
|
||||
|
||||
@@ -531,36 +588,35 @@ DynamicSpriteList.prototype.handleMouseDown = function(spid, x, y) {
|
||||
* 需要在 utlmousemove 回调中调用
|
||||
*
|
||||
* @param {number} spid - 移动的精灵ID
|
||||
* @param {number} offsetY - Y方向移动偏移量
|
||||
* @param {number} offsetMain - 主轴方向移动偏移量(纵向传 Y 方向偏移,横向传 X 方向偏移)
|
||||
* @returns {boolean} 如果事件被处理返回 true
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* // 在 SpriteEventController 或 Game_Modify 中
|
||||
* // 在 SpriteEventController 或 Game_Modify 中(纵向列表传 offmovey,横向列表传 offmovex)
|
||||
* utlmousemove: function(gameid, spid, downx, downy, movex, movey, timelong, offmovex, offmovey) {
|
||||
* if (myList.handleMouseMove(spid, offmovey)) {
|
||||
* if (myList.handleMouseMove(spid, offmovey)) { // 横向列表改传 offmovex
|
||||
* return; // 事件已处理
|
||||
* }
|
||||
* // 其他处理...
|
||||
* }
|
||||
*/
|
||||
DynamicSpriteList.prototype.handleMouseMove = function(spid, offsetY) {
|
||||
DynamicSpriteList.prototype.handleMouseMove = function(spid, offsetMain) {
|
||||
if (!this.enableScroll) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (spid === this.containerId) {
|
||||
if (Math.abs(offsetY) > this.scrollSensitivity) {
|
||||
// 增量移动容器
|
||||
set_self(this.containerId, 19, offsetY, 1, 0, 0);
|
||||
if (Math.abs(offsetMain) > this.scrollSensitivity) {
|
||||
var axis = this._axis;
|
||||
// 增量移动容器(第4参 1 = 相对增量)
|
||||
set_self(this.containerId, axis.posOp, offsetMain, 1, 0, 0);
|
||||
this._isSliding = true;
|
||||
|
||||
// 触发滚动回调
|
||||
|
||||
if (typeof this.onScroll === 'function') {
|
||||
var currentY = get_self(this.containerId, 19, 0, 0, 0);
|
||||
var scrollY = this.clipArea.y - currentY;
|
||||
var contentHeight = this._data.length * this.rowHeight;
|
||||
var maxScrollY = contentHeight - this.clipArea.height;
|
||||
this.onScroll(scrollY, maxScrollY > 0 ? maxScrollY : 0);
|
||||
var current = get_self(this.containerId, axis.posOp, 0, 0, 0);
|
||||
var scrolled = this.clipArea[axis.clipPos] - current;
|
||||
var maxScroll = this._data.length * this._step - this.clipArea[axis.clipSize];
|
||||
this.onScroll(scrolled, maxScroll > 0 ? maxScroll : 0);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -661,29 +717,30 @@ DynamicSpriteList.prototype._addSpriteInternal = function (name, templateSpriteI
|
||||
* @private
|
||||
*/
|
||||
DynamicSpriteList.prototype._render = function() {
|
||||
set_self(this.containerId, 18, this.clipArea.x, 0, 0);
|
||||
set_self(this.containerId, 19, this.clipArea.y, 0, 0);
|
||||
var contentHeight = this._data.length * this.rowHeight;
|
||||
set_self(this.containerId, 21, contentHeight, 0, 0);
|
||||
var axis = this._axis;
|
||||
set_self(this.containerId, axis.posOp, this.clipArea[axis.clipPos], 0, 0);
|
||||
set_self(this.containerId, axis.crossPosOp, this.clipArea[axis.crossClipPos], 0, 0);
|
||||
var contentSize = this._data.length * this._step;
|
||||
set_self(this.containerId, axis.sizeOp, contentSize, 0, 0);
|
||||
|
||||
if (this.templates) {
|
||||
var tnames = Object.keys(this.templates);
|
||||
if (tnames.length > 0) {
|
||||
var w = get_self(this.templates[tnames[0]].spriteId, 20, 0, 0, 0);
|
||||
set_self(this.containerId, 20, w, 0, 0);
|
||||
// 交叉轴尺寸取首个模板的对应尺寸(纵向=宽 op20,横向=高 op21)
|
||||
var crossFromTpl = get_self(this.templates[tnames[0]].spriteId, axis.crossSizeOp, 0, 0, 0);
|
||||
set_self(this.containerId, axis.crossSizeOp, crossFromTpl, 0, 0);
|
||||
}
|
||||
} else {
|
||||
// renderRow 模式:容器宽度取裁剪区宽,保证整行可视宽都在容器 hit 区(点击/拖动检测)内。
|
||||
// 否则容器保持编辑器预置窄宽度,超出部分点击/拖动报的 spid≠containerId 而失效(对账 bak set_self(fSpid,20,bg宽))。
|
||||
set_self(this.containerId, 20, this.clipArea.width, 0, 0);
|
||||
// renderRow 模式:交叉轴尺寸取裁剪区对应边,保证整个可视区都在容器 hit 区(点击/拖动检测)内
|
||||
set_self(this.containerId, axis.crossSizeOp, this.clipArea[axis.crossClipSize], 0, 0);
|
||||
}
|
||||
|
||||
for (var rowIndex = 0; rowIndex < this._data.length; rowIndex++) {
|
||||
var rowData = this._data[rowIndex];
|
||||
var rowY = rowIndex * this.rowHeight;
|
||||
var rowBase = rowIndex * this._step;
|
||||
|
||||
if (this.renderRow) {
|
||||
this.renderRow(this._makeRowContext(rowIndex, rowData, rowY));
|
||||
this.renderRow(this._makeRowContext(rowIndex, rowData, rowBase));
|
||||
} else {
|
||||
var names = Object.keys(this.templates);
|
||||
for (var i = 0; i < names.length; i++) {
|
||||
@@ -691,7 +748,9 @@ DynamicSpriteList.prototype._render = function() {
|
||||
var t = this.templates[name];
|
||||
var ox = (t.offset && t.offset.x) || 0;
|
||||
var oy = (t.offset && t.offset.y) || 0;
|
||||
var sid = this._addSpriteInternal(name, t.spriteId, ox, rowY + oy, rowIndex, t.clickable !== false);
|
||||
var px = (this.orientation === 'horizontal') ? (rowBase + ox) : ox;
|
||||
var py = (this.orientation === 'horizontal') ? oy : (rowBase + oy);
|
||||
var sid = this._addSpriteInternal(name, t.spriteId, px, py, rowIndex, t.clickable !== false);
|
||||
if (t.textProperty && rowData[t.textProperty] !== undefined) {
|
||||
set_self(sid, 7, rowData[t.textProperty], 0, 0);
|
||||
}
|
||||
@@ -708,19 +767,27 @@ DynamicSpriteList.prototype._render = function() {
|
||||
* 创建行上下文对象(传给 renderRow 回调)
|
||||
* @param {number} rowIndex - 行索引
|
||||
* @param {Object} rowData - 行数据
|
||||
* @param {number} rowBaseY - 行基准 Y 坐标
|
||||
* @param {number} rowBase - 行基准坐标(纵向=Y,横向=X)
|
||||
* @returns {Object} 行上下文
|
||||
* @private
|
||||
*/
|
||||
DynamicSpriteList.prototype._makeRowContext = function (rowIndex, rowData, rowBaseY) {
|
||||
DynamicSpriteList.prototype._makeRowContext = function (rowIndex, rowData, rowBase) {
|
||||
var self = this;
|
||||
var isH = (this.orientation === 'horizontal');
|
||||
return {
|
||||
rowIndex: rowIndex,
|
||||
rowData: rowData,
|
||||
rowBaseY: rowBaseY,
|
||||
rowBase: rowBase,
|
||||
// 保留 rowBaseY:renderRow 回调的既有字段协议,不得破坏。
|
||||
// 当前生产消费方为 RecordView.js(唯一使用本组件的调用方);
|
||||
// 回归测试 DynamicSpriteList.test.js 对该字段有硬断言。
|
||||
rowBaseY: rowBase,
|
||||
orientation: this.orientation,
|
||||
containerId: this.containerId,
|
||||
addSprite: function (name, templateSpriteId, x, y) {
|
||||
return self._addSpriteInternal(name, templateSpriteId, x, rowBaseY + y, rowIndex, true);
|
||||
var px = isH ? (rowBase + x) : x;
|
||||
var py = isH ? y : (rowBase + y);
|
||||
return self._addSpriteInternal(name, templateSpriteId, px, py, rowIndex, true);
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -730,38 +797,39 @@ DynamicSpriteList.prototype._makeRowContext = function (rowIndex, rowData, rowBa
|
||||
* @private
|
||||
*/
|
||||
DynamicSpriteList.prototype._handleBounce = function() {
|
||||
var currentY = get_self(this.containerId, 19, 0, 0, 0);
|
||||
var contentHeight = get_self(this.containerId, 21, 0, 0, 0);
|
||||
var clipY = this.clipArea.y;
|
||||
var clipH = this.clipArea.height;
|
||||
|
||||
var targetY = currentY;
|
||||
var axis = this._axis;
|
||||
var current = get_self(this.containerId, axis.posOp, 0, 0, 0);
|
||||
var contentSize = get_self(this.containerId, axis.sizeOp, 0, 0, 0);
|
||||
var clipStart = this.clipArea[axis.clipPos];
|
||||
var clipSize = this.clipArea[axis.clipSize];
|
||||
|
||||
var target = current;
|
||||
var needBounce = false;
|
||||
|
||||
if (contentHeight <= clipH) {
|
||||
// 内容不足一屏,回弹到顶部
|
||||
if (currentY !== clipY) {
|
||||
targetY = clipY;
|
||||
|
||||
if (contentSize <= clipSize) {
|
||||
// 内容不足一屏,回弹到起点
|
||||
if (current !== clipStart) {
|
||||
target = clipStart;
|
||||
needBounce = true;
|
||||
}
|
||||
} else {
|
||||
if (currentY > clipY) {
|
||||
// 超出顶部
|
||||
targetY = clipY;
|
||||
if (current > clipStart) {
|
||||
// 越过起点
|
||||
target = clipStart;
|
||||
needBounce = true;
|
||||
} else {
|
||||
var minY = clipY + clipH - contentHeight;
|
||||
if (currentY < minY) {
|
||||
// 超出底部
|
||||
targetY = minY;
|
||||
var min = clipStart + clipSize - contentSize;
|
||||
if (current < min) {
|
||||
// 越过末端
|
||||
target = min;
|
||||
needBounce = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (needBounce) {
|
||||
play_ani(1, this.containerId, 19, currentY, targetY, 0,
|
||||
Math.abs(targetY - currentY), 0, 0, 0, this.bounceSpeed, 0, 0, 0);
|
||||
play_ani(1, this.containerId, axis.posOp, current, target, 0,
|
||||
Math.abs(target - current), 0, 0, 0, this.bounceSpeed, 0, 0, 0);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -772,6 +840,16 @@ DynamicSpriteList.prototype._handleBounce = function() {
|
||||
* @private
|
||||
*/
|
||||
DynamicSpriteList.prototype._handleClick = function(x, y) {
|
||||
// 裁剪区外的点击一律不算数。
|
||||
// 原因:引擎的 set_clip 只裁【绘制】、不裁【命中】,而 _render 会把容器的主轴尺寸
|
||||
// 撑到内容全长(远超可视区)。于是滑出可视区、屏幕上已经看不见的那些项,其矩形
|
||||
// 仍在容器内,ifast_check_add 照样判为命中 —— 用户点到列表旁的空白带,却选中了
|
||||
// 一个根本看不见的项。这里按 clipArea 兜住,横纵通用。
|
||||
var a = this.clipArea;
|
||||
if (x < a.x || x >= a.x + a.width || y < a.y || y >= a.y + a.height) {
|
||||
return;
|
||||
}
|
||||
|
||||
var clickedTag = ifast_check_add(this.containerId, x, y);
|
||||
if (clickedTag === -99999999) { return; }
|
||||
var rec = this._tagIndex[clickedTag];
|
||||
|
||||
Reference in New Issue
Block a user