I am trying to create a layout with a list of items, where the last item is a button to add a new item of the "standard" type. It should be something like Google Keep lists, with a permanent "add" item at the end of the list. Google Keep list example
I have tried two ways to achieve it. The first one is very simple, but I find a problem. The second one is not that simple, and the performance is worse. In addition, I think with the second way I am using RecyclerView
in a way that is not intended, and I think I could find performance issues due to that.
How would you do it?
First way
The first one should be the easiest, that is adding in the same layout a RecyclerView
followed by a ViewGroup
containing the "add" item. As parent I use a LinearLayout
with the RecyclerView
and an Include
tag referenced to the "add" item layout. This is actually inside a CoordinatorLayout
because I use a BottomSheet
to pick the new "standard" item to be added.
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorBackgroundLight"
android:orientation="vertical">
<!-- Other Views -->
<android.support.v7.widget.RecyclerView
android:id="@+id/mainRecyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<include
layout="@layout/itemAdd"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
The problem is that the "add" item is positioned below the RecyclerView
, and both disappear when the RecyclerView
is empty.
Second way
Then I tried the second way, that is creating the "add" item from the RecyclerView.Adapter
. Doing it this way is working so far, although I am sure that this cannot be the best way to do it. I will paste the code and below it I explain the reasoning behind.
Adapter
public class MyAdapter extends RecyclerView.Adapter<MyViewHolder> {
private final LayoutInflater mInflater;
private BottomSheetBehavior bottomSheetBehavior;
private int count = 0;
private final List<Object> mDataset;
// Standard constructor. I get the mInflater from Context here
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
final View itemView;
if (count>=mDataset.size()) {
itemView = mInflater.inflate(R.layout.item_add, parent, false);
itemView.setTag("ADD");
}
else{
itemView = mInflater.inflate(R.layout.item_sandard, parent, false);
itemView.setTag(null);
}
count += 1;
return new MyViewHolder(itemView, bottomSheetBehavior);
}
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
if (position<mDataset.size()) {
final Object object = mDataset.get(position);
holder.bind(object); // I bind it on the ViewHolder
}
}
@Override
public int getItemCount() {
return mDataset.size()+1;
}
}
To do so I have created a private variable mCount that I use to count the amount of ViewHolder
created. Then I start my onCreateViewHolder
method with an if statment, where I check if I have already created the ViewHolder
for the last "standard" item of my mDataset. Depending on it I inflate the "standard" view or the "add" view. Then I would have to be updating mCount values each time the user adds or deletes an item (not implemented yet). I also add a tag to the view to get which View
I am using at the ViewHolder
without big troubles.
I have to change the onBindViewHolder
because in case I am inflating the "add" view, I don't want to bind anything as the view is already defined.
I also have to modify the getItemCount
method to return the size of my mDataset plus 1 for the "add" view.
Finally I have to be sending the BottomSheetBehaviour
instance all the way to the ViewHolder
through the Adapter
to be able to open the BottomSheet
with the onClick
defined in the ViewHolder
, which I don't think is very efficient.
ViewHolder
public class MyViewHolder extends RecyclerView.ViewHolder{
private final TextView mText;
private final ImageButton mButton;
public MyViewHolder(View itemView, final BottomSheetBehavior bottomSheetBehavior) {
super(itemView);
if (itemView.getTag() == "ADD"){
mText = null;
mButton = null;
// I also set the OnClickListener to open the BottomSheet
}
else{
// Set mText and an OnClickListener to the button. I use the Adapter here
}
}
// The bind method goes here
}
Here I get the tag from the View
, and depending on it I set a different listener.
I might be late here but try out this way if you have not found any solution..!!
class AddMorePhotosAdapter(
val context: Context,
private val addImageListener: () -> Unit
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private val imageList = ArrayList<Bitmap>()
fun addAll(list: ArrayList<Bitmap>) {
imageList.clear()
imageList.addAll(list)
notifyDataSetChanged()
}
inner class MyItemHolder(val binding: ItemSelectedPhotoBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(path: Bitmap, position: Int) {
Glide.with(context)
.asBitmap()
.placeholder(R.mipmap.ic_launcher)
.load(path)
.into(object : SimpleTarget<Bitmap>() {
override fun onResourceReady(
resource: Bitmap,
transition: Transition<in Bitmap>?
) {
binding.tvSelectedImage.apply {
setImageBitmap(resource)
scaleType = ImageView.ScaleType.FIT_XY
}
}
})
}
}
inner class MyFooterHolder(val binding: LayoutMultiplePhotoBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind() {
binding.tvAddImage.setOnClickListener {
addImageListener.invoke()
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val binding: ViewBinding
if (viewType == TYPE_ITEM) {
binding =
ItemSelectedPhotoBinding.inflate(LayoutInflater.from(context), parent, false)
return MyItemHolder(binding)
} else if (viewType == TYPE_FOOTER) {
binding =
LayoutMultiplePhotoBinding.inflate(LayoutInflater.from(context), parent, false)
return MyFooterHolder(binding)
}
throw RuntimeException("There is no type that matches the type $viewType. Make sure you are using view types correctly!")
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (getItemViewType(position)) {
TYPE_ITEM -> (holder as MyItemHolder).bind(imageList[position], position)
TYPE_FOOTER -> (holder as MyFooterHolder).bind()
}
}
override fun getItemCount(): Int = imageList.size + 1
override fun getItemViewType(position: Int): Int {
return if (position == imageList.size) {
TYPE_FOOTER
} else {
TYPE_ITEM
}
}
companion object {
private const val TYPE_ITEM = 1
private const val TYPE_FOOTER = 0
}
}
-- I've used TYPE_TIME to show listing with recyclerview & TYPE_FOOTER to show "Add" image. both layout will be different to inflate.
check this image for refernece.Initialy it will be only "Add" image
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With