Как в RecyclerView только для одной позиции заменить TextView на EditText и обратно
В моем приложении каждая позиция в RecyclerView состоит из TextView и кнопки "more", при нажатии на которую отображается PopupMenu.
При нажатии на TextView открывается следующий фрагмент с детализацией. PopupMenu состоит из двух действий: переименовать позицию и удалить позицию.
Цель: при нажатии на "переименовать" конкретная позиция в RecyclerView должна стать редактируемой, а кнопка "more" заменяться на кнопку, скажем, "save" (которая сохраняет результат и блокирует возможность редактирования). Если EditText открыт у другой позиции - закрывать его и отображать у вновь выбранной.
Первоначально была попытка в разметке item'ов для RecyclerView использовать EditText вместо TextView. И далее менять статусы EditText (enabled, focusable, clickable). Возникло несколько проблем:
- Когда для EditText устанавливается значение
focusable = falseиclickable = false(чтобы переименовать позицию), по нему все равно проходят клики, т.е. открывается фрагмент с детализацией. - Когда для EditText устанавливается значение
enabled = false, то по нему наоборот клики перестают проходить и невозможно перейти на фрагмент с детализацией.
Поэтому было решено добавить в разметку и TextView, и EditText одновременно, меняя им видимость в нужный момент. Но при таком способе появилась другая проблема:
- При нажатии на кнопку "переименовать" EditText отображается, TextView скрывается и вроде бы все нормально. Но если сразу же нажать кнопку "переименовать" для другой позиции, то на экране отображается уже два EditText'а.
Соответственно вопрос: как скрывать предыдущий EditText перед отображением следующего?
Возможно есть еще какие-либо способы, позволяющие достичь поставленной цели, которые мной были не замечены. Было бы здорово, если они тоже будут предложены в качестве решения.
Код.
item.xml
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<variable
name="item"
type="...data.entity.Item" />
<variable
name="callback"
type="...adapters.ItemListener" />
</data>
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="70dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical">
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_toStartOf="@+id/btn_more"
android:layout_marginStart="8dp"
android:background="@android:color/transparent"
android:textSize="24sp"
android:onClick="@{()-> callback.onItemClick(item)}"
android:text="@{item.title}" />
<EditText
android:id="@+id/field_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_toStartOf="@+id/btn_more"
android:layout_marginStart="8dp"
android:background="@android:color/transparent"
android:inputType="text"
android:textSize="24sp"
android:imeOptions="actionDone"
android:visibility="gone"
android:text="@{item.title}" />
<ImageView
android:id="@+id/btn_more"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:src="@drawable/ic_more"
android:tint="@color/colorNavNormal"
android:onClick="@{()-> callback.onButtonMoreClick(item)}" />
<ImageView
android:id="@+id/btn_save"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:src="@drawable/button_create"
android:onClick="@{()-> callback.onButtonSaveClick(item)}"
android:visibility="gone" />
</RelativeLayout>
</androidx.cardview.widget.CardView>
</layout>
ItemAdapter.kt
class ItemAdapter(
list: List<Item>,
private val viewModel: ItemViewModel
) : RecyclerView.Adapter<ItemAdapter.ItemHolder>() {
var list: List<Item> = list
set(list) {
field = list
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemHolder {
val binding: ItemBinding = DataBindingUtil.inflate(
LayoutInflater.from(parent.context),
R.layout.item,
parent, false)
val listener = object : ItemListener {
override fun onItemClick(item: Item) {
Toast.makeText(parent.context, item.title, Toast.LENGTH_SHORT).show()
}
override fun onButtonMoreClick(item: Item) {
PopupMenu(parent.context, binding.btnMore).apply {
menuInflater.inflate(R.menu.item_menu, menu)
setOnMenuItemClickListener { item ->
when (item.itemId) {
R.id.edit -> {
binding.tvTitle.visibility = View.GONE
binding.fielTitle.visibility = View.VISIBLE
}
R.id.delete -> viewModel.delete(item)
}
true
}
show()
}
}
override fun onButtonSaveClick(item: Item) {
binding.tvTitle.visibility = View.VISIBLE
binding.fielTitle.visibility = View.GONE
viewModel.update(item)
}
}
binding.viewModel = viewModel
binding.callback = listener
return ItemHolder(binding)
}
override fun getItemCount(): Int = list.size
override fun onBindViewHolder(holder: ItemHolder, position: Int) {
val item = list[position]
holder.bind(item)
}
class ItemHolder(private val binding: ItemBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: Item) {
binding.item = item
}
}
}
Ответы (2 шт):
Изменения внутренних элементов RecyclerView должно происходить снаружи самого RecyclerView, задача RecyclerView просто отобразить тот список который вы ему отдали. Из этого следует, что вы должны за пределами RecyclerView (если это MVP то к примеру в Presenter-e) подготовить список элементов которые отразят состояние вашего RecyclerView и передать его в ваш RecyclerView.
Как это можно сделать? Вы можете в том же Presenter-e (или любой другой подобной сущности) создать UI модель. Что-то вроде такого:
class UiModel(
private val data: YourData;
private val boolean: showTextView;
private val boolean: showEditText;
)
Соответственно когда вы получаете ваши данные (YourData) вы мапите их в этот UI обьект и каждому значению присваиваете нужный параметр. И вот уже список этих UI моделей вы отправляете в свой RecyclerView который и отображает то что есть в модели.
Соответственно вам всякий раз придется полностью обновлять список элементов с помощью метода notifyDataSetChanged().
Если вы не хотите всегда обновлять весь список, то вы можете использовать DiffUtils, так RecyclerView будет обновлять только те элементы которые изменились.
Вы можете сделать EditText и TextView в одном месте один поверх другого:
когда елемент RecyclerView находится в обычном состоянии то :
editText.setVisiblity(Viev.Gone);
textView.setVisiblity(Viev.Visible);
При нажатии на "переименовать":
editText.setVisiblity(Viev.Visible);
textView.setVisiblity(Viev.Gone);

