Добавить / удалить EditText в / из TextInputLayout

"У нас уже есть EditText, может быть только один"

Я создал фрагмент для моего приложения (LoginFragment), который обрабатывает оба основных режима аутентификации; а именно вход в систему и регистрация пользователя. Существует кнопка, позволяющая пользователю переключаться между режимом входа в систему и режимом регистрации. Каждый "режим" имеет несколько дополнительных представлений, которые не требуются другим. Поэтому необходимо добавлять и удалять представления при переключении режима.

Я использую представления EditText в макетах TextInputLayout. Мое приложение падает, когда я делаю следующее:

  • Добавить EditText программно
  • Удалить EditText программно
  • Добавьте EditText программно -> Сбой

Это ошибка, которую я получаю:

java.lang.IllegalArgumentException: We already have an EditText, can only have one
                at android.support.design.widget.TextInputLayout.setEditText(TextInputLayout.java:166)
                at android.support.design.widget.TextInputLayout.addView(TextInputLayout.java:155)
                at android.view.ViewGroup.addView(ViewGroup.java:3985)
                at android.view.ViewGroup.addView(ViewGroup.java:3961)
                at com.mydomain.myapp.fragments.LoginFragment.showActivateAccountViews(LoginFragment.java:317)

Это происходит от android.support.design.widget.TextInputLayout, который имеет внутреннюю частную переменную EditText, которая устанавливается при добавлении представления (источник ниже). Похоже, что когда я пытаюсь добавить представление в TextInputLayout во второй раз, когда переменная mEditText уже установлена. У класса нет собственного метода.removeView(), поэтому я не знаю, как его удалить?

Я подозреваю, что я удаляю представление EditText неправильно, но не могу понять, что я делаю неправильно. Я также прочитал некоторые другие сообщения о переполнении стека, которые касаются удаления представлений, но эти подходы также не решили проблему.

У кого-нибудь есть идеи, как мне заставить это работать?

Ниже мой собственный код для справки.

LoginFragment.java

...
import android.support.design.widget.TextInputLayout;
import android.widget.EditText;

public class LoginFragment extends Fragment {

    private RelativeLayout mContainer;

    ...

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

        final View view = inflater.inflate(R.layout.fragment_login, container, false);
        mContainer = ((RelativeLayout) view.findViewById(R.id.login_container));

        showLoginViews();

        LayoutTransition layoutTransition = mContainer.getLayoutTransition();
        layoutTransition.enableTransitionType(LayoutTransition.CHANGING);

        return view;
    }

    /**
     * Show the view elements for Login mode
     */
    private void showLoginViews() {

        LayoutInflater li = (LayoutInflater)getActivity().getSystemService(
                Context.LAYOUT_INFLATER_SERVICE);

        // Configure the button for the primary action
        Button loginButton = (Button)mContainer.findViewById(R.id.button_login_fragment_primary_action);
        ...

        // Configure the toggle button to navigate to Activate Account mode
        TextView toggleButton = (TextView)mContainer.findViewById(R.id.button_toggle_mode);
        toggleButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                LoginFragment.this.showActivateAccountViews();
            }
        });
        toggleButton.setText(getResources().getString(R.string.action_activate_account));

        // Hide the Member ID EditText
        ((TextInputLayout)mContainer.findViewById(R.id.member_id_inputlayout)).removeView(mContainer.findViewById(R.id.editText_member_id_field));
    }

    /**
     * Show view elements for Activate Account mode
     */
    private void showActivateAccountViews() {
        LayoutInflater li = (LayoutInflater)getActivity().getSystemService(
                Context.LAYOUT_INFLATER_SERVICE);

        // Configure the primary button for the primary action - Activate Account
        Button activateAccountButton = (Button)mContainer.findViewById(R.id.button_login_fragment_primary_action);
        ...

        // Add the Member ID EditText
        ((TextInputLayout)mContainer.findViewById(R.id.member_id_inputlayout)).addView(li.inflate(R.layout.login_member_id_element_layout, (ViewGroup)mContainer.findViewById(R.id.member_id_inputlayout), false));

        // Configure the toggle button to navigate to Login mode
        TextView toggleButton = (TextView)mContainer.findViewById(R.id.button_toggle_mode);
        toggleButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                LoginFragment.this.showLoginViews();
            }
        });
        toggleButton.setText(getResources().getString(R.string.action_login));
    }

    ...
}

login_member_id_element_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<EditText xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/editText_member_id_field"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:hint="@string/member_id" />

login_fragment.xml

<android.support.design.widget.CoordinatorLayout 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"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    tools:context="com.mydomain.myapp.fragments.LoginFragment">

    <RelativeLayout
        android:id="@+id/login_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:animateLayoutChanges="true">

        <!--placeholder layout with params for activate account elements-->
        <android.support.design.widget.TextInputLayout
            android:id="@+id/member_id_inputlayout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
            <!-- a view can be added here-->
        </android.support.design.widget.TextInputLayout>

        <android.support.design.widget.TextInputLayout
            android:id="@+id/email_inputlayout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <EditText
                android:id="@+id/editText_email_field"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:inputType="textEmailAddress" />

        </android.support.design.widget.TextInputLayout>

        <Button
            android:id="@+id/button_login_fragment_primary_action"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@id/password_inputlayout"
            android:text="@string/action_login" />

        <!-- Toggle button for Login/Activate Account-->
        <TextView
            android:id="@+id/button_toggle_mode"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/action_activate_account" />

    </RelativeLayout>

</android.support.design.widget.CoordinatorLayout>

android.support.design.widget.TextInputLayout (из последней библиотеки поддержки 22.2.1)

public class TextInputLayout extends LinearLayout {

    private EditText mEditText;

    ...

    public void addView(View child, int index, LayoutParams params) {
        if(child instanceof EditText) {
            android.widget.LinearLayout.LayoutParams params1 = this.setEditText((EditText)child, params);
            super.addView(child, 0, params1);
        } else {
            super.addView(child, index, params);
        }

    }

    private android.widget.LinearLayout.LayoutParams setEditText(EditText editText, LayoutParams lp) {
        if(this.mEditText != null) {
            throw new IllegalArgumentException("We already have an EditText, can only have one");
        } else {
            this.mEditText = editText;
            this.mCollapsingTextHelper.setExpandedTextSize(this.mEditText.getTextSize());
            this.mEditText.addTextChangedListener(new TextWatcher() {
                public void afterTextChanged(Editable s) {
                    TextInputLayout.this.mHandler.sendEmptyMessage(0);
                }

                public void beforeTextChanged(CharSequence s, int start, int count, int after) {
                }

                public void onTextChanged(CharSequence s, int start, int before, int count) {
                }
            });
            this.mDefaultTextColor = this.mEditText.getHintTextColors().getDefaultColor();
            this.mEditText.setOnFocusChangeListener(new OnFocusChangeListener() {
                public void onFocusChange(View view, boolean focused) {
                    TextInputLayout.this.mHandler.sendEmptyMessage(0);
                }
            });
            if(TextUtils.isEmpty(this.mHint)) {
                this.setHint(this.mEditText.getHint());
                this.mEditText.setHint((CharSequence)null);
            }

            if(this.mErrorView != null) {
                ViewCompat.setPaddingRelative(this.mErrorView, ViewCompat.getPaddingStart(this.mEditText), 0, ViewCompat.getPaddingEnd(this.mEditText), this.mEditText.getPaddingBottom());
            }

            this.updateLabelVisibility(false);
            android.widget.LinearLayout.LayoutParams newLp = new android.widget.LinearLayout.LayoutParams(lp);
            Paint paint = new Paint();
            paint.setTextSize(this.mCollapsingTextHelper.getExpandedTextSize());
            newLp.topMargin = (int)(-paint.ascent());
            return newLp;
        }
    }
}

2 ответа

Решение

Кажется, есть ограничение в библиотеке com.android.support.design (v22.2.1). Вы не можете напрямую удалить, а затем добавить EditText к TextInputLayout во время выполнения. Вы можете отметить эту ошибку здесь.

Я разработал обходной путь для этой проблемы. Я изменил макет XML так, чтобы вместо добавления / удаления представлений EditText из TextInputLayout во время выполнения (что не работает) мы добавляли / удаляли сам TextInputLayout в держатель LinearLayout. С этим решением нам никогда не нужно фактически удалять EditText из TextInputLayout.

Единственное, что следует отметить в этом решении, это то, что оно делает вашу иерархию представлений на 1 уровень глубже, чем это должно быть в противном случае. Так что имейте это в виду, если у вас уже есть проблемы с производительностью пользовательского интерфейса. Если, когда вы читаете это, и доступна версия com.android.support.design v22.2.1, возможно, стоит проверить, решена ли эта проблема.

В противном случае см. Пример кода ниже для моей реализации обходного пути.

LoginFragment.java

import android.support.design.widget.TextInputLayout;
import android.widget.EditText;

public class LoginFragment extends Fragment {

    private RelativeLayout mContainer;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

        final View view = inflater.inflate(R.layout.fragment_login, container, false);
        mContainer = ((RelativeLayout) view.findViewById(R.id.login_container));

        showLoginViews();

        LayoutTransition layoutTransition = mContainer.getLayoutTransition();
        layoutTransition.enableTransitionType(LayoutTransition.CHANGING);

        return view;
    }

    /**
     * Show the view elements for Login mode
     */
    private void showLoginViews() {

        LayoutInflater li = (LayoutInflater)getActivity().getSystemService(
                Context.LAYOUT_INFLATER_SERVICE);

        // Configure the toggle button to navigate to Activate Account mode
s        TextView toggleButton = (TextView)mContainer.findViewById(R.id.button_toggle_mode);
        toggleButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                LoginFragment.this.showActivateAccountViews();
            }
        });
        toggleButton.setText(getResources().getString(R.string.action_activate_account));

        // Hide the Member ID EditText
        ((LinearLayout)mContainer.findViewById(R.id.member_id_holderlayout)).removeView(mContainer.findViewById(R.id.member_id_inputlayout));
    }

    /**
     * Show view elements for Activate Account mode
     */
    private void showActivateAccountViews() {
        LayoutInflater li = (LayoutInflater)getActivity().getSystemService(
                Context.LAYOUT_INFLATER_SERVICE);

        // Configure the primary button for the primary action - Activate Account
        Button activateAccountButton = (Button)mContainer.findViewById(R.id.button_login_fragment_primary_action);

        // Add the Member ID EditText
        ((LinearLayout)mContainer.findViewById(R.id.member_id_holderlayout)).addView(li.inflate(R.layout.login_member_id_element_layout, (ViewGroup) mContainer.findViewById(R.id.member_id_inputlayout), false));

        // Configure the toggle button to navigate to Login mode
        TextView toggleButton = (TextView)mContainer.findViewById(R.id.button_toggle_mode);
        toggleButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                LoginFragment.this.showLoginViews();
            }
        });
        toggleButton.setText(getResources().getString(R.string.action_login));
    }
}

login_member_id_element_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.TextInputLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/member_id_inputlayout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <EditText
        android:id="@+id/editText_member_id_field"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="@string/member_id" />

</android.support.design.widget.TextInputLayout>

login_fragment.xml

<android.support.design.widget.CoordinatorLayout 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"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <RelativeLayout
        android:id="@+id/login_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <!--placeholder for TextInputLayout to be dynamically added at runtime-->
        <LinearLayout
            android:id="@+id/member_id_holderlayout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">
            <!-- a login_member_id_element_layout can be dynamically added/removed here at runtime-->
        </LinearLayout>


        <!--TextInputLayout for static fields, the EditText is not removed at runtime-->
        <android.support.design.widget.TextInputLayout
            android:id="@+id/email_inputlayout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@id/member_id_holderlayout">

            <EditText
                android:id="@+id/editText_email_field"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:drawablePadding="@dimen/edittext_drawable_padding"
                android:drawableStart="?emailIcon"
                android:focusable="true"
                android:hint="Email"
                android:inputType="textEmailAddress" />
        </android.support.design.widget.TextInputLayout>

    </RelativeLayout>

</android.support.design.widget.CoordinatorLayout>

Поздравляем, вы (возможно?) Обнаружили возможную ошибку (или я должен сказать, что непредвиденное поведение при удалении TextInputLayout"s EditText?)

Ты это видишь removeView() это метод из ViewGroup. Это удаляет ваш View из массива дочерних представлений ViewGroup, но не ссылки, что ваш InputTextLayout должен EditText,

Что мне тогда делать?

Ты должен продлить TextInputLayout и создайте свой собственный метод, который устанавливает super.mEditText в null, Проблема в том, что вам все равно нужно будет вызывать эти два метода, потому что просто Layout ссылка на нуль может оставить ваш старый макет в памяти на протяжении всего жизненного цикла приложения.