web-dev-qa-db-fra.com

Création d'une RecyclerView extensible

J'essaie d'implémenter une vue d'ensemble du recyclage qui se comporte comme mon croquis ci-dessous:

enter image description here

L'idée est qu'il existe une liste parent, lorsqu'un élément de liste dans la liste parent est exploité, cet élément de liste révèle une liste enfant qui contient ses propres données. lorsqu'un élément de liste est exploité dans la liste enfant, la valeur de cet enfant est reflétée et met à jour la valeur du parent dans l'élément de liste parent.

J'ai essayé de le faire fonctionner pendant les 3 derniers jours en vain. J'ai essayé d'utiliser la bibliothèque AdvancedReyclerview mais pour un débutant comme moi, c'était un gâchis géant de choses qui n'avaient pas de sens, surtout lors de la transmission des données. J'ai copié et collé les fichiers dont j'avais besoin pour obtenir une version de travail minimale mais je ne savais pas comment passer mes données dans la vue de recyclage et comment les mettre à jour avec la nouvelle valeur sélectionnée.

Est-il même possible de faire ce que j'essaie de faire ou suis-je loin de ma profondeur ici?

Si c'est encore difficile à comprendre, je peux l'expliquer davantage.

EDIT: quelqu'un m'a recommandé de le faire avec ExpandableListView plutôt qu'avec RecyclerView. Des réflexions là-dessus?

18
fadelakin

1.ExpandableRecyclerAdapter.class

public abstract class ExpandableRecyclerAdapter<T extends ExpandableRecyclerAdapter.ListItem> extends RecyclerView.Adapter<ExpandableRecyclerAdapter.ViewHolder> {
    protected Context mContext;
    protected List<T> allItems = new ArrayList<>();
    protected List<T> visibleItems = new ArrayList<>();
    private List<Integer> indexList = new ArrayList<>();
    private SparseIntArray expandMap = new SparseIntArray();
    private int mode;

    protected static final int TYPE_HEADER = 1000;

    private static final int ARROW_ROTATION_DURATION = 150;

    public static final int MODE_NORMAL = 0;
    public static final int MODE_ACCORDION = 1;

    public ExpandableRecyclerAdapter(Context context) {
    mContext = context;
    }

    public static class ListItem {
    public int ItemType;

    public ListItem(int itemType) {
        ItemType = itemType;
    }
    }

    @Override
    public long getItemId(int i) {
    return i;
    }

    @Override
    public int getItemCount() {
    return visibleItems == null ? 0 : visibleItems.size();
    }

    protected View inflate(int resourceID, ViewGroup viewGroup) {
    return LayoutInflater.from(mContext).inflate(resourceID, viewGroup, false);
    }

    public class ViewHolder extends RecyclerView.ViewHolder {
    public ViewHolder(View view) {
        super(view);
    }
    }

    public class HeaderViewHolder extends ViewHolder {
    ImageView arrow;

    public HeaderViewHolder(View view) {
        super(view);

        view.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                toggleExpandedItems(getLayoutPosition(),false);
                /*if(isExpanded(getLayoutPosition())){
                    collapseItems(getLayoutPosition(),false);
                }else {
                    expandItems(getLayoutPosition(),true);
                }*/
            }
        });
    }

    public HeaderViewHolder(View view, final ImageView arrow) {
        super(view);

        this.arrow = arrow;

        arrow.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                handleClick();
            }
        });
    }

    protected void handleClick() {
        if (toggleExpandedItems(getLayoutPosition(), false)) {
            openArrow(arrow);
        } else {
            closeArrow(arrow);
        }
    }

    public void bind(int position) {
        arrow.setRotation(isExpanded(position) ? 90 : 0);
    }
    }

    public boolean toggleExpandedItems(int position, boolean notify) {
    if (isExpanded(position)) {
        collapseItems(position, notify);
        return false;
    } else {
        expandItems(position, notify);

        if (mode == MODE_ACCORDION) {
            collapseAllExcept(position);
        }

        return true;
    }
    }

    public void expandItems(int position, boolean notify) {
    int count = 0;
    int index = indexList.get(position);
    int insert = position;

    for (int i=index+1; i<allItems.size() && allItems.get(i).ItemType != TYPE_HEADER; i++) {
        insert++;
        count++;
        visibleItems.add(insert, allItems.get(i));
        indexList.add(insert, i);
    }

    notifyItemRangeInserted(position + 1, count);

    int allItemsPosition = indexList.get(position);
    expandMap.put(allItemsPosition, 1);

    if (notify) {
        notifyItemChanged(position);
    }
}

public void collapseItems(int position, boolean notify) {
    int count = 0;
    int index = indexList.get(position);

    for (int i=index+1; i<allItems.size() && allItems.get(i).ItemType != TYPE_HEADER; i++) {
        count++;
        visibleItems.remove(position + 1);
        indexList.remove(position + 1);
    }

    notifyItemRangeRemoved(position + 1, count);

    int allItemsPosition = indexList.get(position);
    expandMap.delete(allItemsPosition);

    if (notify) {
        notifyItemChanged(position);
    }
    }


protected boolean isExpanded(int position) {
    int allItemsPosition = indexList.get(position);
    return expandMap.get(allItemsPosition, -1) >= 0;
}

@Override
public int getItemViewType(int position) {
    return visibleItems.get(position).ItemType;
}

public void setItems(List<T> items) {
    allItems = items;
    List<T> visibleItems = new ArrayList<>();
    expandMap.clear();
    indexList.clear();

    for (int i=0; i<items.size(); i++) {
        if (items.get(i).ItemType == TYPE_HEADER) {
            indexList.add(i);
            visibleItems.add(items.get(i));
        }
    }

    this.visibleItems = visibleItems;
    notifyDataSetChanged();
    }



protected void removeItemAt(int visiblePosition) {
    int allItemsPosition = indexList.get(visiblePosition);

    allItems.remove(allItemsPosition);
    visibleItems.remove(visiblePosition);

    incrementIndexList(allItemsPosition, visiblePosition, -1);
    incrementExpandMapAfter(allItemsPosition, -1);

    notifyItemRemoved(visiblePosition);
}

private void incrementExpandMapAfter(int position, int direction) {
    SparseIntArray newExpandMap = new SparseIntArray();

    for (int i=0; i<expandMap.size(); i++) {
        int index = expandMap.keyAt(i);
        newExpandMap.put(index < position ? index : index + direction, 1);
    }

    expandMap = newExpandMap;
    }

    private void incrementIndexList(int allItemsPosition, int visiblePosition, int direction) {
    List<Integer> newIndexList = new ArrayList<>();

    for (int i=0; i<indexList.size(); i++) {
        if (i == visiblePosition) {
            if (direction > 0) {
                newIndexList.add(allItemsPosition);
            }
        }

        int val = indexList.get(i);
        newIndexList.add(val < allItemsPosition ? val : val + direction);
        }

    indexList = newIndexList;
    }

    public void collapseAll() {
    collapseAllExcept(-1);
    }

    public void collapseAllExcept(int position) {
    for (int i=visibleItems.size()-1; i>=0; i--) {
        if (i != position && getItemViewType(i) == TYPE_HEADER) {
            if (isExpanded(i)) {
                collapseItems(i, true);
            }
        }
    }
    }

    public void expandAll() {
    for (int i=visibleItems.size()-1; i>=0; i--) {
        if (getItemViewType(i) == TYPE_HEADER) {
            if (!isExpanded(i)) {
                expandItems(i, true);
            }
        }
    }
    }

    public static void openArrow(View view) {
    view.animate().setDuration(ARROW_ROTATION_DURATION).rotation(180);

    }

    public static void closeArrow(View view) {
    view.animate().setDuration(ARROW_ROTATION_DURATION).rotation(0);
    }

    public int getMode() {
    return mode;
    }

    public void setMode(int mode) {
    this.mode = mode;
    }
}

2.activity_main

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
Android:orientation="vertical" 
Android:layout_width="match_parent"
Android:layout_height="match_parent">

<Android.support.v7.widget.RecyclerView      Android:id="@+id/main_recycler"
    Android:layout_width="match_parent"
    Android:layout_height="match_parent" />

3.item_header

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout   xmlns:Android="http://schemas.Android.com/apk/res/Android"
xmlns:tools="http://schemas.Android.com/tools"
Android:layout_width="match_parent"
Android:layout_height="wrap_content"
Android:orientation="horizontal"
Android:padding="@dimen/standard_padding">

<LinearLayout
    Android:id="@+id/lnr_1"
    Android:layout_width="wrap_content"
    Android:layout_height="wrap_content"
    Android:layout_centerHorizontal="true">

    <TextView
        Android:id="@+id/txt_header_address"
        Android:layout_width="wrap_content"
        Android:layout_height="wrap_content"
        Android:drawableLeft="@mipmap/ic_usa"
        Android:gravity="center"
        Android:text="Beverly Hills"
        Android:textStyle="bold" />

</LinearLayout>

<ImageView
    Android:id="@+id/img_arrow"
    Android:layout_width="@dimen/arrow_size"
    Android:layout_height="@dimen/arrow_size"
    Android:layout_alignParentRight="true"
    Android:src="@mipmap/arrow" />

<TextView
    Android:id="@+id/txt_header_name"
    Android:layout_width="wrap_content"
    Android:layout_height="wrap_content"
    Android:layout_alignParentLeft="true"
    Android:layout_alignParentStart="true"
    Android:layout_centerVertical="true"
    Android:text="Home"
    Android:textStyle="bold" />

4.item_content.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
xmlns:app="http://schemas.Android.com/apk/res-auto"
Android:layout_width="match_parent"
Android:layout_height="wrap_content"
Android:orientation="vertical">

<RelativeLayout
    Android:id="@+id/rcl_header_btn"
    Android:layout_width="wrap_content"
    Android:layout_height="wrap_content"
    Android:orientation="horizontal">

    <Button
        style="?android:attr/borderlessButtonStyle"
        Android:layout_width="wrap_content"
        Android:layout_height="wrap_content"
        Android:text="@string/btn_cancle" />

    <Button
        style="?android:attr/borderlessButtonStyle"
        Android:layout_width="wrap_content"
        Android:layout_height="wrap_content"
        Android:layout_alignParentRight="true"
        Android:text="@string/btn_save" />

</RelativeLayout>

<LinearLayout
    Android:id="@+id/lnr_parent"
    Android:layout_width="match_parent"
    Android:layout_height="wrap_content"
    Android:layout_below="@+id/rcl_header_btn"
    Android:gravity="center_vertical"
    Android:orientation="vertical">

    <EditText
        Android:id="@+id/edt_description"
        Android:layout_width="match_parent"
        Android:layout_height="wrap_content"
        Android:hint="DESCRIPTION" />

    <EditText
        Android:id="@+id/edt_address"
        Android:layout_width="match_parent"
        Android:layout_height="wrap_content"
        Android:hint="Address" />

    <LinearLayout
        Android:id="@+id/lnr_child_1"
        Android:layout_width="match_parent"
        Android:layout_height="wrap_content">

        <EditText
            Android:id="@+id/edt_city"
            Android:layout_width="match_parent"
            Android:layout_height="wrap_content"
            Android:layout_weight="1"
            Android:hint="City" />

        <EditText
            Android:id="@+id/edt_state"
            Android:layout_width="match_parent"
            Android:layout_height="wrap_content"
            Android:layout_weight="1"
            Android:hint="State" />

    </LinearLayout>

    <LinearLayout
        Android:id="@+id/lnr_child_2"
        Android:layout_width="match_parent"
        Android:layout_height="wrap_content">

        <EditText
            Android:id="@+id/edt_zipcode"
            Android:layout_width="match_parent"
            Android:layout_height="wrap_content"
            Android:layout_weight="1"
            Android:hint="Zip Code" />

        <EditText
            Android:id="@+id/edt_country"
            Android:layout_width="match_parent"
            Android:layout_height="wrap_content"
            Android:layout_weight="1"
            Android:hint="Country" />

    </LinearLayout>
</LinearLayout>

<RelativeLayout
    Android:id="@+id/rcl_bottom"
    Android:layout_width="wrap_content"
    Android:layout_height="wrap_content"
    Android:layout_below="@+id/lnr_parent">

    <CheckBox
        Android:id="@+id/chk_marked"
        Android:layout_width="wrap_content"
        Android:layout_height="wrap_content"
        Android:layout_alignBaseline="@+id/txt_type" />

    <TextView
        Android:id="@+id/txt_type"
        Android:layout_width="wrap_content"
        Android:layout_height="wrap_content"
        Android:layout_alignBaseline="@+id/btn_delete"
        Android:layout_toRightOf="@+id/chk_marked"
        Android:gravity="center"
        Android:text="SET AS DEFAULT" />

    <Button
        Android:id="@+id/btn_delete"
        Android:layout_width="wrap_content"
        Android:layout_height="wrap_content"
        Android:layout_alignParentRight="true"
        Android:text="DELETE" />

</RelativeLayout>

5.Adaptateur

public class PeopleAdapter extends ExpandableRecyclerAdapter<PeopleAdapter.PeopleListItem> {
public static final int TYPE_PERSON = 1001;

public PeopleAdapter(Context context) {
    super(context);

    setItems(getSampleItems());
}

public static class PeopleListItem extends ExpandableRecyclerAdapter.ListItem {
    public String Text;

    public PeopleListItem(String group) {
        super(TYPE_HEADER);

        Text = group;
    }

    public PeopleListItem(String first, String last) {
        super(TYPE_PERSON);

        Text = first + " " + last;
    }
    }

    public class HeaderViewHolder extends ExpandableRecyclerAdapter.HeaderViewHolder {
    TextView name;

    public HeaderViewHolder(View view) {
        super(view, (ImageView) view.findViewById(R.id.img_arrow));

        name = (TextView) view.findViewById(R.id.txt_header_name);
    }

    public void bind(int position) {
        super.bind(position);

        name.setText(visibleItems.get(position).Text);
    }
    }

    public class PersonViewHolder extends ExpandableRecyclerAdapter.ViewHolder {
    EditText name;

    public PersonViewHolder(View view) {
        super(view);

        name = (EditText) view.findViewById(R.id.edt_description);
    }

    public void bind(int position) {
        name.setText(name.getText());
    }

    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    switch (viewType) {
        case TYPE_HEADER:
            return new HeaderViewHolder(inflate(R.layout.item_header, parent));
        case TYPE_PERSON:
        default:
            return new PersonViewHolder(inflate(R.layout.item_content, parent));
    }
    }

    @Override
    public void onBindViewHolder(ExpandableRecyclerAdapter.ViewHolder holder, int position) {
    switch (getItemViewType(position)) {
        case TYPE_HEADER:
            ((HeaderViewHolder) holder).bind(position);
            break;
        case TYPE_PERSON:
        default:
            ((PersonViewHolder) holder).bind(position);
            break;
    }
    }

    private List<PeopleListItem> getSampleItems() {
    List<PeopleListItem> items = new ArrayList<>();
    items.add(new PeopleListItem("Friends"));
    items.add(new PeopleListItem("", ""));
    items.add(new PeopleListItem("Friends"));
    items.add(new PeopleListItem("", ""));
    return items;
}

}

6.MainActivity.Java

public class MainActivity extends AppCompatActivity {
RecyclerView recycler;
PeopleAdapter adapter;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);

    recycler = (RecyclerView) findViewById(R.id.main_recycler);

    adapter = new PeopleAdapter(this);
    adapter.setMode(ExpandableRecyclerAdapter.MODE_ACCORDION);
    recycler.setLayoutManager(new LinearLayoutManager(this));
    recycler.setAdapter(adapter);
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    super.onCreateOptionsMenu(menu);

    getMenuInflater().inflate(R.menu.menu_main, menu);

    return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        case R.id.action_expand_all:
            adapter.expandAll();
            return true;
        case R.id.action_collapse_all:
            adapter.collapseAll();
            return true;
        default:
            return super.onOptionsItemSelected(item);
    }
}
}
14
Shweta Chauhan

Vous pouvez consulter ma bibliothèque en ici

Et créez quelque chose comme le code ci-dessous:

public class PurchaseItemRecyclerViewAdapter extends  ExpandableRecyclerView.Adapter<PurchaseItemRecyclerViewAdapter.ChildViewHolder,ExpandableRecyclerView.SimpleGroupViewHolder,String,String>
{

List<ItemModel> itemModels;

public PurchaseItemRecyclerViewAdapter() {
    this.itemModels = new ArrayList<>();
    itemModels.add(new ItemModel("group 0",3,"subitem :"));
    itemModels.add(new ItemModel("group 1",3,"subitem :"));
    itemModels.add(new ItemModel("group 2",2,"subitem :"));
    itemModels.add(new ItemModel("group 3",1,"subitem :"));
    itemModels.add(new ItemModel("group 4",3,"subitem :"));
    itemModels.add(new ItemModel("group 5",5,"subitem :"));
}

@Override
public int getGroupItemCount() {
    return itemModels.size();
}

@Override
public int getChildItemCount(int i) {
    return itemModels.get(i).getSubItemCount();
}

@Override
public String getGroupItem(int i) {
    return itemModels.get(i).getParentName();
}

@Override
public String getChildItem(int group, int child) {
    return itemModels.get(group).getSubItemPrefix() + child;
}

@Override
protected ExpandableRecyclerView.SimpleGroupViewHolder onCreateGroupViewHolder(ViewGroup parent)
{
    return new ExpandableRecyclerView.SimpleGroupViewHolder(parent.getContext());
}

@Override
protected ChildViewHolder onCreateChildViewHolder(ViewGroup parent, int viewType)
{
    View rootView = LayoutInflater.from(parent.getContext()).inflate(R.layout.purchase_list_content,parent,false);
    return new ChildViewHolder(rootView);
}

@Override
public void onBindGroupViewHolder(ExpandableRecyclerView.SimpleGroupViewHolder holder, int group) {
    super.onBindGroupViewHolder(holder, group);
    holder.setText(getGroupItem(group));

}

@Override
public void onBindChildViewHolder(ChildViewHolder holder, final int group, int position)
{
    super.onBindChildViewHolder(holder, group, position);
    holder.name.setText(getChildItem(group, position));
    holder.itemView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            itemModels.get(group).setParentName("edited Parent");
            notifyItemChanged(group);
        }
    });
}

@Override
public int getChildItemViewType(int i, int i1) {
    return 1;
}

public class ChildViewHolder extends RecyclerView.ViewHolder
{
    private TextView name;
    public ChildViewHolder(View itemView) {
        super(itemView);
        name = (TextView) itemView.findViewById(R.id.item_name);
    }
}
}

et cette classe ItemModel:

public class ItemModel {
String parentName;
int subItemCount;
String subItemPrefix;

public ItemModel(String parentName, int subItemCount, String subItemPrefix) {
    this.parentName = parentName;
    this.subItemCount = subItemCount;
    this.subItemPrefix = subItemPrefix;
}

public String getParentName() {
    return parentName;
}

public void setParentName(String parentName) {
    this.parentName = parentName;
}

public int getSubItemCount() {
    return subItemCount;
}

public void setSubItemCount(int subItemCount) {
    this.subItemCount = subItemCount;
}

public String getSubItemPrefix() {
    return subItemPrefix;
}

public void setSubItemPrefix(String subItemPrefix) {
    this.subItemPrefix = subItemPrefix;
}
}
5
Ali mohammadi

Vous pouvez facilement y parvenir avec cette bibliothèque , il y a un exemple complet ici .

Fondamentalement, vous regroupez vos articles en sections:

class MySection extends StatelessSection {

    String header;
    List<String> list;
    boolean expanded = true;

    public MySection(String header, List<String> list) {
        // call constructor with layout resources for this Section header and items 
        super(R.layout.section_header, R.layout.section_item);
        this.myHeader = header;
        this.myList = list;
    }

    @Override
    public int getContentItemsTotal() {
        return expanded? list.size() : 0;
    }

    @Override
    public RecyclerView.ViewHolder getHeaderViewHolder(View view) {
        return new HeaderViewHolder(view);
    }

    @Override
    public void onBindHeaderViewHolder(RecyclerView.ViewHolder holder) {
        final HeaderViewHolder headerHolder = (HeaderViewHolder) holder;

        headerHolder.tvTitle.setText(title);

        headerHolder.rootView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                expanded = !expanded;
                headerHolder.imgArrow.setImageResource(
                        expanded ? R.drawable.ic_keyboard_arrow_up_black_18dp : R.drawable.ic_keyboard_arrow_down_black_18dp
                );
                sectionAdapter.notifyDataSetChanged();
            }
        });
    }

    @Override
    public RecyclerView.ViewHolder getItemViewHolder(View view) {
        // return a custom instance of ViewHolder for the items of this section
        return new MyItemViewHolder(view);
    }

    @Override
    public void onBindItemViewHolder(RecyclerView.ViewHolder holder, int position) {
        MyItemViewHolder itemHolder = (MyItemViewHolder) holder;

        // bind your view here
        itemHolder.tvItem.setText(list.get(position));
    }
}

Créez ensuite une instance de vos sections et configurez votre adaptateur:

// Create an instance of SectionedRecyclerViewAdapter 
SectionedRecyclerViewAdapter sectionAdapter = new SectionedRecyclerViewAdapter();

// Add your Sections
sectionAdapter.addSection(new MySection("A", Arrays.asList(new String[] {"a", "b", "c" })));
sectionAdapter.addSection(new MySection("B", Arrays.asList(new String[] {"d", "e", "f" })));

// Set up your RecyclerView with the SectionedRecyclerViewAdapter
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerview);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
recyclerView.setAdapter(sectionAdapter);
1
Gustavo

C'est un peu tard, mais jetez un œil à cette bibliothèque de recyclage avancée https://github.com/h6ah4i/Android-advancedrecyclerview

Dans leur documentation, vous pouvez voir les classes/interfaces liées aux éléments extensibles .

0
Pushpendra