问题
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:
- Use a extra view for header in each row item
- 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.
- By default all rows header will be hidden.
- 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