原生的UI实在是太丑了,而且对触屏、滑动等的支持也不太好,我们需要重新构建一个。
功能构想
我期望的UI主要有窗口和按钮构成,窗口至少要有普通窗口、可滚动窗口、可滚动列表窗口(列表item可交互)等几个类型。
对于滚动窗口,还需要有滚动条,以及滚动惯性(快速滚动后松开鼠标,减速滑动一段距离直到停止),滚动极限反馈(超过滚动极限时会“弹”回来),列表Item长按功能(比如显示详情)
原型
以上一些功能窗口,和按钮都有一个共同点,就是可以点击交互,点击还要包括长按和非长按,以及按住滚动等,为此可建一个类叫 Clickable
, 为了让它有 transform
,可以作为 child 添加给别人我们要继承 PIXI.Container
,先写一个简单的框架:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| class Clickable extends PIXI.Container { constructor(x = 0, y = 0, w = 0, h = 0) { super(); this.x = x; this.y = y;
this._width = w; this._height = h;
this._active = true; }
get width() {return this._width;} get w() {return this._width;} get height() {return this._height;} get h() {return this._height;}
get active() { return this._active;} Activate() { this._active = true;} Deactivate() { this._active = false;} }
|
在点击判断中,首先要判断点击是否在我们对象的区域内:
1 2 3 4 5 6
| IsMouseOver() { const pos = this.worldTransform.applyInverse(new Point(TouchInput.x, TouchInput.y)); return pos.x >= 0 && pos.x <= this.w && pos.y >= 0 && pos.y <= this.h; }
|
接写来需要处理点击事件, 按照RM的风格我们把这个放到 update函数中。判定点击需要考虑以下情况:
- 鼠标按下 -> OnPress
- 鼠标按下后立即原地释放 -> OnRelease & OnClick
- 鼠标按下一段时间后原地释放 -> OnRelease & OnLongPressRelease (长按后即使原地释放一般也不认为是点击,考虑到长按一般是弹出悬浮窗口显示详情,这是释放应该是窗口消失,直接触发点击并不适宜。)
- 鼠标按下后移动到区域内另一位置释放 -> OnRelease & OnLongPressRelease
- 鼠标按下后移动到区域之外 -> OnLongPressRelease (鼠标处于点击状态移动到区域之外,如果有长按悬浮窗这时也应该消失,所以也处理为长按释放)
整理之后代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| _pressed = false; _longPressed = false; _pressPoint = new Point(0, 0); _releasePoint = new Point(0, 0);
IsEnabled() { return this.visible && this._active; }
update() { if (!this.IsEnabled()) return;
for (const child of this.children.reverse.clone()) { if (child.update) { child.update(); } }
if (this._pressed) { if (this.IsMouseOver()) { if (!this._longPressed && TouchInput.isLongPressed()) { this._longPressed = true; this.OnLongPress(); } if(TouchInput.isReleased()) { if(this._longPressed) { this.OnLongPressRelease(true); } else if(TouchInput.isClicked()){ this.OnClick(); } this._pressed = false; this._longPressed = false; this.OnRelease(); } } else { if(this._longPressed) { this.OnLongPressRelease(false); } this._pressed = false; this._longPressed = false; this.OnRelease(); } } else if (TouchInput.isTriggered()&&this.IsMouseOver()) { this._pressed = true; this.OnPress(); } }
|
接下来是几个触发事件的函数,为了方便测试这里先用几个 console.log 好查看效果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| _handlers = {};
SetHandler(func, handle_func, ...args) { if(!this[func.name]) { throw new Error(`No method named ${func.name} found!`); } else { this._handlers[func.name] = handle_func.bind(this, ...args); } }
CallHandler(name) { if(this._handlers[name]) { this._handlers[name](); } } s OnPress() { this._pressPoint.set(TouchInput.x, TouchInput.y); console.log('press'); this.CallHandler(this.OnPress.name); }
OnRelease() { this._releasePoint.set(TouchInput.x, TouchInput.y); console.log('release'); this.CallHandler(this.OnRelease.name); }
OnClick() { console.log('click'); this.CallHandler(this.OnClick.name); }
OnLongPress() { console.log('long press start'); this.CallHandler(this.OnLongPress.name); }
OnLongPressRelease(isClick) { console.log(`long press release, this is${isClick?'':' not'} a click`); this.CallHandler(this.OnLongPressRelease.name); }
|