我已经成功地实现了onRetainNonConfigurationInstance()为我的主活动保存和恢复某些关键组件跨屏幕方向的变化。

但看起来,当方向改变时,我的自定义视图正在从头重新创建。这是有意义的,尽管在我的例子中不方便,因为所讨论的自定义视图是一个X/Y图形,并且绘制的点存储在自定义视图中。

是否有一种巧妙的方法来实现类似于onRetainNonConfigurationInstance()的自定义视图,或者我只需要在自定义视图中实现方法,允许我获得和设置其“状态”?


当前回答

除了使用onSaveInstanceState和onRestoreInstanceState,你还可以使用ViewModel。让你的数据模型扩展ViewModel,然后你可以在每次Activity被重新创建时使用ViewModelProviders来获得你的模型的相同实例:

class MyData extends ViewModel {
    // have all your properties with getters and setters here
}

public class MyActivity extends FragmentActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // the first time, ViewModelProvider will create a new MyData
        // object. When the Activity is recreated (e.g. because the screen
        // is rotated), ViewModelProvider will give you the initial MyData
        // object back, without creating a new one, so all your property
        // values are retained from the previous view.
        myData = ViewModelProviders.of(this).get(MyData.class);

        ...
    }
}

要使用ViewModelProviders,在app/build.gradle中添加以下依赖项:

implementation "android.arch.lifecycle:extensions:1.1.1"
implementation "android.arch.lifecycle:viewmodel:1.1.1"

注意你的MyActivity扩展了FragmentActivity而不仅仅是扩展了Activity。

你可以在这里阅读更多关于ViewModels的信息:

Android开发者指南,处理配置更改 Android开发者指南,保存UI状态,使用ViewModel处理配置更改 教程视图模型:一个简单的例子

其他回答

除了使用onSaveInstanceState和onRestoreInstanceState,你还可以使用ViewModel。让你的数据模型扩展ViewModel,然后你可以在每次Activity被重新创建时使用ViewModelProviders来获得你的模型的相同实例:

class MyData extends ViewModel {
    // have all your properties with getters and setters here
}

public class MyActivity extends FragmentActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // the first time, ViewModelProvider will create a new MyData
        // object. When the Activity is recreated (e.g. because the screen
        // is rotated), ViewModelProvider will give you the initial MyData
        // object back, without creating a new one, so all your property
        // values are retained from the previous view.
        myData = ViewModelProviders.of(this).get(MyData.class);

        ...
    }
}

要使用ViewModelProviders,在app/build.gradle中添加以下依赖项:

implementation "android.arch.lifecycle:extensions:1.1.1"
implementation "android.arch.lifecycle:viewmodel:1.1.1"

注意你的MyActivity扩展了FragmentActivity而不仅仅是扩展了Activity。

你可以在这里阅读更多关于ViewModels的信息:

Android开发者指南,处理配置更改 Android开发者指南,保存UI状态,使用ViewModel处理配置更改 教程视图模型:一个简单的例子

这里的答案已经很好了,但并不一定适用于自定义viewgroup。为了让所有自定义视图保留它们的状态,你必须在每个类中重写onSaveInstanceState()和onRestoreInstanceState(Parcelable state)。 您还需要确保它们都具有唯一的id,无论它们是从xml扩展的还是以编程方式添加的。

我想出的答案与Kobor42的答案非常相似,但错误仍然存在,因为我以编程方式将视图添加到自定义ViewGroup中,而没有分配唯一的id。

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

问题是,当多个viewgroup被添加到一个布局中时,它们在xml中的元素id不再是唯一的(如果它是在xml中定义的)。在运行时,你可以调用静态方法View. generateviewid()来获取一个视图的唯一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);
    } 
}

我发现这个答案在Android版本9和10上导致了一些崩溃。我认为这是一个很好的方法,但当我在看一些Android代码时,我发现它缺少一个构造函数。答案是相当古老的,所以在当时可能没有必要。当我添加了缺失的构造函数并从创建者调用它时,崩溃就修复了。

下面是编辑后的代码:

public class CustomView extends LinearLayout {

    private int stateToSave;

    ...

    @Override
    public Parcelable onSaveInstanceState() {
        Parcelable superState = super.onSaveInstanceState();
        SavedState ss = new SavedState(superState);

        // your custom state
        ss.stateToSave = this.stateToSave;

        return ss;
    }

    @Override
    protected void dispatchSaveInstanceState(SparseArray<Parcelable> container)
    {
        dispatchFreezeSelfOnly(container);
    }

    @Override
    public void onRestoreInstanceState(Parcelable state) {
        SavedState ss = (SavedState) state;
        super.onRestoreInstanceState(ss.getSuperState());

        // your custom state
        this.stateToSave = ss.stateToSave;
    }

    @Override
    protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container)
    {
        dispatchThawSelfOnly(container);
    }

    static class SavedState extends BaseSavedState {
        int stateToSave;

        SavedState(Parcelable superState) {
            super(superState);
        }

        private SavedState(Parcel in) {
            super(in);
            this.stateToSave = in.readInt();
        }

        // This was the missing constructor
        @RequiresApi(Build.VERSION_CODES.N)
        SavedState(Parcel in, ClassLoader loader)
        {
            super(in, loader);
            this.stateToSave = in.readInt();
        }

        @Override
        public void writeToParcel(Parcel out, int flags) {
            super.writeToParcel(out, flags);
            out.writeInt(this.stateToSave);
        }    
        
        public static final Creator<SavedState> CREATOR =
            new ClassLoaderCreator<SavedState>() {
          
            // This was also missing
            @Override
            public SavedState createFromParcel(Parcel in, ClassLoader loader)
            {
                return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N ? new SavedState(in, loader) : new SavedState(in);
            }

            @Override
            public SavedState createFromParcel(Parcel in) {
                return new SavedState(in, null);
            }

            @Override
            public SavedState[] newArray(int size) {
                return new SavedState[size];
            }
        };
    }
}

我认为这是一个更简单的版本。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;
    }
}