web-dev-qa-db-ja.com

拡張可能なRecyclerViewの作成

私は下のスケッチのように動作するrecyclerviewを実装しようとしています:

enter image description here

アイデアは、親リストがあり、親リスト内のリストアイテムがタップされると、そのリストアイテムはそれ自身のデータを含む子リストを表示するというものです。リストアイテムが子リストでタップされると、その子の値が反映され、親リストアイテムの親の値が更新されます。

過去3日間、機能しないようにしようとしました。 AdvancedReyclerview library を使用してみましたが、私のような初心者にとっては、特にデータを渡す際に意味をなさない巨大な混乱でした。最小限の作業バージョンを取得するために必要なファイルをコピーして貼り付けましたが、データをrecyclerviewに渡す方法と、新しく選択した値でデータを更新する方法がわかりませんでした。

私がやろうとしていることをすることも可能ですか、ここで私の深さから外れていますか?

それでも理解が難しい場合は、さらに説明できます。

編集:誰かが私がRecyclerViewではなくExpandableListViewでこれを行うことをお勧めします。それについて何か考えはありますか?

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.アダプター

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

ライブラリは here で確認できます

そして、以下のようなコードを作成します:

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

この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

library で簡単に実現できます。完全な例 here があります。

基本的に、アイテムをセクションにグループ化します:

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

次に、セクションのインスタンスを作成し、アダプターをセットアップします。

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

これは少し遅れていますが、この高度なrecyclerviewライブラリをご覧ください https://github.com/h6ah4i/Android-advancedrecyclerview

ドキュメントでは、拡張可能なアイテムに関連するクラス/インターフェースを確認できます。

0
Pushpendra