Back to Home

RecyclerView tree list (without third-party libraries and child arrays)

Good day Β· dear readers. In my article Β· I want to share the implementation of the tree view using RecyclerView. Without using any additional libraries and without ...

RecyclerView tree list (without third-party libraries and child arrays)

    Good day, dear readers.

    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.

    Connected Libraries
    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.

    Markup
    <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.

    Class for Data.java data
    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.

    Markup for item.xml
    <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.

    Adapter for RecyclerViewAdapter.java list
    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 formation
    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.

    Read Next