天天看點

Cocos Creator移動端适配元件-Ts版

序言

平時在使用Cocos開發移動端的項目時,總會遇到适配類的問題,索性寫了一個适配的元件,最近在整理之前的項目和經驗,把元件優化了一下,提供給大家使用。

使用方法

  1. 挂載到所需适配的節點上
  2. 往規則清單添加适配規則

适配規則

該适配元件可自适配移動端螢幕,提供多種适配規則:

  1. 吸附頂部、底部、左邊、右邊,可設定上下或左右扣除的值(為1以下小數時為目前螢幕寬度、高度乘于該值,為1以上的值時為目前螢幕寬度、高度減去該值)
    Cocos Creator移動端适配元件-Ts版
  2. 适配寬度、高度,即鋪滿目前螢幕的寬度、高度,可設定上下或左右扣除的值(為1以下小數時為目前螢幕寬度、高度乘于該值,為1以上的值時為目前螢幕寬度、高度減去該值)
    Cocos Creator移動端适配元件-Ts版
  3. 根據範圍,自适應吸附頂部、底部、左邊、右邊。在螢幕尺寸大于等于最大範圍下,吸附值為最小值,在螢幕尺寸小于等于最小範圍下,吸附值為最大值。
    Cocos Creator移動端适配元件-Ts版
    如果還是不太清楚,給你們放一下示例的圖檔:
    Cocos Creator移動端适配元件-Ts版

    我們來根據這張圖檔來分析一下,在最大尺寸1624下,取值為100,在小尺寸下,取值則在100-200之間浮動。

    這樣在大螢幕下,不會出現主要元素都處于安全區,出現其他區域很空的狀況。顯示效果更好

    小螢幕下,也不會出現主要元素被裁剪的情況。

  4. 根據範圍顯示或不顯示滾動條,在螢幕尺寸小于範圍下會顯示滾動條,防止元素被截取不可操作。在螢幕尺寸大于範圍的情況,則不會顯示滾動條。

    添加這個規則,是防止出現極端尺寸下(列如:iframe嵌套),主要元素和按鈕等被遮擋不可操作。

這個是github位址:https://github.com/kenvschen/ListView-for-CocosCreator

為了友善不使用github的同學,我這邊也把代碼copy出來,如有疑問和錯誤,歡迎大家指出問題所在。

const { ccclass, property } = cc._decorator;

const config = {
    gameWidth: 0,
    gameHeight: 0
};

const adaptationType = cc.Enum({
    // 吸附頂部,距離為 value,判斷基準固定為高度
    吸附頂部: 0,
    // 吸附底部,距離為 value,判斷基準固定為高度
    吸附底部: 1,
    // 吸附左邊,距離為 value,判斷基準固定為寬度
    吸附左邊: 2,
    // 吸附右邊,距離為 value,判斷基準固定為寬度
    吸附右邊: 3,    
    // 适配高度,可設定上下扣除高度或扣除百分比高度
    适配高度: 4,
    // 适配寬度,可設定左右扣除寬度或扣除百分比寬度
    适配寬度: 5,
    // 根據最大範圍和最小範圍來自适應吸附頂部,數值必須是固定值
    根據範圍自适應吸附頂部: 6,
    // 根據最大範圍和最小範圍來自适應吸附底部,數值必須是固定值
    根據範圍自适應吸附底部: 7,
    // 根據最大範圍和最小範圍來自适應吸附左邊,數值必須是固定值
    根據範圍自适應吸附左邊: 8,
    // 根據最大範圍和最小範圍來自适應吸附右邊,數值必須是固定值
    根據範圍自适應吸附右邊: 9,
    // 适配是否在範圍内,如果小于範圍則顯示滾動條,适配極端尺寸
    自适應顯示滾動條: 10,
});

const szieType = cc.Enum({
    /**
     * 根據寬度适配
     */
    adaptationWidth: 0,
    /**
     * 根據高度适配
     */
    adaptationHeight: 1
});

@ccclass('adaptationListClass')
class adaptationListClass {
    // 類型
    @property({
        type: adaptationType,
        displayName: 'adaptationType',
        tooltip: '适配規則類型'
    })
    type = adaptationType.吸附頂部;

    @property({
        type: szieType,
        displayName: 'adaptationFromType',
        tooltip: '适配規則判斷标準'
    })
    szieType = szieType.adaptationWidth;

    // 數值
    @property({
        visible() {
            return (this.type != adaptationType.适配寬度 && this.type != adaptationType.适配高度 && this.type != adaptationType.根據範圍自适應吸附頂部 && this.type != adaptationType.根據範圍自适應吸附底部 && this.type != adaptationType.根據範圍自适應吸附左邊 && this.type != adaptationType.根據範圍自适應吸附右邊 && this.type != adaptationType.自适應顯示滾動條);
        },
        displayName: 'value',
        tooltip: '根據規則扣除的數值,1以下小數為百分比,其他為固定扣除'
    })
    value: number = 0;

    // 固定頂部數值
    @property({
        visible() {
            return (this.type == adaptationType.适配高度);
        },
        displayName: 'costTopValue',
        tooltip: '距離頂邊扣除數值, 1以下小數為百分比,其他為固定扣除'
    })
    costTopValue: number = 0;

    // 固定底部數值
    @property({
        visible() {
            return (this.type == adaptationType.适配高度);
        },
        displayName: 'costBottomValue',
        tooltip: '距離底邊扣除數值, 1以下小數為百分比,其他為固定扣除'
    })
    costBottomValue: number = 0;

    // 固定左邊數值
    @property({
        visible() {
            return (this.type == adaptationType.适配寬度);
        },
        displayName: 'costLeftValue',
        tooltip: '距離左邊扣除數值, 1以下小數為百分比,其他為固定扣除'
    })
    costLeftValue: number = 0;

    // 右邊數值(1以下小數為百分比,其他為固定扣除)
    @property({
        visible() {
            return (this.type == adaptationType.适配寬度);
        },
        displayName: 'costRightValue',
        tooltip: '距離右邊扣除數值, 1以下小數為百分比,其他為固定扣除'
    })
    costRightValue: number = 0;

    // 最大範圍的情況下,适配的數值
    @property({
        visible() {
            return (this.type == adaptationType.根據範圍自适應吸附頂部 || this.type == adaptationType.根據範圍自适應吸附底部 || this.type == adaptationType.根據範圍自适應吸附左邊 || this.type == adaptationType.根據範圍自适應吸附右邊);
        },
        displayName: 'minValue',
        tooltip: '最大範圍下的數值,如正常尺寸下的适配數值'
    })
    minValue: number = 0;

    // 高度最小範圍的情況下,距離頂部的數值
    @property({
        visible() {
            return (this.type == adaptationType.根據範圍自适應吸附頂部 || this.type == adaptationType.根據範圍自适應吸附底部 || this.type == adaptationType.根據範圍自适應吸附左邊 || this.type == adaptationType.根據範圍自适應吸附右邊);
        },
        displayName: 'maxValue',
        tooltip: '最小範圍下的數值,如安全區下的适配數值'
    })
    maxValue: number = 0;

    // 最小範圍
    @property({
        visible() {
            return (this.type == adaptationType.根據範圍自适應吸附頂部 || this.type == adaptationType.根據範圍自适應吸附底部 || this.type == adaptationType.根據範圍自适應吸附左邊 || this.type == adaptationType.根據範圍自适應吸附右邊 || this.type == adaptationType.自适應顯示滾動條);
        },
        displayName: 'minRange',
        tooltip: '最小範圍數值,如設計稿最小安全區尺寸'
    })
    minRange: number = 0;

    // 最大範圍
    @property({
        visible() {
            return (this.type == adaptationType.根據範圍自适應吸附頂部 || this.type == adaptationType.根據範圍自适應吸附底部 || this.type == adaptationType.根據範圍自适應吸附左邊 || this.type == adaptationType.根據範圍自适應吸附右邊);
        },
        displayName: 'maxRange',
        tooltip: '最大範圍數值,如設計稿最大尺寸'
    })
    maxRange: number = 0;
}

@ccclass
export default class adaptation extends cc.Component {

    // 設計稿尺寸
    @property({
        displayName: 'configSize',
        tooltip: '設計稿尺寸'
    })
    configSize: cc.Size = cc.size(0, 0);
    
    @property({
        type: [adaptationListClass],
        displayName: 'adaptationList',
        tooltip: '适配規則清單,從頭到底一條條執行'
    })
    adaptationList: adaptationListClass[] = [];

    // LIFE-CYCLE CALLBACKS:
    private nowSize: cc.Size = null;
    private firstAdaptation: boolean = true;

    onLoad() {
        this.nowSize = cc.size(cc.winSize.width, cc.winSize.height);
        // cc.log(cc.winSize);
        this.setWidget();

        this.firstAdaptation = false;
    }

    /**
     * 開始适配規則
     */
    setWidget() {
        // 系統尺寸
        let winSize = cc.size(cc.winSize.width, cc.winSize.height);
        // 設計尺寸
        let configSize = this.returnConfigSize();
        // 對齊元件
        let widget: cc.Widget = null;

        let _top: number, _bottom: number, _left: number, _right: number;
        let _height: number, _width: number;

        cc.log('winSize', winSize);
        cc.log('configSize', configSize);

        if (this.adaptationList.length > 0) {
            widget = this.node.getComponent(cc.Widget) ? this.node.getComponent(cc.Widget) : this.node.addComponent(cc.Widget);
        } else {
            // this.node.removeComponent(cc.Widget);
            this.node.getComponent(cc.Widget) ? this.node.getComponent(cc.Widget).enabled = false : void(0);
        }

        this.adaptationList.map(value => {
            value.value = Math.abs(value.value);
            value.costBottomValue = Math.abs(value.costBottomValue);
            value.costTopValue = Math.abs(value.costTopValue);
            value.costLeftValue = Math.abs(value.costLeftValue);
            value.costRightValue = Math.abs(value.costRightValue);
            switch (value.type) {
                // 吸附頂部
                case adaptationType.吸附頂部:
                    _top = (configSize.height - winSize.height) / 2;
                    cc.log('吸附頂部', _top);
                    widget.enabled = true;
                    widget.top = (value.value < 1 && value.value != 0) ? _top + winSize.height * value.value : _top + value.value;
                    widget.isAlignTop = true;
                    widget.updateAlignment();
                    break;
                // 吸附底部
                case adaptationType.吸附底部:
                    let _bottom = (configSize.height - winSize.height) / 2;
                    cc.log('吸附底部', _bottom);
                    widget.enabled = true;
                    widget.bottom = (value.value < 1 && value.value != 0) ? _bottom + winSize.height * value.value : _bottom + value.value;
                    widget.isAlignBottom = true;
                    widget.updateAlignment();
                    break;
                // 吸附左邊
                case adaptationType.吸附左邊:
                    _left = (configSize.width - winSize.width) / 2;
                    cc.log('吸附左邊', _left);
                    widget.enabled = true;
                    widget.left = (value.value < 1 && value.value != 0) ? _left + winSize.width * value.value : _left + value.value;
                    widget.isAlignLeft = true;
                    widget.updateAlignment();
                    break;
                // 吸附右邊
                case adaptationType.吸附右邊:
                    _right = (configSize.width - winSize.width) / 2;
                    cc.log('吸附右邊', _right);
                    widget.enabled = true;
                    widget.right = (value.value < 1 && value.value != 0) ? _right + winSize.width * value.value : _right + value.value;
                    widget.isAlignRight = true;
                    widget.updateAlignment();
                    break;
                // 适配高度
                case adaptationType.适配高度:
                    _top = value.costTopValue < 1 && value.costTopValue != 0 ? value.costTopValue * winSize.height : value.costTopValue;
                    _bottom = value.costBottomValue < 1 && value.costBottomValue != 0 ? value.costBottomValue * winSize.height : value.costBottomValue;
                    if (_top + _bottom > winSize.height) {
                        _top = winSize.height / 2;
                        _bottom = winSize.height / 2;
                    }
                    _height = winSize.height - _top - _bottom;
                    cc.log('适配高度', _height);
                    this.node.height = _height;
                    break;
                // 适配寬度
                case adaptationType.适配寬度:
                    _left = value.costLeftValue < 1 && value.costLeftValue != 0 ? value.costLeftValue * winSize.width : value.costLeftValue;
                    _right = value.costRightValue < 1 && value.costRightValue != 0 ? value.costRightValue * winSize.width : value.costRightValue;
                    if (_left + _right > winSize.width) {
                        _left = winSize.width / 2;
                        _right = winSize.width / 2;
                    }
                    _width = winSize.width - _left - _right;
                    cc.log('适配寬度', _width);
                    this.node.width = _width;
                    break;
                // 根據範圍自适應吸附頂部,在最大範圍時,适配取值為最小值,最小範圍下,适配取值為最大值
                case adaptationType.根據範圍自适應吸附頂部:
                    if (value.minRange > (value.szieType == szieType.adaptationWidth ? winSize.width : winSize.height)) {
                        _top = value.maxValue;
                    } else if (value.maxRange < (value.szieType == szieType.adaptationWidth ? winSize.width : winSize.height)) {
                        _top = value.minValue;
                    } else {
                        _top = ((1 - ((value.szieType == szieType.adaptationWidth ? winSize.width : winSize.height) - value.minRange) / (value.maxRange - value.minRange)) * (value.maxValue - value.minValue)) + value.minValue;
                    }
                    cc.log('根據範圍自适應吸附頂部', _top);

                    widget.enabled = true;
                    widget.top = _top;
                    widget.isAlignTop = true;
                    widget.updateAlignment();
                    break;
                // 根據範圍自适應吸附底部,在最大範圍時,适配取值為最小值,最小範圍下,适配取值為最大值
                case adaptationType.根據範圍自适應吸附底部:
                    // cc.log('minRange: ' + value.minRange + ' winSize.width:' + winSize.width);
                    if (value.minRange > (value.szieType == szieType.adaptationWidth ? winSize.width : winSize.height)) {
                        _bottom = value.maxValue;
                        // cc.log('高度小于最小值', _bottom);
                    } else if (value.maxRange < (value.szieType == szieType.adaptationWidth ? winSize.width : winSize.height)) {
                        _bottom = value.minValue;
                        // cc.log('高度大于最大值', _bottom);
                    } else {
                        _bottom = ((1 - ((value.szieType == szieType.adaptationWidth ? winSize.width : winSize.height) - value.minRange) / (value.maxRange - value.minRange)) * (value.maxValue - value.minValue)) + value.minValue;
                        // cc.log('範圍', _bottom);
                    }
                    
                    widget.enabled = true;
                    widget.bottom = _bottom;
                    widget.isAlignBottom = true;
                    widget.updateAlignment();
                    break;
                // 根據範圍自适應吸附左邊,在最大範圍時,适配取值為最小值,最小範圍下,适配取值為最大值
                case adaptationType.根據範圍自适應吸附左邊:
                    // let _left = null;
                    if (value.minRange > (value.szieType == szieType.adaptationWidth ? winSize.width : winSize.height)) {
                        _left = value.maxValue;
                    } else if (value.maxRange < (value.szieType == szieType.adaptationWidth ? winSize.width : winSize.height)) {
                        _left = value.minValue;
                    } else {
                        _left = ((1 - ((value.szieType == szieType.adaptationWidth ? winSize.width : winSize.height) - value.minRange) / (value.maxRange - value.minRange)) * (value.maxValue - value.minValue)) + value.minValue;
                    }
                    
                    widget.enabled = true;
                    widget.left = _left;
                    widget.isAlignLeft = true;
                    widget.updateAlignment();
                    break;
                // 根據範圍自适應吸附右邊,在最大範圍時,适配取值為最小值,最小範圍下,适配取值為最大值
                case adaptationType.根據範圍自适應吸附右邊:
                    // let _right = null;
                    if (value.minRange > (value.szieType == szieType.adaptationWidth ? winSize.width : winSize.height)) {
                        _right = value.maxValue;
                    } else if (value.maxRange < (value.szieType == szieType.adaptationWidth ? winSize.width : winSize.height)) {
                        _right = value.minValue;
                    } else {
                        _right = ((1 - ((value.szieType == szieType.adaptationWidth ? winSize.width : winSize.height) - value.minRange) / (value.maxRange - value.minRange)) * (value.maxValue - value.minValue)) + value.minValue;
                    }
                    
                    widget.enabled = true;
                    widget.right = _right;
                    widget.isAlignRight = true;
                    widget.updateAlignment();
                    break;
                // 适配是否在範圍内,如果小于範圍則顯示滾動條
                case adaptationType.自适應顯示滾動條:
                    let scrollView = this.node.getComponent(cc.ScrollView);
                    if (!scrollView) {
                        scrollView = this.node.addComponent(cc.ScrollView);
                        scrollView.vertical = false;
                        scrollView.horizontal = false;
                        // 生成遮罩節點
                        let viewNode = new cc.Node('view');
                        viewNode.setContentSize(winSize);
                        // 添加mask元件
                        let mask = viewNode.addComponent(cc.Mask);
                        mask.type = cc.Mask.Type.RECT;
                        mask.enabled = true;
                        // 生成content節點
                        let contentNode = new cc.Node('content');
                        contentNode.setContentSize(this.node.getContentSize());

                        let childrens = this.node.children.concat([]);
                        this.node.removeAllChildren(false);
                        childrens.map(item => {
                            // cc.log('item', item, item.getComponent(cc.Camera));
                            if (!item.getComponent(cc.Camera)) {
                                // item.removeFromParent(false);
                                contentNode.addChild(item, item.zIndex);
                            }
                        });

                        contentNode.parent = viewNode;
                        viewNode.parent = this.node;
                        scrollView.content = contentNode;
                    } else {
                        // 設定遮罩節點尺寸
                        scrollView.content.parent.setContentSize(winSize);
                        // 設定content尺寸
                        scrollView.content.setContentSize(this.node.getContentSize());
                    }
                    if (value.minRange > (value.szieType == szieType.adaptationWidth ? winSize.width : winSize.height)) {
                        cc.log('螢幕尺寸不足開啟滾動, 滾動至居中');
                        scrollView.enabled = true;
                        if (value.szieType == szieType.adaptationWidth) {
                            scrollView.horizontal = true;
                            scrollView.horizontalScrollBar ? scrollView.horizontalScrollBar.node.active = true : void (0);
                        } else {
                            scrollView.vertical = true;
                            scrollView.verticalScrollBar ? scrollView.verticalScrollBar.node.active = true : void (0);
                        }

                        cc.director.once(cc.Director.EVENT_AFTER_DRAW, () => {
                            scrollView.scrollToOffset(cc.v2(value.szieType == szieType.adaptationWidth ? (configSize.width - winSize.width) / 2 : 0, value.szieType == szieType.adaptationWidth ? 0 : (configSize.height - winSize.height) / 2));
                        });
                    } else {
                        cc.log('螢幕尺寸足夠不開啟滾動');
                        scrollView.enabled = false;
                        scrollView.verticalScrollBar ? scrollView.verticalScrollBar.node.active = false : void (0);
                        scrollView.horizontalScrollBar ? scrollView.horizontalScrollBar.node.active = false : void (0);
                    }
                    break;
            }
        });
    }

    /**
     * 傳回螢幕尺寸
     * 如設定了config裡的寬高不為0,則使用config裡的資料作為基準
     * 否則使用configSize裡的資料為基準
     */
    returnConfigSize (): cc.Size {
        if (config.gameWidth !== 0 && config.gameHeight !== 0) {
            return cc.size(config.gameWidth, config.gameHeight);
        } else {
            return this.configSize;
        }
    }

    // start () {}

    update (dt: number) {
        if (!this.firstAdaptation && (cc.winSize.width != this.nowSize.width || cc.winSize.height != this.nowSize.height)) {
            this.nowSize = cc.size(cc.winSize.width, cc.winSize.height);

            this.setWidget();
        }
    }
}
           

繼續閱讀