如何防止自定义视图在屏幕方向更改时丢失状态

Bra*_*ein 243 android state screen-orientation android-activity

我已经成功实现onRetainNonConfigurationInstance()了我的主要功能,Activity以保存和恢复屏幕方向更改中的某些关键组件.

但看起来,当方向发生变化时,我的自定义视图将从头开始重新创建.这是有道理的,虽然在我的情况下它很不方便,因为所讨论的自定义视图是X/Y图,并且绘制的点存储在自定义视图中.

是否有一种狡猾的方法来实现类似于onRetainNonConfigurationInstance()自定义视图的东西,或者我是否需要在自定义视图中实现允许我获取并设置其"状态"的方法?

Kob*_*r42 453

我认为这是一个更简单的版本.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);
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 这个解决方案可能没问题,但绝对不安全.通过实现这个,你假设基本的`View`状态不是`Bundle`.当然,目前情况确实如此,但您依赖的是当前的实施事实,并不能保证是真的. (15认同)
  • 如果`onSaveInstanceState`返回一个Bundle,为什么*不会用Bundle调用``onRestoreInstanceState`? (5认同)
  • `OnRestoreInstance`是继承的.我们无法更改标题.`Parcelable`只是一个接口,`Bundle`是一个实现. (5认同)
  • 感谢这种方式更好,并且在使用SavedState框架进行自定义视图时避免BadParcelableException,因为保存的状态似乎无法为自定义SavedState正确设置类加载器! (5认同)
  • 我在一个活动中有几个相同视图的实例.它们在xml中都有唯一的id.但仍然所有人都获得了最后一个视图的设置.有任何想法吗? (3认同)
  • @Christoffer帮我:[保存的Android查看状态正确](http://trickyandroid.com/saving-android-view-state-correctly/); 在结束他使用[dispatchSaveInstanceState](http://developer.android.com/intl/es/reference/android/view/View.html#dispatchSaveInstanceState(android.util.SparseArray <android.os.Parcelable>))和[dispatchRestoreInstanceState](http://developer.android.com/intl/es/reference/android/view/View.html#dispatchRestoreInstanceState(android.util.SparseArray <android.os.Parcelable>))! (3认同)
  • @A.Steenbergen `Bundle` 始终是一个 `Parcelable`,你在这一点上是对的。但“Parcelable”并不总是“Bundle”。 (2认同)

Ric*_*ler 408

您可以通过执行做到这一点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];
          }
    };
  }
}
Run Code Online (Sandbox Code Playgroud)

该工作分为View和View的SavedState类.你应该ParcelSavedState课堂上做所有的阅读和写作工作.然后,您的View类可以完成提取状态成员的工作,并完成将类恢复到有效状态所需的工作.

注意:如果返回值> = 0 View#onSavedInstanceState,View#onRestoreInstanceState则会自动为您View#getId调用setId.当您在xml中为其指定id或手动调用时,会发生这种情况.否则,您必须调用View#onSaveInstanceState并写入Parcelable返回给你的包裹Activity#onSaveInstanceState,保存状态,然后阅读并把它传递给View#onRestoreInstanceStateActivity#onRestoreInstanceState.

另一个简单的例子是 CompoundButton

  • 请注意,您应用此选项的CustomView应具有唯一的ID设置,否则它们将彼此共享状态.SavedState是根据CustomView的id存储的,所以如果你有多个具有相同id或没有id的CustomView,那么保存在最终CustomView.onSaveInstanceState()中的parcel将被传递给CustomView.onRestoreInstanceState()的所有调用.视图已恢复. (65认同)
  • 对于那些到达这里因为在使用带有v4支持库的Fragments时不起作用的人,我注意到支持库似乎没有为你调用View的onSaveInstanceState/onRestoreInstanceState; 你必须自己从FragmentActivity或Fragment中一个方便的地方自己调用它. (14认同)
  • 这个**不能很好地工作**当为扩展RecyclerView的类保存自定义`BaseSaveState`时,你会得到`Parcel:在解组时找不到类:android.support.v7.widget.RecyclerView $ SavedState java.lang. ClassNotFoundException:android.support.v7.widget.RecyclerView $ SavedState`所以你需要做这里写的错误修复:https://github.com/ksoichiro/Android-ObservableScrollView/commit/6f915f7482635c1f4889281c7941a586f2cfb611(使用RecyclerView的ClassLoader .class加载超级状态) (7认同)
  • 这个方法对我来说不适用于两个自定义视图(一个扩展另一个).恢复视图时,我不断收到ClassNotFoundException.我不得不在Kobor42的答案中使用Bundle方法. (5认同)
  • `onSaveInstanceState()`和`onRestoreInstanceState()`应该是`protected`(就像他们的超类),而不是`public`.没理由暴露他们...... (3认同)

Blu*_*ell 18

这是使用上述两种方法的混合的另一种变体.结合速度和正确性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;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 这不起作用.我得到一个ClassCastException ......那是因为它需要一个公共静态CREATOR,以便它从包中实例化你的`State`.请查看:http://charlesharley.com/2012/programming/views-saving-instance-state-in-android/ (3认同)

Zak*_*nov 14

使用 kotlin 很容易

@Parcelize
class MyState(val superSavedState: Parcelable?, val loading: Boolean) : View.BaseSavedState(superSavedState), Parcelable


class MyView : View {

    var loading: Boolean = false

    override fun onSaveInstanceState(): Parcelable? {
        val superState = super.onSaveInstanceState()
        return MyState(superState, loading)
    }

    override fun onRestoreInstanceState(state: Parcelable?) {
        val myState = state as? MyState
        super.onRestoreInstanceState(myState?.superSaveState ?: state)

        loading = myState?.loading ?: false
        //redraw
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 干净而且工作完美!谢谢! (3认同)

Fle*_*hns 8

这里的答案已经很好了,但不一定适用于自定义ViewGroups.要使所有自定义视图保留其状态,您必须覆盖onSaveInstanceState()onRestoreInstanceState(Parcelable state)在每个类中.您还需要确保它们都具有唯一ID,无论它们是从xml中膨胀还是以编程方式添加.

我想出的非常像Kobor42的答案,但错误仍然存​​在,因为我是以编程方式将Views添加到自定义ViewGroup而不是分配唯一ID.

mato共享的链接可以工作,但这意味着没有一个单独的视图管理它们自己的状态 - 整个状态保存在ViewGroup方法中.

问题是当这些ViewGroup中的多个被添加到布局时,来自xml的元素的id不再是唯一的(如果它在xml中定义).在运行时,您可以调用静态方法View.generateViewId()来获取View的唯一ID.这仅适用于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);
    } 
}
Run Code Online (Sandbox Code Playgroud)