在 Kanzi Android framework (droidfw) 中观察状态变化和处理事件。¶
观察状态更改¶
您可以实现 KanziViewListener 来监听 KanziView 中的状态变化。使用此方法注入自定义初始化和反初始化逻辑。
例如:
使用
KanziViewListener.onAttachedToWindow回调来访问域,注册 Kanzi Engine Java 插件或手动设置视图的节点树。view.addListener(new KanziViewListener() { ... @Override public void onAttachedToWindow(View view, Domain domain) { // Register a plugin. domain.registerPlugin(new MyPlugin()); // Manually setup the node tree. // Instantiate a node. ObjectRef<TextBlock2D> text = TextBlock2D.create(domain, "text"); // Set as root of the view. ((KanziView) view).setRoot(text.get()); // Set properties on a node. text.get().setProperty(TextBox2D.TextProperty, "Hello Kanzi!");
view.addListener(object : KanziViewListener { ... override fun onAttachedToWindow(view: View, domain: Domain) { // Register a plugin. domain.registerPlugin(MyPlugin()) // Manually setup the node tree. // Instantiate a node. val text = TextBlock2D.create(domain, "text") // Set as root of the view. (view as KanziView).root = text.get() // Set properties on a node. text.get().setProperty(TextBox2D.TextProperty, "Hello Kanzi!")
使用
KanziViewListener.onStartupPrefabLoaded回调来访问和修改您配置的启动预设实例化的节点树。view.addListener(new KanziViewListener() { ... @Override public void onStartupPrefabLoaded(View view, Domain domain) { // Look up a child node starting from the root. TextBox2D text = ((KanziView)view).getRoot().lookupNode("#Text"); // Set properties on a node. text.setProperty(TextBox2D.TextProperty, "Hello world!");
view.addListener(object : KanziViewListener { ... override fun onStartupPrefabLoaded(view: View, domain: Domain) { // Look up a child node starting from the root. val text = (view as KanziView).root.lookupNode<TextBox2D>("#Text") // Set properties on a node. text.setProperty(TextBox2D.TextProperty, "Hello world!")
要了解有关 Kanzi Engine Java 插件的更多信息,请参阅 创建 Kanzi Engine Java 插件。
处理输入¶
默认情况下,KanziSurfaceView 和 KanziTextureView 处理输入事件。如果您希望 Kanzi Engine 处理使用 KanziViewAdapter 的任意视图的事件,则必须手动转发此类输入事件。为此,请使用这些接口:
默认情况下,所有 Kanzi 视图将 focusableInTouchMode 设置为 True,除非通过编程或布局属性另行设置。这使得它们在被点击时获得并保持焦点。请参阅 Android touch mode。
处理来自 Android Automotive 旋转控制器的输入¶
默认情况下,KanziSurfaceView 和 KanziTextureView 处理旋转控制器输入。
旋转操作 |
Kanzi 如何处理这些操作 |
|
|---|---|---|
|
|
Kanzi Android framework (droidfw) 将这些操作处理为相应的箭头键事件。 Kanzi 不支持并忽略来自对角微调的输入。 |
|
居中按钮 |
Kanzi Android framework (droidfw) 尝试为聚焦的 Kanzi 视图启用直接操作模式。如果 Kanzi Android framework (droidfw) 无法启用直接操作模式,它会将按键事件转发给 Kanzi Engine。Kanzi Engine 将旋转中心按钮的按键代码 |
|
上一步按钮 (Back button) |
Kanzi Android framework (droidfw) 将键事件转发到 Kanzi Engine。如果 Kanzi Engine 不消费该事件,Kanzi Android framework (droidfw) 将禁用 Kanzi 视图的直接操作模式。 |
|
|
Android 发送一个通用运动事件,其中包含旋转方向和按压次数。 Kanzi 视图将所有通用运动事件转发到
|
要禁用默认的旋转控制器输入处理,使用以下方法之一:
在布局文件中,为 Kanzi 视图元素,将
rotaryHandlingEnabled属性设置为false:<com.rightware.kanzi.KanziSurfaceView android:id="@+id/rotaryview" app:name="Rotary" app:kzbPathList="rotary.kzb" app:startupPrefabUrl="kzb://rotary/StartupPrefab" app:rotaryHandlingEnabled="false"/>
在活动代码中,将
false传递给KanziViewAdapter.setRotaryHandlingEnabled函数调用:protected void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.activity_main); mView = findViewById(R.id.rotaryview); mView.getAdapter().setRotaryHandlingEnabled(false); }
处理方向更改¶
当屏幕方向发生变化时,Android 应用程序默认情况下会销毁并重新创建活动。这允许应用程序为纵向和横向屏幕使用备用布局,但这也意味着您会丢失所有内部 Kanzi 状态。
您可以通过在 AndroidManifest.xml 文件中添加 android:configChanges="orientation" 属性来覆盖此设置。使用此选项,活动在方向更改时不再被销毁,而是在活动上调用 onConfigurationChanged()。手动将此事件转发到 KanziView.handleOrientationChange。此选项避免重新创建 Kanzi 控件,因此保留了所有内部状态。请参阅 Android 配置更改。
您可以在视图拥有表面之前或视图失去表面之后转发该事件。在这种情况下,Kanzi 会忽略该事件。
该事件不会旋转 Kanzi 内容。要设置 Kanzi 内容的方向,请使用 屏幕节点 的 Orientation 属性。
处理 Android 生命周期¶
虽然 KanziSurfaceView 和 KanziTextureView 默认情况下处理窗口和表面事件,但使用 KanziViewAdapter 的任意视图必须通过 KanziViewAdapter.handleAttachedToWindow、KanziViewAdapter.handleSurfaceCreated、KanziViewAdapter.handleSurfaceChanged、KanziViewAdapter.handleSurfaceDestroyed 和 KanziViewAdapter.handleDetachedFromWindow 接口手动转发它希望 Kanzi 处理的窗口和表面事件。
处理视图的用户可见性¶
Kanzi Android framework (droidfw) 会自动持续跟踪每个已连接的视图对用户是否可见,并停止隐藏视图的 rendering 和输入处理。
要检查视图是否对用户可见,Kanzi Android framework (droidfw) 考虑:
视图的可见性状态
视图的所有祖先的可见性状态
视图的 alpha 值
通过
View.getGlobalVisibleRect()计算的视图的有效可见部分。
使用 KanziViewAdapter 的任意视图必须手动转发可见性事件,以便通过 KanziViewAdapter.handleVisibilityChange 接口处理 Kanzi。
空闲状态¶
Kanzi Android framework (droidfw) 在没有工作时会自动挂起不必要的操作。当至少一个 Kanzi 视图处于活动状态时,Kanzi Android framework (droidfw) 会在有工作可做时唤醒。当没有视图处于活动状态时,Kanzi Android framework (droidfw) 不运行任何帧。请参阅 应用程序空闲状态 和 在没有视图处于活动状态时提交任务。
为了在暂停期间检测来自 Kanzi 主循环外部的更改,Kanzi Android framework (droidfw) 可以:
订阅 Android
MessageQueue.IdleHandler事件。在 Android Choreographer 上运行轻量级任务。
默认情况下,Kanzi Android framework (droidfw) 订阅 Android 空闲事件,并在 Android 应用程序进入空闲状态时检查外部更改。在此模式下,Kanzi 在暂停期间的 CPU 使用率真正降为零。
您可以通过调用 KanziRuntime.enableSuspendedChangeTrackingTask 将 Kanzi 切换为使用后台任务。在此模式下,Kanzi 以通过 KanziRuntime.setSuspendedChangeCheckRate 设置的频率在 Android Choreographer 上运行轻量级任务。通过更改此频率,您可以控制 Kanzi 检查外部更改的频率。默认值为 0 时,Kanzi 以显示器 vsync 频率跟踪外部更改。您可以进一步降低此频率及相关的 CPU 消耗,但代价是在有工作时唤醒会延迟。此频率独立于 rendering 和动画的频率,后两者由帧率限制控制。
在没有视图处于活动状态时提交任务¶
Kanzi Android framework (droidfw) 仅在至少一个 Kanzi 视图处于活动状态时运行 Kanzi 主循环。Kanzi 视图在拥有表面且其可见性为 VISIBLE 时处于活动状态。当 KanziTextureView 的 activity 停止时,该视图也不再处于活动状态。仅因用户可见性而被 Kanzi Android framework (droidfw) 隐藏的视图(例如因为被另一个视图遮挡)仍保持活动状态。请参阅 处理视图的用户可见性。
当没有视图处于活动状态时,例如您的应用程序在后台时,Kanzi 不运行任何帧。在此期间您提交给 TaskDispatcher 的任务会处于等待状态。Kanzi 会在视图再次变为活动状态后的第一帧中执行所有这些任务。如果您的应用程序持续以高频率提交任务,例如每次数据源更新时都提交任务,这些任务就会堆积。它们会使视图的第一帧延迟,延迟时长为执行所有这些任务所需的时间。
在没有视图处于活动状态期间,不要为每次更新都提交任务。而应保留每个数据项的最新值,并且仅在没有等待中的应用这些值的任务时才提交任务。这样,无论没有视图处于活动状态的时间有多长,最多只有一个任务处于等待状态。例如:
private final AtomicReference<String> mLatestValue = new AtomicReference<>();
private final AtomicBoolean mTaskPending = new AtomicBoolean(false);
// Set this to domain.getTaskDispatcher(), for example in
// KanziViewListener.onAttachedToWindow. Start the updates only after that.
private volatile TaskDispatcher mTaskDispatcher;
// Called from any thread for each update of the data source.
void onDataChanged(String value)
{
mLatestValue.set(value);
if (mTaskPending.compareAndSet(false, true))
{
mTaskDispatcher.submit(() -> {
mTaskPending.set(false);
// Set the latest value to a Kanzi object, for example a data object.
applyValue(mLatestValue.get());
});
}
}
private val latestValue = AtomicReference<String>()
private val taskPending = AtomicBoolean(false)
// Set this to domain.taskDispatcher, for example in
// KanziViewListener.onAttachedToWindow. Start the updates only after that.
@Volatile
private lateinit var taskDispatcher: TaskDispatcher
// Called from any thread for each update of the data source.
fun onDataChanged(value: String) {
latestValue.set(value)
if (taskPending.compareAndSet(false, true)) {
taskDispatcher.submit {
taskPending.set(false)
// Set the latest value to a Kanzi object, for example a data object.
applyValue(latestValue.get())
}
}
}