Android 在复合视图小部件上保存状态 问题:

Android 在复合视图小部件上保存状态 问题:,android,android-layout,android-custom-view,Android,Android Layout,Android Custom View,当通过使用XML定义的小部件布局,单个小部件实例的组件都具有相同的ID时,如何保存视图小部件实例状态 例子 例如,NumberPicker小部件中使用的NumberPicker小部件(请注意,NumberPicker未向SDK公开)。这是一个简单的小部件,有三个组件,它们由number\u picker.xml膨胀而成:一个递增按钮、一个递减按钮和一个EditText,您可以在其中直接输入数字。为了让代码与这些小部件交互,它们都有id(R.id.increment,R.id.decrement和

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

例子 例如,
NumberPicker
小部件中使用的
NumberPicker
小部件(请注意,
NumberPicker
未向SDK公开)。这是一个简单的小部件,有三个组件,它们由
number\u picker.xml
膨胀而成:一个递增按钮、一个递减按钮和一个
EditText
,您可以在其中直接输入数字。为了让代码与这些小部件交互,它们都有id(
R.id.increment
R.id.decrement
R.id.timepicker\u input

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

不幸的是,这三个
NumberPicker
s具有
EditText
s,它们都共享相同的ID-
R.ID.timepicker\u输入
。因此,当恢复活动时,视图层次结构中最下面的一个是其状态似乎为所有三个活动保留的活动。此外,恢复时焦点会转到第一个
NumberPicker
,而不管保存时哪一个具有焦点

TimePicker
通过单独保存状态本身来解决这个问题。不幸的是,如果没有更多的工作,这将无法保留光标位置或聚焦视图。如果它真的保持了这种状态,我不确定它是如何保持这种状态的(快速播放时间输入对话框似乎表明它可以以某种方式保持这种状态)

请参阅示例代码以演示此问题:



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

非常简单:您没有。只需在清单文件中禁用方向更改垃圾。视图状态保存机制本身就有缺陷,他们根本没有考虑清楚这一点

如果要保持状态,则不能在单个活动中重用id。这本质上意味着您不能多次使用单个布局,这使得更复杂的小部件(如TimePicker)基本上无法正常工作

您可以通过重写
dispatchSaveInstanceState
并将其插入,让孩子们保持他们的状态,但除了自己管理外,我没有找到保持注意力的方法


我想他们可以通过在不破坏API的情况下创建状态范围来解决这个问题,但是不要屏住呼吸。

我刚刚遇到了与复合视图完全相同的问题 有三个号码牌。我在另一个网站上找到了解决方案 这涉及将时间选择器输入的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代码的一行更改。简单地 加:

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

mText = (EditText) findViewById(R.id.timepicker_input);
当然,偏移量可以根据应用要求进行调整

@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);
}
我想同样的方法也适用于其他复合小部件

对于上面的示例,此方法允许 要保存和还原的单个EditText小部件,以及
还有聚焦视图。

我比赛有点晚了,但我想给出我的意见

您可以使用
android:tag
对每个
视图进行分组。因此,您可以“重新加载”每个状态

网址:

安卓:标签

为此视图提供一个包含字符串的标记,以便稍后检索 使用View.getTag()或使用View.findViewWithTag()搜索


我有一个复合小部件,其状态描述由单个整数
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());
        }
    }
}

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

我在尝试创建自己的复合视图时遇到了相同的问题。从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);
}
关于NumberPicker/TimePicker的问题,如另一条评论中所述,NumberPicker a似乎存在一个bug
@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);
}