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

得到如下输出:

提前致谢!

你可以这样解决:

  1. 在每个行项目中为 header 使用额外的视图
  2. 检查连续 2 行是否有日期(在你的例子中是月份)差异,如果发现差异则显示后面的行 header。假设位置 3 和位置 4 行与显示 header 的位置 4.
  3. 有月份差异
  4. 默认情况下,所有行 header 都将被隐藏。
  5. 您的数据应按日期排序

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中你需要添加Beanheader。所以你需要创建一个额外的 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;
    }
}

注意:可能代码需要一些调整,因为我还没有测试过。