Добавить / удалить 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 неправильно, но не могу понять, что я делаю неправильно. Я также прочитал некоторые другие сообщения о переполнении стека, которые касаются удаления представлений, но эти подходы также не решили проблему.
- Добавление представления - /questions/2685827/sozdavajte-predstavleniya-programmno-ispolzuya-xml-na-android/2685834#2685834
- Удалить представление - сначала вызовите removeView () у родителя ребенка
У кого-нибудь есть идеи, как мне заставить это работать?
Ниже мой собственный код для справки.
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
ссылка на нуль может оставить ваш старый макет в памяти на протяжении всего жизненного цикла приложения.