BaseArray

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;
}