在复合视图窗口小部件上保存状态

问题描述 投票:9回答:5

Question

当使用XML定义的窗口小部件布局时,各个窗口小部件实例的组件都具有相同的ID时,如何保存视图窗口小部件实例状态?

Example

NumberPicker小部件中使用的TimePicker小部件为例(请注意,NumberPicker不会暴露给SDK)。这是一个简单的小部件,其中有三个组件从number_picker.xml中膨胀:一个增量按钮,一个减量按钮和一个EditText,您可以直接输入一个数字。为了使代码与这些小部件交互,它们都具有ID(分别为R.id.incrementR.id.decrementR.id.timepicker_input)。

假设您在XML布局中有三个NumberPickers,并为它们提供不同的ID(例如,R.id.hourR.id.minute).¹然后将此布局扩展到活动的内容视图。我们决定更改活动的方向,因此Activity.onSaveInstanceState(Bundle)有助于为每个具有ID的视图保存视图状态(这是默认行为)。

不幸的是,三个NumberPickers有EditTexts所有共享相同的ID - R.id.timepicker_input。因此,当活动恢复时,视图层次结构中最下面的那个是其状态似乎为所有这三个都保留的状态。此外,重点放在第一个NumberPicker恢复时,无论哪个人在保存时都有焦点。

TimePicker通过分别保留国家本身来解决这个问题。不幸的是,如果没有更多的工作,这将不会保留光标位置或聚焦视图。我不确定它是如何保留该状态的(如果它完全一样)(并且快速播放时间输入对话框似乎表明它可能以某种方式)。

请参阅示例代码以演示此问题:https://github.com/xxv/AndroidNumberPickerBug


¹在视图层次结构中,这将设置LinearLayout扩展到您的ID的NumberPicker的ID。

android android-layout android-custom-view
5个回答
16
投票

在尝试创建自己的复合视图时,我偶然发现了同样的问题。通过查看Android源代码,我认为实现复合视图的正确方法是复合视图本身承担保存和恢复其子实例状态的责任,并防止调用保存和恢复实例状态传递到子视图。这解决了当您在活动中具有多个相同复合视图的实例时,子视图的ID不唯一的问题。

这可能听起来很复杂但实际上非常简单,API实际上为这个确切的场景做了准备。我已经写了一篇关于如何完成的博客文章here,但实际上在你的复合视图中你需要实现以下4种方法,自定义onSaveInstanceState()和onRestoreInstanceState()以满足你的特定要求。

@Override
protected Parcelable onSaveInstanceState() {
    Parcelable superState = super.onSaveInstanceState();
    return new SavedState(superState, numberPicker1.getValue(), numberPicker2.getValue(), numberPicker3.getValue());
}

@Override
protected void onRestoreInstanceState(Parcelable state) {
    SavedState savedState = (SavedState) state;
    super.onRestoreInstanceState(savedState.getSuperState());

    numberPicker1.setValue(savedState.getNumber1());
    numberPicker2.setValue(savedState.getNumber2());
    numberPicker3.setValue(savedState.getNumber3());
}

@Override
protected void dispatchSaveInstanceState(SparseArray container) {
    // As we save our own instance state, ensure our children don't save 
    // and restore their state as well.
    super.dispatchFreezeSelfOnly(container);
}

@Override
protected void dispatchRestoreInstanceState(SparseArray container) {
    /** See comment in {@link #dispatchSaveInstanceState(android.util.SparseArray)} */
    super.dispatchThawSelfOnly(container);
}

关于NumberPicker / TimePicker的问题,如另一条评论所述,似乎有一个NumberPicker和TimePicker的错误。要修复它,您可以覆盖它们并实现我所描述的解决方案。


0
投票

我刚刚遇到与三个NumberPickers的复合视图完全相同的问题。我在另一个网站上找到了一个解决方案,该解决方案涉及将timepicker_input的Id重新分配给唯一的随机ID。这有效但很复杂,因为一旦选择了新的Id,该Id必须在配置更改中保持不变,因此需要额外的代码才能执行此操作。

对于我的应用程序,可能在99%的其他应用程序中,更简单的方法(hack)有效。我意识到Ids的命名空间有65536个唯一标识符(0x7f090000到0x7f09ffff)的空间,但我的应用程序只使用20个左右,并且它们从一开始就单调增加。此外,NumberPicker小部件本身在树中具有唯一的标识符(例如,在原始帖子中,R.id.hour,R.id.minute和R.id.second)。我的解决方案是将EditText小部件的Id重新分配给NumberPicker小部件的Id以及偏移量。

这是对NumberPicker代码的一行更改。只需添加:

mText.setId(getId() + 1000);

在NumberPicker.java中的以下行之后:

mText = (EditText) findViewById(R.id.timepicker_input);

当然可以根据应用要求调整偏移量。

我想相同的方法将适用于其他复合小部件。

对于上面的示例,此方法允许保存和恢复各个EditText小部件的状态,以及焦点视图。


0
投票

我比赛有点晚了,但我想提出意见。

你可以使用qazxsw poi对每个qazxsw poi进行分组。这样,你可以“重新加载”每个州。

来自android:tag网站:

机器人:标签

为此视图提供包含String的标记,稍后将使用View.getTag()检索或使用View.findViewWithTag()搜索。


0
投票

我有一个复合Widget,状态描述由一个整数View组成 现在覆盖以下两个方法,如下所示:

Android Developer

mValue,你正在序列化你的州。更复杂的状态将需要更复杂的存储结构。 确保正确@Override protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) { if(getId() != NO_ID) { Parcel p = Parcel.obtain(); p.writeInt(mValue); p.setDataPosition(0); container.put(getId(), new MyParcelable(p)); } } @Override protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) { if(getId() != NO_ID) { Parcelable p = container.get(getId()); if(p != null && p instanceof MyParcelable) { MyParcelable mp = (MyParcelable) p; updateAllValues(mp.getValue()); } } } 。 我只保存状态,如果小部件有一个id,我正在使用它作为parcel的标签

dispatchSaveInstanceState(),你打开你的包裹。 如果窗口小部件已分配id并且容器包含带有与此id匹配的标记的parcel,则我只执行解压缩。 setDataPosition()也必须属于正确的级别。 对于更复杂的小部件,解包会更复杂。

所需的最后一个组件是dispatchRestoreInstanceState()子类,如下所示:

Parcelable

使用此框架比处理片段和管理配置要容易得多


-3
投票

很简单:你没有。只需在清单文件中禁用方向更改废话。视图状态保存机制本质上存在缺陷,他们根本就没有想到这一点。

如果要保留状态,则不能在单个活动中重复使用ID。这实际上意味着您不能多次使用单个布局,这使得像TimePicker这样的更复杂的小部件基本上无法正常执行。

你可以通过覆盖Parcelable并将其黑客入侵来让孩子保持自己的状态,但我也没有找到一种方法来保持专注,除了自己管理它。

我认为他们可以通过制作状态范围而不破坏API来解决这个问题,但不要屏住呼吸。

© www.soinside.com 2019 - 2024. All rights reserved.