如何防止自定义视图在屏幕方向更改中丢失状态
我已经成功实现onRetainNonConfigurationInstance()
为我的主要Activity
保存和恢复跨屏幕方向更改某些关键组件。
但是,看起来,我的自定义视图正在重新创建方向改变时从头开始。 这是有道理的,虽然在我的情况下,这是不方便的,因为有问题的自定义视图是一个X / Y的情节,绘制点存储在自定义视图。
有没有一种狡猾的方式来实现自定义视图onRetainNonConfigurationInstance()
相似的东西,还是我只需要在自定义视图中实现方法,让我得到并设置其“状态”?
您可以通过实现View#onSaveInstanceState
和View#onRestoreInstanceState
并扩展View.BaseSavedState
类来实现。
public class CustomView extends View { private int stateToSave; ... @Override public Parcelable onSaveInstanceState() { //begin boilerplate code that allows parent classes to save state Parcelable superState = super.onSaveInstanceState(); SavedState ss = new SavedState(superState); //end ss.stateToSave = this.stateToSave; return ss; } @Override public void onRestoreInstanceState(Parcelable state) { //begin boilerplate code so parent classes can restore state if(!(state instanceof SavedState)) { super.onRestoreInstanceState(state); return; } SavedState ss = (SavedState)state; super.onRestoreInstanceState(ss.getSuperState()); //end this.stateToSave = ss.stateToSave; } static class SavedState extends BaseSavedState { int stateToSave; SavedState(Parcelable superState) { super(superState); } private SavedState(Parcel in) { super(in); this.stateToSave = in.readInt(); } @Override public void writeToParcel(Parcel out, int flags) { super.writeToParcel(out, flags); out.writeInt(this.stateToSave); } //required field that makes Parcelables from a Parcel public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() { public SavedState createFromParcel(Parcel in) { return new SavedState(in); } public SavedState[] newArray(int size) { return new SavedState[size]; } }; } }
该工作在View和View的SavedState类之间进行分割。 您应该在SavedState
类中完成从Parcel
读写的所有工作。 然后你的View类可以完成提取状态成员的工作,并完成将类返回到有效状态所需的工作。
注意: View#onSavedInstanceState
和View#onRestoreInstanceState
会自动为你调用,如果View#getId
返回值> = 0。当你在xml中给它一个id或者手动调用setId
时,会发生这种情况。 否则,您必须调用View#onSaveInstanceState
并将返回的View#onSaveInstanceState
写入您在Activity#onSaveInstanceState
获取的包中,以保存状态并随后读取它,并将其从Activity#onRestoreInstanceState
传递到View#onRestoreInstanceState
Activity#onRestoreInstanceState
。
另一个简单的例子是CompoundButton
我认为这是一个更简单的版本。 Bundle
是一个实现Parcelable
的内置类型
public class CustomView extends View { private int stuff; // stuff @Override public Parcelable onSaveInstanceState() { Bundle bundle = new Bundle(); bundle.putParcelable("superState", super.onSaveInstanceState()); bundle.putInt("stuff", this.stuff); // ... save stuff return bundle; } @Override public void onRestoreInstanceState(Parcelable state) { if (state instanceof Bundle) // implicit null check { Bundle bundle = (Bundle) state; this.stuff = bundle.getInt("stuff"); // ... load stuff state = bundle.getParcelable("superState"); } super.onRestoreInstanceState(state); } }
这是另外一种使用上述两种方法的变体。 结合Parcelable的速度和正确性与Bundle
的简单性:
@Override public Parcelable onSaveInstanceState() { Bundle bundle = new Bundle(); // The vars you want to save - in this instance a string and a boolean String someString = "something"; boolean someBoolean = true; State state = new State(super.onSaveInstanceState(), someString, someBoolean); bundle.putParcelable(State.STATE, state); return bundle; } @Override public void onRestoreInstanceState(Parcelable state) { if (state instanceof Bundle) { Bundle bundle = (Bundle) state; State customViewState = (State) bundle.getParcelable(State.STATE); // The vars you saved - do whatever you want with them String someString = customViewState.getText(); boolean someBoolean = customViewState.isSomethingShowing()); super.onRestoreInstanceState(customViewState.getSuperState()); return; } // Stops a bug with the wrong state being passed to the super super.onRestoreInstanceState(BaseSavedState.EMPTY_STATE); } protected static class State extends BaseSavedState { protected static final String STATE = "YourCustomView.STATE"; private final String someText; private final boolean somethingShowing; public State(Parcelable superState, String someText, boolean somethingShowing) { super(superState); this.someText = someText; this.somethingShowing = somethingShowing; } public String getText(){ return this.someText; } public boolean isSomethingShowing(){ return this.somethingShowing; } }
这里的答案已经很好,但不一定适用于自定义ViewGroups。 要让所有自定义视图保持其状态,必须在每个类中重写onSaveInstanceState()
和onRestoreInstanceState(Parcelable state)
。 您还需要确保它们都具有唯一的ID,无论它们是从xml充值还是以编程方式添加。
我想到的是非常像Kobor42的答案,但错误仍然是因为我将视图以编程方式添加到自定义ViewGroup,而不是分配唯一的ID。
mato共享的链接可以工作,但这意味着没有任何一个Views管理自己的状态 – 整个状态保存在ViewGroup方法中。
问题是,当这些ViewGroups中的多个被添加到布局时,来自xml的元素的id不再是唯一的(如果它在xml中定义的话)。 在运行时,您可以调用静态方法View.generateViewId()
来获取View的唯一标识。 这仅适用于API 17。
这是我从ViewGroup的代码(它是抽象的,mOriginalValue是一个类型变量):
public abstract class DetailRow<E> extends LinearLayout { private static final String SUPER_INSTANCE_STATE = "saved_instance_state_parcelable"; private static final String STATE_VIEW_IDS = "state_view_ids"; private static final String STATE_ORIGINAL_VALUE = "state_original_value"; private E mOriginalValue; private int[] mViewIds; // ... @Override protected Parcelable onSaveInstanceState() { // Create a bundle to put super parcelable in Bundle bundle = new Bundle(); bundle.putParcelable(SUPER_INSTANCE_STATE, super.onSaveInstanceState()); // Use abstract method to put mOriginalValue in the bundle; putValueInTheBundle(mOriginalValue, bundle, STATE_ORIGINAL_VALUE); // Store mViewIds in the bundle - initialize if necessary. if (mViewIds == null) { // We need as many ids as child views mViewIds = new int[getChildCount()]; for (int i = 0; i < mViewIds.length; i++) { // generate a unique id for each view mViewIds[i] = View.generateViewId(); // assign the id to the view at the same index getChildAt(i).setId(mViewIds[i]); } } bundle.putIntArray(STATE_VIEW_IDS, mViewIds); // return the bundle return bundle; } @Override protected void onRestoreInstanceState(Parcelable state) { // We know state is a Bundle: Bundle bundle = (Bundle) state; // Get mViewIds out of the bundle mViewIds = bundle.getIntArray(STATE_VIEW_IDS); // For each id, assign to the view of same index if (mViewIds != null) { for (int i = 0; i < mViewIds.length; i++) { getChildAt(i).setId(mViewIds[i]); } } // Get mOriginalValue out of the bundle mOriginalValue = getValueBackOutOfTheBundle(bundle, STATE_ORIGINAL_VALUE); // get super parcelable back out of the bundle and pass it to // super.onRestoreInstanceState(Parcelable) state = bundle.getParcelable(SUPER_INSTANCE_STATE); super.onRestoreInstanceState(state); } }