Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Recyclerview with a different last item as "add" item

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 ViewHolderfor 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.

like image 639
Daniel Reina Avatar asked Mar 10 '16 20:03

Daniel Reina


1 Answers

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

like image 170
Nidhi Avatar answered Nov 16 '22 00:11

Nidhi