Why EditText in a custom compound view is re-using

2019-02-01 22:38发布

I'm trying to write a custom compound view composed by a TextView and an EditText, _compound_view.xml_:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/compoundText"
android:layout_width="match_parent"
android:layout_height="wrap_content" >

<TextView
    android:id="@+id/textLabel"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Label" />

<EditText
    android:id="@+id/textEdit"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:hint="enter text here" >
</EditText>

and this is the class extending LinearLayout:

public class CompoundView extends LinearLayout {

public CompoundView(Context context, AttributeSet attrs) {
    super(context, attrs);

    readAttributes(context, attrs);
    init(context);
}

public CompoundView(Context context) {
    super(context);

    init(context);
}

private void init(Context c) {

    final LayoutInflater inflater = (LayoutInflater) c
            .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    inflater.inflate(R.layout.compound_view, this);

}
   }

Now, if I use 2 of these View in my _activity_main.xml_:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<it.moondroid.compoundview.example.CompoundView
    android:id="@+id/compoundview1"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentTop="true" />

<it.moondroid.compoundview.example.CompoundView
    android:id="@+id/compoundview2"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_below="@id/compoundview1" />
</RelativeLayout>

and in the Activity code I only inflate the RelativeLayout, without managing onSaveInstanceState:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

}

then when I write something in the 2nd EditText and I rotate my device, the same text appears in the EditText of the first custom View.

Why is happening this behaviour?

EDIT:

I solved the issue by removing android:id and using android:tag for the EditText in compound_view.xml, then managing the saving of the EditText state in CompoundView class:

@Override
protected Parcelable onSaveInstanceState() {

    Bundle bundle = new Bundle();
    bundle.putParcelable("instanceState", super.onSaveInstanceState());
    bundle.putString("currentEdit", mEditText.getText().toString());
    bundle.putBoolean("isFocused", mEditText.hasFocus());
    return bundle;

}

@Override
protected void onRestoreInstanceState(Parcelable state) {

    if (state instanceof Bundle) {
        Bundle bundle = (Bundle) state;
        mEditText.setText(bundle.getString("currentEdit"));
        if (bundle.getBoolean("isFocused")) {
            mEditText.requestFocus();
        }
        super.onRestoreInstanceState(bundle.getParcelable("instanceState"));
        return;
    }

    super.onRestoreInstanceState(state);
}

4条回答
ゆ 、 Hurt°
2楼-- · 2019-02-01 22:47

I'll start off by saying that I haven't confirmed this... but I experienced the same issues when using a compound view, similar to what you were doing.

I think the root of the problem is how Android automatically saves the state of EditText controls, and I think it saves it by "id". So if you have multiple controls in your xml with same "id", then when it saves state, and then restores state, all controls with the same id get the same value. You can try this by adding 2 EditText contols to you normal xml and give them the same android:id value and see if they end up getting the same value.

In your case, you can try to NOT use ids in the compound view and rather find the elements another way, either by tag (View.findViewWithTag), or by name, and see if that makes a difference.

In my case, I solved the problem by doing the latter.

查看更多
劳资没心,怎么记你
3楼-- · 2019-02-01 22:49

You need to disable SaveEnabled property of EditText using android:saveEnabled="false"

In your custom view, you are inflating layout from XML which has ID defined. Android OS has default functionality to save and restore the state of the view if the view has ID defined.

It means that it will save the text of the EditText when Activity gets paused and restore automatically when Activity gets restored. In your case, you are using this custom view multiple times and that is inflating the same layout so your all EditText have the same ID. Now when Activity will get pause Android will retrieve the value of the EditText and will save against their ID but as you have the same ID for each EditText, values will get override and so it will restore same value in all your EditText.

查看更多
唯我独甜
4楼-- · 2019-02-01 22:54

Take a look at my comment in your question and also make sure that you're getting correctly the references to your views.

I'm using your code like this:

CompoundView cv1 = (CompoundView) findViewById(R.id.compoundview1);
TextView tv1 = (TextView) cv1.findViewById(R.id.textEdit);

CompoundView cv2 = (CompoundView) findViewById(R.id.compoundview2);
TextView tv2 = (TextView) cv2.findViewById(R.id.textEdit);
查看更多
一夜七次
5楼-- · 2019-02-01 22:59

I got the same annoying problem and solved it with another solution than the ones suggested. When the custom view is inflated, I don't find them with IDs or tags, I get them using the getChildAt method. I did not have to save the instance state.

Here is an example:

Custom XML to inflate (custom_view.xml):

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android" >
    <TextView
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:gravity="center"
        android:textAppearance="?android:attr/textAppearanceMedium"/>
    <EditText
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:gravity="center"/>
</merge>

You then just get the views this way during the view initialization:

private void initView(Context context) {
    LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    inflater.inflate(R.layout.custom_view, this, true);

    setOrientation(LinearLayout.HORIZONTAL);
    setGravity(Gravity.CENTER);

    TextView mTitleTV = (TextView) getChildAt(0);
    EditText mContentET = (EditText) getChildAt(1);
}

IMPORTANT: you must remove all IDs from the XML

查看更多
登录 后发表回答