LayaBox -- 实现ScrollView效果

这一篇我们实现一个简易的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);
    }
}
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值