RecyclerView tree list (without third-party libraries and child arrays)
In my article, I want to share the implementation of the tree view using RecyclerView. Without using any additional libraries and without using a child array.
Who cares, I ask under the cat. I will try to describe as much as possible what yes how.

The principle of forming a list of elements is that the child elements will be shown or hidden.
Although I said that the implementation will be without additional libraries, but the standard libraries still need to connect.
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'
}
The markup will be the most minimal - only the RecyclerView list.
<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"><android.support.v7.widget.RecyclerViewandroid:layout_width="match_parent"android:layout_height="match_parent"android:id="@+id/recycler_list"></android.support.v7.widget.RecyclerView></RelativeLayout>Additionally, you will need a separate class with which we will store the values ββof the list.
publicfinalclassData{
private String valueText = ""; //Π½Π°Π·Π²Π°Π½ΠΈΠ΅ Π·Π½Π°ΡΠ΅Π½ΠΈΡprivateint valueId = 0; //ΠΈΠ΄Π΅Π½ΡΠΈΡΠΈΠΊΠ°ΡΠΎΡ Π·Π½Π°ΡΠ΅Π½ΠΈΡprivateboolean itemParent = false; //ΡΠΎΠ΄ΠΈΡΠ΅Π»ΡΡΠΊΠΈΠΉ ΠΈΠ»ΠΈ Π½Π΅Ρ ΡΠ»Π΅ΠΌΠ΅Π½Ρprivateint parentId = -1; //id ΡΠ»Π΅ΠΌΠ΅Π½ΡΠ°, ΠΊΠΎΡΠΎΡΡΠΉ ΡΠ²Π»ΡΠ΅ΡΡΡ ΡΠΎΠ΄ΠΈΡΠ΅Π»ΡΡΠΊΠΈΠΌprivateboolean childVisibility = false; //Π²ΠΈΠ΄ΠΈΠΌΠΎΡΡΡ Π΄ΠΎΡΠ΅ΡΠ½ΠΈΡ
ΡΠ»Π΅ΠΌΠ΅Π½ΡΠΎΠ²//ΠΏΡΠΎΠ²Π΅ΡΠΈΡΡ ΡΠΎΠ΄ΠΈΡΠ΅Π»ΡΡΠΊΠΈΠΉ ΡΠ»Π΅ΠΌΠ΅Π½Ρ ΠΈΠ»ΠΈ Π½Π΅ΡpublicbooleanisItemParent(){
return itemParent;
}
//ΡΡΡΠ°Π½ΠΎΠ²ΠΈΡΡ Π·Π½Π°ΡΠ΅Π½ΠΈΠ΅ ΡΠΎΠ΄ΠΈΡΠ΅Π»ΡΡΠΊΠΎΠ³ΠΎ ΡΠ»Π΅ΠΌΠ΅Π½ΡΠ°publicvoidsetItemParent(boolean newItemParent){
itemParent = newItemParent;
}
//ΠΏΡΠΎΠ²Π΅ΡΠΈΡΡ Π²ΠΈΠ΄ΠΈΠΌΠΎΡΡΡ Π΄ΠΎΡΠ΅ΡΠ½ΠΈΡ
ΡΠ»Π΅ΠΌΠ΅Π½ΡΠΎΠ²publicbooleanisChildVisibility(){
return childVisibility;
}
//ΡΡΡΠ°Π½ΠΎΠ²ΠΈΡΡ Π²ΠΈΠ΄ΠΈΠΌΠΎΡΡΡ Π΄Π»Ρ Π΄ΠΎΡΠ΅ΡΠ½ΠΈΡ
ΡΠ»Π΅ΠΌΠ΅Π½ΡΠΎΠ²publicvoidsetChildVisibility(boolean newChildVisibility){
childVisibility = newChildVisibility;
}
//ΠΏΠΎΠ»ΡΡΠΈΡΡ Π½ΠΎΠΌΠ΅Ρ ΡΠΎΠ΄ΠΈΡΠ΅Π»ΡΡΠΊΠΎΠ³ΠΎ ΡΠ»Π΅ΠΌΠ΅Π½ΡΠ°publicintgetParentId(){
return parentId;
}
//ΡΡΡΠ°Π½ΠΎΠ²ΠΈΡΡ Π½ΠΎΠΌΠ΅Ρ ΡΠΎΠ΄ΠΈΡΠ΅Π»ΡΡΠΊΠΎΠ³ΠΎ ΡΠ»Π΅ΠΌΠ΅Π½ΡΠ°publicvoidsetParentId(int newParentId){
parentId = newParentId;
}
//ΠΏΠΎΠ»ΡΡΠΈΡΡ Π½Π°Π·Π²Π°Π½ΠΈΠ΅ Π·Π½Π°ΡΠ΅Π½ΠΈΡpublic String getValueText(){
return valueText;
}
//ΡΡΡΠ°Π½ΠΎΠ²ΠΈΡΡ Π½Π°Π·Π²Π°Π½ΠΈΠ΅ Π·Π½Π°ΡΠ΅Π½ΠΈΡpublicvoidsetValueText(String newValueText){
valueText = newValueText;
}
//ΠΏΠΎΠ»ΡΡΠΈΡΡ ΠΈΠ΄Π΅Π½ΡΠΈΡΠΈΠΊΠ°ΡΠΎΡ Π·Π½Π°ΡΠ΅Π½ΠΈΡpublicintgetValueId(){
return valueId;
}
//ΡΡΡΠ°Π½ΠΎΠ²ΠΈΡΡ ΠΈΠ΄Π΅Π½ΡΠΈΡΠΈΠΊΠ°ΡΠΎΡ Π·Π½Π°ΡΠ΅Π½ΠΈΡpublicvoidsetValueId(int newValueId){
valueId = newValueId;
}
}
The comments should be clear, but I will explain. For each element of the list, we will keep it a id ValueID , name valueText , the ID of the parent element parentId , a label that element is the parent itemParent and the visibility for children childVisibility .
The next preparatory stage is the creation of markup for the list item itself.
<LinearLayoutxmlns: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"><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"><!-- ΠΈΠΊΠΎΠ½ΠΊΠ° Π΄Π»Ρ ΡΠΎΠ΄ΠΈΡΠ΅Π»ΡΡΠΊΠΎΠ³ΠΎ Π·Π½Π°ΡΠ΅Π½ΠΈΡ --><android.support.v7.widget.AppCompatImageViewandroid: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"/><LinearLayoutandroid:id="@+id/block_text"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_toRightOf="@+id/icon_tree"><!-- Π½Π°Π·Π²Π°Π½ΠΈΠ΅ --><TextViewandroid:id="@+id/value_name"android:layout_width="match_parent"android:layout_height="wrap_content"android:foreground="?android:attr/selectableItemBackground"android:text="sdfdsf"/></LinearLayout><!-- ΡΠ°Π·Π΄Π΅Π»ΠΈΡΠ΅Π»Ρ --><Viewandroid: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 is needed to display the status of the parent element. TextView - to display the value of the item. View - just for sharing.
The final preparatory step is to create a class for handling the list adapter.
publicclassRecyclerViewAdapterextendsRecyclerView.Adapter<RecyclerViewAdapter.ViewHolder> {
private View vv;
private List<Data> allRecords; //ΡΠΏΠΈΡΠΎΠΊ Π²ΡΠ΅Ρ
Π΄Π°Π½Π½ΡΡ
publicRecyclerViewAdapter(List<Data> records){
allRecords = records;
}
@Overridepublic RecyclerViewAdapter.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i){
View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item, viewGroup, false);
returnnew RecyclerViewAdapter.ViewHolder(v);
}
@OverridepublicvoidonBindViewHolder(final RecyclerViewAdapter.ViewHolder viewHolder, int i){
Data record = allRecords.get(i);
String value = record.getValueText();
int id = record.getValueId();
int parentId = record.getParentId();
finalint 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() {
@OverridepublicvoidonClick(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();
}
}
});
}
//ΡΡΡΠ°Π½ΠΎΠ²ΠΊΠ° Π²ΠΈΠ΄ΠΈΠΌΠΎΡΡΠΈ ΡΠ»Π΅ΠΌΠ΅Π½ΡΠ°privatevoidsetVisibility(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);
}
@OverridepublicintgetItemCount(){
return allRecords.size();
}
classViewHolderextendsRecyclerView.ViewHolder{
private LinearLayout item;
private TextView valueText;
private AppCompatImageView iconTree;
publicViewHolder(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);
}
}
}
The main processing takes place in the onBindViewHolder procedure . For each element of the list, its identifier, value and parameters of the parent value are obtained. Child elements are shown or hidden, as well as the status icon for the parent element. Well, hangs up the processing of clicks on the list. Here everyone decides how he needs to process the list. The example simply displays a message with the id and value of the element.
In the procedure for showing or hiding the setVisibility child element, additional text is indented for a child element of 80 pixels.
It remains only to fill the list in the right place.
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);
The result is such a simple list with support for children. This implementation allows you to fill in several nested elements. But you need quite a bit to refine the indents for the children, if the nesting level is more than 1.
Thank you all for your attention and successful projects.