Динамический заголовок RecyclerView

Я работаю с RecyclerView, чтобы добавить динамические заголовки. Я предоставил образец изображения, как показано ниже, что я хочу.

введите здесь описание изображения

На изображении выше дети конечны, что не в моем случае. У меня неуверенные дети после заголовка.

В основном вместо header0 это будет название месяца, например MAR, а ниже будут происходить события, которые произошли в месяце MAR. Данные поступают из API, т.е. веб-службы, в которой также наступает дата, но я изо всех сил пытаюсь создать логику для того, что я объяснил выше.

То, что я пробовал, показано ниже. Ниже приведен пример проекта, который я создал, поскольку исходный код является конфиденциальным и имеет аналогичную логическую проблему, как и исходный.

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

Адаптер.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);
        }
    }
}

Бин.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;
    }
}

Получение вывода в виде:

Получение вывода

Заранее спасибо.!


person Maulik Dodia    schedule 28.03.2018    source источник
comment
Так в чем же проблема?   -  person Eselfar    schedule 28.03.2018
comment
Я добавил изображение вывода, которое я получаю. Пожалуйста, проверьте @Eselfar   -  person Maulik Dodia    schedule 28.03.2018


Ответы (2)


Проблема в том, что у вас есть список Bean, и вы хотите добавить заголовок, когда это новый месяц. Поэтому, когда месяц меняется, в beanArrayListToPopulate вам нужно добавить заголовок Bean и. Поэтому вам нужно создать дополнительный Bean, чтобы добавить заголовок.

Таким образом, цикл 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);
}

EDIT: вы тоже можете попробовать этот подход

Но я думаю, что было бы даже лучше использовать выделенный объект, чтобы не загрязнять данные, которые вы извлекаете (Bean), информацией, используемой только для их отображения.

Итак, что вы можете сделать, так это создать объект ListItem и использовать его в своем адаптере вместо самого Bean.

В действии

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

Примечание. Возможно, код потребует некоторых корректировок, так как я его не тестировал.

person Eselfar    schedule 28.03.2018
comment
Позвольте мне проверить и вернуться к вам@Eselfar - person Maulik Dodia; 28.03.2018
comment
Я обновил свой ответ с некоторыми улучшениями. Вы можете попробовать их, если оригинальный ответ работает :) - person Eselfar; 28.03.2018
comment
Насколько мне известно, это будет работать, если данные поступают из веб-службы в правильном порядке (например, по возрастанию или по убыванию). Что, если он не придет без какого-либо приказа? @Eselfar - person Maulik Dodia; 29.03.2018
comment
Если не заказать, у вас будет один и тот же заголовок несколько раз. Если вы хотите иметь только один заголовок одного типа (например, «март 2018»), вам нужно сначала отсортировать список либо в вашем запросе (если он поступает из базы данных), либо путем сортировки результата (из веб-сервис), прежде чем отображать их. - person Eselfar; 29.03.2018
comment
Большое спасибо @Eselfar - person Maulik Dodia; 29.03.2018
comment
Отличное решение, удивлен, почему у него нет голосов. Я сделал свою часть. !! Спасибо @Eselfar - person Aalap Patel; 21.10.2019

Вы можете решить так:

  1. Используйте дополнительный вид для заголовка в каждом элементе строки
  2. Проверьте, что 2 последовательные строки имеют разницу в дате (в вашем случае месяц), если найденная разница показывает более поздний заголовок строки. Предположим, что в строке позиции 3 и позиции 4 разница в месяце больше, чем в заголовке позиции 4.
  3. По умолчанию все заголовки строк будут скрыты.
  4. Ваши данные должны быть отсортированы по дате

строка.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;  
}

In onBindViewHolder

if(displayHeader(position)){
   // set to visiable 
}else{
   // set visibility to gone
}
person Abu Yousuf    schedule 28.03.2018
comment
Позвольте мне попробовать, и я свяжусь с вами. Спасибо@АбуЮсуф - person Maulik Dodia; 28.03.2018