0%

Android外部imgui绘制(二)

在 Android 外部 imgui 绘制 (一) 里介绍了怎么实现一个外部 imgui, 其实还有一种方法,就是在 aosp 环境下编译一个可执行文件出来,有点类似于
可执行文件 + skia 绘制
但这个其实很麻烦,需要下载 android 源码 (100 多 g), 然后还要自己编译,最后还得修改代码。所以不太建议这么做。虽然效率可能确实会有提升.

获取触摸

在绘制 imgui 后,其实是没有触摸反馈的,原因很简单,在官方示例中,他的入口是

1
void android_main(struct android_app* app)

这个就很像之前 ndk-sample 的 native-activity 了.
https://github.com/android/ndk-samples/tree/main/native-activity
, 其实可以把这个官方的 imgui 示例改造成 native-activity.

注意看官方示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Poll all events. If the app is not visible, this loop blocks until g_Initialized == true.
while (ALooper_pollAll(g_Initialized ? 0 : -1, NULL, &out_events, (void**)&out_data) >= 0)
{
// Process one event
if (out_data != NULL)
out_data->process(app, out_data);

// Exit the app by returning from within the infinite loop
if (app->destroyRequested != 0)
{
// shutdown() should have been called already while processing the
// app command APP_CMD_TERM_WINDOW. But we play save here
if (!g_Initialized)
shutdown();

return;
}
}

你就可以发现它是通过调用 ALooper_pollAll 来获取系统的输入事件,
然而在我们的 hello-gl2 代码里并不能通过 ALooper_pollAll 来获取输入事件的,
那么怎么获取系统输入呢?

(ue4) 找到 android_app 并 hook onInputEvent 函数

这个方法可以在 ue4 的游戏中找到,可以把 ue4 的游戏理解成 native-activity, 然后找到 android_app, 在它的 onInputEvent 里挂钩子,将屏幕输入事件转发给 imgui.

双悬浮窗

比较简单的实现方案,将一个可点击透明的悬浮窗覆盖在 imgui 窗口上,且该悬浮窗随着 imgui 窗口移动,把对该窗口的输入事件转发到 imgui 窗口里.

读取系统触摸

这个方法就是直接读系统输入的方法了,也就是安卓源码是怎么读取屏幕输入的.
原理就是读取 /dev/input/event? 文件,这里的?表示代表屏幕输入文件的数字.

1
2
sprintf(dev_name, "/dev/input/event%d", i);
fd = open(dev_name, O_RDWR);

然后将原始的屏幕输入转成安卓的屏幕输入。最后传给 imgui 即可.
这里可以参考
linux 读取触摸屏事件数据
至于怎么加工原始的输入数据,可以参考安卓源码.

在我的手机上,触摸屏的分辨率是 14400x32000.
而我的屏幕分辨率是 1080x2400.
所以需要进一步转换.
大致如下:

1
2
x = (x-xmin) * 手机像素宽 / (xmax-xmin);
y = (y-ymin) * 手机像素高 / (ymax-ymin);

这里 xmin,xmax,ymin,ymax 可以通过命令拿到.

1
getevent -p /dev/input/event7

event

1
2
0035  : value 0, min 0, max 14399, fuzz 0, flat 0, resolution 0
0036 : value 0, min 0, max 31999, fuzz 0, flat 0, resolution 0

屏幕像素可以通过 getRealMetrics 获取.

1
2
3
4
DisplayMetrics dm = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getRealMetrics(dm);
int widthPixel = dm.widthPixels;
int heightPixel = dm.heightPixels;

参考
Android 从触碰屏幕开始的事件采集,解析及分发 (1).

屏幕旋转判断

当屏幕旋转后系统会回调 onSurfaceChanged 方法
在 onSurfaceChanged 里读取屏幕旋转方向,然后将屏幕方向赋值给 native 的全局变量.

1
2
3
4
public void onSurfaceChanged(GL10 gl, int width, int height) {
set_ImGui_Orientation(MainActivity.windowManager.getDefaultDisplay().getOrientation());
update_ImGui(width,height);
}

屏幕旋转后输入事件的处理

旋转后,屏幕坐标会旋转,但是读取到的输入坐标不会改变,此时需要将横坐标改为纵坐标,横坐标改为屏幕宽度减去横坐标.

1
io.MousePos = ImVec2(y, physic_w - x);

Android12 的改进

按照官方说明
不受信任的触摸事件被屏蔽.
需要做出如下改动

1
2
3
4
5
6
7
# A specific app
adb shell am compat disable BLOCK_UNTRUSTED_TOUCHES com.example.app

# All apps
# If you'd still like to see a Logcat message warning when a touch would be
# blocked, use 1 instead of 0.
adb shell settings put global block_untrusted_touches 0

你可以直接敲下面的命令

1
adb shell settings put global block_untrusted_touches 0

之后你的 imgui 就可以随意的拖动,点击了.