RecyclerView Dynamic Header

雨燕双飞 提交于 2019-12-14 03:57:33

问题


I have been working with RecyclerView to add dynamic headers. I have provided a sample Image like below what I wants.

In above image the children are finite which is not in my case. I'm having uncertain children after the header.

Basically instead of header0 it will be Month name like MAR and below that things which have occurred in MAR month will come. The data is coming from API i.e Web Service in which date is also coming but I'm struggling to create a logic for what I have explained above.

What I have tried is like below. Below is a sample project I created as original code is confidential and It has similar logic issue like original one.

MainActivity.java

    public class MainActivity extends AppCompatActivity {

    private RecyclerView rv;
    private ArrayList<Bean> beanArrayList = new ArrayList<>();
    private ArrayList<Bean> beanArrayListToPopulate = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        addDataToBean();

        rv = findViewById(R.id.rv);
        rv.setLayoutManager(new LinearLayoutManager(this));

        String prevMonth = null;

        for (int i = 0; i < beanArrayList.size(); i++) {

            Bean bean = new Bean();
            bean.setFirstName(beanArrayList.get(i).getFirstName());

            String newMonth;
            String createdDate = beanArrayList.get(i).getCreatedDate();

            if (createdDate != null) {
                newMonth = createdDate.split(" ")[1].trim();
                if (!newMonth.equalsIgnoreCase(prevMonth)) {
                    bean.setViewType(Adapter.VIEW_TYPE_ITEM);
                } else if (newMonth.equalsIgnoreCase(prevMonth)) {
                    bean.setViewType(Adapter.VIEW_TYPE_HEADER);
                    bean.setHeaderString(newMonth);
                }
                prevMonth = newMonth;
            }
            beanArrayListToPopulate.add(bean);
        }
        rv.setAdapter(new Adapter(beanArrayListToPopulate));
    }

    private void addDataToBean() {

        SimpleDateFormat sdf = new SimpleDateFormat("dd MMM yyyy");

        Date currentDate = new Date();
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(currentDate);

        for (int i = 0; i < 50; i++) {
            Bean bean = new Bean();
            calendar.add(Calendar.DATE, 10);
            Date newDate = calendar.getTime();
            bean.setCreatedDate(sdf.format(newDate));
            bean.setFirstName("Maulik - " + i);
            beanArrayList.add(bean);
        }
    }
}

Adapter.java

    public class Adapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    public static final int VIEW_TYPE_ITEM = 0;
    public static final int VIEW_TYPE_HEADER = 1;

    private ArrayList<Bean> mBeanList;

    public Adapter(ArrayList<Bean> beanList) {
        this.mBeanList = beanList;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view;
        if (viewType == VIEW_TYPE_HEADER) {
            view = LayoutInflater.from(parent.getContext()).inflate(R.layout.header, parent, false);
            return new VHHeader(view);
        } else if (viewType == VIEW_TYPE_ITEM) {
            view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false);
            return new VHItem(view);
        }
        throw new RuntimeException("There is no type that matches the type " + viewType + ". Make sure you are using view types correctly!");
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {

        if (holder instanceof VHHeader) {
            ((VHHeader) holder).tvHeader.setText(mBeanList.get(position).getHeaderString());
        } else if (holder instanceof VHItem) {
            ((VHItem) holder).tvItem.setText(mBeanList.get(position).getFirstName());
        }
    }

    @Override
    public int getItemCount() {
        return mBeanList.size();
    }

    @Override
    public int getItemViewType(int position) {
        return mBeanList.get(position).getViewType();
    }

    class VHItem extends RecyclerView.ViewHolder {
        private TextView tvItem;

        public VHItem(View itemView) {
            super(itemView);
            tvItem = itemView.findViewById(R.id.tv_item);
        }
    }

    class VHHeader extends RecyclerView.ViewHolder {
        private TextView tvHeader;

        public VHHeader(View itemView) {
            super(itemView);
            tvHeader = itemView.findViewById(R.id.tv_header);
        }
    }
}

Bean.java

    public class Bean {

    private String createdDate;
    private String firstName;
    private int viewType;
    private String headerString;

    public String getCreatedDate() {
        return createdDate;
    }

    public void setCreatedDate(String createdDate) {
        this.createdDate = createdDate;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public int getViewType() {
        return viewType;
    }

    public void setViewType(int viewType) {
        this.viewType = viewType;
    }

    public String getHeaderString() {
        return headerString;
    }

    public void setHeaderString(String headerString) {
        this.headerString = headerString;
    }
}

Getting Output like:

Thanks in Advance.!


回答1:


The problem is that you have have a list of Bean and you want to add a header when this is a new month. So when the month changes, in beanArrayListToPopulate you need to add the Bean and the header. So you need to create an extra Bean to add the header.

So the for loop should be like that (in MainActivity)

String prevMonth = null;
String newMonth;
String createdDate;
for (Bean bean : beanArrayList) {
    createdDate = bean.getCreatedDate();
    // Create header Bean if necessary
    if (createdDate != null) {
        newMonth = createdDate.split(" ")[1].trim();
        if (!newMonth.equalsIgnoreCase(prevMonth)) {
            Bean headerBean = new Bean();
            headerBean.setViewType(Adapter.VIEW_TYPE_HEADER);
            headerBean.setHeaderString(newMonth);
            beanArrayListToPopulate.add(headerBean);
            prevMonth = newMonth;
        }
    }
    // Update data Bean
    bean.setViewType(Adapter.VIEW_TYPE_ITEM);
    beanArrayListToPopulate.add(bean);
}

EDIT: You can try this approach too

But I think I'd even be better to use a dedicated object to not pollute the data you retrieve (Bean) with information only used to display them.

So what you can do is to create a ListItem object and use it in your Adapter instead of the Bean itself.

In the Activity

private ArrayList<Bean> beanArrayList = new ArrayList<>();
private ArrayList<ListItem> mListItems = new ArrayList<>();

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    addDataToBean();

    rv = findViewById(R.id.rv);
    rv.setLayoutManager(new LinearLayoutManager(this));

    String prevMonth = null;
    String newMonth;
    String createdDate;
    for (Bean bean : beanArrayList) {
        createdDate = bean.getCreatedDate();
        // Create a header if necessary
        if (createdDate != null) {
            newMonth = createdDate.split(" ")[1].trim();
            if (!newMonth.equalsIgnoreCase(prevMonth)) {
                ListItem headerItem = new ListItem(newMonth, Adapter.VIEW_TYPE_HEADER);
                mListItems.add(headerItem);
                prevMonth = newMonth;
            }
        }
        // Create a new item with the Bean data
        ListItem item = new ListItem(bean.getFirstName(), Adapter.VIEW_TYPE_ITEM);
        mListItems.add(item);
    }

    rv.setAdapter(new Adapter(mListItems));
}

In the Adapter (I've put only what I've changed)

private final ArrayList<ListItem> mItems;

public Adapter(ArrayList<ListItem> items) {
    this.mItems = items;
}

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    View view;
    final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
    if (viewType == VIEW_TYPE_HEADER) {
        view = inflater.inflate(R.layout.header, parent, false);
        return new VHHeader(view);
    } else if (viewType == VIEW_TYPE_ITEM) {
        view = inflater.inflate(R.layout.item, parent, false);
        return new VHItem(view);
    }
    throw new RuntimeException("There is no type that matches the type " + viewType + ". Make sure you are using view types correctly!");
}

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
    ListItem item = mItems.get(position);
    // Note: You have the type information directly inside the ViewHolder so you don't need
    // to check the ViewHolder's instance type
    switch (holder.getItemViewType()) {
        case VIEW_TYPE_HEADER:
            ((VHHeader) holder).tvHeader.setText(item.getData());
            break;
        case VIEW_TYPE_ITEM:
            ((VHItem) holder).tvItem.setText(item.getData());
            break;
    }
}

The ListItem

public class ListItem {
    private String mData;
    private int mType;

    public ListItem(String data, int type) {
        mData = data;
        mType = type;
    }

    public String getData() {
        return mData;
    }

    public void setData(String data) {
        mData = data;
    }

    public int getType() {
        return mType;
    }
}

Note: Maybe the code will required some adjustments as I haven't tested it.




回答2:


You can solve this way:

  1. Use a extra view for header in each row item
  2. Check 2 consecutive row has date(in your case month) difference, if found difference show the later row header. Suppose position 3 and position 4 row have month difference than show header of the position 4.
  3. By default all rows header will be hidden.
  4. You data should be sort by date

row.xml:

    <LinearLayout

    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    >

     <!--Header -->
    <LinearLayout
        android:id="@+id/header"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:visibility="gone">
         .................
         ................
     </LinearLayout>

     .......................
     .......................
    </LinearLayout>

In Adapter:

public boolean displayHeader(int position) {

   if(position == 0) return true;
   //.....
  // check month difference to previous position if any then return true; 
  // else return false;  
}

In onBindViewHolder

if(displayHeader(position)){
   // set to visiable 
}else{
   // set visibility to gone
}


来源:https://stackoverflow.com/questions/49538393/recyclerview-dynamic-header

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!