RecyclerView 动态 Header
RecyclerView Dynamic Header
我一直在使用 RecyclerView 添加动态 headers。我提供了一个示例图片,如下所示。
在上图中,children 是有限的,这在我的例子中不是。在 header.
之后我不确定 children
基本上而不是 header0 它将是月份名称,如 MAR 和下面发生在 MAR 月将至。数据来自 API 即 Web 服务,其中的日期也即将到来,但我正在努力为我上面解释的内容创建逻辑。
我试过的如下。以下是我创建的示例项目,因为原始代码是机密的,它与原始代码有类似的逻辑问题。
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;
}
}
得到如下输出:
提前致谢!
你可以这样解决:
- 在每个行项目中为 header 使用额外的视图
- 检查连续 2 行是否有日期(在你的例子中是月份)差异,如果发现差异则显示后面的行 header。假设位置 3 和位置 4 行与显示 header 的位置 4.
有月份差异
- 默认情况下,所有行 header 都将被隐藏。
- 您的数据应按日期排序
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>
在适配器中:
public boolean displayHeader(int position) {
if(position == 0) return true;
//.....
// check month difference to previous position if any then return true;
// else return false;
}
在onBindViewHolder
if(displayHeader(position)){
// set to visiable
}else{
// set visibility to gone
}
问题是你有一个 Bean
的列表,你想在新的月份添加一个 header。所以当月份改变时,在beanArrayListToPopulate
中你需要添加Bean
和header。所以你需要创建一个额外的 Bean
来添加 header.
所以 for 循环应该是这样的(在 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);
}
编辑:您也可以尝试这种方法
但我认为我最好使用专用 object 来避免使用仅用于显示它们的信息污染您检索的数据 (Bean
)。
所以你可以做的是创建一个 ListItem
object 并在你的适配器中使用它而不是 Bean
本身。
在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));
}
在适配器中(我只放了我更改的内容)
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;
}
}
列表项
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;
}
}
注意:可能代码需要一些调整,因为我还没有测试过。
我一直在使用 RecyclerView 添加动态 headers。我提供了一个示例图片,如下所示。
在上图中,children 是有限的,这在我的例子中不是。在 header.
之后我不确定 children基本上而不是 header0 它将是月份名称,如 MAR 和下面发生在 MAR 月将至。数据来自 API 即 Web 服务,其中的日期也即将到来,但我正在努力为我上面解释的内容创建逻辑。
我试过的如下。以下是我创建的示例项目,因为原始代码是机密的,它与原始代码有类似的逻辑问题。
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;
}
}
得到如下输出:
提前致谢!
你可以这样解决:
- 在每个行项目中为 header 使用额外的视图
- 检查连续 2 行是否有日期(在你的例子中是月份)差异,如果发现差异则显示后面的行 header。假设位置 3 和位置 4 行与显示 header 的位置 4. 有月份差异
- 默认情况下,所有行 header 都将被隐藏。
- 您的数据应按日期排序
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>
在适配器中:
public boolean displayHeader(int position) {
if(position == 0) return true;
//.....
// check month difference to previous position if any then return true;
// else return false;
}
在onBindViewHolder
if(displayHeader(position)){
// set to visiable
}else{
// set visibility to gone
}
问题是你有一个 Bean
的列表,你想在新的月份添加一个 header。所以当月份改变时,在beanArrayListToPopulate
中你需要添加Bean
和header。所以你需要创建一个额外的 Bean
来添加 header.
所以 for 循环应该是这样的(在 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);
}
编辑:您也可以尝试这种方法
但我认为我最好使用专用 object 来避免使用仅用于显示它们的信息污染您检索的数据 (Bean
)。
所以你可以做的是创建一个 ListItem
object 并在你的适配器中使用它而不是 Bean
本身。
在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));
}
在适配器中(我只放了我更改的内容)
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;
}
}
列表项
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;
}
}
注意:可能代码需要一些调整,因为我还没有测试过。