스크롤 뷰(Scroll View)와 스크롤 바(Scroll Bar)
엔진에서 Node with Scroll View를 선택하면 위와 같이 기본 스크롤 뷰가 생성된다.
이 스크롤 뷰 안에는 스크롤 바가 포함돼있다.
움짤을 잘 보면 스크롤 바를 움직일 때 스크롤 바가 반대 방향으로 움직이는 것을 볼 수 있다.
그렇다. 스크롤 바를 드래그하는 것처럼 보이지만,
사실은 터치 이벤트는 스크롤 뷰가 받아서 스크롤 뷰가 움직이고 있는 것이고
연쇄적으로 스크롤 바의 위치도 업데이트가 되는 것이다.
그래서 스크롤 바가 반대 방향으로 움직이는 것처럼 보이는 것이다.
스크롤 뷰와 별개로 스크롤 바를 드래그해서 먼저 움직일 수는 없다.
정리하면
[ a. 스크롤 뷰 드래그 → b. 스크롤 뷰 움직임 → c. 스크롤 바 움직임 ]
이 행동은 디폴트 스크롤 뷰에서 지원을 해줘서 작동을 하지만
[ d. 스크롤 바 드래그 -> e. 스크롤 바 움직임 → f. 스크롤 뷰 움직임 ]
이 행동은 지원을 해주지 않는다.
d. 스크롤 바 드래그 단계부터 불가능하기 때문에 d와 e는 스크롤 바가 아닌 슬라이더로 대체해야 한다.
슬라이더는 스크롤 뷰에 연결된 스크롤 바와 다르게, 드래그해서 독립적으로 UI를 움직일 수 있다.
그러면 스크롤 뷰에 연결됐던 스크롤 바는 더 이상 필요 없어지게 된다.
이 스크롤 바를 없애게 되면 c. 단계를 지원받을 수 없기 때문에 직접 구현해야 하며
추가로 f. 단계까지 구현하면 된다.
const {ccclass, property, requireComponent, menu} = cc._decorator;
@ccclass
@requireComponent(cc.Slider)
@menu("UI/ScrollBarEnhance")
export class ScrollBarEnhance extends cc.Component {
@property(cc.ScrollView)
private scrollView: cc.ScrollView = null!;
@property(cc.Component.EventHandler)
private renderEvent: cc.Component.EventHandler[] = [];
private baseSlider: cc.Slider = null!;
private static creatEventHandler(target: cc.Node, component: string, handler: string) {
const e = new cc.Component.EventHandler();
e.target = target;
e.component = component;
e.handler = handler;
return e;
}
public refresh(p: number) {
let progress, slider = this.baseSlider || this.node.getComponent(cc.Slider);
progress = Math.min(p, 1);
progress = Math.max(p, 0);
slider.progress = progress;
this.onSliderEvent(slider, "");
}
protected start() {
this.baseSlider = this.node.getComponent(cc.Slider);
let className = cc.js.getClassName(this);
this.baseSlider.slideEvents.push(ScrollBarEnhance.creatEventHandler(this.node, className, 'o
this.scrollView.scrollEvents.push(ScrollBarEnhance.creatEventHandler(this.node, className, '
this.scrollMoveEvent();
}
private onScrollEvent(scroll: cc.ScrollView, eventType: cc.ScrollView.EventType, customEventData
switch (eventType) {
case cc.ScrollView.EventType.SCROLL_BEGAN:
break;
case cc.ScrollView.EventType.SCROLL_ENDED:
break;
case cc.ScrollView.EventType.SCROLLING:
this.scrollMoveEvent();
break;
}
}
private scrollMoveEvent() {
let vertical = this.scrollView.vertical, maxScrollOffset, getScrollOffset;
if (vertical) {
maxScrollOffset = this.scrollView.getMaxScrollOffset().y;
getScrollOffset = this.scrollView.getScrollOffset().y;
} else {
maxScrollOffset = this.scrollView.getMaxScrollOffset().x;
getScrollOffset = this.scrollView.getScrollOffset().x * (-1);
}
if (getScrollOffset / maxScrollOffset > 0 && getScrollOffset / maxScrollOffset < 1) {
this.baseSlider.progress = getScrollOffset / maxScrollOffset;
} else if (getScrollOffset / maxScrollOffset <= 0) {
this.baseSlider.progress = 0;
} else {
this.baseSlider.progress = 1;
}
if (this.renderEvent && this.renderEvent.length) {
cc.Component.EventHandler.emitEvents(this.renderEvent, this.baseSlider.progress);
}
}
private onSliderEvent(slider, customEventData: string) {
let vertical = this.scrollView.vertical, maxScrollOffsetX = 0, maxScrollOffsetY = 0;
if (vertical) {
maxScrollOffsetX = this.scrollView.getScrollOffset().x;
maxScrollOffsetY = this.scrollView.getMaxScrollOffset().y * slider.progress;
} else {
maxScrollOffsetX = this.scrollView.getMaxScrollOffset().x * slider.progress;
maxScrollOffsetY = this.scrollView.getScrollOffset().y;
}
this.scrollView.scrollToOffset(cc.v2(maxScrollOffsetX, maxScrollOffsetY), 0.05);
}
}
관련된 내용을 중국 코코스 커뮤니티에서 찾을 수 있었다.
위에서 얘기한 내용들을 구현한 진화형 스크롤 바라고 할 수 있는 ScrollBarEnhance 컴포넌트의 소스 코드는 위와 같다.
스크롤 바를 만들고, 같은 노드에 ScrollBarEnhance 컴포넌트를 추가해준다.
그 뒤에 스크롤 뷰를 연결해주면 제대로 작동할 것이다.
그리고 Progress가 0일 때 핸들이 맨 위, Progress가 1일 때 핸들이 맨 아래에 위치하게 하려고
스케일을 -1로 해서 슬라이더를 뒤집었다.