Добавление динамического представления в элемент RecyclerView

Я хочу добавить дополнительный TextView к элементу RecyclerView.Adapter на основе объекта ModalObject, отправленного в RecyclerView.Adapter

Пример:

У меня был модальный объект с переменной isAddText. В onCreateViewHolder будет создан объект Holder с отправкой itemView в качестве параметра.

@Override
public SolventViewHolders onCreateViewHolder(ViewGroup parent, int viewType) {
    View layoutView = LayoutInflater.from(parent.getContext()).inflate(R.layout.solvent_list, null);
    SolventViewHolders rcv = new SolventViewHolders(layoutView);
    return rcv;
}

SolventViewHolders.java

public SolventViewHolders(View itemView) {
    super(itemView);
    itemView.setOnClickListener(this);
    textView = (TextView) itemView.findViewById(R.id.country_name);
    imageView = (ImageView) itemView.findViewById(R.id.country_photo);
    //  Now i need add a view to the itemView parent based on ModalObject isAddView property and how to do that. 
}

Ответы

Ответ 1

Проблема

Как правило, вам нужно будет выполнить itemView.addView(view, layoutParams), чтобы добавить другой вид программно к вашему макету, но вещь с ViewHolders такова, что они будут повторно использоваться RecyclerView для привязки разных элементов от адаптера, так что вы можете Не делайте этого, или этот новый TextView, который вы хотите добавить, появится, когда другой элемент снова привязан к этому экземпляру viewHolder.

Я могу придумать несколько способов достижения вашей цели:


1. Переключить видимость вида

Первый способ заключается в том, чтобы добавить этот текст в ваш макет и получить его видимость по GONE по умолчанию и установить его в VISIBLE при привязке одного элемента, для которого вам нужен этот TextView, например:

@Override
public void onBindViewHolder(AbstractViewHolder holder, int position) {
    holder.extraTextView.setVisibility(View.GONE);
    if(shouldShowExtraTextView) {
        holder.extraTextView.setVisibility(View.VISIBLE);
        holder.extraTextView.setText(/* your text */);
    }
    // proceed to bind item to holder
}

2. Внедрение различных ViewHolders

Второй способ более элегантный, он предполагает создание другого типа ViewHolder. Сначала вам нужно создать int-константы для двух типов представлений, которые будут показаны RecyclerView. Вставьте в свой адаптер следующий код:

private static final int VIEW_ORDINARY = 0;
private static final int VIEW_WITH_EXTRA_TEXT_VIEW = 1;

Затем реализуем метод getItemViewType (int position) адаптера:

@Override
public int getItemViewType(int position) {
    if(position == /* position of item that needs extra text view */) {
        return VIEW_WITH_EXTRA_TEXT_VIEW;
    } else {
        return VIEW_ORDINARY;
    }
}

Следующий шаг - создать другой держатель в зависимости от возвращаемого типа:

@Override
public SolventViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    if(viewType == VIEW_WITH_EXTRA_TEXT_VIEW) {
        View itemView = LayoutInflater.from(parent.getContext())
                                      .inflate(R.layout.solvent_list_with_extra_text_view, parent, false);
        return new SolventViewHolderWithExtraTextView(itemView);
    } else {
        View itemView = LayoutInflater.from(parent.getContext())
                                      .inflate(R.layout.solvent_list, parent, false);
        return new SolventViewHolder(itemView);
    }
}

Конечно, вам необходимо определить SolventViewHolderWithExtraTextView:

class SolventViewHolderWithExtraTextView extends SolventViewHolder {
    TextView extraTextView;

    public SolventViewHolderWithExtraTextView(View itemView) {
        super(itemView);
        this.extraTextView = (TextView) findViewById(R.id.extra_text_view);
    }
}

И затем в onBindViewHolder():

@Override
public void onBindViewHolder(SolventViewHolder holder, int position) {
    // you can use inheritance to handle binding logic or check the item view type again:
    // bind ordinary view
    if(getItemViewType(position)) {
        // bind the extra textView
        ((SolventViewHolderWithExtraTextView)holder).extraTextView.setText("I hope this works");
    }
}

Таким образом у вас есть другой тип ViewHolder для ваших элементов, которым нужен другой TextView, и он будет повторно использован в этой категории. Это предпочтительный способ сделать то, что вы хотите.


3. Добавление/удаление Просмотр динамически

Еще один ответ (который действительно вызывает уродливое решение, если вы меня спрашиваете) заключается в том, чтобы динамически добавлять и удалять вид, например:

@Override
public void onBindViewHolder(AbstractViewHolder holder, int position) {
    if(shouldDisplayExtraTextView) {
        if(!holder.displaysExtraTextView() {
            holder.addExtraTextView();
        }
        holder.extraTextView.setText("This solution is really ugly");
        } else {
        if(holder.displaysExtraTextView() {
            holder.removeExtraTextView();
        }
    }
}

class SolventViewHolders extends RecyclerView.ViewHolder {
    TextView extraTextView;
    boolean viewAdded = false;

    public SolventViewHolders(View itemView) {
        super(itemView);
        itemView.setOnClickListener(this);
        textView = (TextView) itemView.findViewById(R.id.country_name);
        imageView = (ImageView) itemView.findViewById(R.id.country_photo);
         //  Now i need add a view to the itemView parent based on ModalObject isAddView property and how to do that. 

        extraTextView = new TextView();
}

public void addExtraTextView() {
    ((ViewGroup)itemView).addView(extraTextView, layoutParams);
    viewAdded = true;
}

public void removeExtraTextView() {
    ((ViewGroup)itemView).removeView(extraTextView);
    viewAdded = false;
}

public boolean displaysExtraTextView() {
    return viewAdded;
}

Это действительно уродливо, потому что для добавления представления в itemView вам нужно отдать его в ViewGroup (чего не должно быть), поэтому убедитесь, что он всегда будет ViewGroup или его подклассом. Кроме того, вам нужно предоставить правильные макеты для добавления дополнительногоTextView в itemView.

Ответ 2

Вы также можете установить itemView.setHasTransientState(true) в этом случае представление не будет повторно использовано до setHasTransientState пор, пока для setHasTransientState будет установлено значение false.

Ответ 3

С помощью ответа @grgrssll я смог решить мою проблему. В моем случае мне нужно N-количество TextViews внутри каждой строки.

В моем onBindViewHolder

@Override
public void onBindViewHolder(@NonNull final HistoryViewHolder vh, int position) {
    ...
    //Generating N-number of TextViews depending on size of itemsMap
    for (Map.Entry<String, Model> entry : itemsMap.entrySet()) {
        SelectedModel s = entry.getValue();
        // Set setHasTransientState to true
        vh.itemDetailsContainer.setHasTransientState(true);    
        // Dynamically adding the views
        vh.itemDetailsContainer.addView(getItemTextView(context, s.getItemName()));
        ...
    }
    //Set setHasTranssientState to false after loop
    vh.itemDetailsContainer.setHasTransientState(false);
    ...
}