Laya.Widget 使用指南

简介

Laya.Widget 是 LayaAir 引擎的 相对布局组件,用于设置显示对象相对于父容器的位置。通过设置 topbottomleftrightcenterXcenterY 等属性,可以实现响应式布局,当父容器尺寸变化时自动调整位置。

适用场景

  • UI 元素的相对布局(按钮、面板、标签等)
  • 响应式界面设计
  • 锚点定位(如固定在屏幕角落的元素)
  • 屏幕适配和自适应布局

继承关系

Laya.Widget → Laya.Component → Laya.Object

目录


API 参考

边距属性

属性 类型 说明
top number 距顶边的距离(像素)
bottom number 距底边的距离(像素)
left number 距左边的距离(像素)
right number 距右边的距离(像素)

居中属性

属性 类型 说明
centerX number 距水平中心轴的距离(像素)
centerY number 距垂直中心轴的距离(像素)

只读属性

属性 类型 说明
owner Sprite 所属的显示对象(只读)

方法

方法 参数 返回值 说明
resetLayout() - void 重新计算布局
resetLayoutX() - boolean 重置水平布局
resetLayoutY() - boolean 重置垂直布局
onReset() - void 重置所有边界和中心坐标为 null

静态属性

属性 类型 说明
EMPTY Widget 已初始化的 Widget 静态实例

基础用法

1. 添加 Widget 组件

@regClass()
export class GameUI extends Laya.Sprite {
    private panel: Laya.Sprite;
    private widget: Laya.Widget;

    onAwake(): void {
        // 创建一个面板
        this.panel = new Laya.Sprite();
        this.panel.graphics.drawRect(0, 0, 200, 100, "#FF0000");
        this.addChild(this.panel);

        // 添加 Widget 组件
        this.widget = this.panel.addComponent(Laya.Widget);
    }
}

2. 固定在父容器边缘

// 固定在左上角
this.widget.top = 10;
this.widget.left = 10;

// 固定在右上角
this.widget.top = 10;
this.widget.right = 10;

// 固定在左下角
this.widget.bottom = 10;
this.widget.left = 10;

// 固定在右下角
this.widget.bottom = 10;
this.widget.right = 10;

3. 居中对齐

// 水平居中
this.widget.centerX = 0;

// 垂直居中
this.widget.centerY = 0;

// 完全居中(水平+垂直)
this.widget.centerX = 0;
this.widget.centerY = 0;

// 居中并偏移
this.widget.centerX = 50;   // 水平居中后向右偏移 50 像素
this.widget.centerY = -20;  // 垂直居中后向上偏移 20 像素

4. 拉伸填充

// 顶部固定,填充整个宽度
this.widget.top = 0;
this.widget.left = 0;
this.widget.right = 0;

// 左右固定,填充整个高度
this.widget.left = 0;
this.widget.top = 0;
this.widget.bottom = 0;

// 填充整个父容器
this.widget.top = 0;
this.widget.bottom = 0;
this.widget.left = 0;
this.widget.right = 0;

5. 重置布局

// 重置所有属性
this.widget.onReset();

// 重新计算布局
this.widget.resetLayout();

// 只重置水平布局
this.widget.resetLayoutX();

// 只重置垂直布局
this.widget.resetLayoutY();

实用示例

示例1: 屏幕四角固定元素

@regClass()
export class CornerElements extends Laya.Sprite {
    onAwake(): void {
        this.setupCorners();
    }

    private setupCorners(): void {
        // 左上角 - 小地图
        let miniMap = this.createPanel(150, 150, "#4488FF");
        let widget1 = miniMap.addComponent(Laya.Widget);
        widget1.top = 20;
        widget1.left = 20;
        this.addChild(miniMap);

        // 右上角 - 玩家信息
        let playerInfo = this.createPanel(200, 80, "#44FF88");
        let widget2 = playerInfo.addComponent(Laya.Widget);
        widget2.top = 20;
        widget2.right = 20;
        this.addChild(playerInfo);

        // 左下角 - 技能栏
        let skillBar = this.createPanel(300, 60, "#FF8844");
        let widget3 = skillBar.addComponent(Laya.Widget);
        widget3.bottom = 20;
        widget3.left = 20;
        this.addChild(skillBar);

        // 右下角 - 聊天框
        let chatBox = this.createPanel(250, 150, "#FF44FF");
        let widget4 = chatBox.addComponent(Laya.Widget);
        widget4.bottom = 20;
        widget4.right = 20;
        this.addChild(chatBox);
    }

    private createPanel(w: number, h: number, color: string): Laya.Sprite {
        let panel = new Laya.Sprite();
        panel.graphics.drawRect(0, 0, w, h, color);
        panel.size(w, h);
        return panel;
    }
}

示例2: 居中弹窗

@regClass()
export class CenterDialog extends Laya.Sprite {
    private bg: Laya.Sprite;
    private widget: Laya.Widget;

    constructor() {
        super();
        this.setupDialog();
    }

    private setupDialog(): void {
        // 创建背景
        this.bg = new Laya.Sprite();
        this.bg.graphics.drawRect(0, 0, 400, 300, "#333333");
        this.bg.size(400, 300);
        this.addChild(this.bg);

        // 添加 Widget 实现居中
        this.widget = this.addComponent(Laya.Widget);
        this.widget.centerX = 0;
        this.widget.centerY = 0;
    }

    // 显示对话框
    public show(parent: Laya.Sprite): void {
        parent.addChild(this);
    }
}

示例3: 响应式头部栏

@regClass()
export class ResponsiveHeader extends Laya.Sprite {
    private bg: Laya.Sprite;
    private title: Laya.Text;

    constructor() {
        super();
        this.setupHeader();
    }

    private setupHeader(): void {
        // 背景条 - 横向填充整个宽度
        this.bg = new Laya.Sprite();
        this.bg.graphics.drawRect(0, 0, 100, 60, "#222222");
        this.bg.size(100, 60);

        let bgWidget = this.bg.addComponent(Laya.Widget);
        bgWidget.top = 0;
        bgWidget.left = 0;
        bgWidget.right = 0;

        this.addChild(this.bg);

        // 标题 - 居中显示
        this.title = new Laya.Text();
        this.title.text = "游戏标题";
        this.title.fontSize = 24;
        this.title.color = "#FFFFFF";
        this.title.size(200, 40);

        let titleWidget = this.title.addComponent(Laya.Widget);
        titleWidget.centerX = 0;
        titleWidget.top = 10;

        this.bg.addChild(this.title);
    }
}

示例4: 侧边栏面板

@regClass()
export class SidePanel extends Laya.Sprite {
    private panel: Laya.Sprite;
    private toggleBtn: Laya.Sprite;

    constructor() {
        super();
        this.setupPanel();
    }

    private setupPanel(): void {
        // 侧边栏 - 左侧固定,填充整个高度
        this.panel = new Laya.Sprite();
        this.panel.graphics.drawRect(0, 0, 200, 400, "#444444");
        this.panel.size(200, 400);

        let panelWidget = this.panel.addComponent(Laya.Widget);
        panelWidget.top = 0;
        panelWidget.bottom = 0;
        panelWidget.left = 0;

        this.addChild(this.panel);

        // 切换按钮 - 固定在侧边栏右边缘外侧
        this.toggleBtn = new Laya.Sprite();
        this.toggleBtn.graphics.drawRect(0, 0, 30, 60, "#FF8800");
        this.toggleBtn.size(30, 60);

        let btnWidget = this.toggleBtn.addComponent(Laya.Widget);
        btnWidget.centerY = 0;
        btnWidget.left = 200;  // 相对于父容器左侧 200 像素

        this.addChild(this.toggleBtn);
    }
}

示例5: 动态布局更新

@regClass()
export class DynamicLayout extends Laya.Script {
    @property(Laya.Widget)
    public widget: Laya.Widget = null;

    private originalTop: number = 10;

    onAwake(): void {
        this.originalTop = this.widget.top;
    }

    // 切换到顶部
    public stickToTop(): void {
        this.widget.onReset();
        this.widget.top = this.originalTop;
        this.widget.left = 10;
        this.widget.resetLayout();
    }

    // 切换到右下角
    public moveToBottomRight(): void {
        this.widget.onReset();
        this.widget.bottom = 10;
        this.widget.right = 10;
        this.widget.resetLayout();
    }

    // 居中显示
    public center(): void {
        this.widget.onReset();
        this.widget.centerX = 0;
        this.widget.centerY = 0;
        this.widget.resetLayout();
    }

    // 填充整个父容器
    public fill(): void {
        this.widget.onReset();
        this.widget.top = 0;
        this.widget.bottom = 0;
        this.widget.left = 0;
        this.widget.right = 0;
        this.widget.resetLayout();
    }
}

高级技巧

1. 相对定位组合

// 距离右下角各 50 像素,但自身尺寸为 100x100
let panel = new Laya.Sprite();
panel.size(100, 100);

let widget = panel.addComponent(Laya.Widget);
widget.bottom = 50;  // 底边向上 50
widget.right = 50;   // 右边向左 50

// 这会将面板定位在距离父容器右下角 (50, 50) 的位置

2. 居中偏移

// 水平居中后向右偏移 100 像素
let widget = panel.addComponent(Laya.Widget);
widget.centerX = 100;

// 垂直居中后向上偏移 50 像素
widget.centerY = -50;

3. 监听父容器尺寸变化

@regClass()
export class ResponsiveElement extends Laya.Sprite {
    private widget: Laya.Widget;

    onAwake(): void {
        this.widget = this.addComponent(Laya.Widget);
        this.widget.centerX = 0;
        this.widget.centerY = 0;

        // 监听父容器尺寸变化
        if (this.parent) {
            this.parent.on(Laya.Event.RESIZE, this, this.onParentResize);
        }
    }

    private onParentResize(): void {
        console.log("父容器尺寸变化,重新计算布局");
        this.widget.resetLayout();
    }
}

4. 条件布局切换

@regClass()
export class AdaptiveLayout extends Laya.Script {
    @property(Laya.Widget)
    public widget: Laya.Widget = null;

    // 根据屏幕方向切换布局
    public adaptToOrientation(isLandscape: boolean): void {
        this.widget.onReset();

        if (isLandscape) {
            // 横屏:靠左显示
            this.widget.top = 10;
            this.widget.left = 10;
            this.widget.bottom = 10;
        } else {
            // 竖屏:靠上显示
            this.widget.top = 10;
            this.widget.left = 10;
            this.widget.right = 10;
        }

        this.widget.resetLayout();
    }

    // 根据屏幕尺寸切换布局
    public adaptToSize(width: number, height: number): void {
        this.widget.onReset();

        if (width < 500) {
            // 小屏幕:完全居中
            this.widget.centerX = 0;
            this.widget.centerY = 0;
        } else {
            // 大屏幕:固定在左上角
            this.widget.top = 20;
            this.widget.left = 20;
        }

        this.widget.resetLayout();
    }
}

5. 多元素相对布局

@regClass()
export class MultiElementLayout extends Laya.Sprite {
    onAwake(): void {
        this.setupLayout();
    }

    private setupLayout(): void {
        // 主面板 - 居中
        let mainPanel = this.createPanel(400, 300, "#446688");
        let mainWidget = mainPanel.addComponent(Laya.Widget);
        mainWidget.centerX = 0;
        mainWidget.centerY = 0;
        this.addChild(mainPanel);

        // 关闭按钮 - 相对于主面板右上角
        let closeBtn = this.createPanel(30, 30, "#FF4444");
        let btnWidget = closeBtn.addComponent(Laya.Widget);
        btnWidget.top = 10;
        btnWidget.right = 10;
        mainPanel.addChild(closeBtn);
    }

    private createPanel(w: number, h: number, color: string): Laya.Sprite {
        let panel = new Laya.Sprite();
        panel.graphics.drawRect(0, 0, w, h, color);
        panel.size(w, h);
        return panel;
    }
}

最佳实践

1. 布局模式选择

需求 推荐方案 示例
固定在某个角落 设置对应的边距 top=10, left=10
居中显示 使用 centerX/centerY centerX=0, centerY=0
拉伸填充 设置相反的边距 top=0, bottom=0
宽度固定且居中 centerX + 固定宽度 centerX=0, width=200

2. 避免冲突

// ❌ 错误:同时设置 top 和 bottom 可能导致不可预期的行为
widget.top = 10;
widget.bottom = 10;
// 元素会被拉伸,但高度未定义

// ✅ 正确:使用组合方式明确意图
// 方式1:固定高度,设置顶部
widget.top = 10;
// 元素高度由 size() 设置

// 方式2:拉伸填充
widget.top = 0;
widget.bottom = 0;

3. 与 Box 组件配合

@regClass()
export class UILayout extends Laya.Sprite {
    onAwake(): void {
        // 使用 Box 作为容器
        let container = new Laya.Box();
        container.size(800, 600);

        // 添加 Widget 使容器居中
        let containerWidget = container.addComponent(Laya.Widget);
        containerWidget.centerX = 0;
        containerWidget.centerY = 0;

        this.addChild(container);

        // 子元素使用绝对定位
        let item = new Laya.Sprite();
        item.graphics.drawRect(0, 0, 100, 50, "#FF0000");
        item.pos(50, 50);

        container.addChild(item);
    }
}

4. 性能优化

// ❌ 频繁调用 resetLayout
onUpdate(): void {
    this.widget.resetLayout();  // 每帧都重置,性能差
}

// ✅ 只在必要时重置
onSomeConditionChange(): void {
    this.widget.resetLayout();  // 只在布局变化时重置
}

// ✅ 使用脏标记
private _layoutDirty: boolean = false;

markLayoutDirty(): void {
    this._layoutDirty = true;
}

onUpdate(): void {
    if (this._layoutDirty) {
        this.widget.resetLayout();
        this._layoutDirty = false;
    }
}

5. 屏幕适配

@regClass()
export class ScreenAdapter extends Laya.Sprite {
    onAwake(): void {
        this.setupUI();
        this.adaptToScreen();

        // 监听屏幕尺寸变化
        Laya.stage.on(Laya.Event.RESIZE, this, this.adaptToScreen);
    }

    private setupUI(): void {
        // 创建 UI 元素
        let panel = new Laya.Sprite();
        panel.graphics.drawRect(0, 0, 200, 100, "#444488");
        panel.size(200, 100);

        let widget = panel.addComponent(Laya.Widget);
        widget.centerX = 0;
        widget.centerY = 0;

        this.addChild(panel);
    }

    private adaptToScreen(): void {
        let screenWidth = Laya.stage.width;
        let screenHeight = Laya.stage.height;

        // 根据屏幕比例调整 UI 布局
        if (screenWidth / screenHeight > 1.5) {
            // 宽屏
            console.log("宽屏模式");
        } else {
            // 窄屏
            console.log("窄屏模式");
        }
    }
}

6. 布局调试

// 显示布局信息
function debugWidget(widget: Laya.Widget): void {
    console.log("=== Widget Info ===");
    console.log("top:", widget.top);
    console.log("bottom:", widget.bottom);
    console.log("left:", widget.left);
    console.log("right:", widget.right);
    console.log("centerX:", widget.centerX);
    console.log("centerY:", widget.centerY);
    console.log("owner:", widget.owner);

    if (widget.owner) {
        console.log("owner.x:", widget.owner.x);
        console.log("owner.y:", widget.owner.y);
        console.log("owner.width:", widget.owner.width);
        console.log("owner.height:", widget.owner.height);
    }
}

注意事项

  1. 父容器尺寸:Widget 基于父容器的尺寸计算相对位置,确保父容器已设置尺寸
  2. 属性冲突:同时设置相反方向的边距(如 top + bottom)会拉伸元素
  3. 性能考虑:避免频繁调用 resetLayout(),只在必要时更新
  4. 居中偏移centerXcenterY 的值为 0 时完全居中,正值向右/下偏移,负值向左/上偏移
  5. 组件生命周期:Widget 在 onEnable 时自动计算布局,之后监听父容器的 RESIZE 事件
  6. 静态实例:可使用 Laya.Widget.EMPTY 作为空 Widget 实例的替代
  7. 重置属性:调用 onReset() 会将所有边界和中心属性设为 null

相关文档

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐