这一篇我们实现一个简易的ScrollView效果,先看一下效果图。
为什么要实现,我们在自己项目中,如果商店界面或者选择角色界面有需要居中显示,或者居中放大等效果的时候,使用自带的List就不能满足我们的要求了,这个时候自己实现一个是最好的。
由于基本都是代码,所以上代码是最舒服的。自己创建一个叫ScrollView的ts文件。由于自己只需要横向的,所以ScrollView只实现了横向的效果,如果需要竖直的,也可以自己定义一下。
/*
* 滚动视图
*/
class ScrollView extends Laya.Box implements Laya.IRender, Laya.IItem{
// 数据源
private _array: Array<any>;
// 单元格渲染处理器
private _renderHandler: Laya.Handler;
// 单元格鼠标事件处理器
private _mouseHandler: Laya.Handler;
// 改变List的选择项时执行的处理器
private _selectHandler: Laya.Handler;
// 是否已经渲染过单元格
private _hadRender: boolean = false;
private _hadInit: boolean = false;
private _hadInitItem: boolean = false;
// 抬起鼠标是否继续滑动
private _isSensitive: boolean = true;
// 单元格宽
private _cellWidth: number;
// 单元格高
private _cellHeight: number;
// 左边距
private _leftAlign: number = 0;
// 右边距
private _rightAlign: number = 0;
// 单元格集合
private _cells: Array<any>;
private _itemRender: any;
/**
* 两个单元格之间的间隔
*/
public space: number = 0;
constructor(){
super();
this.mouseEnabled = true;
this.on(Laya.Event.MOUSE_DOWN, this, this.mouseDown);
this.on(Laya.Event.MOUSE_UP, this, this.mouseUp, [Laya.Event.MOUSE_UP]);
this.on(Laya.Event.MOUSE_MOVE, this, this.mouseMove);
this.on(Laya.Event.MOUSE_OUT, this, this.mouseUp, [Laya.Event.MOUSE_OUT]);
}
/**
* 设置数据源
*/
public set array(arr: Array<any>) {
this._array = arr;
this.init();
}
/**
* 获取数据源
*/
public get array(): Array<any> {
return this._array;
}
/**
* 单元格渲染器。
* <p><b>取值:</b>
* <ol>
* <li>单元格类对象。</li>
* <li> UI 的 JSON 描述。</li>
* </ol></p>
*/
public set itemRender(itemRender: any) {
this._itemRender = itemRender;
this.init();
}
/**
* 设置单元格渲染处理器,返回(cell:any, index:number)
*/
public set renderHandler(hander: Laya.Handler) {
this._renderHandler = hander;
this.init();
}
/**
* 单元格鼠标事件处理器,返回(e:event,index:number)
*/
public set mouseHandler(hander: Laya.Handler) {
this._mouseHandler = hander;
}
/**
* 改变List的选择项时执行的处理器
*/
public set selectHandler(hander: Laya.Handler) {
this._selectHandler = hander;
}
/**
* 设置单元格的宽
*/
public set cellWidth(cellWidth: number) {
this._cellWidth = cellWidth;
}
/**
* 获取单元格的宽
*/
public get cellWidth(): number {
return this._cellWidth;
}
/**
* 设置单元格的高
*/
public set cellHeight(cellHeight: number) {
this._cellHeight = cellHeight;
}
/**
* 获取单元格的高
*/
public get cellHeight(): number {
return this._cellHeight;
}
/**
* 左边界
*/
public set leftAlign(leftAlign: number) {
this._leftAlign = leftAlign;
}
public get leftAlign(): number {
return this._leftAlign;
}
/**
* 右边界
*/
public set rightAlign(rightAlign: number) {
this._rightAlign = rightAlign;
}
public get rightAlign() {
return this._rightAlign;
}
public addItem(): void {
}
/**
* 通过索引获取对应的单元格
* @param index
*/
public getItemByIndex(index: number): any {
return this._cells[index];
}
/**
* 根据单元格获取单元格的位置
* @param cell
*/
public getItemIndex(cell: any): number {
for (var i = 0; i < this._cells.length; i++) {
if (cell == this._cells[i]) {
return i;
}
}
return -1;
}
/**
* 初始化ScrollView渲染,数据
*/
private init(): void {
if (!this._hadInit) {
// 初始化单元格
this.initItems();
// 初始化渲染
this.initRender();
if (this._hadInitItem && this._hadRender) {
this._hadInit = true;
}
}
}
/**
* 单元格响应事件
*/
private onCellEvent(event:Event, cell: any) {
var index = this.getItemIndex(cell);
if (index == -1) {
return;
}
if (this._selectHandler) {
this._selectHandler.runWith([event, index]);
}
}
/**
* 初始化所有的Item
*/
public initItems(): void {
if (!this._hadInitItem && this._itemRender != null && this._array != null && this._array.length > 0) {
this._cells = new Array<any>()
for (var i = 0; i < this._array.length; i++) {
let item = new this._itemRender(this._cellWidth, this._cellHeight);
this._cells.push(item);
this.addChild(item);
}
this._hadInitItem = true;
this.refreshCellsPos();
}
}
/**
* 所有单元格执行渲染
*/
private initRender(): void {
if (!this._hadRender && this._renderHandler != null && this._array != null && this._array.length > 0) {
for (var i = 0; i < this._array.length; i++) {
this._renderHandler.runWith([this._cells[i], i]);
}
this._hadRender = true;
}
}
/**
* 单个单元格执行渲染
*/
private doSingleRender(index: number): void {
if (!this._hadRender) {
this.initRender();
return;
}
if (this._renderHandler != null) {
this._renderHandler.runWith([this._cells[index], index]);
}
}
/**
* 刷新ScrollView下Cell的位置
*/
private refreshCellsPos() {
var cellCount = this._cells.length;
for (var i = 0; i < cellCount; i++) {
let cell: Laya.Box = this._cells[i] as Laya.Box;
let posX: number = this.getCellPosByIndex(i);
cell.pos(posX, this.height / 2);
}
this.width = this._leftAlign + cellCount * this._cellWidth + (cellCount - 1) * this.space + this._rightAlign;
}
/**
* 根据单元格索引获取单元格位置
* @param index
*/
public getCellPosByIndex(index: number):number {
return this._leftAlign + (index + 0.5) * this._cellWidth + index * this.space;
}
// ----------------------- mouse event start ------------------------
private mouseDown() {
if (this._mouseHandler != null) {
var e: Event = new Event(Laya.Event.MOUSE_DOWN);
this._mouseHandler.runWith([e]);
}
}
/**
* 鼠标离开屏幕
*/
private mouseUp(event: string) {
if (this._mouseHandler != null) {
var e: Event = new Event(Laya.Event.MOUSE_UP);
this._mouseHandler.runWith([e]);
}
}
/**
* 鼠标移动
*/
private mouseMove() {
if (this._mouseHandler != null) {
var e: Event = new Event(Laya.Event.MOUSE_MOVE);
this._mouseHandler.runWith([e]);
}
}
// ----------------------- mouse event end ------------------------
}
使用示例:
import ColorFilter = Laya.ColorFilter;
import Box = laya.ui.Box;
/**
* 角色信息
*/
class RoleInfo {
name:string;
cost:number;
skin:string;
constructor(name:string, cost:number, skin:string){
this.name = name;
this.cost = cost;
this.skin = skin;
}
}
/**
* 商店
*/
class TMShopUI extends ui.gameUI.StoreUI implements TMBaseUI {
private roleInfos: Array<RoleInfo>;
private _mouseX: number = 0;
private scrollView: ScrollView;
private coinNumLab: Laya.Label;
private priceLab: Laya.Label;
private buyBtn: Laya.Button;
private gameStartImg: Laya.Image;
private coinImg: Laya.Image;
// 商店角色图缩放大小。
private itemMaxScale: number = 0.7;
private itemMinScale: number = 0.4;
// ScrollView操作
// 鼠标按下
private _mouseDown: boolean = false;
// 鼠标移动速度
private _mouseSpeed: number = 0;
private _mouseStartPosX: number = 0;
private _curMoveFrame: number = 0;
private forkMoveSpeed: number = 1;
private forkGroup: Array<Laya.Sprite> = new Array<Laya.Sprite>();
// 商店界面按钮
shopBtns: Array<Laya.Button> = [
this.backBtn
];
constructor()
{
super();
this.init();
Laya.timer.frameLoop(1, this, this.onUpdate);
this.roleInfos = TMParsingThythm.ParsingRole();
this.setScrollView();
}
/**
* 初始化商店街面引用
*/
private init() {
this.coinNumLab = this.coinBox.getChildByName("coinNumLab") as Laya.Label;
this.priceLab = this.purchaseBox.getChildByName("priceLab") as Laya.Label;
this.buyBtn = this.purchaseBox.getChildByName("buyBtn") as Laya.Button;
this.gameStartImg = this.purchaseBox.getChildByName("gameStartImg") as Laya.Image;
this.coinImg = this.purchaseBox.getChildByName("coinImg") as Laya.Image;
this.shopBtns.push(this.buyBtn);
for (var button of this.shopBtns) {
let buttons: Array<string> = [button.name]
button.clickHandler = new Laya.Handler(this, this.addButtonEvent, buttons);
this.addMouseOverEvent(button);
this.addMouseOutEvent(button);
}
}
private onUpdate() {
if (!this.visible) {
return;
}
if (!this._mouseDown && this._mouseSpeed != 0) {
var direction = Math.abs(this._mouseSpeed) / this._mouseSpeed;
var absSpeed = Math.sqrt(Math.abs(this._mouseSpeed));
var moveDis = this._mouseSpeed;
this.updateScrollViewPos(moveDis);
this.updateScale();
absSpeed = absSpeed - 0.3;
if (absSpeed < 1) {
absSpeed = 0;
this._mouseSpeed = 0;
// 居中显示
this.centeringControl();
} else {
this._mouseSpeed = absSpeed * absSpeed * direction;
}
}
}
/**
* 添加按钮点击事件
* @param buttons
*/
private addButtonEvent(name: string) {
if (name == "backBtn") {
// 响应返回按钮点击事件
gameUIInstance.showUI(UIType.mainMenu);
} else if (name == "buyBtn") {
// 购买按钮,如果不需要购买,当前按钮是开始游戏
var centerIndex = this.getScreenCenterCellIndex();
if (this.getHadRole(centerIndex)) {
// 开始游戏
gameInstance.gameStart();
gameUIInstance.gameReStart();
} else {
// 购买角色
var rolePrice: number = this.roleInfos[centerIndex].cost;
if (gameDataInstance.coin >= rolePrice) {
var roleName = this.roleInfos[centerIndex].name;
gameDataInstance.addRoles = roleName;
gameDataInstance.addCoin = -rolePrice;
this.showRolePrice();
// 变正常颜色
var item: Item = this.scrollView.getItemByIndex(centerIndex) as Item;
item.role.filters = null;
}
}
}
}
/**
* 鼠标进入到按钮,按钮效果
* @param button
*/
private addMouseOverEvent(button: Laya.Button) {
button.on(Laya.Event.MOUSE_OVER, button, function() {
button.scale(1.2, 1.2);
});
}
/**
* 鼠标离开到按钮,按钮效果
* @param button
*/
private addMouseOutEvent(button: Laya.Button) {
button.on(Laya.Event.MOUSE_OUT, button, function() {
button.scale(1, 1);
});
}
/**
* 设置ScrollView信息
*/
private setScrollView() {
this.scrollView = new ScrollView();
this.scrollViewContainer.addChild(this.scrollView);
this.initScrollView();
var array = [];
for (var i = 0; i < this.roleInfos.length; i++) {
var roleInfo: RoleInfo = this.roleInfos[i];
var skinStr: string = "ui/" + roleInfo.skin + ".png";
array.push({role:{skin: skinStr}});
}
this.scrollView.array = array;
this.scrollView.renderHandler = new Laya.Handler(this, this.onScrollRender);
this.scrollView.mouseHandler = new Laya.Handler(this, this.onScrollMouse);
}
/**
* 设置ScrollView属性
*/
private initScrollView() {
this.scrollView.leftAlign = 210;
this.scrollView.rightAlign = 210;
this.scrollView.space = 50;
this.scrollView.cellWidth = 300;
this.scrollView.cellHeight = 300;
this.scrollView.itemRender = Item;
this.scrollView.height = 1280;
this.scrollView.anchorY = 0.5;
this.scrollView.pos(0, 600);
}
/**
* ScrollView单元格渲染回调
* @param cell
* @param index
*/
private onScrollRender(cell: Box, index: number){
if (index > this.roleInfos.length) {
return;
}
var item: Item = cell as Item;
var data: any = this.scrollView.array[index];
var roleImg: Laya.Image = item.role;
var skinStr: string = data.role.skin;
roleImg.skin = skinStr;
// 设置灰色角色
if (!this.getHadRole(index)) {
this.grayingRole(roleImg);
}
}
/**
* ScrollView鼠标操作响应
* @param e
*/
private onScrollMouse(e: Event) {
// 移动ScrollView时其中单元格缩放
if (e.type == Laya.Event.MOUSE_DOWN) {
this.mouseDown();
} else if(e.type == Laya.Event.MOUSE_UP) {
this.mouseUp();
} else if (e.type == Laya.Event.MOUSE_MOVE) {
this.mouseMove();
}
}
/**
* 鼠标按下响应事件
*/
private mouseDown() {
if (this._mouseDown) {
console.error("mouse had down");
}
this._mouseDown = true;
this._mouseStartPosX = Laya.MouseManager.instance.mouseX;
this._mouseX = Laya.MouseManager.instance.mouseX;
}
/**
* 鼠标抬起响应事件
*/
private mouseUp() {
if (!this._mouseDown) {
return;
}
var stableFrame = Laya.timer.currFrame - this._curMoveFrame;
// 滑动
if (stableFrame > 2) {
this._mouseSpeed = 0;
this.centeringControl();
}
this._mouseDown = false;
}
/**
* 鼠标移动事件响应
*/
private mouseMove() {
if (this._mouseDown) {
var dis = Laya.MouseManager.instance.mouseX - this._mouseX;
this._mouseX = Laya.MouseManager.instance.mouseX;
this.updateScrollViewPos(dis);
this.updateScale();
this._curMoveFrame = Laya.timer.currFrame;
this._mouseSpeed = dis;
}
}
/**
* 调整图像大小
*/
private updateScale() {
var centerIndex = this.getScreenCenterCellIndex();
var leftIndex = Math.max(centerIndex - 1, 0);
var rightIndex = Math.min(centerIndex + 1, this.scrollView.array.length - 1);
var scrollPosX = this.scrollView.x;
var centerPos = Laya.stage.width / 2 - scrollPosX;
for (var index = leftIndex; index <= rightIndex; index++) {
let cellPos = this.scrollView.getCellPosByIndex(index);
let cellDis = Math.abs(cellPos - centerPos);
if (cellDis < 180) {
let scaleRate = this.itemMaxScale - (this.itemMaxScale - this.itemMinScale) / 180 * cellDis;
let item: Item = this.scrollView.getItemByIndex(index) as Item;
item.role.scale(scaleRate, scaleRate);
} else {
let item: Item = this.scrollView.getItemByIndex(index) as Item;
item.role.scale(0.4, 0.4)
}
}
}
/**
* 更新ScrollView位置
* @param dis
*/
private updateScrollViewPos(dis: number) {
var posX: number = dis + this.scrollView.x;
if (posX > 0) {
posX = 0;
}
if (posX < -this.scrollView.width + Laya.stage.width) {
posX = -this.scrollView.width + Laya.stage.width;
}
this.scrollView.pos(posX, this.scrollView.y);
}
/**
* 将角色居中显示
*/
private centeringControl() {
var centerIndex = this.getScreenCenterCellIndex()
var cellPosX = this.getCellPosByIndex(centerIndex);
var posX = Laya.stage.width / 2 - cellPosX;
Laya.Tween.to(this.scrollView, {x: posX}, 500, Laya.Ease.cubicOut).update = new Laya.Handler(this, this.updateScale);
this.showRolePrice();
}
/**
* 获取屏幕中间的单元格
*/
public getScreenCenterCellIndex(): number {
var distance = -this.scrollView.x;
var index: number = (distance - this.scrollView.leftAlign + this.scrollView.space + (Laya.stage.width + this.scrollView.cellWidth) / 2 ) / (this.scrollView.cellWidth + this.scrollView.space);
return Math.round(index) - 1;
}
/**
* 根据单元格索引获取单元格位置
* @param index
*/
public getCellPosByIndex(index: number):number {
return this.scrollView.leftAlign + (index + 0.5) * this.scrollView.cellWidth + index * this.scrollView.space;
}
/**
* 商店界面显示金币数量
*/
private showCoin(){
this.coinNumLab.changeText(gameDataInstance.coin.toString());
}
/**
* 将角色设置为灰色的。
*/
private grayingRole(roleImg: Laya.Image): void {
//由 20 个项目(排列成 4 x 5 矩阵)组成的数组,灰图
var grayscaleMat: Array<number> = [0.3086, 0.6094, 0.0820, 0, 0, 0.3086, 0.6094, 0.0820, 0, 0, 0.3086, 0.6094, 0.0820, 0, 0, 0, 0, 0, 1, 0];
//创建一个颜色滤镜对象,灰图
var grayscaleFilter: ColorFilter = new ColorFilter(grayscaleMat);
// 灰度猩猩
roleImg.filters = [grayscaleFilter];
}
/**
* 显示角色价格
* 如果已经拥有,则显示开始游戏按钮
*/
private showRolePrice() {
var centerIndex = this.getScreenCenterCellIndex();
if (this.getHadRole(centerIndex)) {
this.buyBtn.skin = "ui/buttonOrange.png";
this.priceLab.visible = false;
this.coinImg.visible = false;
this.gameStartImg.visible = true;
} else {
this.buyBtn.skin = "ui/buttonBlue.png";
this.priceLab.visible = true;
this.coinImg.visible = true;
this.gameStartImg.visible = false;
this.priceLab.changeText(this.roleInfos[centerIndex].cost.toString());
}
}
/**
* 是否拥有当前的角色
* @param index
*/
private getHadRole(index: number) {
var roles: Array<string> = gameDataInstance.roles;
var centerRoleName = this.roleInfos[index].name;
for (var i = 0; i < roles.length; i++) {
if (roles[i] == centerRoleName) {
return true;
}
}
return false;
}
/**
* 显示UI
*/
public showUI() {
this.visible = true;
// ScrollView 重置位置
this.scrollView.pos(0, 600);
// 调整图像大小
this.updateScale();
// 显示金币
this.showCoin();
// 显示角色价格
this.showRolePrice();
}
/**
* 隐藏UI
*/
public hideUI() {
this.visible = false;
}
}
/**
* 显示皮肤资源ScrollView的单元格样式
*/
class Item extends Laya.Box {
role: Laya.Image;
constructor(width: number, height: number) {
super();
this.width = width;
this.height = height;
this.graphics.drawRect(0, 0, this.width, this.height, null);
this.anchorX = 0.5;
this.anchorY = 0.5;
this.role = new Laya.Image();
this.role.width = 532;
this.role.height = 565;
this.role.scale(0.4, 0.4);
this.role.anchorX = 0.5;
this.role.anchorY = 0.5;
this.role.pos(this.width / 2, this.height / 2);
this.addChild(this.role);
}
}