Главная » Хабрахабр » Деревовидный список RecyclerView (без сторонних библиотек и дочерних массивов)

Деревовидный список RecyclerView (без сторонних библиотек и дочерних массивов)

Доброго времени суток, уважаемые читатели.

Без использования каких-либо дополнительных библиотек и без использования дочернего массива.
Кому интересно, прошу под кат. В своей статье я хочу поделиться реализацией деревовидного списка с помощью RecyclerView. Постараюсь описать как можно подробнее что да как.

Принцип формирования списка элементов заключается в том, что будут показываться или скрываться дочерние элементы.

Хоть я и сказал, что реализация будет без дополнительных библиотек, однако стандартные библиотеки все же нужно подключить.

Подключенные библиотеки

dependencies { implementation 'com.android.support:appcompat-v7:26.1.0' implementation 'com.android.support:design:26.1.0' implementation 'com.android.support:recyclerview-v7:26.1.0'
}

Разметка будет самой минимальной — только список RecyclerView.

Разметка

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.v7.widget.RecyclerView android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/recycler_list"> </android.support.v7.widget.RecyclerView> </RelativeLayout>

Дополнительно понадобится отдельный класс, с помощью которого будем хранить значения списка.

Класс для данных Data.java

public final class Data //установить значение родительского элемента public void setItemParent(boolean newItemParent) { itemParent = newItemParent; } //проверить видимость дочерних элементов public boolean isChildVisibility() { return childVisibility; } //установить видимость для дочерних элементов public void setChildVisibility(boolean newChildVisibility) { childVisibility = newChildVisibility; } //получить номер родительского элемента public int getParentId() { return parentId; } //установить номер родительского элемента public void setParentId(int newParentId) { parentId = newParentId; } //получить название значения public String getValueText() { return valueText; } //установить название значения public void setValueText(String newValueText) { valueText = newValueText; } //получить идентификатор значения public int getValueId() { return valueId; } //установить идентификатор значения public void setValueId(int newValueId) { valueId = newValueId; }
}

По комментариям должно быть понятно, но поясню. Для каждого элемента списка мы будем хранить его некий идентификатор valueId, название valueText, идентификатор родительского элемента parentId, метку о том, что элемент является родительским itemParent и значение видимости для дочерних элементов childVisibility.

Следующим подготовительным этапом является создание разметки для самого элемента списка.

Разметка для элемента item.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/item" android:layout_width="match_parent" android:layout_height="wrap_content"> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent"> <!-- иконка для родительского значения --> <android.support.v7.widget.AppCompatImageView android:id="@+id/icon_tree" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/icon_hide" android:visibility="gone" app:backgroundTint="@color/colorPrimary" android:layout_centerVertical="true"/> <LinearLayout android:id="@+id/block_text" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_toRightOf="@+id/icon_tree"> <!-- название --> <TextView android:id="@+id/value_name" android:layout_width="match_parent" android:layout_height="wrap_content" android:foreground="?android:attr/selectableItemBackground" android:text="sdfdsf"/> </LinearLayout> <!-- разделитель --> <View android:layout_width="match_parent" android:layout_height="1dp" android:background="@color/colorPrimary" android:layout_below="@+id/block_text" android:layout_marginTop="4dp" android:layout_marginLeft="4dp" android:layout_marginRight="4dp"/> </RelativeLayout>
</LinearLayout>

AppCompatImageView нужен для отображения состояния родительского элемента. TextView — для отображения значения элемента. View — просто для разделения.

Последним подготовительным этапом является создания класса для обработки адаптера списка.

Адаптер для списка RecyclerViewAdapter.java

public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder> { private View vv; private List<Data> allRecords; //список всех данных public RecyclerViewAdapter(List<Data> records) { allRecords = records; } @Override public RecyclerViewAdapter.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) { View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item, viewGroup, false); return new RecyclerViewAdapter.ViewHolder(v); } @Override public void onBindViewHolder(final RecyclerViewAdapter.ViewHolder viewHolder, int i) { Data record = allRecords.get(i); String value = record.getValueText(); int id = record.getValueId(); int parentId = record.getParentId(); final int position = i; final String text = "#" + id + ": " + value + " (id родительского элемента: " + parentId + ")"; //покажем или скроем элемент, если он дочерний if (parentId >= 0) { //видимость делаем по параметру родительского элемента setVisibility(viewHolder.item, allRecords.get(parentId).isChildVisibility(), parentId); } else { //элемент не дочерний, показываем его setVisibility(viewHolder.item, true, parentId); } //покажем или скроем иконку деревовидного списка if (record.isItemParent()) { viewHolder.iconTree.setVisibility(View.VISIBLE); //показываем нужную иконку if (record.isChildVisibility()) //показываются дочерние элементы viewHolder.iconTree.setBackgroundResource(R.drawable.icon_show); else //скрыты дочерние элементы viewHolder.iconTree.setBackgroundResource(R.drawable.icon_hide); } else //элемент не родительский viewHolder.iconTree.setVisibility(View.GONE); //устанавливаем текст элемента if (!TextUtils.isEmpty(value)) { viewHolder.valueText.setText(value); } //добавляем обработку нажатий по значению viewHolder.valueText.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Data dataItem = allRecords.get(position); if (dataItem.isItemParent()) { //нажали по родительскому элементу, меняем видимость дочерних элементов dataItem.setChildVisibility(!dataItem.isChildVisibility()); notifyDataSetChanged(); } else { //нажали по обычному элементу, обрабатываем как нужно Snackbar snackbar = Snackbar.make(vv, text, Snackbar.LENGTH_LONG); snackbar.show(); } } }); } //установка видимости элемента private void setVisibility(View curV, boolean visible, int parentId) { //найдем блок, благодаря которому будем сдвигать текст LinearLayout vPadding = curV.findViewById(R.id.block_text); LinearLayout.LayoutParams params; if (visible) { params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); if (vPadding != null) { if (parentId >= 0) { //это дочерний элемент, делаем отступ vPadding.setPadding(80, 0, 0, 0); } else { vPadding.setPadding(0, 0, 0, 0); } } } else params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0); curV.setLayoutParams(params); } @Override public int getItemCount() { return allRecords.size(); } class ViewHolder extends RecyclerView.ViewHolder { private LinearLayout item; private TextView valueText; private AppCompatImageView iconTree; public ViewHolder(View itemView) { super(itemView); vv = itemView; item = vv.findViewById(R.id.id_item); valueText = vv.findViewById(R.id.value_name); iconTree = vv.findViewById(R.id.icon_tree); } }
}

Основная обработка происходит в процедуре onBindViewHolder. Для каждого элемента списка получается его идентификатор, значение и параметры родительского значения. Показываются или скрываются дочерние элементы, а так же иконка состояния для родительского элемента. Ну и вешается обработка нажатий по списку. Тут каждый сам решает, как ему нужно обрабатывать список. В примере просто показывается сообщение с id и значением элемента.
В процедуре показа или скрытия дочернего элемента setVisibility дополнительно делается отступ текста для дочернего элемента в 80 пикселей.

Осталось только заполнить список в нужном месте.

Формирование списка

List<Data> records = new ArrayList<Data>(); //список значений
Data record;
RecyclerViewAdapter adapter;
int parentId;
RecyclerView recyclerView = findViewById(R.id.recycler_list); record = new Data();
record.setValueId(1);
record.setValueText("Родительское значение 1");
record.setItemParent(true); //родительское значение
records.add(record);
parentId = records.size() -1;
for (int ind = 1; ind <= 3; ind ++) { record = new Data(); record.setValueId(ind); record.setValueText("Текст " + ind); record.setParentId(parentId); records.add(record);
} record = new Data();
record.setValueId(1);
record.setValueText("Второе родительское значение");
record.setItemParent(true); //родительское значение
records.add(record);
parentId = records.size() -1;
for (int ind = 4; ind <= 7; ind ++) { record = new Data(); record.setValueId(ind); record.setValueText("Дочерний текст " + ind); record.setParentId(parentId); records.add(record);
} record = new Data();
record.setValueId(1);
record.setValueText("Еще родительское значение");
record.setItemParent(true); //родительское значение
records.add(record);
parentId = records.size() -1;
for (int ind = 8; ind <= 12; ind ++) { record = new Data(); record.setValueId(ind); record.setValueText("Значение " + ind); record.setParentId(parentId); records.add(record);
} for (int ind = 13; ind <= 18; ind ++) { record = new Data(); record.setValueId(ind); record.setValueText("Текст без родителя" + ind); records.add(record);
} for (int ind = 19; ind <= 21; ind ++) { record = new Data(); record.setValueId(ind); record.setValueText("Элемент тоже без родителя" + ind); records.add(record);
} record = new Data();
record.setValueId(1);
record.setValueText("Опять родительское значение");
record.setItemParent(true); //родительское значение
records.add(record);
parentId = records.size() -1;
for (int ind = 22; ind <= 30; ind ++) { record = new Data(); record.setValueId(ind); record.setValueText("Дочернее: " + ind); record.setParentId(parentId); records.add(record);
} for (int ind = 31; ind <= 45; ind ++) { record = new Data(); record.setValueId(ind); record.setValueText("Последние без родителя " + ind); records.add(record);
} adapter = new RecyclerViewAdapter(records);
RecyclerView.ItemAnimator itemAnimator = new DefaultItemAnimator();
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
recyclerView.setAdapter(adapter);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setItemAnimator(itemAnimator);

В итоге получается такой простой список с поддержкой дочерних элементов. Такая реализация позволяет заполнять несколько вложенных элементов. Но нужно совсем чуток доработать отступы для дочерних элементов, если уровень вложенности будет больше 1.

Всем спасибо за внимание и успешных проектов.


Оставить комментарий

Ваш email нигде не будет показан
Обязательные для заполнения поля помечены *

*

x

Ещё Hi-Tech Интересное!

В России приступили к тестированию отечественного нейроинтерфейса «Нейрочат»

Эта система предназначена для пациентов с ограниченными физическими способностями. В конце прошлого года компания Neurotrand разработала вместе с партнерами программно-аппаратный комплекс с нейрогарнитурой. Это могут быть пациенты клиник, перенесшие инсульт, военные с тяжелыми ранениями, люди, получившие травму на производстве. Она ...

Зрители не могут отличить нативную картинку 4K от интерполяции

Такие выводы можно сделать из результатов российского исследования, проведённого холдингом «Ромир». Человеческого зрения недостаточно, чтобы отличить настоящее видео 4K от картинки, которую получили из изображения HDTV с помощью интерполяции. Опрошенным показывали на телеэкране фрагменты двух видеороликов и спрашивали о восприятии ...