40

I want to show a button at the end of RecyclerView.

With ListView there was a method addFooterView(), how to do the same with RecylerView.

Vrutin Rathod
  • 900
  • 1
  • 12
  • 16

5 Answers5

53

I came across this problem and found this answer. The current selected answer is correct but it is missing some details. Here is the full implementation,

Add this method to your adapter

@Override
public int getItemViewType(int position) {
    return (position == myItems.size()) ? R.layout.button : R.layout.item;
}

It will check if the current position is past the last item in your list, if it is then it will return the button layout value to this method,

@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

    View itemView;

    if(viewType == R.layout.item){
        itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false);
    }

    else {
        itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.button, parent, false);
    }

    return new MyViewHolder(itemView);
}

The above method checks if the passed viewType is the item layout or the button layout and then inflates the view occordingly and sends the inflated view to this method,

@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
    if(position == myItems.size()) {
        holder.button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Toast.makeText(context, "Button Clicked", Toast.LENGTH_LONG).show();
            }
        });
    }
    else {
        final String name = myItems.get(position);
        holder.title.setText(name);
    }
}

This checks if we are past the last item in the list and if we are then it sets the onClick method to our button. Otherwise it creates an item like normal.

We also need to change the getItemCount method to this

@Override
public int getItemCount() {
    return myItems.size() + 1;
}

Since we are going through all the items and then adding a button at the end, the + 1 is our button.

Then lastly we need to add our view to the myViewHolder class

public class MyViewHolder extends RecyclerView.ViewHolder {
    public TextView title;
    public Button button;

    public MyViewHolder(View view) {
        super(view);
        title  = (TextView) view.findViewById(R.id.title);
        button = (Button) view.findViewById(R.id.bottom_button);
    }
}

I hope this helps!

Gary Holiday
  • 3,297
  • 3
  • 31
  • 72
  • 2
    This really helped me @Gary – Pinkesh Darji Mar 10 '17 at 17:57
  • @Gary Do we have to add our button to the same xml layout R.id.title resides within? If so, won't the button appear anytime the layout containing the title is drawn since they'll be declared in the same layout? – Cody May 24 '17 at 17:13
  • 1
    @Cody No, the button and item should be in two separate xml files. – Gary Holiday May 25 '17 at 01:56
  • I had an issue which is an extra item appears at the end of the list if for some conditions I hide the button so I solved it by this: @Override public int getItemCount() { if (myCondition) { return myItems.size() + 1; //want to show the button } else { return myItems.size();//doesn't want to show the button } } – Maher Nabil Jul 20 '17 at 11:21
  • return myItems.size() + 1; saved my time – Ali Zarei Dec 23 '17 at 11:32
  • @GaryHoliday incredibly detailed and helpful, thank you! – Deem Apr 08 '18 at 06:20
45

One way to do it would be to make your footer view a "ViewType" of your adapter. In order to do that, overrides getItemViewType to return a different value for your last item.

@Override
public int getItemViewType(int position) {
    return (position == mData.size()) ? VIEW_TYPE_FOOTER : VIEW_TYPE_CELL;
}

Then in the onCreateViewHolder, handle the different viewType.

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

    if (viewType == VIEW_TYPE_CELL) {

        //Create viewholder for your default cell
    }
    else {

        //Create viewholder for your footer view
    }
}

Don't forget to update the value return by getCount() by adding 1, and to distinguish the 2 types of ViewHolder in OnBindViewHolder (with instanceof for example).

Gyome
  • 1,333
  • 2
  • 15
  • 23
  • Where are you getting this mData ? – TheCoderGuy Nov 29 '18 at 09:16
  • @Spritzig mData is the list that will populate your adapter. You should have created a global instance in your adapter of a certain type (depending on your business logic), and that same variable should be used in here, which in this case it's mData. – Red M Nov 29 '18 at 18:24
  • 3
    Isn't it suppose to be return (position == mData.size() -1) ? VIEW_TYPE_FOOTER : VIEW_TYPE_CELL; – Red M Nov 29 '18 at 18:32
  • @RedM if the last object in the list is suppose to be used to bind with the footer view, it could. But most likely all objects in mData will bind to a VIEW_TYPE_CELL, and the footer won't bind with any object. – Gyome Nov 30 '18 at 10:09
  • @Spritzig mData is just a list containing the objects you will use to bind with the views. It is each adapters own implementation. The important part here is the size of this list, that equals the number of views of type VIEW_TYPE_CELL. – Gyome Nov 30 '18 at 10:11
  • @Gyome Thank you can you see my question it is about with recyclerview. Below is the link of my question https://stackoverflow.com/questions/53534835/how-to-save-data-in-recycler-view – TheCoderGuy Nov 30 '18 at 10:13
9

This is what I am using in kotlin. Hope it helps somebody.

class ThumbnailsAdapter(
        private val mContext: Context,
        private val list: ArrayList<Image>
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return when (viewType) {
            R.layout.z_thumbnail -> {
                val view = LayoutInflater.from(mContext)
                        .inflate(R.layout.z_thumbnail, parent, false)
                ThumbnailViewHolder(view)
            }
            R.layout.z_thumbanail_add -> {
                val view = LayoutInflater.from(mContext)
                        .inflate(R.layout.z_thumbanail_add, parent, false)
                PlaceholderViewHolder(view)
            }
            else -> throw IllegalArgumentException("unknown view type $viewType")
        }
    }

    override fun getItemCount(): Int {
        return list.size + 1
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        when (getItemViewType(position)) {
            R.layout.z_thumbnail -> (holder as ThumbnailViewHolder).bind(list[position].path)
            R.layout.z_thumbanail_add -> (holder as PlaceholderViewHolder).bind()
        }
    }

    override fun getItemViewType(position: Int): Int {
        return when (position) {
            list.size -> R.layout.z_thumbanail_add
            else -> R.layout.z_thumbnail
        }
    }

    inner class ThumbnailViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        private val imageView = itemView.findViewById<ImageView>(R.id.thumbnail)

        fun bind(path: String) {
            Glide.with(mContext).load(path).into(imageView)
        }
    }

    inner class PlaceholderViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        private val btn = itemView.findViewById<ImageView>(R.id.add_images_btn)

        fun bind() {
            btn.setOnClickListener {
                //Do your logic here for the button
            }
        }
    }

}
Nux
  • 5,890
  • 13
  • 48
  • 74
2

Here is the trick that work for me without creating a new view layout. Basically you have to compare the size of array and position of view holder to generate a new view in onBindViewHolder.
Write the following code:

@Override
public void onBindViewHolder(final ViewHolder holder, int position) {
    //This is necessary even if you mention view gone in xml file.
    holder.mAddBtn.setVisibility(View.GONE);
    //Compare size and add button at buttom of view,ie arraylist size
    if(position==mComplaintList.size()-1){
        holder.mAddBtn.setVisibility(View.VISIBLE);
    }
    holder.mAddBtn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
        //Write code
        }
    });
}
kalehmann
  • 4,821
  • 6
  • 26
  • 36
Kunchok Tashi
  • 2,413
  • 1
  • 22
  • 30
1

There is a very simple way to that first you need to add +1 to size of the list

    @Override
    public int getItemCount() {
        return badgesModels.size() + 1;
    }

then check if the position of the list is grater than the size of the list do something. in my case:

        if (position == badgesModels.size()) {
            holder.imageViewBadege.setImageDrawable(getResources().getDrawable(R.drawable.plus));
            holder.imageViewBadege.setBackgroundColor(getResources().getColor(R.color.bg_gray));
            holder.textViewBadgeTitle.setText("Make Badge");

            holder.imageViewBadege.setOnClickListener(v -> {
                ListDialog.newInstanceChooseBadge().show(Objects.requireNonNull(getFragmentManager()),"makeBadge");
            });
        } else {
            GlideApp.with(Objects.requireNonNull(getContext())).load(results.get(position).getImage_link()).placeholder(R.drawable.badge).into(holder.imageViewBadege);
            holder.textViewBadgeTitle.setText(results.get(position).getTitle());
            holder.imageViewBadege.setOnClickListener(v -> {
                apiService.assginBadgesStudent(classId, studentId, results.get(position).getId());
                dismiss();
                Objects.requireNonNull(getTargetFragment()).onResume();
                playFromAsset("sounds/ding.wav");
            });
        }
SinaMN75
  • 6,742
  • 5
  • 28
  • 56