4、窗口管理:创建窗口、设置窗口属性(大小、位置、透明度)、窗口类型(应用窗口、背景窗口、覆盖窗口)

窗口管理,说白了就是告诉Screen系统:「嘿,给我一块画布,我要在上面画东西。」

这块画布怎么摆、多大、能不能让后面的东西透出来,都由你来定。我在项目中见过不少新手,上来就急着画图,结果窗口属性没设对,画面要么跑偏,要么叠得乱七八糟。嗯,咱们先把窗口管好,再谈渲染。

4.1 创建窗口:你的第一块画布

创建窗口的核心API就一个——screen_create_window()。但别小看它,参数里藏着不少门道。

screen_window_t win;
int rc = screen_create_window(&win, screen_ctx);
if (rc != 0) {
    // 处理错误,我习惯直接assert或者打日志
}

第一个参数是窗口句柄,第二个是之前创建的context。创建成功后,win就是一个「空壳子」——它还没有大小、没有位置、没有类型。你得一步步给它「穿衣服」。

我的习惯:创建窗口后立刻检查返回值。QNX的错误码有时候很隐晦,比如返回ENOMEM,可能是显存不够了。我曾经在低端硬件上踩过这个坑,后来加了个重试机制才搞定。

4.2 设置窗口属性:大小、位置、透明度

窗口属性通过screen_set_window_property_iv()来设置。注意那个「iv」后缀——它表示设置的是整型数组。Screen里大部分属性都用整型表示,包括坐标、尺寸、透明度。

4.2.1 大小和位置

int pos_size[4] = {100, 200, 800, 480}; // x, y, width, height
screen_set_window_property_iv(win, SCREEN_PROPERTY_POSITION, pos_size);
screen_set_window_property_iv(win, SCREEN_PROPERTY_SIZE, &pos_size[2]);

这里有个细节:SCREEN_PROPERTY_POSITIONSCREEN_PROPERTY_SIZE是分开设置的。为什么不分一个接口搞定?我猜是为了灵活性——有时候你只想改位置,不想动大小。

坐标原点在屏幕左上角,x向右为正,y向下为正。嗯,和大多数图形系统一样。

4.2.2 透明度

透明度用SCREEN_PROPERTY_ALPHA控制,取值范围0~255。0是全透明,255是不透明。

int alpha = 128; // 半透明
screen_set_window_property_iv(win, SCREEN_PROPERTY_ALPHA, &alpha);
注意:透明度生效的前提是窗口的像素格式支持alpha通道。如果你用的是SCREEN_FORMAT_RGBX8888,那alpha值会被忽略。必须用SCREEN_FORMAT_RGBA8888才行。我曾经在这个问题上浪费了半天,画面死活透不出来,最后发现是格式选错了。

4.3 窗口类型:三种核心角色

Screen把窗口分成几种类型,每种类型决定了窗口在合成时的行为。最常用的三种是:应用窗口、背景窗口、覆盖窗口。

窗口类型 枚举值 典型用途 Z序位置
应用窗口 SCREEN_APPLICATION_WINDOW 普通App界面 中间层
背景窗口 SCREEN_BACKGROUND_WINDOW 壁纸、桌面背景 最底层
覆盖窗口 SCREEN_OVERLAY_WINDOW 状态栏、弹窗、HUD 最顶层

4.3.1 应用窗口(Application Window)

这是最常用的类型。你的主界面、二级页面,都用它。

int type = SCREEN_APPLICATION_WINDOW;
screen_set_window_property_iv(win, SCREEN_PROPERTY_TYPE, &type);

应用窗口之间可以互相遮挡,谁在上面取决于创建顺序或者你手动调整的Z序。我一般用SCREEN_PROPERTY_ZORDER来控制层级,数值越大越靠上。

4.3.2 背景窗口(Background Window)

背景窗口永远在最底层。它通常用来显示壁纸或者纯色背景。

int type = SCREEN_BACKGROUND_WINDOW;
screen_set_window_property_iv(win, SCREEN_PROPERTY_TYPE, &type);

你想想看,如果所有窗口都是应用窗口,那最底下的窗口被挡住的部分就浪费了。背景窗口的存在,就是为了让合成器知道:「这个窗口不需要和其他窗口做复杂的混合计算,直接垫底就行。」

性能提示:背景窗口通常不需要alpha通道,用SCREEN_FORMAT_RGBX8888就够了。这样可以减少显存带宽消耗。我在做车载仪表盘时,背景窗口直接用了纯色填充,连buffer都省了。

4.3.3 覆盖窗口(Overlay Window)

覆盖窗口永远在最顶层。它适合做状态栏、通知弹窗、音量调节条这类需要「浮」在所有界面之上的东西。

int type = SCREEN_OVERLAY_WINDOW;
screen_set_window_property_iv(win, SCREEN_PROPERTY_TYPE, &type);

覆盖窗口有个特点:它不参与常规的Z序排序。不管你创建多少个覆盖窗口,它们都在应用窗口之上。多个覆盖窗口之间,可以用Z序来调整相对位置。

避坑指南:我曾经在一个项目中,把弹窗设成了覆盖窗口,结果发现它挡住了状态栏。后来才意识到,状态栏本身也是覆盖窗口。解决办法是给状态栏设一个更高的Z序值。记住:覆盖窗口之间也是分先来后到的。

4.4 窗口属性的完整设置流程

把上面这些串起来,一个典型的窗口初始化流程是这样的:

// 1. 创建窗口
screen_window_t win;
screen_create_window(&win, screen_ctx);

// 2. 设置类型
int type = SCREEN_APPLICATION_WINDOW;
screen_set_window_property_iv(win, SCREEN_PROPERTY_TYPE, &type);

// 3. 设置位置和大小
int pos[2] = {100, 200};
int size[2] = {800, 480};
screen_set_window_property_iv(win, SCREEN_PROPERTY_POSITION, pos);
screen_set_window_property_iv(win, SCREEN_PROPERTY_SIZE, size);

// 4. 设置透明度(如果需要)
int alpha = 255;
screen_set_window_property_iv(win, SCREEN_PROPERTY_ALPHA, &alpha);

// 5. 设置像素格式
int format = SCREEN_FORMAT_RGBA8888;
screen_set_window_property_iv(win, SCREEN_PROPERTY_FORMAT, &format);

// 6. 创建buffer(下一章会详细讲)
screen_create_window_buffers(win, 2);

嗯,到这里,窗口的「骨架」就搭好了。接下来就可以往里面填充内容了。

4.5 小结

窗口管理是Screen编程的第一步,也是最容易出错的一步。我个人建议:

  • 先确定窗口类型,再设其他属性。类型决定了行为,改起来牵一发动全身。
  • 透明度别乱用,除非你真的需要半透明效果。每个带alpha的窗口都会增加合成器的计算量。
  • 调试时,先检查窗口属性是否设置成功。我经常用screen_get_window_property_iv()把属性读回来验证一遍。

下一章,我们会深入buffer管理——窗口有了,数据怎么往里塞?到时候见。