3

«У нас уже есть EditText, может иметь только один»Добавить/удалить EditText в/из TextInputLayout

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

Я использую вид 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.TextInputLay который имеет внутреннюю частную переменную 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; 
     } 
    } 
} 

ответ

3

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

Я разработал обходное решение проблемы. Я изменил формат 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> 
0

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

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

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

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

+0

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

+1

Ну, я этого не ожидал. Возможно, расширение 'LinearLayout' и копирование всего исходного кода' TextInputLayout' и объявление метода 'setEditText()'? –

+0

@MauriceGavin любые результаты? –