Creación de un RecyclerView ampliable

Estoy tratando de implementar una recyclerview que se comporta como mi boceto a continuación:

Introduzca aquí la descripción de la imagen

La idea es que hay una lista de padres, cuando un elemento de lista en la lista de padres es aprovechado, ese elemento de lista revela una lista secundaria que contiene sus propios datos. Cuando se pulsa un elemento de lista en la lista secundaria, el valor de ese hijo se refleja y actualiza el valor del padre en el elemento de lista principal.

He tratado de conseguir que funcione durante los últimos 3 días sin éxito. Intenté usar la biblioteca AdvancedReyclerview, pero para un principiante como yo, era un desorden gigante de cosas que no tenían sentido, especialmente al pasar los datos. Copié y pegé los archivos que necesitaba para obtener una versión mínima de trabajo, pero no tenía ni idea de cómo pasar mis datos en el recyclerview y cómo actualizar con el valor recién seleccionado.

¿Incluso es posible hacer lo que estoy tratando de hacer o estoy fuera de mi profundidad aquí?

Si todavía es difícil de entender, puedo explicarlo más.

EDIT: alguien me recomendó que hiciera esto con ExpandableListView en lugar de con RecyclerView. ¿Alguna idea sobre eso?

Puedes consultar mi biblioteca aquí

Y crear algo como código debajo:

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); } } } 

Y esta clase 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; } } 

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

 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); } } } 

Usted puede fácilmente lograrlo con esta biblioteca , hay un ejemplo completo aquí .

Básicamente, agrupa los elementos en secciones:

 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)); } } 

A continuación, cree la instancia de sus secciones y configure su adaptador:

 // 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); 

Esto es un poco tarde, pero eche un vistazo en esta biblioteca avanzada recyclerview https://github.com/h6ah4i/android-advancedrecyclerview

En su documentación puede ver las clases / interfaces relacionadas con el elemento expandible .

  • StaggaredGridLayoutManager no funciona correctamente
  • Cargando imagen con deslizamiento es lento
  • (Smooth) ScrollToPosition no funciona correctamente con RecyclerView
  • Cómo agregar Android Support v7 bibliotecas en eclipse?
  • TableLayout Vs Recycler Ver?
  • RecyclerView scrollToPosition no dispara scrollListener
  • El uso del ejemplo de RecyclerView no compila
  • HorizontalGridView / RecyclerLa posición de desplazamiento se restablece una vez que la imagen de Picasso se carga
  • Quiero que mi RecyclerView no recicle algunos artículos
  • Recyclerview vincula todas las vistas al mismo tiempo
  • RecyclerView item layout_weight, tamaños no actualizados
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.