OnRetainCustomConfigurationInstance 之后所有编辑文本中的重复文本
Duplicated text in all EditTexts after OnRetainCustomConfigurationInstance
当我使用 onRetainCustomNonConfigurationInstance 保存我的自定义 EditText 中包含的文本时,在我旋转设备后,最后一个 EditText 中包含的文本被复制到目标布局中的所有其他文本。
在断点调试的过程中,发现所有的文本值一路都是正确的。但是在更改完成后,所有 EditText 都会显示相同的文本(最后一个的文本),并且焦点将放在布局中的第一个。
我在尽可能简单的项目中复制了这种行为。我尝试了 android API 级别 24 和 28。
这种行为从何而来,我该如何解决?
MainActivity.java :
public class MainActivity extends AppCompatActivity {
private ArrayList<CustomEdit> editList=new ArrayList<CustomEdit>();
private LinearLayout layout;
private Button addButton;
@RequiresApi(api = Build.VERSION_CODES.M)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
addButton=findViewById(R.id.add_button);
layout = findViewById(R.id.layout);
addButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
addEdit();
}
});
CustomSave data = (CustomSave)getLastCustomNonConfigurationInstance();
if(data==null) return;
for(int i = 0; i<data.texts.size();i++){
addEdit(data.texts.get(i));
}
}
@Override
public Object onRetainCustomNonConfigurationInstance() {
CustomSave data = save();
return data;}
private CustomSave save(){
ArrayList<String> texts = new ArrayList<String>();
for(int i =0; i<editList.size(); i++)
texts.add(editList.get(i).getText());
return new CustomSave(texts);}
/**
* Create a new custom EditText with hint
*/
private void addEdit(){
CustomEdit newEdit = new CustomEdit(this,editList.size());
layout.addView(newEdit,editList.size());
editList.add(newEdit);}
/**
* Create a new custom editText with text
* @param text
*/
private void addEdit(String text){
CustomEdit newEdit;
if(text==null) newEdit = new CustomEdit(this, editList.size());
else newEdit = new CustomEdit(this, editList.size(),text);
layout.addView(newEdit,editList.size());
editList.add(newEdit);}
}
activity_main.xml :
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="20dp"
android:text="Title"
android:gravity ="center"/>
<HorizontalScrollView
android:id="@+id/scroll1"
android:layout_below="@id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="10dp"
android:paddingLeft="10dp">
<LinearLayout
android:id="@+id/layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/add_button"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:text="+"/>
</LinearLayout>
</HorizontalScrollView>
</RelativeLayout>
CustomEdit.java :
public class CustomEdit extends RelativeLayout {
private EditText editText;
private Button closeButton;
private int indexNumber;
public CustomEdit(Context context, int indexNumber) {
super(context);
this.indexNumber =indexNumber;
init();
}
public CustomEdit(Context context, int indexNumber, String text){
super(context);
this.indexNumber=indexNumber;
init();
editText.setText(text);
}
public CustomEdit(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public CustomEdit(Context context, AttributeSet attrs, int defStyleAttr){
super(context, attrs, defStyleAttr);
init();
}
private void init(){
inflate(getContext(),R.layout.custom_edit_text,this);
editText = (EditText)findViewById(R.id.edit);
editText.setHint("EditText "+(indexNumber+1));
closeButton = (Button)findViewById(R.id.close_button);
closeButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
((MainActivity)getContext()).closeEdit(indexNumber);
}
});
}
public String getText(){
return editText.getText().toString();
}
}
custom_edit_text.xml :
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="100dp"
android:layout_height="40dp">
<EditText
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/edit"
android:inputType="text"
android:hint="Element"
android:maxLength="30"/>
<Button
android:id="@+id/close_button"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_alignRight="@id/edit"
android:layout_alignEnd="@id/edit"
android:layout_alignTop="@id/edit"
android:text="X"/>
</RelativeLayout>
CustomSave.java :
public class CustomSave {
public ArrayList<String> texts;
CustomSave(ArrayList<String> texts){
this.texts = texts;
}
}
谢谢。
对于如何解决这个问题,您有两种选择:将您的代码移动到 activity 生命周期中的不同点,或者更改 CustomEdit
的 xml 定义。
Android生命周期
将此代码移出 onCreate()
:
CustomSave data = (CustomSave)getLastCustomNonConfigurationInstance();
if(data==null) return;
for(int i = 0; i<data.texts.size();i++){
addEdit(data.texts.get(i));
}
并将其放入 onRestoreInstanceState()
中:
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
CustomSave data = (CustomSave)getLastCustomNonConfigurationInstance();
if(data==null) return;
for(int i = 0; i<data.texts.size();i++){
addEdit(data.texts.get(i));
}
}
或
XML定义
将此属性添加到 custom_edit_text.xml
文件中的 <EditText>
标记:
android:saveEnabled="false"
您为 save/restore 文本值编写的代码没有任何问题。但是,after onCreate()
、Android 会自动执行其 own save/restore 逻辑,这会覆盖什么你完成了。
如果您将代码从 onCreate()
移动到 onRestoreInstanceState()
,那么您的代码将在 Android 的自动 save/restore 之后 运行,因此您我 "win"。或者,您可以通过添加 saveEnabled=false
属性来禁用自动 save/restore。
Android 的自动 save/restore 不起作用的原因是它基于每个视图的 android:id
属性,而您的 EditText
标签都有同一个身份证。这意味着所有四个值都使用相同的键保存,因此最后一个值会覆盖所有以前的值。
所以这里发生的事情是 Android 也在为您的自定义 EditText
实现处理状态保存,这将覆盖您的实现。由于您的 CustomEdit
实例列表是动态生成的,您可能不想依赖 Android 来保存视图的状态,因为它们没有唯一的 ID。
由于您的 CustomEdit
扩展了 custom_edit_text.xml
(将 EditText
ID 声明为 @+id/edit
),这意味着您添加到布局中的每个 CustomEdit
内部 EditText
-- R.id.edit
的相同 ID。由于它们都有相同的 ID,每个视图都会将其状态保存到该 ID,因此最后一个保存其状态的视图最终将成为恢复状态时应用于所有视图的文本。
您可以采取两种措施来避免这种情况:
在您的 custom_edit_text.xml
中,将 android:saveEnabled="false"
添加到 EditText
。这将阻止 View
的状态被保存。这是首选,因为它避免了不必要的工作。
在 onRestoreInstanceState()
当前正在恢复视图状态的地方执行状态恢复。
当我使用 onRetainCustomNonConfigurationInstance 保存我的自定义 EditText 中包含的文本时,在我旋转设备后,最后一个 EditText 中包含的文本被复制到目标布局中的所有其他文本。
在断点调试的过程中,发现所有的文本值一路都是正确的。但是在更改完成后,所有 EditText 都会显示相同的文本(最后一个的文本),并且焦点将放在布局中的第一个。
我在尽可能简单的项目中复制了这种行为。我尝试了 android API 级别 24 和 28。
这种行为从何而来,我该如何解决?
MainActivity.java :
public class MainActivity extends AppCompatActivity {
private ArrayList<CustomEdit> editList=new ArrayList<CustomEdit>();
private LinearLayout layout;
private Button addButton;
@RequiresApi(api = Build.VERSION_CODES.M)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
addButton=findViewById(R.id.add_button);
layout = findViewById(R.id.layout);
addButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
addEdit();
}
});
CustomSave data = (CustomSave)getLastCustomNonConfigurationInstance();
if(data==null) return;
for(int i = 0; i<data.texts.size();i++){
addEdit(data.texts.get(i));
}
}
@Override
public Object onRetainCustomNonConfigurationInstance() {
CustomSave data = save();
return data;}
private CustomSave save(){
ArrayList<String> texts = new ArrayList<String>();
for(int i =0; i<editList.size(); i++)
texts.add(editList.get(i).getText());
return new CustomSave(texts);}
/**
* Create a new custom EditText with hint
*/
private void addEdit(){
CustomEdit newEdit = new CustomEdit(this,editList.size());
layout.addView(newEdit,editList.size());
editList.add(newEdit);}
/**
* Create a new custom editText with text
* @param text
*/
private void addEdit(String text){
CustomEdit newEdit;
if(text==null) newEdit = new CustomEdit(this, editList.size());
else newEdit = new CustomEdit(this, editList.size(),text);
layout.addView(newEdit,editList.size());
editList.add(newEdit);}
}
activity_main.xml :
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="20dp"
android:text="Title"
android:gravity ="center"/>
<HorizontalScrollView
android:id="@+id/scroll1"
android:layout_below="@id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="10dp"
android:paddingLeft="10dp">
<LinearLayout
android:id="@+id/layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/add_button"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:text="+"/>
</LinearLayout>
</HorizontalScrollView>
</RelativeLayout>
CustomEdit.java :
public class CustomEdit extends RelativeLayout {
private EditText editText;
private Button closeButton;
private int indexNumber;
public CustomEdit(Context context, int indexNumber) {
super(context);
this.indexNumber =indexNumber;
init();
}
public CustomEdit(Context context, int indexNumber, String text){
super(context);
this.indexNumber=indexNumber;
init();
editText.setText(text);
}
public CustomEdit(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public CustomEdit(Context context, AttributeSet attrs, int defStyleAttr){
super(context, attrs, defStyleAttr);
init();
}
private void init(){
inflate(getContext(),R.layout.custom_edit_text,this);
editText = (EditText)findViewById(R.id.edit);
editText.setHint("EditText "+(indexNumber+1));
closeButton = (Button)findViewById(R.id.close_button);
closeButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
((MainActivity)getContext()).closeEdit(indexNumber);
}
});
}
public String getText(){
return editText.getText().toString();
}
}
custom_edit_text.xml :
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="100dp"
android:layout_height="40dp">
<EditText
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/edit"
android:inputType="text"
android:hint="Element"
android:maxLength="30"/>
<Button
android:id="@+id/close_button"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_alignRight="@id/edit"
android:layout_alignEnd="@id/edit"
android:layout_alignTop="@id/edit"
android:text="X"/>
</RelativeLayout>
CustomSave.java :
public class CustomSave {
public ArrayList<String> texts;
CustomSave(ArrayList<String> texts){
this.texts = texts;
}
}
谢谢。
对于如何解决这个问题,您有两种选择:将您的代码移动到 activity 生命周期中的不同点,或者更改 CustomEdit
的 xml 定义。
Android生命周期
将此代码移出 onCreate()
:
CustomSave data = (CustomSave)getLastCustomNonConfigurationInstance(); if(data==null) return; for(int i = 0; i<data.texts.size();i++){ addEdit(data.texts.get(i)); }
并将其放入 onRestoreInstanceState()
中:
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
CustomSave data = (CustomSave)getLastCustomNonConfigurationInstance();
if(data==null) return;
for(int i = 0; i<data.texts.size();i++){
addEdit(data.texts.get(i));
}
}
或
XML定义
将此属性添加到 custom_edit_text.xml
文件中的 <EditText>
标记:
android:saveEnabled="false"
您为 save/restore 文本值编写的代码没有任何问题。但是,after onCreate()
、Android 会自动执行其 own save/restore 逻辑,这会覆盖什么你完成了。
如果您将代码从 onCreate()
移动到 onRestoreInstanceState()
,那么您的代码将在 Android 的自动 save/restore 之后 运行,因此您我 "win"。或者,您可以通过添加 saveEnabled=false
属性来禁用自动 save/restore。
Android 的自动 save/restore 不起作用的原因是它基于每个视图的 android:id
属性,而您的 EditText
标签都有同一个身份证。这意味着所有四个值都使用相同的键保存,因此最后一个值会覆盖所有以前的值。
所以这里发生的事情是 Android 也在为您的自定义 EditText
实现处理状态保存,这将覆盖您的实现。由于您的 CustomEdit
实例列表是动态生成的,您可能不想依赖 Android 来保存视图的状态,因为它们没有唯一的 ID。
由于您的 CustomEdit
扩展了 custom_edit_text.xml
(将 EditText
ID 声明为 @+id/edit
),这意味着您添加到布局中的每个 CustomEdit
内部 EditText
-- R.id.edit
的相同 ID。由于它们都有相同的 ID,每个视图都会将其状态保存到该 ID,因此最后一个保存其状态的视图最终将成为恢复状态时应用于所有视图的文本。
您可以采取两种措施来避免这种情况:
在您的
custom_edit_text.xml
中,将android:saveEnabled="false"
添加到EditText
。这将阻止View
的状态被保存。这是首选,因为它避免了不必要的工作。在
onRestoreInstanceState()
当前正在恢复视图状态的地方执行状态恢复。