序言
平時在使用Cocos開發移動端的項目時,總會遇到适配類的問題,索性寫了一個适配的元件,最近在整理之前的項目和經驗,把元件優化了一下,提供給大家使用。
使用方法
- 挂載到所需适配的節點上
- 往規則清單添加适配規則
适配規則
該适配元件可自适配移動端螢幕,提供多種适配規則:
- 吸附頂部、底部、左邊、右邊,可設定上下或左右扣除的值(為1以下小數時為目前螢幕寬度、高度乘于該值,為1以上的值時為目前螢幕寬度、高度減去該值)
Cocos Creator移動端适配元件-Ts版 - 适配寬度、高度,即鋪滿目前螢幕的寬度、高度,可設定上下或左右扣除的值(為1以下小數時為目前螢幕寬度、高度乘于該值,為1以上的值時為目前螢幕寬度、高度減去該值)
Cocos Creator移動端适配元件-Ts版 - 根據範圍,自适應吸附頂部、底部、左邊、右邊。在螢幕尺寸大于等于最大範圍下,吸附值為最小值,在螢幕尺寸小于等于最小範圍下,吸附值為最大值。 如果還是不太清楚,給你們放一下示例的圖檔:
Cocos Creator移動端适配元件-Ts版 Cocos Creator移動端适配元件-Ts版 我們來根據這張圖檔來分析一下,在最大尺寸1624下,取值為100,在小尺寸下,取值則在100-200之間浮動。
這樣在大螢幕下,不會出現主要元素都處于安全區,出現其他區域很空的狀況。顯示效果更好
小螢幕下,也不會出現主要元素被裁剪的情況。
-
根據範圍顯示或不顯示滾動條,在螢幕尺寸小于範圍下會顯示滾動條,防止元素被截取不可操作。在螢幕尺寸大于範圍的情況,則不會顯示滾動條。
添加這個規則,是防止出現極端尺寸下(列如: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();
}
}
}