BaseArray
这类节点拥有数组的结构,意味着可以用一个索引去顺次获取其内部的每个元素。
数组内的每个元素被抽象为一个个 element。
这是一个抽象类,不应该被实例化。
API 列表
class BaseArray extends SD2DNode {
// 索引管理
start(): number;
start(start: number): this;
end(): number;
length(): number;
length(length: number): this;
resize(length: number): this;
idx(id: number): number;
indexOf(element: SDNode): number;
// 元素访问管理
element(id: number): SDNode | undefined;
elements(): Array<SDNode>;
lastElement(): SDNode | undefined;
firstElement(): SDNode | undefined;
forEachElement(callback: (element: SDNode, id: number) => void): this;
// 元素属性管理
opacity(id: number): number;
opacity(id: number, opacity: number): this;
color(color: SDColor): this;
color(id: number): PacketColor;
color(id: number, color: SDColor): this;
color(l: number, r: number, color: SDColor): this;
text(id: number): string;
text(id: number, text: string): this;
intValue(id: number): number;
value(id: number): SDNode;
value(id: number, value: SDNode): this;
// 插入管理
insert(id: number, value: any): this;
insertFromExistValue(id: number, value: SDNode): this;
insertFromExistElement(id: number, element: SDNode): this;
push(value: any): this;
pushFromExistValue(value: SDNode): this;
pushFromExistElement(element: SDNode): this;
pushArray(array: Array<any>): this;
// 删除管理
pop(): this;
erase(id: number): this;
dropElement(id: number): SDNode | undefined;
dropFirstElement(): SDNode | undefined;
dropLastElement(): SDNode | undefined;
dropValue(id: number): SDNode | undefined;
dropFirstValue(): SDNode | undefined;
dropLastValue(): SDNode | undefined;
// 其他
sort(comparator?: (a: SDNode, b: SDNode) => number): this;
sort(l: number, r: number, comparator?: (a: SDNode, b: SDNode) => number): this;
}
索引管理
class BaseArray {
start(): number; // 获取起始索引
start(start: number): this; // 设置起始索引
end(): number; // 获取结束索引
length(): number; // 获取数组长度
length(length: number): this; // 设置数组长度,可能发生数组的插入或删除
resize(length: number): this; // 设置数组长度,可能发生数组的插入或删除
idx(id: number): number; // 将逻辑索引转化为物理索引,对用户而言用不到
indexOf(element: SDNode): number; // 获取数组某个元素的索引
}
start & end & length
class BaseArray {
start(): number; // 获取起始索引
start(start: number): this; // 设置起始索引
end(): number; // 获取结束索引
length(): number; // 获取数组长度
}
数组的起始索引和长度是可以调节的,这两个确定后数组的结束索引也就确定了,它们满足关系: $$ end=start+length-1 $$
大部分继承于
sd.BaseArray的节点,默认的起始索引都是 0。
length & resize
class BaseArray {
length(length: number): this; // 设置数组长度,可能发生数组的插入或删除
resize(length: number): this; // 设置数组长度,可能发生数组的插入或删除
}
可以用这两个方法去设置一个数组的长度。当前数组长度比设置长度更短的时候,会在数组的末尾添加新的 value 为 null 的元素。当前数组长度比设置长度更长的时候,会删除数组末尾的多余元素。
const svg = sd.svg();
const arr1 = new sd.Array(svg).pushArray("abcdefg");
const arr2 = new sd.Array(svg).y(50).pushArray("123");
const arr3 = new sd.Array(svg).y(100).pushArray("");
sd.main(async () => {
await sd.pause();
arr1.startAnimate().length(5).endAnimate();
arr2.startAnimate().length(5).endAnimate();
arr3.startAnimate().resize(5).endAnimate(); // resize 和 length 在此处的作用是一样的
});
indexOf
class BaseArray {
indexOf(element: SDNode): number;
}
获取某个元素对应的索引。
console.assert(arr.indexOf(arr.element(1)) === 1); // pass
console.assert(arr.indexOf(arr.element(2)) === 2); // pass
如果该元素不存在于数组中,则会返回 -1。此行为与 JS 中数组的行为是一致的。
不建议把数组的起始索引设置为负数,因为这样就不能区分没找到元素和找到的目标元素索引为-1 这两种情况了。
元素访问管理
class BaseArray {
element(id: number): SDNode | undefined; // 根据索引获取元素
elements(): Array<SDNode>; // 获取数组的所有元素
lastElement(): SDNode | undefined; // 获取数组的最后一个元素
firstElement(): SDNode | undefined; // 获取数组的第一个元素
forEachElement(callback: (element: SDNode, id: number) => void): this; // 遍历数组每一个元素
}
元素属性管理
class BaseArray {
opacity(id: number): number; // 获取目标元素的透明度
opacity(id: number, opacity: number): this; // 设置目标元素的透明度
color(color: SDColor): this; // 设置所有元素的颜色
color(id: number): PacketColor; // 获取目标元素的颜色
color(id: number, color: SDColor): this; // 设置目标元素的颜色
color(l: number, r: number, color: SDColor): this; // 设置范围内元素的颜色
text(id: number): string; // 获取目标元素的文本
text(id: number, text: string): this; // 设置目标元素的文本
intValue(id: number): number; // 将目标元素解析为整数
value(id: number): SDNode; // 获取目标元素的价值物
value(id: number, value: SDNode): this; // 设置目标元素的价值物
}
opacity
class BaseArray {
opacity(): number; // 继承自 SD2DNode
opacity(opacity: number): this; // 继承自 SD2DNode
opacity(id: number): number; // 获取目标元素的透明度
opacity(id: number, opacity: number): this; // 设置目标元素的透明度
}
管理 element 的透明度。
值得一提的是,以下两个方法在部分参数传递时有歧义:
class BaseArray { opacity(opacity: number): this; // 继承自 SD2DNode opacity(id: number): number; // 获取目标元素的透明度 }设想以下场景:
arr.opacity(0); // 到底是获取 0 号元素的透明度,还是把数组的透明度设置为 0? arr.opacity(1); // 到底是获取 1 号元素的透明度,还是把数组的透明度设置为 1?为此做出约定,继承方法优先级高于特化方法。当传入的第一个参数位于 $[0,1]$ 时,认为在调用继承过来的 opacity 方法,否则认为是在调用自身特化的 opacity 方法。对于上述场景,如欲获取目标元素的透明度,可以更改代码如下:
arr.element(0).opacity(); // 获取 0 号元素的透明度 arr.element(1).opacity(); // 获取 1 号元素的透明度
color
class BaseArray {
color(color: SDColor): this; // 设置每一个元素的颜色
color(id: number): PacketColor; // 获取目标元素的颜色
color(id: number, color: SDColor): this; // 设置目标元素的颜色
color(l: number, r: number, color: SDColor): this; // 设置区间内元素的颜色
}
管理 element 的颜色。注意 element 和 value 的颜色是互不干扰的。例如:
const svg = sd.svg();
const C = sd.color();
const arr = new sd.Array(svg).pushArray("abc");
sd.main(async () => {
await sd.pause();
arr.startAnimate().color(0, C.yellow).color(1, C.blue).endAnimate(); // 只会对目标元素的背景染色,不会对其中的文字 a/b 染色
});
text
class BaseArray {
text(id: number): string; // 获取目标元素的文本
text(id: number, text: string): this; // 设置目标元素的文本
}
管理 element 的文本。当 element 本身就是文本元素,或者 element 的 value 是文本元素时有效。
const svg = sd.svg();
const arr = new sd.Array(svg).push("A").push(new sd.Circle(svg));
console.log(arr.text(0)); // "A"
// console.log(arr.text(1)); // this is an invalid invoke
sd.main(async () => {
await sd.pause();
arr.text(0, "B");
});
intValue
class BaseArray {
intValue(id: number): number; // 将目标元素解析为整数
}
将 element 对应的文本解析为整数类型,并返回。如果 value 不存在,或者对应文本是空串,则会解析为整数 0。在解析其他非数值文本的情况下会抛出错误。
const svg = new sd.svg();
const arr = new sd.Array(svg).push("1").push("2.8").push().push("A");
console.log(arr.intValue(0)); // 1
console.log(arr.intValue(1)); // 2
console.log(arr.intValue(2)); // 0
// console.log(arr.intValue(3)); // this is an invalid invoke
value
class BaseArray {
value(id: number): SDNode; // 获取目标元素的价值物
value(id: number, value: SDNode): this; // 设置目标元素的价值物
}
管理 element 的 value。
插入管理
class BaseArray {
insert(id: number, value: any): this;
insertFromExistValue(id: number, value: SDNode): this;
insertFromExistElement(id: number, element: SDNode): this;
push(value: any): this;
pushFromExistValue(value: SDNode): this;
pushFromExistElement(element: SDNode): this;
pushArray(array: Array<any>): this;
}
insert & push
insert 类型的插入可以指定插入的位置,push 类型的插入则是插入到数组的最末尾。不妨以 push 类型的插入为例,演示三种插入的区别:
const svg = sd.svg();
const arr = new sd.Array(svg).resize(3);
const value1 = new sd.Circle(svg).cx(arr.element(0).cx()).cy(100);
const value2 = new sd.Text(svg, "A").cx(arr.element(1).cx()).cy(100);
const value3 = new sd.Box(svg, "B").cx(arr.element(2).cx()).cy(100);
sd.main(async () => {
await sd.pause();
arr.startAnimate().push(value1).endAnimate();
await sd.pause();
arr.startAnimate().pushFromExistValue(value2).endAnimate();
await sd.pause();
arr.startAnimate().pushFromExistElement(value3).endAnimate();
});
pushArray
使得数组的初始化更简单:
const svg = sd.svg();
const arr1 = new sd.Array(svg).pushArray([1, 2, 3, 4, 5]);
const arr2 = new sd.Array(svg).y(60).pushArray("ABC");
删除管理
用来删掉数组中的元素。
erase & pop
class BaseArray {
erase(id: number): this; // 删除目标元素
pop(): this; // 删除最后一个元素
}
顾名思义,把某个 element 从数组中删除。默认情况下删除 element 会导致其永远地离开场景。
const svg = sd.svg();
const arr1 = new sd.Array(svg).pushArray("1234").start(1);
const arr2 = new sd.Array(svg).pushArray("1234").start(1).y(60);
sd.main(async () => {
await sd.pause();
arr1.startAnimate().erase(1).erase(2).endAnimate(); // 先删除 element[1] 后,原本的 element[3] 变成了现在的 element[2]
arr2.startAnimate().erase(3).erase(1).endAnimate(); // 先删除 element[3] 在删除 element[1]
});
值得注意的是,删除元素的顺序会影响动画的观感。上例中分别从两个数组内,以不同的顺序删除 element[1] 和 element[3],可以对比它们的动画效果。先删 element[1] 再删 element[3],会导致 element[3] 额外产生一段向前移动的动画。这是因为在 element[1] 被删掉而 element[3] 还没有被删掉的那个短暂瞬间,布局系统会把 element[3] 向前移动。
drop
class BaseArray {
dropElement(id: number): SDNode | undefined; // 丢弃目标元素
dropFirstElement(): SDNode | undefined; // 丢弃第一个元素
dropLastElement(): SDNode | undefined; // 丢弃最后一个元素
dropValue(id: number): SDNode | undefined; // 丢弃目标元素的价值物
dropFirstValue(): SDNode | undefined; // 丢弃第一个元素的价值物
dropLastValue(): SDNode | undefined; // 丢弃最后一个元素的价值物
}
前文提到,使用 erase 一类的方法去删除某个 element,会导致 element(及其内部的 value,如果有)永远地离开场景。但部分情况下,我们可能希望被删除的 element 或者 value 只是被数组丢弃了,它仍然存在于场景之中。有两种达成这个目的的解决方案,最直接的便是 drop 方法。下面的代码以交换数组中两个元素为例进行演示:
const svg = sd.svg();
const EN = sd.enter();
const arr = new sd.Array(svg).pushArray("123456789").start(1);
function swapByValue(a, b) {
arr.startAnimate();
const va = arr.dropValue(a);
const vb = arr.dropValue(b);
arr.element(b).valueFromExist(va);
arr.element(a).valueFromExist(vb);
arr.endAnimate();
}
function swapByElement(a, b) {
// 保证 a < b
arr.startAnimate();
const ea = arr.dropElement(a);
const eb = arr.dropElement(b - 1); // 因为 a 的移除,b 往前移动了
arr.insertFromExistElement(a, eb);
arr.insertFromExistElement(b, ea);
arr.endAnimate();
}
sd.main(async () => {
await sd.pause();
swapByValue(3, 4);
await sd.pause();
swapByElement(7, 8);
});
试一试,dropValue 和 dropElement 的区别。
其他
class BaseArray {
// 其他
sort(comparator?: (a: SDNode, b: SDNode) => number): this;
sort(l: number, r: number, comparator?: (a: SDNode, b: SDNode) => number): this;
}