我已成功为我的主Activity
实施onRetainNonConfigurationInstance()
,以便在屏幕方向更改中保存和恢复某些关键组件。
但看起来,当方向发生变化时,我的自定义视图将从头开始重新创建。这是有道理的,虽然在我的情况下它很不方便,因为所讨论的自定义视图是X / Y图,并且绘制的点存储在自定义视图中。
是否有一种狡猾的方法来实现类似于onRetainNonConfigurationInstance()
的自定义视图,或者我是否需要在自定义视图中实现允许我获取并设置其“状态”的方法?
答案 0 :(得分:439)
我认为这是一个更简单的版本。 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);
}
}
答案 1 :(得分:405)
您可以通过实施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类之间分开。您应该完成Parcel
课程中SavedState
的所有阅读和写作工作。然后,您的View类可以完成提取状态成员的工作,并完成将类恢复到有效状态所需的工作。
注意:如果View#onSavedInstanceState
返回值&gt; = 0,则会自动为您调用View#onRestoreInstanceState
和View#getId
。当您在xml中为其指定id或调用{{1手动。否则,您必须致电setId
并将Parcelable返回到您在View#onSaveInstanceState
中获得的地块以保存状态,然后将其读取并从Activity#onSaveInstanceState
传递给View#onRestoreInstanceState
。
另一个简单的例子是CompoundButton
答案 2 :(得分: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;
}
}
答案 3 :(得分:8)
这里的答案已经很好了,但不一定适用于自定义ViewGroups。要让所有自定义视图保留其状态,您必须覆盖每个类中的onSaveInstanceState()
和onRestoreInstanceState(Parcelable state)
。
您还需要确保它们都具有唯一的ID,无论它们是从xml中膨胀还是以编程方式添加。
我想出的非常像Kobor42的回答,但错误仍然存在,因为我是以编程方式将视图添加到自定义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);
}
}
答案 4 :(得分:5)
轻松使用Kotlin
@Parcelize
class MyState(val superSaveState: Parcelable?, val loading: Boolean) : View.BaseSavedState(superSaveState), 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)
loading = myState?.loading ?: false
//redraw
}
}
答案 5 :(得分:2)
我遇到的问题是onRestoreInstanceState用最后一个视图的状态还原了所有自定义视图。我通过将这两种方法添加到自定义视图中来解决了这个问题:
@Override
protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
dispatchFreezeSelfOnly(container);
}
@Override
protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
dispatchThawSelfOnly(container);
}
答案 6 :(得分:1)
除了使用While SDK is an interface, usually functions that allows the devs to interact with the library. It can also be groups of libraries for certain task
和onSaveInstanceState
,还可以使用ViewModel
。使您的数据模型扩展onRestoreInstanceState
,然后每次重新创建Activity时,都可以使用ViewModel
获取模型的相同实例:
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
中的dependencies
:
app/build.gradle
请注意,您的implementation "android.arch.lifecycle:extensions:1.1.1"
implementation "android.arch.lifecycle:viewmodel:1.1.1"
扩展了MyActivity
,而不仅仅是扩展了FragmentActivity
。
您可以在此处了解有关ViewModels的更多信息:
答案 7 :(得分:0)
要增加其他答案-如果您有多个具有相同ID的自定义复合视图,并且它们都在配置更改时以最后一个视图的状态恢复,那么您要做的就是告诉该视图仅分派保存/通过覆盖两种方法将事件恢复为自身。
class MyCompoundView : ViewGroup {
...
override fun dispatchSaveInstanceState(container: SparseArray<Parcelable>) {
dispatchFreezeSelfOnly(container)
}
override fun dispatchRestoreInstanceState(container: SparseArray<Parcelable>) {
dispatchThawSelfOnly(container)
}
}
要了解发生了什么以及为什么起作用,请see this blog post。基本上,每个复合视图都共享复合视图的子视图ID,并且状态恢复会很混乱。通过仅为复合视图本身调度状态,我们可以防止其子级从其他复合视图获取混合消息。
答案 8 :(得分:0)
我发现this answer在Android版本9和10上导致了某些崩溃。我认为这是一种好方法,但是当我查看某些Android code时,我发现它缺少构造函数。答案很老,所以当时可能没有必要了。当我添加缺少的构造函数并从创建者调用它时,崩溃已修复。
所以这是编辑后的代码:
public class CustomView extends View {
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];
}
};
}
}
答案 9 :(得分:0)
基于@Fletcher Johns 的回答,我想出了:
open class AddressView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
defStyleRes: Int = 0
) : LinearLayout(context, attrs, defStyleAttr, defStyleRes) {
protected lateinit var countryInputLayout: TextInputLayout
protected lateinit var countryAutoCompleteTextView: CountryAutoCompleteTextView
protected lateinit var cityInputLayout: TextInputLayout
protected lateinit var cityEditText: CityEditText
protected lateinit var postCodeInputLayout: TextInputLayout
protected lateinit var postCodeEditText: PostCodeEditText
protected lateinit var streetInputLayout: TextInputLayout
protected lateinit var streetEditText: StreetEditText
init {
initView()
}
private fun initView() {
val view = inflate(context, R.layout.view_address, this)
orientation = VERTICAL
countryInputLayout = view.findViewById(R.id.countryInputLayout)
countryAutoCompleteTextView = view.findViewById(R.id.countryAutoCompleteTextView)
streetInputLayout = view.findViewById(R.id.streetInputLayout)
streetEditText = view.findViewById(R.id.streetEditText)
cityInputLayout = view.findViewById(R.id.cityInputLayout)
cityEditText = view.findViewById(R.id.cityEditText)
postCodeInputLayout = view.findViewById(R.id.postCodeInputLayout)
postCodeEditText = view.findViewById(R.id.postCodeEditText)
}
// Declare your direct and indirect child views that need to be saved
private val childrenToSave get() = mapOf<String, View>(
"coutryIL" to countryInputLayout,
"countryACTV" to countryAutoCompleteTextView,
"streetIL" to streetInputLayout,
"streetET" to streetEditText,
"cityIL" to cityInputLayout,
"cityET" to cityEditText,
"postCodeIL" to postCodeInputLayout,
"postCodeET" to postCodeEditText,
)
private var viewIds: HashMap<String, Int>? = null
override fun onSaveInstanceState(): Parcelable? {
// Create a bundle to put super parcelable in
val bundle = Bundle()
bundle.putParcelable(SUPER_INSTANCE_STATE, super.onSaveInstanceState())
// Store viewIds in the bundle - initialize if necessary.
if (viewIds == null) {
childrenToSave.values.forEach { view -> view.id = generateViewId() }
viewIds = HashMap<String, Int>(childrenToSave.mapValues { (key, view) -> view.id })
}
bundle.putSerializable(STATE_VIEW_IDS, viewIds)
return bundle
}
override fun onRestoreInstanceState(state: Parcelable?) {
// We know state is a Bundle:
val bundle = state as Bundle
// Get mViewIds out of the bundle
viewIds = bundle.getSerializable(STATE_VIEW_IDS) as HashMap<String, Int>
// For each id, assign to the view of same index
if (viewIds != null) {
viewIds!!.forEach { (key, id) -> childrenToSave[key]!!.id = id }
}
super.onRestoreInstanceState(bundle.getParcelable(SUPER_INSTANCE_STATE))
}
companion object {
private const val SUPER_INSTANCE_STATE = "saved_instance_state_parcelable"
private const val STATE_VIEW_IDS = "state_view_ids"
}
}