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

Ste*_*roy 9 android android-custom-view android-layout

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

举例来说,在NumberPicker窗口小部件中使用的TimePicker窗口小部件(注意NumberPicker未暴露给SDK).这是一个简单的小部件,其中包含三个组件number_picker.xml:一个增量按钮,一个减量按钮,以及一个EditText可以直接输入数字的组件.为了使代码与这些小部件进行交互,它们都具有标识(R.id.increment,R.id.decrementR.id.timepicker_input分别).

比方说,你有三个NumberPicker在XML布局S和你给他们不同的ID(例如R.id.hour,R.id.minute).¹那么这种布局膨胀到活动的内容视图.我们决定更改活动的方向,因此Activity.onSaveInstanceState(Bundle)有助于为具有ID的每个视图保存我们的视图状态(这是默认行为).

不幸的是,这三个NumberPicker■找EditTexts表示都有着相同的ID - R.id.timepicker_input.因此,当活动恢复时,视图层次结构中最下面的那个是其状态似乎为所有三个都保留的状态.此外,NumberPicker无论在保存时哪一个都有焦点,焦点都会转移到第一个.

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

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


¹在视图层次结构,这是设置的ID LinearLayoutNumberPicker延伸到你的ID.

Cha*_*ley 16

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

这可能听起来很复杂但实际上非常简单,API实际上为这个确切的场景做了准备.我在这里写了一篇关于如何完成的博客文章,但实际上在你的复合视图中你需要实现以下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);
}
Run Code Online (Sandbox Code Playgroud)

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