Android 5.0 - Add header/footer to a RecyclerView - java

I spent a moment trying to figure out a way to add a header to a RecyclerView, unsuccessfully.
This is what I got so far:
#Override
protected void onCreate(Bundle savedInstanceState) {
...
layouManager = new LinearLayoutManager(getActivity());
recyclerView.setLayoutManager(layouManager);
LayoutInflater inflater = (LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
headerPlaceHolder = inflater.inflate(R.layout.view_header_holder_medium, null, false);
layouManager.addView(headerPlaceHolder, 0);
...
}
The LayoutManager seems to be the object handling the disposition of the RecyclerView items. As I couldn't find any addHeaderView(View view) method, I decided to go with the LayoutManager's addView(View view, int position) method and to add my header view in the first position to act as a header.
Aaand this is where things get uglier:
java.lang.NullPointerException: Attempt to read from field 'android.support.v7.widget.RecyclerView$ViewHolder android.support.v7.widget.RecyclerView$LayoutParams.mViewHolder' on a null object reference
at android.support.v7.widget.RecyclerView.getChildViewHolderInt(RecyclerView.java:2497)
at android.support.v7.widget.RecyclerView$LayoutManager.addViewInt(RecyclerView.java:4807)
at android.support.v7.widget.RecyclerView$LayoutManager.addView(RecyclerView.java:4803)
at com.mathieumaree.showz.fragments.CategoryFragment.setRecyclerView(CategoryFragment.java:231)
at com.mathieumaree.showz.fragments.CategoryFragment.access$200(CategoryFragment.java:47)
at com.mathieumaree.showz.fragments.CategoryFragment$2.success(CategoryFragment.java:201)
at com.mathieumaree.showz.fragments.CategoryFragment$2.success(CategoryFragment.java:196)
at retrofit.CallbackRunnable$1.run(CallbackRunnable.java:41)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5221)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:899)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694)
After getting several NullPointerExceptions trying to call the addView(View view) at different moments of the Activity creation (also tried adding the view once everything is set up, even the Adapter's data), I realized I have no idea if this is the right way to do it (and it doesn't look to be).
PS: Also, a solution that could handle the GridLayoutManager in addition to the LinearLayoutManager would be really appreciated!

I had to add a footer to my RecyclerView and here I'm sharing my code snippet as I thought it might be useful. Please check the comments inside the code for better understanding of the overall flow.
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import java.util.ArrayList;
public class RecyclerViewWithFooterAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int FOOTER_VIEW = 1;
private ArrayList<String> data; // Take any list that matches your requirement.
private Context context;
// Define a constructor
public RecyclerViewWithFooterAdapter(Context context, ArrayList<String> data) {
this.context = context;
this.data = data;
}
// Define a ViewHolder for Footer view
public class FooterViewHolder extends ViewHolder {
public FooterViewHolder(View itemView) {
super(itemView);
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// Do whatever you want on clicking the item
}
});
}
}
// Now define the ViewHolder for Normal list item
public class NormalViewHolder extends ViewHolder {
public NormalViewHolder(View itemView) {
super(itemView);
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// Do whatever you want on clicking the normal items
}
});
}
}
// And now in onCreateViewHolder you have to pass the correct view
// while populating the list item.
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v;
if (viewType == FOOTER_VIEW) {
v = LayoutInflater.from(context).inflate(R.layout.list_item_footer, parent, false);
FooterViewHolder vh = new FooterViewHolder(v);
return vh;
}
v = LayoutInflater.from(context).inflate(R.layout.list_item_normal, parent, false);
NormalViewHolder vh = new NormalViewHolder(v);
return vh;
}
// Now bind the ViewHolder in onBindViewHolder
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
try {
if (holder instanceof NormalViewHolder) {
NormalViewHolder vh = (NormalViewHolder) holder;
vh.bindView(position);
} else if (holder instanceof FooterViewHolder) {
FooterViewHolder vh = (FooterViewHolder) holder;
}
} catch (Exception e) {
e.printStackTrace();
}
}
// Now the critical part. You have return the exact item count of your list
// I've only one footer. So I returned data.size() + 1
// If you've multiple headers and footers, you've to return total count
// like, headers.size() + data.size() + footers.size()
#Override
public int getItemCount() {
if (data == null) {
return 0;
}
if (data.size() == 0) {
//Return 1 here to show nothing
return 1;
}
// Add extra view to show the footer view
return data.size() + 1;
}
// Now define getItemViewType of your own.
#Override
public int getItemViewType(int position) {
if (position == data.size()) {
// This is where we'll add footer.
return FOOTER_VIEW;
}
return super.getItemViewType(position);
}
// So you're done with adding a footer and its action on onClick.
// Now set the default ViewHolder for NormalViewHolder
public class ViewHolder extends RecyclerView.ViewHolder {
// Define elements of a row here
public ViewHolder(View itemView) {
super(itemView);
// Find view by ID and initialize here
}
public void bindView(int position) {
// bindView() method to implement actions
}
}
}
The above code snippet adds a footer to the RecyclerView. You can check this GitHub repository for checking the implementation of adding both header and a footer.

Very simple to solve!!
I don't like an idea of having logic inside adapter as a different view type because every time it checks for the view type before returning the view. Below solution avoids extra checks.
Just add LinearLayout (vertical) header view + recyclerview + footer view inside android.support.v4.widget.NestedScrollView.
Check this out:
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<View
android:id="#+id/header"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<android.support.v7.widget.RecyclerView
android:id="#+id/list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layoutManager="LinearLayoutManager"/>
<View
android:id="#+id/footer"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
Add this line of code for smooth scrolling
RecyclerView v = (RecyclerView) findViewById(...);
v.setNestedScrollingEnabled(false);
This will lose all RV performance and RV will try to lay out all view holders regardless of the layout_height of RV
Recommended using for the small size list like Nav drawer or settings etc.

I had the same problem on Lollipop and created two approaches to wrap the Recyclerview adapter. One is pretty easy to use, but I'm not sure how it will behave with a changing dataset. Because it wraps your adapter and you need to make yourself sure to call methods like notifyDataSetChanged on the right adapter-object.
The other shouldn't have such problems. Just let your regular adapter extend the class, implement the abstract methods and you should be ready. And here they are:
gists
HeaderRecyclerViewAdapterV1.java usage new HeaderRecyclerViewAdapterV1(new RegularAdapter());
HeaderRecyclerViewAdapterV2.java usage RegularAdapter extends HeaderRecyclerViewAdapterV2
HeaderRecyclerViewAdapterV1
import android.support.v7.widget.RecyclerView;
import android.view.ViewGroup;
/**
* Created by sebnapi on 08.11.14.
* <p/>
* This is a Plug-and-Play Approach for adding a Header or Footer to
* a RecyclerView backed list
* <p/>
* Just wrap your regular adapter like this
* <p/>
* new HeaderRecyclerViewAdapterV1(new RegularAdapter())
* <p/>
* Let RegularAdapter implement HeaderRecyclerView, FooterRecyclerView or both
* and you are ready to go.
* <p/>
* I'm absolutely not sure how this will behave with changes in the dataset.
* You can always wrap a fresh adapter and make sure to not change the old one or
* use my other approach.
* <p/>
* With the other approach you need to let your Adapter extend HeaderRecyclerViewAdapterV2
* (and therefore change potentially more code) but possible omit these shortcomings.
* <p/>
* TOTALLY UNTESTED - USE WITH CARE - HAVE FUN :)
*/
public class HeaderRecyclerViewAdapterV1 extends RecyclerView.Adapter {
private static final int TYPE_HEADER = Integer.MIN_VALUE;
private static final int TYPE_FOOTER = Integer.MIN_VALUE + 1;
private static final int TYPE_ADAPTEE_OFFSET = 2;
private final RecyclerView.Adapter mAdaptee;
public HeaderRecyclerViewAdapterV1(RecyclerView.Adapter adaptee) {
mAdaptee = adaptee;
}
public RecyclerView.Adapter getAdaptee() {
return mAdaptee;
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == TYPE_HEADER && mAdaptee instanceof HeaderRecyclerView) {
return ((HeaderRecyclerView) mAdaptee).onCreateHeaderViewHolder(parent, viewType);
} else if (viewType == TYPE_FOOTER && mAdaptee instanceof FooterRecyclerView) {
return ((FooterRecyclerView) mAdaptee).onCreateFooterViewHolder(parent, viewType);
}
return mAdaptee.onCreateViewHolder(parent, viewType - TYPE_ADAPTEE_OFFSET);
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (position == 0 && holder.getItemViewType() == TYPE_HEADER && useHeader()) {
((HeaderRecyclerView) mAdaptee).onBindHeaderView(holder, position);
} else if (position == mAdaptee.getItemCount() && holder.getItemViewType() == TYPE_FOOTER && useFooter()) {
((FooterRecyclerView) mAdaptee).onBindFooterView(holder, position);
} else {
mAdaptee.onBindViewHolder(holder, position - (useHeader() ? 1 : 0));
}
}
#Override
public int getItemCount() {
int itemCount = mAdaptee.getItemCount();
if (useHeader()) {
itemCount += 1;
}
if (useFooter()) {
itemCount += 1;
}
return itemCount;
}
private boolean useHeader() {
if (mAdaptee instanceof HeaderRecyclerView) {
return true;
}
return false;
}
private boolean useFooter() {
if (mAdaptee instanceof FooterRecyclerView) {
return true;
}
return false;
}
#Override
public int getItemViewType(int position) {
if (position == 0 && useHeader()) {
return TYPE_HEADER;
}
if (position == mAdaptee.getItemCount() && useFooter()) {
return TYPE_FOOTER;
}
if (mAdaptee.getItemCount() >= Integer.MAX_VALUE - TYPE_ADAPTEE_OFFSET) {
new IllegalStateException("HeaderRecyclerViewAdapter offsets your BasicItemType by " + TYPE_ADAPTEE_OFFSET + ".");
}
return mAdaptee.getItemViewType(position) + TYPE_ADAPTEE_OFFSET;
}
public static interface HeaderRecyclerView {
public RecyclerView.ViewHolder onCreateHeaderViewHolder(ViewGroup parent, int viewType);
public void onBindHeaderView(RecyclerView.ViewHolder holder, int position);
}
public static interface FooterRecyclerView {
public RecyclerView.ViewHolder onCreateFooterViewHolder(ViewGroup parent, int viewType);
public void onBindFooterView(RecyclerView.ViewHolder holder, int position);
}
}
HeaderRecyclerViewAdapterV2
import android.support.v7.widget.RecyclerView;
import android.view.ViewGroup;
/**
* Created by sebnapi on 08.11.14.
* <p/>
* If you extend this Adapter you are able to add a Header, a Footer or both
* by a similar ViewHolder pattern as in RecyclerView.
* <p/>
* If you want to omit changes to your class hierarchy you can try the Plug-and-Play
* approach HeaderRecyclerViewAdapterV1.
* <p/>
* Don't override (Be careful while overriding)
* - onCreateViewHolder
* - onBindViewHolder
* - getItemCount
* - getItemViewType
* <p/>
* You need to override the abstract methods introduced by this class. This class
* is not using generics as RecyclerView.Adapter make yourself sure to cast right.
* <p/>
* TOTALLY UNTESTED - USE WITH CARE - HAVE FUN :)
*/
public abstract class HeaderRecyclerViewAdapterV2 extends RecyclerView.Adapter {
private static final int TYPE_HEADER = Integer.MIN_VALUE;
private static final int TYPE_FOOTER = Integer.MIN_VALUE + 1;
private static final int TYPE_ADAPTEE_OFFSET = 2;
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == TYPE_HEADER) {
return onCreateHeaderViewHolder(parent, viewType);
} else if (viewType == TYPE_FOOTER) {
return onCreateFooterViewHolder(parent, viewType);
}
return onCreateBasicItemViewHolder(parent, viewType - TYPE_ADAPTEE_OFFSET);
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (position == 0 && holder.getItemViewType() == TYPE_HEADER) {
onBindHeaderView(holder, position);
} else if (position == getBasicItemCount() && holder.getItemViewType() == TYPE_FOOTER) {
onBindFooterView(holder, position);
} else {
onBindBasicItemView(holder, position - (useHeader() ? 1 : 0));
}
}
#Override
public int getItemCount() {
int itemCount = getBasicItemCount();
if (useHeader()) {
itemCount += 1;
}
if (useFooter()) {
itemCount += 1;
}
return itemCount;
}
#Override
public int getItemViewType(int position) {
if (position == 0 && useHeader()) {
return TYPE_HEADER;
}
if (position == getBasicItemCount() && useFooter()) {
return TYPE_FOOTER;
}
if (getBasicItemType(position) >= Integer.MAX_VALUE - TYPE_ADAPTEE_OFFSET) {
new IllegalStateException("HeaderRecyclerViewAdapter offsets your BasicItemType by " + TYPE_ADAPTEE_OFFSET + ".");
}
return getBasicItemType(position) + TYPE_ADAPTEE_OFFSET;
}
public abstract boolean useHeader();
public abstract RecyclerView.ViewHolder onCreateHeaderViewHolder(ViewGroup parent, int viewType);
public abstract void onBindHeaderView(RecyclerView.ViewHolder holder, int position);
public abstract boolean useFooter();
public abstract RecyclerView.ViewHolder onCreateFooterViewHolder(ViewGroup parent, int viewType);
public abstract void onBindFooterView(RecyclerView.ViewHolder holder, int position);
public abstract RecyclerView.ViewHolder onCreateBasicItemViewHolder(ViewGroup parent, int viewType);
public abstract void onBindBasicItemView(RecyclerView.ViewHolder holder, int position);
public abstract int getBasicItemCount();
/**
* make sure you don't use [Integer.MAX_VALUE-1, Integer.MAX_VALUE] as BasicItemViewType
*
* #param position
* #return
*/
public abstract int getBasicItemType(int position);
}
Feedback and forks appreciated. I will use HeaderRecyclerViewAdapterV2 by my self and evolve, test and post the changes in the future.
EDIT: #OvidiuLatcu Yes I had some problems. Actually I stopped offsetting the Header implicitly by position - (useHeader() ? 1 : 0) and instead created a public method int offsetPosition(int position) for it. Because if you set an OnItemTouchListener on Recyclerview, you can intercept the touch, get the x,y coordinates of the touch, find the according child view and then call recyclerView.getChildPosition(...)and you will always get the non-offsetted position in the adapter! This is a shortcomming in the RecyclerView Code, I don't see an easy method to overcome this. This is why I now offset the positions explicit when I need to by my own code.

I haven't tried this, but I would simply add 1 (or 2, if you want both a header and footer) to the integer returned by getItemCount in your adapter. You can then override getItemViewType in your adapter to return a different integer when i==0: https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html#getItemViewType(int)
createViewHolder is then passed the integer you returned from getItemViewType, allowing you to create or configure the view holder differently for the header view: https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html#createViewHolder(android.view.ViewGroup, int)
Don't forget to subtract one from the position integer passed to bindViewHolder.

You can used this GitHub library allowing to add Header and/or Footer in your RecyclerView in the simplest way possible.
You need to add HFRecyclerView library in your project or you can also grab it from Gradle:
compile 'com.mikhaellopez:hfrecyclerview:1.0.0'
This is a result in image:
EDIT:
If you just want to add a margin at the top and/or bottom with this library: SimpleItemDecoration:
int offsetPx = 10;
recyclerView.addItemDecoration(new StartOffsetItemDecoration(offsetPx));
recyclerView.addItemDecoration(new EndOffsetItemDecoration(offsetPx));

I ended up implementing my own adapter to wrap any other adapter and provide methods to add header and footer views.
Created a gist here: HeaderViewRecyclerAdapter.java
The main feature I wanted was a similar interface to a ListView, so I wanted to be able to inflate the views in my Fragment and add them to the RecyclerView in onCreateView. This is done by creating a HeaderViewRecyclerAdapter passing the adapter to be wrapped, and calling addHeaderView and addFooterView passing your inflated views. Then set the HeaderViewRecyclerAdapter instance as the adapter on the RecyclerView.
An extra requirement was that I needed to be able to easily swap out adapters while keeping the headers and footers, I didn't want to have multiple adapters with multiple instances of these headers and footers. So you can call setAdapter to change the wrapped adapter leaving the headers and footers intact, with the RecyclerView being notified of the change.

recyclerview:1.2.0 introduces ConcatAdapter class which concatenates multiple adapters into a single one. So it allows to create separate header/footer adapters and reuse them across multiple lists.
myRecyclerView.adapter = ConcatAdapter(headerAdapter, listAdapter, footerAdapter)
Take a look at the announcement article. It contains a sample how to display a loading progress in header and footer using ConcatAdapter.
For the moment when I post this answer the version 1.2.0 of the library is in alpha stage, so the api might change. You can check the status here.

my "keep it simple stupid" way ...it waste some resources , i know , but i dont care as my code keep simple
so...
First, add a footer with visibility GONE to your item_layout
<LinearLayout
android:id="#+id/footer"
android:layout_width="match_parent"
android:layout_height="80dp"
android:orientation="vertical"
android:visibility="gone">
</LinearLayout>
Then, set it visible on the last item
public void onBindViewHolder(ChannelAdapter.MyViewHolder holder, int position) {
boolean last = position==data.size()-1;
//....
holder.footer.setVisibility(View.GONE);
if (last && showFooter){
holder.footer.setVisibility(View.VISIBLE);
}
}
do the opposite for header

Based on #seb's solution, I created a subclass of RecyclerView.Adapter that supports an arbitrary number of headers and footers.
https://gist.github.com/mheras/0908873267def75dc746
Although it seems to be a solution, I also think this thing should be managed by the LayoutManager. Unfortunately, I need it now and I don't have time to implement a StaggeredGridLayoutManager from scratch (nor even extend from it).
I'm still testing it, but you can try it out if you want. Please let me know if you find any issues with it.

You can use viewtype to solve this problem, here is my demo:
https://github.com/yefengfreedom/RecyclerViewWithHeaderFooterLoadingEmptyViewErrorView
you can define some recycler view display mode:
public static final int MODE_DATA = 0, MODE_LOADING = 1, MODE_ERROR = 2, MODE_EMPTY = 3, MODE_HEADER_VIEW = 4, MODE_FOOTER_VIEW = 5;
2.override the getItemViewType mothod
#Override
public int getItemViewType(int position) {
if (mMode == RecyclerViewMode.MODE_LOADING) {
return RecyclerViewMode.MODE_LOADING;
}
if (mMode == RecyclerViewMode.MODE_ERROR) {
return RecyclerViewMode.MODE_ERROR;
}
if (mMode == RecyclerViewMode.MODE_EMPTY) {
return RecyclerViewMode.MODE_EMPTY;
}
//check what type our position is, based on the assumption that the order is headers > items > footers
if (position < mHeaders.size()) {
return RecyclerViewMode.MODE_HEADER_VIEW;
} else if (position >= mHeaders.size() + mData.size()) {
return RecyclerViewMode.MODE_FOOTER_VIEW;
}
return RecyclerViewMode.MODE_DATA;
}
3.override the getItemCount method
#Override
public int getItemCount() {
if (mMode == RecyclerViewMode.MODE_DATA) {
return mData.size() + mHeaders.size() + mFooters.size();
} else {
return 1;
}
}
4.override the onCreateViewHolder method. create view holder by viewType
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == RecyclerViewMode.MODE_LOADING) {
RecyclerView.ViewHolder loadingViewHolder = onCreateLoadingViewHolder(parent);
loadingViewHolder.itemView.setLayoutParams(
new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, parent.getHeight() - mToolBarHeight)
);
return loadingViewHolder;
}
if (viewType == RecyclerViewMode.MODE_ERROR) {
RecyclerView.ViewHolder errorViewHolder = onCreateErrorViewHolder(parent);
errorViewHolder.itemView.setLayoutParams(
new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, parent.getHeight() - mToolBarHeight)
);
errorViewHolder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(final View v) {
if (null != mOnErrorViewClickListener) {
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
mOnErrorViewClickListener.onErrorViewClick(v);
}
}, 200);
}
}
});
return errorViewHolder;
}
if (viewType == RecyclerViewMode.MODE_EMPTY) {
RecyclerView.ViewHolder emptyViewHolder = onCreateEmptyViewHolder(parent);
emptyViewHolder.itemView.setLayoutParams(
new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, parent.getHeight() - mToolBarHeight)
);
emptyViewHolder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(final View v) {
if (null != mOnEmptyViewClickListener) {
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
mOnEmptyViewClickListener.onEmptyViewClick(v);
}
}, 200);
}
}
});
return emptyViewHolder;
}
if (viewType == RecyclerViewMode.MODE_HEADER_VIEW) {
RecyclerView.ViewHolder headerViewHolder = onCreateHeaderViewHolder(parent);
headerViewHolder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(final View v) {
if (null != mOnHeaderViewClickListener) {
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
mOnHeaderViewClickListener.onHeaderViewClick(v, v.getTag());
}
}, 200);
}
}
});
return headerViewHolder;
}
if (viewType == RecyclerViewMode.MODE_FOOTER_VIEW) {
RecyclerView.ViewHolder footerViewHolder = onCreateFooterViewHolder(parent);
footerViewHolder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(final View v) {
if (null != mOnFooterViewClickListener) {
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
mOnFooterViewClickListener.onFooterViewClick(v, v.getTag());
}
}, 200);
}
}
});
return footerViewHolder;
}
RecyclerView.ViewHolder dataViewHolder = onCreateDataViewHolder(parent);
dataViewHolder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(final View v) {
if (null != mOnItemClickListener) {
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
mOnItemClickListener.onItemClick(v, v.getTag());
}
}, 200);
}
}
});
dataViewHolder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(final View v) {
if (null != mOnItemLongClickListener) {
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
mOnItemLongClickListener.onItemLongClick(v, v.getTag());
}
}, 200);
return true;
}
return false;
}
});
return dataViewHolder;
}
5.Override the onBindViewHolder method. bind data by viewType
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (mMode == RecyclerViewMode.MODE_LOADING) {
onBindLoadingViewHolder(holder, position);
} else if (mMode == RecyclerViewMode.MODE_ERROR) {
onBindErrorViewHolder(holder, position);
} else if (mMode == RecyclerViewMode.MODE_EMPTY) {
onBindEmptyViewHolder(holder, position);
} else {
if (position < mHeaders.size()) {
if (mHeaders.size() > 0) {
onBindHeaderViewHolder(holder, position);
}
} else if (position >= mHeaders.size() + mData.size()) {
if (mFooters.size() > 0) {
onBindFooterViewHolder(holder, position - mHeaders.size() - mData.size());
}
} else {
onBindDataViewHolder(holder, position - mHeaders.size());
}
}
}

You can use the library SectionedRecyclerViewAdapter to group your items in sections and add a header to each section, like on the image below:
First you create your section class:
class MySection extends StatelessSection {
String title;
List<String> list;
public MySection(String title, List<String> list) {
// call constructor with layout resources for this Section header, footer and items
super(R.layout.section_header, R.layout.section_item);
this.title = title;
this.list = list;
}
#Override
public int getContentItemsTotal() {
return list.size(); // number of items of this section
}
#Override
public RecyclerView.ViewHolder getItemViewHolder(View view) {
// return a custom instance of ViewHolder for the items of this section
return new MyItemViewHolder(view);
}
#Override
public void onBindItemViewHolder(RecyclerView.ViewHolder holder, int position) {
MyItemViewHolder itemHolder = (MyItemViewHolder) holder;
// bind your view here
itemHolder.tvItem.setText(list.get(position));
}
#Override
public RecyclerView.ViewHolder getHeaderViewHolder(View view) {
return new SimpleHeaderViewHolder(view);
}
#Override
public void onBindHeaderViewHolder(RecyclerView.ViewHolder holder) {
MyHeaderViewHolder headerHolder = (MyHeaderViewHolder) holder;
// bind your header view here
headerHolder.tvItem.setText(title);
}
}
Then you set up the RecyclerView with your sections and change the SpanSize of the headers with a GridLayoutManager:
// Create an instance of SectionedRecyclerViewAdapter
SectionedRecyclerViewAdapter sectionAdapter = new SectionedRecyclerViewAdapter();
// Create your sections with the list of data
MySection section1 = new MySection("My Section 1 title", dataList1);
MySection section2 = new MySection("My Section 2 title", dataList2);
// Add your Sections to the adapter
sectionAdapter.addSection(section1);
sectionAdapter.addSection(section2);
// Set up a GridLayoutManager to change the SpanSize of the header
GridLayoutManager glm = new GridLayoutManager(getContext(), 2);
glm.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
#Override
public int getSpanSize(int position) {
switch(sectionAdapter.getSectionItemViewType(position)) {
case SectionedRecyclerViewAdapter.VIEW_TYPE_HEADER:
return 2;
default:
return 1;
}
}
});
// Set up your RecyclerView with the SectionedRecyclerViewAdapter
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerview);
recyclerView.setLayoutManager(glm);
recyclerView.setAdapter(sectionAdapter);

I would just add an alternative to all those HeaderRecyclerViewAdapter implementation. CompoundAdapter:
https://github.com/negusoft/CompoundAdapter-android
It is a more flexible approach, since you can create a AdapterGroup out of Adapters. For the header example, use your adapter as it is, along with an adapter containing one item for the header:
AdapterGroup adapterGroup = new AdapterGroup();
adapterGroup.addAdapter(SingleAdapter.create(R.layout.header));
adapterGroup.addAdapter(new MyAdapter(...));
recyclerView.setAdapter(adapterGroup);
It is fairly simple and readable. You can implement more complex adapter easily using the same principle.

Excellent answer by #reaz-murshed shared here . But I dont like the part where datasize is added with +1 and returning Footer View if the end is reached.
It tells that every last element is a Footer View and I had really hard time removing the footer view.
Instead did something like this for my case -
private List<RealResponse> addEmptyLoaderResponse(List<RealResponse> originalList){
if(originalList == null){
originalList= new ArrayList<>();
}
originalList.add(new EmptyRealResponse());
return originalList;
}
private class EmptyRealResponse extends RealResponse{
/**Just an Empty class as placeholder for loader at Footer View
*
*/
}
public void setItems(List<InconcurPostResponse> items) {
this.items = addEmptyLoaderResponse(items);
}
#Override
public int getItemCount() {
return items.size();
}
#Override
public int getItemViewType(int position){
if(this.items.get(position) instanceof EmptyRealResponse){
return ViewTypes.FOOTER_VIEW_TYPE.getViewType();
}
return super.getItemViewType(position);
}
This is way cleaner for me and It loads actual Object into Recycler View. Plus I did get the benefit of removing the Footer View when I dont need it Or If I want to add more Placeholder Footer View.

I know I come late, but only recently I was able to implement such "addHeader" to the Adapter. In my FlexibleAdapter project you can call setHeader on a Sectionable item, then you call showAllHeaders. If you need only 1 header then the first item should have the header. If you delete this item, then the header is automatically linked to the next one.
Unfortunately footers are not covered (yet).
The FlexibleAdapter allows you to do much more than create headers/sections.
You really should have a look: https://github.com/davideas/FlexibleAdapter.

Related

onBindViewHolder repeats the results [duplicate]

Here's the XML for my items inside the RecyclerView
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="#+id/cvItems"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:layout_margin="2dp"
card_view:cardElevation="0dp"
card_view:contentPadding="0dp"
card_view:cardBackgroundColor="#FFFFFF"
>
<LinearLayout
android:orientation="horizontal"
android:layout_height="fill_parent"
android:layout_width="fill_parent">
<TextView
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="0.8"
android:id="#+id/tvContent"
android:textSize="15dp"
android:paddingLeft="5dp"
android:paddingRight="5dp" />
<CheckBox
android:id="#+id/cbSelect"
android:layout_width="0dip"
android:layout_weight="0.2"
android:layout_height="match_parent"
android:button="#drawable/cb_checked"
android:gravity="center_horizontal"
android:textAlignment="center"
android:layout_gravity="center_horizontal" />
</LinearLayout>
</android.support.v7.widget.CardView>
And here's the RecyclerView adapter that inflate the layout above for each of its items:
public class AdapterTrashIncome extends RecyclerView.Adapter<AdapterTrashIncome.ViewHolder> {
private ArrayList<ObjectIncome> myItems = new ArrayList<>();
public AdapterTrashIncome(ArrayList<ObjectIncome> getItems, Context context){
try {
mContext = context;
myItems = getItems;
}catch (Exception e){
Log.e(FILE_NAME, "51: " + e.toString());
e.printStackTrace();
}
}
public class ViewHolder extends RecyclerView.ViewHolder {
public TextView tvContent;
public CheckBox cbSelect;
public ViewHolder(View v) {
super(v);
tvContent = (TextView) v.findViewById(R.id.tvContent);
cbSelect = (CheckBox) v.findViewById(R.id.cbSelect);
}
}
#Override
public void onBindViewHolder(ViewHolder holder, final int position) {
final ObjectIncome objIncome = myItems.get(position);
String content = "<b>lalalla</b>";
holder.tvContent.setText(Html.fromHtml(content));
}
}
The problem is, let's say I have 10 items inside the RecyclerView. When I checked the checkbox on item 1,2,3 then I scroll down the RecyclerView, suddenly some of the other items eg items 8,9 is checked. And when I scroll up again, item 1 and 3 is checked but not item 2. Any idea why this happen?
That's an expected behavior. You are not setting your checkbox selected or not. You are selecting one and View holder keeps it selected. You can add a boolean variable into your ObjectIncome object and keep your item's selection status.
You may look at my example. You can do something like that:
public class AdapterTrashIncome extends RecyclerView.Adapter<AdapterTrashIncome.ViewHolder> {
private ArrayList<ObjectIncome> myItems = new ArrayList<>();
public AdapterTrashIncome(ArrayList<ObjectIncome> getItems, Context context){
try {
mContext = context;
myItems = getItems;
}catch (Exception e){
Log.e(FILE_NAME, "51: " + e.toString());
e.printStackTrace();
}
}
public class ViewHolder extends RecyclerView.ViewHolder {
public TextView tvContent;
public CheckBox cbSelect;
public ViewHolder(View v) {
super(v);
tvContent = (TextView) v.findViewById(R.id.tvContent);
cbSelect = (CheckBox) v.findViewById(R.id.cbSelect);
}
}
#Override
public void onBindViewHolder(ViewHolder holder, final int position) {
final ObjectIncome objIncome = myItems.get(position);
String content = "<b>lalalla</b>";
holder.tvContent.setText(Html.fromHtml(content));
//in some cases, it will prevent unwanted situations
holder.cbSelect.setOnCheckedChangeListener(null);
//if true, your checkbox will be selected, else unselected
holder.cbSelect.setChecked(objIncome.isSelected());
holder.cbSelect.setOnCheckedChangeListener(new OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
//set your object's last status
objIncome.setSelected(isChecked);
}
});
}
}
In short, its because of recycling the views and using them again!
how can you avoid that :
1.In onBindViewHolder check whether you should check or uncheck boxes.
don't forget to put both if and else
if (...)
holder.cbSelect.setChecked(true);
else
holder.cbSelect.setChecked(false);
Put a listener for check box! whenever its checked statues changed, update the corresponding object too in your myItems array ! so whenever a new view is shown, it read the newest statue of the object.
Just add two override methods of RecyclerView
#Override
public long getItemId(int position) {
return position;
}
#Override
public int getItemViewType(int position) {
return position;
}
Use this only if you have limited number of items in your RecyclerView.
I tried using boolean value in model and keep the CheckBox status, but it did not help in my case. What worked for me is this.setIsRecyclable(false);
public class ComponentViewHolder extends RecyclerView.ViewHolder {
public MyViewHolder(View itemView) {
super(itemView);
...
this.setIsRecyclable(false);
}
More explanation on this can be found here
NOTE: This is a workaround. To use it properly you can refer the document which states
Calls to setIsRecyclable() should always be paired (one call to setIsRecyclabe(false) should always be matched with a later call to setIsRecyclable(true)). Pairs of calls may be nested, as the state is internally reference-counted.
I don't know how to do this in code, if someone can provide more code on this.
You can use Model class to keep track of each RecyclerView item's CheckBox. Full reference is from : RecyclerView Checkbox Android
setTag and getTag is used to keep track of CheckBox status. Check full reference link for more information. It also teaches how to send checked items to Next Activity.
Make Model class:
public class Model {
private boolean isSelected;
private String animal;
public String getAnimal() {
return animal;
}
public void setAnimal(String animal) {
this.animal = animal;
}
public boolean getSelected() {
return isSelected;
}
public void setSelected(boolean selected) {
isSelected = selected;
}
}
Create integer.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="btnplusview">1</integer>
<integer name="btnpluspos">2</integer>
</resources>
Finally RecyclerView.Adapter looks like this:
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.TextView;
import android.widget.Toast;
import java.util.ArrayList;
public class CustomAdapter extends RecyclerView.Adapter<CustomAdapter.MyViewHolder> {
private LayoutInflater inflater;
public static ArrayList<Model> imageModelArrayList;
private Context ctx;
public CustomAdapter(Context ctx, ArrayList<Model> imageModelArrayList) {
inflater = LayoutInflater.from(ctx);
this.imageModelArrayList = imageModelArrayList;
this.ctx = ctx;
}
#Override
public CustomAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = inflater.inflate(R.layout.rv_item, parent, false);
MyViewHolder holder = new MyViewHolder(view);
return holder;
}
#Override
public void onBindViewHolder(final CustomAdapter.MyViewHolder holder, int position) {
holder.checkBox.setText("Checkbox " + position);
holder.checkBox.setChecked(imageModelArrayList.get(position).getSelected());
holder.tvAnimal.setText(imageModelArrayList.get(position).getAnimal());
// holder.checkBox.setTag(R.integer.btnplusview, convertView);
holder.checkBox.setTag(position);
holder.checkBox.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Integer pos = (Integer) holder.checkBox.getTag();
Toast.makeText(ctx, imageModelArrayList.get(pos).getAnimal() + " clicked!", Toast.LENGTH_SHORT).show();
if (imageModelArrayList.get(pos).getSelected()) {
imageModelArrayList.get(pos).setSelected(false);
} else {
imageModelArrayList.get(pos).setSelected(true);
}
}
});
}
#Override
public int getItemCount() {
return imageModelArrayList.size();
}
class MyViewHolder extends RecyclerView.ViewHolder {
protected CheckBox checkBox;
private TextView tvAnimal;
public MyViewHolder(View itemView) {
super(itemView);
checkBox = (CheckBox) itemView.findViewById(R.id.cb);
tvAnimal = (TextView) itemView.findViewById(R.id.animal);
}
}
}
Using Kotlin the only thing which solved this problem for me was to clear the OnCheckedChangeListener before setting the variable and then create a new OnCheckedChangeListener after checked has been set.
I do the following in my RecyclerView.ViewHolder
task.setOnCheckedChangeListener(null)
task.isChecked = item.status
task.setOnCheckedChangeListener { _: CompoundButton, checked: Boolean ->
item.status = checked
...
do more stuff
...
}
I recommend that not to use checkBox.setOnCheckedChangeListener in RecyclerView.Adapter. Because on scrolling RecyclerView, checkBox.setOnCheckedChangeListener will be fired by adapter. It's not safe. Instead, use checkBox.setOnClickListener to interact with user inputs.
For example:
public void onBindViewHolder(final ViewHolder holder, int position) {
...
holder.checkBoxAdapterTasks.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
boolean isChecked = holder.checkBoxAdapterTasks.isChecked();
if (isChecked) {
// checkBox clicked and checked
} else {
// checkBox clicked and unchecked
}
}
});
}
It might be very late but the simplest of all answers is to assign the check state in bind ViewHolder. RecyclerView will check and apply that state when reusing.
override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {
binding.checkbox.isChecked = item.isChecked
}
Maintain that state in your class. (Assign a initial default value)
class MyItem {
val isChecked: Boolean = false
}
onClickListener do your stuff and assign the state to class variable. In my case, I have delegate clickListener in view. So, it is like this in Adapter:
binding.checkbox.setOnClickListener {
onClickListener.invoke(item)
}
Then, in view, I am doing this:
val adapter = MyItem { item->
viewModel.checkedContactsList.value?.let { list ->
if (list.contains(item)) {
item.isChecked = false
list.remove(item)
} else {
item.isChecked = true
list.add(item)
}
}
}
In my case this worked.
#Override
public void onViewRecycled(MyViewHolder holder) {
holder.checkbox.setChecked(false); // - this line do the trick
super.onViewRecycled(holder);
}
As stated above, the checked state of the object should be included within object properties. In some cases you may need also to change the object selection state by clicking on the object itself and let the CheckBox inform about the actual state (either selected or unselected). The checkbox will then use the state of the object at the actual position of the given adapter which is (by default/in most cases) the position of the element in the list.
Check the snippet below, it may be useful.
import android.content.Context;
import android.graphics.Bitmap;
import android.net.Uri;
import android.provider.MediaStore;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.ImageView;
import java.io.File;
import java.io.IOException;
import java.util.List;
public class TakePicImageAdapter extends RecyclerView.Adapter<TakePicImageAdapter.ViewHolder>{
private Context context;
private List<Image> imageList;
public TakePicImageAdapter(Context context, List<Image> imageList) {
this.context = context;
this.imageList = imageList;
}
#Override
public TakePicImageAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view= LayoutInflater.from(context).inflate(R.layout.image_item,parent,false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(final TakePicImageAdapter.ViewHolder holder, final int position) {
File file=new File(imageList.get(position).getPath());
try {
Bitmap bitmap= MediaStore.Images.Media.getBitmap(context.getContentResolver(), Uri.fromFile(file));
holder.image.setImageBitmap(bitmap
);
} catch (IOException e) {
e.printStackTrace();
}
holder.selectImage.setOnCheckedChangeListener(null);
holder.selectImage.setChecked(imageList.get(position).isSelected());
holder.selectImage.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
holder.selectImage.setChecked(isChecked);
imageList.get(position).setSelected(isChecked);
}
});
holder.image.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (imageList.get(position).isSelected())
{
imageList.get(position).setSelected(false);
holder.selectImage.setChecked(false);
}else
{
imageList.get(position).setSelected(true);
holder.selectImage.setChecked(true);
}
}
});
}
#Override
public int getItemCount() {
return imageList.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
public ImageView image;public CheckBox selectImage;
public ViewHolder(View itemView) {
super(itemView);
image=(ImageView)itemView.findViewById(R.id.image);
selectImage=(CheckBox) itemView.findViewById(R.id.ch);
}
}
}
Use an array to hold the state of the items
In the adapter use a Map or a SparseBooleanArray (which is similar to a Map, but is a key-value pair of int and boolean) to store the state of all the items in our list of items and then use the keys and values to compare when toggling the checked state.
In the Adapter create a SparseBooleanArray:
// sparse boolean array for checking the state of the items
private SparseBooleanArray itemStateArray = new SparseBooleanArray();
Then in the item click handler onClick() use the state of the items in the itemStateArray to check before toggling, here is an example
#Override
public void onClick(View v) {
int adapterPosition = getAdapterPosition();
if (!itemStateArray.get(adapterPosition, false)) {
mCheckedTextView.setChecked(true);
itemStateArray.put(adapterPosition, true);
} else {
mCheckedTextView.setChecked(false);
itemStateArray.put(adapterPosition, false);
}
}
Also, use sparse boolean array to set the checked state when the view is bound:
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.bind(position);
}
#Override
public int getItemCount() {
if (items == null) {
return 0;
}
return items.size();
}
void loadItems(List<Model> tournaments) {
this.items = tournaments;
notifyDataSetChanged();
}
class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
CheckedTextView mCheckedTextView;
ViewHolder(View itemView) {
super(itemView);
mCheckedTextView = (CheckedTextView) itemView.findViewById(R.id.checked_text_view);
itemView.setOnClickListener(this);
}
// use the sparse boolean array to check
void bind(int position) {
if (!itemStateArray.get(position, false)) {
mCheckedTextView.setChecked(false);}
else {
mCheckedTextView.setChecked(true);
}
}
}
and final adapter will be like this.
This will happened when use setOnCheckedChangeListener instead of that use setObClickListener and inside that just do this easy handle:
if (list.get(position).isCheck()) {
list.get(position).setCheck(false);
} else {
list.get(position).setCheck(true);
}
Note: in your list model add one boolean variable with name check and set getter and setter for that , in above case mine is setCheck and isCheck
This is a Kotlin Solution That Worked for Me
class SpecialtyFragmentRecyclerAdapter : RecyclerView.Adapter<SpecialtyFragmentRecyclerAdapter.SpecialtyViewHolder>(){
private var _specialtySet = mutableSetOf(
"Yoruba Attires",
"Hausa Attires",
"Senator",
"Embroidery",
"Africa Fashion",
"School Uniform",
"Military and Para-Military Uniforms",
"Igbo Attires",
"South-South Attires",
"Kaftans",
"Contemporary",
"Western Fashion",
"Caps"
).toSortedSet()
val specialtySet: Set<String> get() = _specialtySet
val savedSpecialtySet = mutableSetOf<String>().toSortedSet()
inner class SpecialtyViewHolder(
var itemBinding: SpecialtyFragmentRecyclerItemBinding
) :
RecyclerView.ViewHolder(itemBinding.root) {
fun bind(specialty: String) = with(itemBinding) {
specialtyFragmentYorubaAttiresCheckBox.text = specialty
specialtyFragmentYorubaAttiresCheckBox.isChecked = savedSpecialtySet.contains(specialty)
//AREA OF INTEREST
//Either Setting the CheckBox onCheckChangeListener to works
specialtyFragmentYorubaAttiresCheckBox.setOnCheckedChangeListener(null)
specialtyFragmentYorubaAttiresCheckBox.setOnCheckedChangeListener(
CompoundButton.OnCheckedChangeListener { buttonView, isChecked ->
if (buttonView.isPressed) { //OR this Also Works {Check if the Button is Pressed Before verifying the Checked State}
if (isChecked) {
savedSpecialtySet.add(specialty) //Perform Your Operation for Checked State
} else {
savedSpecialtySet.remove(specialty) //Perform Your Operation for unChecked State
}
}
}
)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SpecialtyViewHolder {
val viewBinding = SpecialtyFragmentRecyclerItemBinding
.inflate(LayoutInflater.from(parent.context), parent, false)
return SpecialtyViewHolder(viewBinding)
}
override fun onBindViewHolder(holder: SpecialtyViewHolder, position: Int) {
val specialty = _specialtySet.elementAt(position)
holder.bind(specialty)
}
override fun getItemCount(): Int {
return _specialtySet.size
}
fun populateList(list: MutableList<String>) {
savedSpecialtySet.addAll(list)
_specialtySet.addAll(list)
notifyDataSetChanged()
}
fun addNewSpecialty(specialty: String) {
_specialtySet.add(specialty.trim())
savedSpecialtySet.add(specialty.trim())
notifyDataSetChanged()
}
fun removeSpecialty(element: String) {
_specialtySet.remove(element)
savedSpecialtySet.remove(element)
notifyDataSetChanged()
}
}
I had the same problem in a RecyclerView list with switches, and solved it using #oguzhand answer, but with this code inside the checkedChangeListener:
if (buttonView.isPressed()) {
if (isChecked) {
group.setSelected(true);
} else {
group.setSelected(false);
}
} else {
if (isChecked) {
buttonView.setChecked(false);
} else {
buttonView.setChecked(true);
}
}
Where 'group' is the entity I want to select/deselect.
I've had the same issue. When I was clicking on item's - toggle buttons become checked in my RecyclerView. Toggle buttons appeared in every 10th item (for example if it was clicked in item with 0 index, items with 9, 18, 27 indexes were getting clicked too).
My code in onBindViewHolder was:
if (newsItems.get(position).getBookmark() == 1) {
holder.getToggleButtonBookmark().setChecked(true);
}
But then I added else statement:
/**
* Else statement prevents auto toggling.
*/
if (newsItems.get(position).getBookmark() == 1) {
holder.getToggleButtonBookmark().setChecked(true);
} else{
holder.getToggleButtonBookmark().setChecked(false);
}
And the problem was solved!
You need to separate onBindViewHolder(logic) interactions with CheckBox and user interactions with checkBox. I used OnCheckedChangeListener for user interactions (obviously) and ViewHolder.bind() for logic, that's why you need to set checked listener to null before setting up holder and after holder is ready - configure checked listener for user interactions.
boolean[] checkedStatus = new boolean[numberOfRows];
#Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) {
final ViewHolderItem itemHolder = (ViewHolderItem) holder;
// holder.bind should not trigger onCheckedChanged, it should just update UI
itemHolder.checkBox.setOnCheckedChangeListener(null);
itemHolder.bind(position);
itemHolder.checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
checkedStatus[holder.getAdapterPosition()] = true;
performCheckedActions(); //your logic here
} else {
checkedStatus[holder.getAdapterPosition()] = false;
performUncheckedActions(); //your logic here
}
}
});
}
public void bind(int position) {
boolean checked = checkedStatus[position];
if (checked) {
checkBox.setChecked(false);
} else {
checkBox.setChecked(true);
}
}
I solved this problem by creating a static global array and using it in onBindViewHolder
RecyclerView.Adapter realization class:
In which I created all global variables/objects needed.
public class RVAdapter extends RecyclerView.Adapter<RVAdapter.PersonViewHolder> {
private Context context;
...
public static class PersonViewHolder extends RecyclerView.ViewHolder {
CardView cv;
TextView question, category;
TextView personAge;
ImageView upvote;
Button b1;
public static int k;
private int visibleThreshold = 5;
public static int i = 0;
static int check[]; //Static array
PersonViewHolder(View itemView, int i) {
super(itemView);
if(i == PersonViewHolder.k) {
b1 = (Button) itemView.findViewById(R.id.loadmore);
} else {
cv = (CardView) itemView.findViewById(R.id.cv);
question = (TextView) itemView.findViewById(R.id.question);
category = (TextView) itemView.findViewById(R.id.text_categ);
personAge = (TextView) itemView.findViewById(R.id.text1);
upvote = (ImageView) itemView.findViewById(R.id.upvote);
}
}
}
...
}
Here (in contructor of RVAdapter class) I gave size to the array equals to the size of items I'm going to display in the RecyclerView:
List<Person> persons;
RVAdapter(List<Person> persons){
this.persons = persons;
PersonViewHolder.check = new int[persons.size()];
PersonViewHolder.k = persons.size();
}
In onBindViewHolder I applied this concept on a button. When I click on a button - the background image of the button changes.
Object of button I used is names as "upvote", as "i" holds the position of each item in RecyclerView. I used it as an index of array which is working as a flag and which is keeping track of status of elements.
#Override
public void onBindViewHolder(final PersonViewHolder personViewHolder, final int i) {
if (i == PersonViewHolder.k) {
personViewHolder.b1.setText("load more");
} else {
personViewHolder.question.setText(persons.get(i).name);
personViewHolder.personAge.setText(persons.get(i).age);
if (personViewHolder.check[i] == 0) {
personViewHolder.upvote.setBackgroundResource(R.drawable.noupvote);
} else {
personViewHolder.upvote.setBackgroundResource(R.drawable.upvote);
}
personViewHolder.upvote.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (personViewHolder.check[i] == 0) {
personViewHolder.check[i] = 1;
personViewHolder.upvote.setBackgroundResource(R.drawable.upvote);
} else {
personViewHolder.check[i] = 0;
personViewHolder.upvote.setBackgroundResource(R.drawable.noupvote);
}
}
});
// personViewHolder.personPhoto.setImageResource(persons.get(i).photoId);
}
}
Okay there is a lot of answers here. But I will post my code and I will simply explain what I did... it maybe help juniors like me :D.
Objective:
We will create a list of RecyclerView that has CheckBox and RadioButton, something like this:
Model for list item with all needed data:
public class ModelClass {
private String time;
private boolean checked;
private boolean free;
private boolean paid;
public TherapistScheduleModel(String time, boolean checked, boolean free, boolean paid) {
this.time = time;
this.checked = checked;
this.free = free;
this.paid = paid;
}
public boolean isFree() {
return free;
}
public void setFree(boolean free) {
this.free = free;
}
public boolean isPaid() {
return paid;
}
public void setPaid(boolean paid) {
this.paid = paid;
}
public String getTime() {
return time;
}
public void setTime(String time) {
this.time = time;
}
public boolean getChecked() {
return checked;
}
public void setChecked(boolean checked) {
this.checked= checked;
}
}
My RecyclerView.Adapter amazing realization:
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
private Context context;
private ListAllListeners listAllListeners;
private ArrayList<ModelClass> mDataList;
public MyAdapter(
Context context,
ArrayList<ModelClass> mDataList,
ListAllListeners listAllListeners
) {
this.mDataList = mDataList;
this.listAllListeners = listAllListeners;
this.context = context;
}
#NonNull
#Override
public MyViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View view = inflater.inflate(R.layout.single_view, parent, false);
return new MyViewHolder(view);
}
#Override
public int getItemCount() {
if (mDataList != null) {
return mDataList.size();
} else {
return 0;
}
}
#Override
public void onBindViewHolder(#NonNull final MyViewHolder holder, final int position) {
// important to:
// setOnCheckedChangeListener to 'null'
holder.checkBoxTime.setOnCheckedChangeListener(null);
holder.freeRB.setOnCheckedChangeListener(null);
holder.paidRB.setOnCheckedChangeListener(null);
// Check Box
holder.checkBoxTime.setText(mDataList.get(holder.getAdapterPosition()).getTime());
// Here we check if the item is checked or not from the model.
if(mDataList.get(holder.getAdapterPosition()).getChecked()) {
holder.checkBoxTime.setChecked(true);
} else {
holder.checkBoxTime.setChecked(false);
}
holder.checkBoxTime.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
if (b) {
mDataList.get(holder.getAdapterPosition()).setChecked(true);
listAllListeners.onItemCheck(holder.checkBoxTime.getText().toString(), holder.getAdapterPosition());
} else {
mDataList.get(holder.getAdapterPosition()).setChecked(false);
listAllListeners.onItemUncheck(holder.checkBoxTime.getText().toString(), holder.getAdapterPosition());
}
}
});
// Radio Buttons
if(mDataList.get(holder.getAdapterPosition()).isFree()) {
holder.freeRB.setChecked(true);
} else {
holder.freeRB.setChecked(false);
}
holder.freeRB.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
if (b) {
mDataList.get(holder.getAdapterPosition()).setFree(true);
listAllListeners.onFreeCheck(holder.freeRB.getText().toString(), holder.getAdapterPosition());
} else {
mDataList.get(holder.getAdapterPosition()).setFree(false);
listAllListeners.onFreeUncheck(holder.freeRB.getText().toString(), holder.getAdapterPosition());
}
}
});
// And so on to paidRB
}
/**
* Here is a list of clicked listeners to use them as you want ;).
* You can get a list of checked or unchecked of all.
*/
public interface ListAllListeners {
void onItemCheck(String checkBoxName, int position);
void onItemUncheck(String checkBoxName, int position);
void onFreeCheck(String name, int pos);
void onFreeUncheck(String name, int pos);
void onPaidCheck(String name, int pos);
void onPaidUncheck(String name, int pos);
}
class MyViewHolder extends RecyclerView.ViewHolder {
CheckBox checkBoxTime;
RadioButton freeRB, paidRB;
MyViewHolder(View itemView) {
super(itemView);
checkBoxTime = itemView.findViewById(R.id.timeCheckBox);
freeRB = itemView.findViewById(R.id.freeRadioBtn);
paidRB = itemView.findViewById(R.id.paidRadioBtn);
}
}
}
In Activity you get them something like this:
myAdapter = new MyAdapter(this, mDataList, new MyAdapter.ListAllListeners() {
#Override
public void onItemCheck(String checkBoxName, int position) {
Toast.makeText(getActivity(), "" + checkBoxName + " " + position, Toast.LENGTH_SHORT).show();
}
#Override
public void onItemUncheck(String checkBoxName, int position) {
Toast.makeText(getActivity(), "" + checkBoxName + " " + position, Toast.LENGTH_SHORT).show();
}
#Override
public void onFreeCheck(String name, int position) {
Toast.makeText(getActivity(), "" + name + " " + position, Toast.LENGTH_SHORT).show();
}
#Override
public void onFreeUncheck(String name, int position) {
Toast.makeText(getActivity(), "" + name + " " + position, Toast.LENGTH_SHORT).show();
}
#Override
public void onPaidCheck(String name, int position) {
Toast.makeText(getActivity(), "" + name + " " + position, Toast.LENGTH_SHORT).show();
}
#Override
public void onPaidUncheck(String name, int position) {
Toast.makeText(getActivity(), "" + name + " " + position, Toast.LENGTH_SHORT).show();
}
});
this is due to again and again creating view ,best option is clear cache before setting adapter
recyclerview.setItemViewCacheSize(your array.size());
In onBindViewHolder for views (checkbox, radio, switch, ...) you should setOnCheckedChangeListener(null) before and after new creation. For example:
public void onBindViewHolder(#NonNull ViewHolder holder,
int position) {
...
holder.switchCompat.setOnCheckedChangeListener(null);
...
holder.switchCompat.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton compoundButton,
boolean b) {
// TODO: 10/23/2022 do something
}
});
}
Solution is while CheckBox is checked. Need to store this separate list, and use that list to populate CheckBox in RecyclerView.
You can refer this link.
Complete example:
public class ChildAddressAdapter extends RecyclerView.Adapter<ChildAddressAdapter.CartViewHolder> {
private Activity context;
private List<AddressDetail> addressDetailList;
private int selectedPosition = -1;
public ChildAddressAdapter(Activity context, List<AddressDetail> addressDetailList) {
this.context = context;
this.addressDetailList = addressDetailList;
}
#NonNull
#Override
public CartViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(context);
View myView = inflater.inflate(R.layout.address_layout, parent, false);
return new CartViewHolder(myView);
}
#Override
public void onBindViewHolder(#NonNull CartViewHolder holder, int position) {
holder.adress_checkbox.setOnClickListener(view -> {
selectedPosition = holder.getAdapterPosition();
notifyDataSetChanged();
});
if (selectedPosition==position){
holder.adress_checkbox.setChecked(true);
}
else {
holder.adress_checkbox.setChecked(false);
}
}
#Override
public int getItemCount() {
return addressDetailList.size();
}
class CartViewHolder extends RecyclerView.ViewHolder {
TextView address_text,address_tag;
CheckBox adress_checkbox;
CartViewHolder(View itemView) {
super(itemView);
address_text = itemView.findViewById(R.id.address_text);
address_tag = itemView.findViewById(R.id.address_tag);
adress_checkbox = itemView.findViewById(R.id.adress_checkbox);
}
}
}
public class TagYourDiseaseAdapter extends RecyclerView.Adapter<TagYourDiseaseAdapter.OrderHistoryViewHolder> {
private ReCyclerViewItemClickListener mRecyclerViewItemClickListener;
private Context mContext;
List<Datum> deviceList = Collections.emptyList();
/**
* Initialize the values
*
* #param context : context reference
* #param devices : data
*/
public TagYourDiseaseAdapter(Context context, List<Datum> devices,
ReCyclerViewItemClickListener mreCyclerViewItemClickListener) {
this.mContext = context;
this.deviceList = devices;
this.mRecyclerViewItemClickListener = mreCyclerViewItemClickListener;
}
/**
* #param parent : parent ViewPgroup
* #param viewType : viewType
* #return ViewHolder
* <p>
* Inflate the Views
* Create the each views and Hold for Reuse
*/
#Override
public TagYourDiseaseAdapter.OrderHistoryViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_tag_disease, parent, false);
TagYourDiseaseAdapter.OrderHistoryViewHolder myViewHolder = new TagYourDiseaseAdapter.OrderHistoryViewHolder(view);
return myViewHolder;
}
/**
* #param holder : view Holder
* #param position : position of each Row set the values to the views
*/
#Override
public void onBindViewHolder(final TagYourDiseaseAdapter.OrderHistoryViewHolder holder, final int position) {
Picasso.with(mContext).load(deviceList.get(position).getIconUrl()).into(holder.document);
holder.name.setText(deviceList.get(position).getDiseaseName());
holder.radioButton.setOnCheckedChangeListener(null);
holder.radioButton.setChecked(deviceList.get(position).isChecked());
//if true, your checkbox will be selected, else unselected
//holder.radioButton.setChecked(objIncome.isSelected());
holder.radioButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
deviceList.get(position).setChecked(isChecked);
}
});
}
#Override
public int getItemCount() {
return deviceList.size();
}
/**
* Create The view First Time and hold for reuse
* View Holder for Create and Hold the view for ReUse the views instead of create again
* Initialize the views
*/
public class OrderHistoryViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
ImageView document;
TextView name;
CheckBox radioButton;
public OrderHistoryViewHolder(View itemView) {
super(itemView);
document = itemView.findViewById(R.id.img_tag);
name = itemView.findViewById(R.id.text_tag_name);
radioButton = itemView.findViewById(R.id.rdBtn_tag_disease);
radioButton.setOnClickListener(this);
//this.setIsRecyclable(false);
}
#Override
public void onClick(View view) {
mRecyclerViewItemClickListener.onItemClickListener(this.getAdapterPosition(), view);
}
}
}
If it is not late; this is actually RecyclerView general problem. You can put your RecyclerView into a NestedScrollView and then add one line code to your adapter. All is done.
In your activity or fragment;
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
And in your activity where you set adapter add this:
ViewCompat.setNestedScrollingEnabled(recyclerView, false);
LinearLayoutManager layoutManager = new LinearLayoutManager(getContext());
// your adapter code...
recyclerView.setAdapter(textSearchAdapter);
I faced the similar issue while using checkbox inside recycler view. After some detail analysis I got the root cause. let's look at the code once
In onBindViewHolder the line "holder.cbSelect.setChecked(yourList.isSelected());"
will always execute.
If we scroll up or scroll down the page, the onBindViewHolder will get called. As soon as onBindViewHolder will get called "holder.cbSelect.setChecked(yourList.isSelected());" will get tiggered and as a result
"holder.cbSelect.setOnCheckedChangeListener" will also get called and it will change the checkbox state, even if you have not changed the checkbox state. The reason is simple that it found checkbox state is changed from your updated list (yourList.isSelected()) which you select or dis-select the check box .
Now as a solution in override method of "public void onCheckedChanged(CompoundButton buttonView, boolean isChecked)" method we need to add one condition that is
holder.cbSelect.setOnCheckedChangeListener(new OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (buttonView.isPressed()) {
//Check box state changed by user
//update your list based on checkbox value
// yourList.setSelected(isChecked);
}
}
});
What worked for me is to nullify the listeners on the viewHolder when the view is going to be recycled (onViewRecycled):
override fun onViewRecycled(holder: AttendeeViewHolder) {
super.onViewRecycled(holder)
holder.itemView.hasArrived.setOnCheckedChangeListener(null);
holder.itemView.edit.setOnClickListener { null }
}
Adding setItemViewCacheSize(int size) to RecyclerView and passing size of list solved my problem.
My code:
mrecyclerview.setItemViewCacheSize(mOrderList.size());
mBinding.mrecyclerview.setAdapter(mAdapter);
Source: link

Change text of TextView after inflating layout

I have a layout, default_label.xml, like so:
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/label"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
Then, I have this class, which basically allows me to set the default button text for a spinner:
public class NothingSelectedSpinnerAdapter implements SpinnerAdapter, ListAdapter {
protected static final int EXTRA = 1;
protected SpinnerAdapter adapter;
protected Context context;
protected int nothingSelectedLayout;
protected int nothingSelectedDropdownLayout;
protected LayoutInflater layoutInflater;
protected TextView label;
/**
* Use this constructor to have NO 'Select One...' item, instead use
* the standard prompt or nothing at all.
*
* #param spinnerAdapter wrapped Adapter.
* #param nothingSelectedLayout layout for nothing selected, perhaps
* you want text grayed out like a prompt...
* #param context Context
*/
public NothingSelectedSpinnerAdapter(
SpinnerAdapter spinnerAdapter,
int nothingSelectedLayout, Context context) {
this(spinnerAdapter, nothingSelectedLayout, -1, context);
}
/**
* Use this constructor to Define your 'Select One...' layout as the first
* row in the returned choices.
* If you do this, you probably don't want a prompt on your spinner or it'll
* have two 'Select' rows.
*
* #param spinnerAdapter wrapped Adapter. Should probably return false for isEnabled(0)
* #param nothingSelectedLayout layout for nothing selected, perhaps you want
* text grayed out like a prompt...
* #param nothingSelectedDropdownLayout layout for your 'Select an Item...' in
* the dropdown.
* #param context Context
*/
public NothingSelectedSpinnerAdapter(SpinnerAdapter spinnerAdapter,
int nothingSelectedLayout, int nothingSelectedDropdownLayout, Context context) {
this.adapter = spinnerAdapter;
this.context = context;
this.nothingSelectedLayout = nothingSelectedLayout;
this.nothingSelectedDropdownLayout = nothingSelectedDropdownLayout;
layoutInflater = LayoutInflater.from(context);
}
#Override
public final View getView(int position, View convertView, ViewGroup parent) {
// This provides the View for the Selected Item in the Spinner, not
// the dropdown (unless dropdownView is not set).
if (position == 0) {
return getNothingSelectedView(parent);
}
return adapter.getView(position - EXTRA, null, parent); // Could re-use
// the convertView if possible.
}
public int getPosition(String value) {
int index = 0;
for (int i = 1; i < getCount() + EXTRA; i++) {
if (getItem(i).equals(value)) {
index = i;
break;
}
}
return index;
}
public TextView getNothingSelectedView(ViewGroup parent) {
label = (TextView) layoutInflater.inflate(nothingSelectedLayout, parent, false);
return label;
}
#Override
public View getDropDownView(int position, View convertView, ViewGroup parent) {
// Android BUG! http://code.google.com/p/android/issues/detail?id=17128 -
// Spinner does not support multiple view types
if (position == 0) {
return new View(context);
}
if (adapter.getItem(position-EXTRA).toString().equals("")){
View view = nothingSelectedDropdownLayout == -1 ?
new View(context) :
getNothingSelectedDropdownView(parent);
view.setEnabled(false);
view.setOnClickListener(null);
return view;
}
// Could re-use the convertView if possible, use setTag...
return adapter.getDropDownView(position - EXTRA, null, parent);
}
protected View getNothingSelectedDropdownView(ViewGroup parent) {
return layoutInflater.inflate(nothingSelectedDropdownLayout, parent, false);
}
#Override
public int getCount() {
int count = adapter.getCount();
return count == 0 ? 0 : count + EXTRA;
}
#Override
public Object getItem(int position) {
return position == 0 ? null : adapter.getItem(position - EXTRA);
}
#Override
public int getItemViewType(int position) {
return 0;
}
#Override
public int getViewTypeCount() {
return 1;
}
#Override
public long getItemId(int position) {
return position >= EXTRA ? adapter.getItemId(position - EXTRA) : position - EXTRA;
}
#Override
public boolean hasStableIds() {
return adapter.hasStableIds();
}
#Override
public boolean isEmpty() {
return adapter.isEmpty();
}
#Override
public void registerDataSetObserver(DataSetObserver observer) {
adapter.registerDataSetObserver(observer);
}
#Override
public void unregisterDataSetObserver(DataSetObserver observer) {
adapter.unregisterDataSetObserver(observer);
}
#Override
public boolean areAllItemsEnabled() {
return false;
}
#Override
public boolean isEnabled(int position) {
return position != 0; // Don't allow the 'nothing selected'
// item to be picked.
}
}
What I want to be able to do is change the text of the TextView, something like:
NothingSelectedSpinnerAdapter myAdapter = new NothingSelectedSpinnerAdapter(adapter, R.layout.default_label, getContext());
myAdapter.setText("Custom Label");
mySpinner.setAdapter(myAdapter);
However, when I try adding the following method to the above NothingSelectedSpinnerAdapter class:
public void setText(String text) {
label.setText(text);
}
I get the following error:
java.lang.NullPointerException: Attempt to invoke virtual method 'void
android.widget.TextView.setText(java.lang.CharSequence)' on a null
object reference
What should I change?
Take a look at the following three lines:
NothingSelectedSpinnerAdapter myAdapter = new NothingSelectedSpinnerAdapter(adapter, R.layout.default_label, getContext());
The Adapter is instantiated, label is null
myAdapter.setText("Custom Label");
The TextView label is still null, that's why label.setText(); causes a NullPointerException
mySpinner.setAdapter(myAdapter);
Now the runtime will be able to draw the Spinner items. This involves repeated calling of getView() (at least once for every item which should be drawn). After getView() has been executed for position = 0, you will have assigned a value to label. From now on, you can safely call setText() on it.
If you want to be able to call myAdapter.setText("Custom Label"); whenever you like without having to bother about the inner workings of the Adapter, then you can introduce a field private String mLabelText and implement the method as follows
public void setText(String labelText){
mLabelText = labelText;
if (label != null){
label.setText(labelText);
}
}
One last step: don't forget to set the text right after initializing label
public TextView getNothingSelectedView(ViewGroup parent) {
label = (TextView) layoutInflater.inflate(nothingSelectedLayout, parent, false);
label.setText(mLabelText);
return label;
}

Different layouts for cardViews in a RecycleView [duplicate]

From Create dynamic lists with RecyclerView:
When we create a RecyclerView.Adapter we have to specify ViewHolder that will bind with the adapter.
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
private String[] mDataset;
public MyAdapter(String[] myDataset) {
mDataset = myDataset;
}
public static class ViewHolder extends RecyclerView.ViewHolder {
public TextView mTextView;
public ViewHolder(TextView v) {
super(v);
mTextView = v;
}
}
#Override
public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.some_layout, parent, false);
//findViewById...
ViewHolder vh = new ViewHolder(v);
return vh;
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.mTextView.setText(mDataset[position]);
}
#Override
public int getItemCount() {
return mDataset.length;
}
}
Is it possible to create RecyclerView with multiple view types?
Yes, it's possible. Just implement getItemViewType(), and take care of the viewType parameter in onCreateViewHolder().
So you do something like:
public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
class ViewHolder0 extends RecyclerView.ViewHolder {
...
public ViewHolder0(View itemView){
...
}
}
class ViewHolder2 extends RecyclerView.ViewHolder {
...
public ViewHolder2(View itemView){
...
}
#Override
public int getItemViewType(int position) {
// Just as an example, return 0 or 2 depending on position
// Note that unlike in ListView adapters, types don't have to be contiguous
return position % 2 * 2;
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case 0: return new ViewHolder0(...);
case 2: return new ViewHolder2(...);
...
}
}
#Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) {
switch (holder.getItemViewType()) {
case 0:
ViewHolder0 viewHolder0 = (ViewHolder0)holder;
...
break;
case 2:
ViewHolder2 viewHolder2 = (ViewHolder2)holder;
...
break;
}
}
}
If the layouts for view types are only a few and binding logics are simple, follow Anton's solution. But the code will be messy if you need to manage the complex layouts and binding logics.
I believe the following solution will be useful for someone who need to handle complex view types.
Base DataBinder class
abstract public class DataBinder<T extends RecyclerView.ViewHolder> {
private DataBindAdapter mDataBindAdapter;
public DataBinder(DataBindAdapter dataBindAdapter) {
mDataBindAdapter = dataBindAdapter;
}
abstract public T newViewHolder(ViewGroup parent);
abstract public void bindViewHolder(T holder, int position);
abstract public int getItemCount();
......
}
The functions needed to define in this class are pretty much same as the adapter class when creating the single view type.
For each view type, create the class by extending this DataBinder.
Sample DataBinder class
public class Sample1Binder extends DataBinder<Sample1Binder.ViewHolder> {
private List<String> mDataSet = new ArrayList();
public Sample1Binder(DataBindAdapter dataBindAdapter) {
super(dataBindAdapter);
}
#Override
public ViewHolder newViewHolder(ViewGroup parent) {
View view = LayoutInflater.from(parent.getContext()).inflate(
R.layout.layout_sample1, parent, false);
return new ViewHolder(view);
}
#Override
public void bindViewHolder(ViewHolder holder, int position) {
String title = mDataSet.get(position);
holder.mTitleText.setText(title);
}
#Override
public int getItemCount() {
return mDataSet.size();
}
public void setDataSet(List<String> dataSet) {
mDataSet.addAll(dataSet);
}
static class ViewHolder extends RecyclerView.ViewHolder {
TextView mTitleText;
public ViewHolder(View view) {
super(view);
mTitleText = (TextView) view.findViewById(R.id.title_type1);
}
}
}
In order to manage DataBinder classes, create an adapter class.
Base DataBindAdapter class
abstract public class DataBindAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return getDataBinder(viewType).newViewHolder(parent);
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
int binderPosition = getBinderPosition(position);
getDataBinder(viewHolder.getItemViewType()).bindViewHolder(viewHolder, binderPosition);
}
#Override
public abstract int getItemCount();
#Override
public abstract int getItemViewType(int position);
public abstract <T extends DataBinder> T getDataBinder(int viewType);
public abstract int getPosition(DataBinder binder, int binderPosition);
public abstract int getBinderPosition(int position);
......
}
Create the class by extending this base class, and then instantiate DataBinder classes and override abstract methods
getItemCount
Return the total item count of DataBinders
getItemViewType
Define the mapping logic between the adapter position and view type.
getDataBinder
Return the DataBinder instance based on the view type
getPosition
Define convert logic to the adapter position from the position in the specified DataBinder
getBinderPosition
Define convert logic to the position in the DataBinder from the adapter position
I left a more detailed solution and samples on GitHub, so please refer to RecyclerView-MultipleViewTypeAdapter if you need.
The below is not pseudocode. I have tested it and it has worked for me.
I wanted to create a headerview in my recyclerview and then display a list of pictures below the header which the user can click on.
I used a few switches in my code and don't know if that is the most efficient way to do this, so feel free to give your comments:
public class ViewHolder extends RecyclerView.ViewHolder{
//These are the general elements in the RecyclerView
public TextView place;
public ImageView pics;
//This is the Header on the Recycler (viewType = 0)
public TextView name, description;
//This constructor would switch what to findViewBy according to the type of viewType
public ViewHolder(View v, int viewType) {
super(v);
if (viewType == 0) {
name = (TextView) v.findViewById(R.id.name);
decsription = (TextView) v.findViewById(R.id.description);
} else if (viewType == 1) {
place = (TextView) v.findViewById(R.id.place);
pics = (ImageView) v.findViewById(R.id.pics);
}
}
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent,
int viewType)
{
View v;
ViewHolder vh;
// create a new view
switch (viewType) {
case 0: //This would be the header view in my Recycler
v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.recyclerview_welcome, parent, false);
vh = new ViewHolder(v,viewType);
return vh;
default: //This would be the normal list with the pictures of the places in the world
v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.recyclerview_picture, parent, false);
vh = new ViewHolder(v, viewType);
v.setOnClickListener(new View.OnClickListener(){
#Override
public void onClick(View v) {
Intent intent = new Intent(mContext, nextActivity.class);
intent.putExtra("ListNo",mRecyclerView.getChildPosition(v));
mContext.startActivity(intent);
}
});
return vh;
}
}
//Overridden so that I can display custom rows in the recyclerview
#Override
public int getItemViewType(int position) {
int viewType = 1; //Default is 1
if (position == 0) viewType = 0; //If zero, it will be a header view
return viewType;
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
//position == 0 means it's the info header view on the Recycler
if (position == 0) {
holder.name.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Toast.makeText(mContext,"name clicked", Toast.LENGTH_SHORT).show();
}
});
holder.description.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Toast.makeText(mContext,"description clicked", Toast.LENGTH_SHORT).show();
}
});
//This means it is beyond the headerview now as it is no longer 0. For testing purposes, I'm alternating between two pics for now
} else if (position > 0) {
holder.place.setText(mDataset[position]);
if (position % 2 == 0) {
holder.pics.setImageDrawable(mContext.getResources().getDrawable(R.drawable.pic1));
}
if (position % 2 == 1) {
holder.pics.setImageDrawable(mContext.getResources().getDrawable(R.drawable.pic2));
}
}
}
Here is a complete sample to show a RecyclerView with two types, the view type decided by the object.
Class model
open class RecyclerViewItem
class SectionItem(val title: String) : RecyclerViewItem()
class ContentItem(val name: String, val number: Int) : RecyclerViewItem()
Adapter code
const val VIEW_TYPE_SECTION = 1
const val VIEW_TYPE_ITEM = 2
class UserAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
var data = listOf<RecyclerViewItem>()
override fun getItemViewType(position: Int): Int {
if (data[position] is SectionItem) {
return VIEW_TYPE_SECTION
}
return VIEW_TYPE_ITEM
}
override fun getItemCount(): Int {
return data.size
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
if (viewType == VIEW_TYPE_SECTION) {
return SectionViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.item_user_section, parent, false)
)
}
return ContentViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.item_user_content, parent, false)
)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val item = data[position]
if (holder is SectionViewHolder && item is SectionItem) {
holder.bind(item)
}
if (holder is ContentViewHolder && item is ContentItem) {
holder.bind(item)
}
}
internal inner class SectionViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(item: SectionItem) {
itemView.text_section.text = item.title
}
}
internal inner class ContentViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(item: ContentItem) {
itemView.text_name.text = item.name
itemView.text_number.text = item.number.toString()
}
}
}
item_user_section.xml
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/text_section"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#eee"
android:padding="16dp" />
item_user_content.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="32dp">
<TextView
android:id="#+id/text_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="Name" />
<TextView
android:id="#+id/text_number"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
Example using
val dataSet = arrayListOf<RecyclerViewItem>(
SectionItem("A1"),
ContentItem("11", 11),
ContentItem("12", 12),
ContentItem("13", 13),
SectionItem("A2"),
ContentItem("21", 21),
ContentItem("22", 22),
SectionItem("A3"),
ContentItem("31", 31),
ContentItem("32", 32),
ContentItem("33", 33),
ContentItem("33", 34),
)
recyclerAdapter.data = dataSet
recyclerAdapter.notifyDataSetChanged()
Create a different ViewHolder for different layouts
RecyclerView can have any number of viewholders you want, but for better readability let’s see how to create one with two ViewHolders.
It can be done in three simple steps
Override public int getItemViewType(int position)
Return different ViewHolders based on the ViewType in onCreateViewHolder() method
Populate View based on the itemViewType in onBindViewHolder() method
Here is a small code snippet:
public class YourListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int LAYOUT_ONE = 0;
private static final int LAYOUT_TWO = 1;
#Override
public int getItemViewType(int position)
{
if(position==0)
return LAYOUT_ONE;
else
return LAYOUT_TWO;
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = null;
RecyclerView.ViewHolder viewHolder = null;
if(viewType==LAYOUT_ONE)
{
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.one,parent,false);
viewHolder = new ViewHolderOne(view);
}
else
{
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.two,parent,false);
viewHolder= new ViewHolderTwo(view);
}
return viewHolder;
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
if(holder.getItemViewType() == LAYOUT_ONE)
{
// Typecast Viewholder
// Set Viewholder properties
// Add any click listener if any
}
else {
ViewHolderOne vaultItemHolder = (ViewHolderOne) holder;
vaultItemHolder.name.setText(displayText);
vaultItemHolder.name.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
.......
}
});
}
}
//**************** VIEW HOLDER 1 ******************//
public class ViewHolderOne extends RecyclerView.ViewHolder {
public TextView name;
public ViewHolderOne(View itemView) {
super(itemView);
name = (TextView)itemView.findViewById(R.id.displayName);
}
}
//**************** VIEW HOLDER 2 ******************//
public class ViewHolderTwo extends RecyclerView.ViewHolder {
public ViewHolderTwo(View itemView) {
super(itemView);
..... Do something
}
}
}
getItemViewType(int position) is the key.
In my opinion, the starting point to create this kind of recyclerView is the knowledge of this method. Since this method is optional to override, it is not visible in RecylerView class by default which in turn makes many developers (including me) wonder where to begin.
Once you know that this method exists, creating such RecyclerView would be a cakewalk.
Let's see one example to prove my point. If you want to show two layout
at alternate positions do this
#Override
public int getItemViewType(int position)
{
if(position%2==0) // Even position
return LAYOUT_ONE;
else // Odd position
return LAYOUT_TWO;
}
Relevant Links:
Check out the project where I have implemented this.
Yes, it is possible.
Write a generic view holder:
public abstract class GenericViewHolder extends RecyclerView.ViewHolder
{
public GenericViewHolder(View itemView) {
super(itemView);
}
public abstract void setDataOnView(int position);
}
then create your view holders and make them extend the GenericViewHolder. For example, this one:
public class SectionViewHolder extends GenericViewHolder{
public final View mView;
public final TextView dividerTxtV;
public SectionViewHolder(View itemView) {
super(itemView);
mView = itemView;
dividerTxtV = (TextView) mView.findViewById(R.id.dividerTxtV);
}
#Override
public void setDataOnView(int position) {
try {
String title= sections.get(position);
if(title!= null)
this.dividerTxtV.setText(title);
}catch (Exception e){
new CustomError("Error!"+e.getMessage(), null, false, null, e);
}
}
}
then the RecyclerView.Adapter class will look like this one:
public class MyClassRecyclerViewAdapter extends RecyclerView.Adapter<MyClassRecyclerViewAdapter.GenericViewHolder> {
#Override
public int getItemViewType(int position) {
// depends on your problem
switch (position) {
case : return VIEW_TYPE1;
case : return VIEW_TYPE2;
...
}
}
#Override
public GenericViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view;
if(viewType == VIEW_TYPE1){
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout1, parent, false);
return new SectionViewHolder(view);
}else if( viewType == VIEW_TYPE2){
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout2, parent, false);
return new OtherViewHolder(view);
}
// Cont. other view holders ...
return null;
}
#Override
public void onBindViewHolder(GenericViewHolder holder, int position) {
holder.setDataOnView(position);
}
Yes, it is possible.
In your adapter getItemViewType Layout like this ....
public class MultiViewTypeAdapter extends RecyclerView.Adapter {
private ArrayList<Model>dataSet;
Context mContext;
int total_types;
MediaPlayer mPlayer;
private boolean fabStateVolume = false;
public static class TextTypeViewHolder extends RecyclerView.ViewHolder {
TextView txtType;
CardView cardView;
public TextTypeViewHolder(View itemView) {
super(itemView);
this.txtType = (TextView) itemView.findViewById(R.id.type);
this.cardView = (CardView) itemView.findViewById(R.id.card_view);
}
}
public static class ImageTypeViewHolder extends RecyclerView.ViewHolder {
TextView txtType;
ImageView image;
public ImageTypeViewHolder(View itemView) {
super(itemView);
this.txtType = (TextView) itemView.findViewById(R.id.type);
this.image = (ImageView) itemView.findViewById(R.id.background);
}
}
public static class AudioTypeViewHolder extends RecyclerView.ViewHolder {
TextView txtType;
FloatingActionButton fab;
public AudioTypeViewHolder(View itemView) {
super(itemView);
this.txtType = (TextView) itemView.findViewById(R.id.type);
this.fab = (FloatingActionButton) itemView.findViewById(R.id.fab);
}
}
public MultiViewTypeAdapter(ArrayList<Model>data, Context context) {
this.dataSet = data;
this.mContext = context;
total_types = dataSet.size();
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view;
switch (viewType) {
case Model.TEXT_TYPE:
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.text_type, parent, false);
return new TextTypeViewHolder(view);
case Model.IMAGE_TYPE:
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.image_type, parent, false);
return new ImageTypeViewHolder(view);
case Model.AUDIO_TYPE:
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.audio_type, parent, false);
return new AudioTypeViewHolder(view);
}
return null;
}
#Override
public int getItemViewType(int position) {
switch (dataSet.get(position).type) {
case 0:
return Model.TEXT_TYPE;
case 1:
return Model.IMAGE_TYPE;
case 2:
return Model.AUDIO_TYPE;
default:
return -1;
}
}
#Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int listPosition) {
Model object = dataSet.get(listPosition);
if (object != null) {
switch (object.type) {
case Model.TEXT_TYPE:
((TextTypeViewHolder) holder).txtType.setText(object.text);
break;
case Model.IMAGE_TYPE:
((ImageTypeViewHolder) holder).txtType.setText(object.text);
((ImageTypeViewHolder) holder).image.setImageResource(object.data);
break;
case Model.AUDIO_TYPE:
((AudioTypeViewHolder) holder).txtType.setText(object.text);
}
}
}
#Override
public int getItemCount() {
return dataSet.size();
}
}
For the reference link: Android RecyclerView Example – Multiple ViewTypes
Simpler than ever, forget about ViewTypes. It is not recommended to use multiple viewtypes inside one adapter. It will mess the code and break the single responsibility principle since now the adapter needs to handle logic to know which view to inflate.
Now imagine working in large teams where each team has to work in one of those viewtypes features. It will be a mess to touch the same adapter by all the teams that work in the different viewtypes. This is solved using ConcatAdapter where you isolate the adapters. Code them one by one and then just merge them inside one view.
From recyclerview:1.2.0-alpha04 you now can use ConcatAdapter.
If you need a view with different viewTypes, you can just write the Adapters for each section and just use ConcatAdapter to merge all of them inside one recyclerview.
ConcatAdapter
This image shows three different viewtypes that one recyclerview has, header, content and footer.
You only create one adapter for each section, and then just use ConcatAdapter to merge them inside one recyclerview:
val firstAdapter: FirstAdapter = …
val secondAdapter: SecondAdapter = …
val thirdAdapter: ThirdAdapter = …
val concatAdapter = ConcatAdapter(firstAdapter, secondAdapter,
thirdAdapter)
recyclerView.adapter = concatAdapter
That's all you need to know. If you want to handle loading state, for example remove the last adapter after some loading happened, you can use LoadState.
For more in depth information follow Florina Muntenescu post here https://medium.com/androiddevelopers/merge-adapters-sequentially-with-mergeadapter-294d2942127a
Following Anton's solution, I came up with this ViewHolder which holds/handles/delegates different type of layouts.
But I am not sure if the replacing new layout would work when the recycling view's ViewHolder is not the type of the data roll in.
So basically,
onCreateViewHolder(ViewGroup parent, int viewType) is only called when new view layout is needed;
getItemViewType(int position) will be called for the viewType;
onBindViewHolder(ViewHolder holder, int position) is always called when recycling the view (new data is brought in and try to display with that ViewHolder).
So when onBindViewHolder is called it needs to be put in the right view layout and update the ViewHolder.
public int getItemViewType(int position) {
TypedData data = mDataSource.get(position);
return data.type;
}
public ViewHolder onCreateViewHolder(ViewGroup parent,
int viewType) {
return ViewHolder.makeViewHolder(parent, viewType);
}
public void onBindViewHolder(ViewHolder holder,
int position) {
TypedData data = mDataSource.get(position);
holder.updateData(data);
}
///
public static class ViewHolder extends
RecyclerView.ViewHolder {
ViewGroup mParentViewGroup;
View mCurrentViewThisViewHolderIsFor;
int mDataType;
public TypeOneViewHolder mTypeOneViewHolder;
public TypeTwoViewHolder mTypeTwoViewHolder;
static ViewHolder makeViewHolder(ViewGroup vwGrp,
int dataType) {
View v = getLayoutView(vwGrp, dataType);
return new ViewHolder(vwGrp, v, viewType);
}
static View getLayoutView(ViewGroup vwGrp,
int dataType) {
int layoutId = getLayoutId(dataType);
return LayoutInflater.from(vwGrp.getContext())
.inflate(layoutId, null);
}
static int getLayoutId(int dataType) {
if (dataType == TYPE_ONE) {
return R.layout.type_one_layout;
} else if (dataType == TYPE_TWO) {
return R.layout.type_two_layout;
}
}
public ViewHolder(ViewGroup vwGrp, View v,
int dataType) {
super(v);
mDataType = dataType;
mParentViewGroup = vwGrp;
mCurrentViewThisViewHolderIsFor = v;
if (data.type == TYPE_ONE) {
mTypeOneViewHolder = new TypeOneViewHolder(v);
} else if (data.type == TYPE_TWO) {
mTypeTwoViewHolder = new TypeTwoViewHolder(v);
}
}
public void updateData(TypeData data) {
mDataType = data.type;
if (data.type == TYPE_ONE) {
mTypeTwoViewHolder = null;
if (mTypeOneViewHolder == null) {
View newView = getLayoutView(mParentViewGroup,
data.type);
/**
* How can I replace a new view with
the view in the parent
view container?
*/
replaceView(mCurrentViewThisViewHolderIsFor,
newView);
mCurrentViewThisViewHolderIsFor = newView;
mTypeOneViewHolder =
new TypeOneViewHolder(newView);
}
mTypeOneViewHolder.updateDataTypeOne(data);
} else if (data.type == TYPE_TWO){
mTypeOneViewHolder = null;
if (mTypeTwoViewHolder == null) {
View newView = getLayoutView(mParentViewGroup,
data.type);
/**
* How can I replace a new view with
the view in the parent view
container?
*/
replaceView(mCurrentViewThisViewHolderIsFor,
newView);
mCurrentViewThisViewHolderIsFor = newView;
mTypeTwoViewHolder =
new TypeTwoViewHolder(newView);
}
mTypeTwoViewHolder.updateDataTypeOne(data);
}
}
}
public static void replaceView(View currentView,
View newView) {
ViewGroup parent = (ViewGroup)currentView.getParent();
if(parent == null) {
return;
}
final int index = parent.indexOfChild(currentView);
parent.removeView(currentView);
parent.addView(newView, index);
}
ViewHolder has member mItemViewType to hold the view.
It looks like in onBindViewHolder(ViewHolder holder, int position) the ViewHolder passed in has been picked up (or created) by looked at getItemViewType(int position) to make sure it is a match, so it may not need to worry there that ViewHolder's type does not match the data[position]'s type.
It looks like The recycle ViewHolder is picked by type, so no warrior there.
Building a RecyclerView LayoutManager – Part 1 answers this question.
It gets the recycle ViewHolder like:
holder = getRecycledViewPool().getRecycledView(mAdapter.getItemViewType(offsetPosition));
Or create a new one if not find recycle ViewHolder of the right type.
public ViewHolder getRecycledView(int viewType) {
final ArrayList<ViewHolder> scrapHeap = mScrap.get(viewType);
if (scrapHeap != null && !scrapHeap.isEmpty()) {
final int index = scrapHeap.size() - 1;
final ViewHolder scrap = scrapHeap.get(index);
scrapHeap.remove(index);
return scrap;
}
return null;
}
View getViewForPosition(int position, boolean dryRun) {
......
if (holder == null) {
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) {
throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item "
+ "position " + position + "(offset:" + offsetPosition + ")."
+ "state:" + mState.getItemCount());
}
final int type = mAdapter.getItemViewType(offsetPosition);
// 2) Find from scrap via stable ids, if exists
if (mAdapter.hasStableIds()) {
holder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
if (holder != null) {
// update position
holder.mPosition = offsetPosition;
fromScrap = true;
}
}
if (holder == null && mViewCacheExtension != null) {
// We are NOT sending the offsetPosition because LayoutManager does not
// know it.
final View view = mViewCacheExtension
.getViewForPositionAndType(this, position, type);
if (view != null) {
holder = getChildViewHolder(view);
if (holder == null) {
throw new IllegalArgumentException("getViewForPositionAndType returned"
+ " a view which does not have a ViewHolder");
} else if (holder.shouldIgnore()) {
throw new IllegalArgumentException("getViewForPositionAndType returned"
+ " a view that is ignored. You must call stopIgnoring before"
+ " returning this view.");
}
}
}
if (holder == null) { // fallback to recycler
// try recycler.
// Head to the shared pool.
if (DEBUG) {
Log.d(TAG, "getViewForPosition(" + position + ") fetching from shared "
+ "pool");
}
holder = getRecycledViewPool()
.getRecycledView(mAdapter.getItemViewType(offsetPosition));
if (holder != null) {
holder.resetInternal();
if (FORCE_INVALIDATE_DISPLAY_LIST) {
invalidateDisplayListInt(holder);
}
}
}
if (holder == null) {
holder = mAdapter.createViewHolder(RecyclerView.this,
mAdapter.getItemViewType(offsetPosition));
if (DEBUG) {
Log.d(TAG, "getViewForPosition created new ViewHolder");
}
}
}
boolean bound = false;
if (mState.isPreLayout() && holder.isBound()) {
// do not update unless we absolutely have to.
holder.mPreLayoutPosition = position;
} else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
if (DEBUG && holder.isRemoved()) {
throw new IllegalStateException("Removed holder should be bound and it should"
+ " come here only in pre-layout. Holder: " + holder);
}
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
mAdapter.bindViewHolder(holder, offsetPosition);
attachAccessibilityDelegate(holder.itemView);
bound = true;
if (mState.isPreLayout()) {
holder.mPreLayoutPosition = position;
}
}
final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
final LayoutParams rvLayoutParams;
if (lp == null) {
rvLayoutParams = (LayoutParams) generateDefaultLayoutParams();
holder.itemView.setLayoutParams(rvLayoutParams);
} else if (!checkLayoutParams(lp)) {
rvLayoutParams = (LayoutParams) generateLayoutParams(lp);
holder.itemView.setLayoutParams(rvLayoutParams);
} else {
rvLayoutParams = (LayoutParams) lp;
}
rvLayoutParams.mViewHolder = holder;
rvLayoutParams.mPendingInvalidate = fromScrap && bound;
return holder.itemView;
}
Although the selected answer is correct, I just want to further elaborate it. I found a useful Custom Adapter for multiple View Types in RecyclerView.
Its Kotlin version is here.
The custom adapter is the following:
public class CustomAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private final Context context;
ArrayList<String> list; // ArrayList of your Data Model
final int VIEW_TYPE_ONE = 1;
final int VIEW_TYPE_TWO = 2;
public CustomAdapter(Context context, ArrayList<String> list) { // you can pass other parameters in constructor
this.context = context;
this.list = list;
}
private class ViewHolder1 extends RecyclerView.ViewHolder {
TextView yourView;
ViewHolder1(final View itemView) {
super(itemView);
yourView = itemView.findViewById(R.id.yourView); // Initialize your All views prensent in list items
}
void bind(int position) {
// This method will be called anytime a list item is created or update its data
// Do your stuff here
yourView.setText(list.get(position));
}
}
private class ViewHolder2 extends RecyclerView.ViewHolder {
TextView yourView;
ViewHolder2(final View itemView) {
super(itemView);
yourView = itemView.findViewById(R.id.yourView); // Initialize your All views prensent in list items
}
void bind(int position) {
// This method will be called anytime a list item is created or update its data
//Do your stuff here
yourView.setText(list.get(position));
}
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == VIEW_TYPE_ONE) {
return new ViewHolder1(LayoutInflater.from(context).inflate(R.layout.your_list_item_1, parent, false));
}
//if its not VIEW_TYPE_ONE then its VIEW_TYPE_TWO
return new ViewHolder2(LayoutInflater.from(context).inflate(R.layout.your_list_item_2, parent, false));
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (list.get(position).type == Something) { // Put your condition, according to your requirements
((ViewHolder1) holder).bind(position);
} else {
((ViewHolder2) holder).bind(position);
}
}
#Override
public int getItemCount() {
return list.size();
}
#Override
public int getItemViewType(int position) {
// Here you can get decide from your model's ArrayList, which type of view you need to load. Like
if (list.get(position).type == Something) { // Put your condition, according to your requirements
return VIEW_TYPE_ONE;
}
return VIEW_TYPE_TWO;
}
}
I have a better solution which allows to create multiple view types in a declarative and type safe way. It’s written in Kotlin which, by the way, is really nice.
Simple view holders for all required view types
class ViewHolderMedium(itemView: View) : RecyclerView.ViewHolder(itemView) {
val icon: ImageView = itemView.findViewById(R.id.icon) as ImageView
val label: TextView = itemView.findViewById(R.id.label) as TextView
}
There is an abstraction of adapter data item. Note that a view type is represented by a hashCode of particular view holder class (KClass in Kotlin)
trait AdapterItem {
val viewType: Int
fun bindViewHolder(viewHolder: RecyclerView.ViewHolder)
}
abstract class AdapterItemBase<T>(val viewHolderClass: KClass<T>) : AdapterItem {
override val viewType: Int = viewHolderClass.hashCode()
abstract fun bindViewHolder(viewHolder: T)
override fun bindViewHolder(viewHolder: RecyclerView.ViewHolder) {
bindViewHolder(viewHolder as T)
}
}
Only bindViewHolder needs to be overridden in concrete adapter item classes (type safe way).
class AdapterItemMedium(val icon: Drawable, val label: String, val onClick: () -> Unit) : AdapterItemBase<ViewHolderMedium>(ViewHolderMedium::class) {
override fun bindViewHolder(viewHolder: ViewHolderMedium) {
viewHolder.icon.setImageDrawable(icon)
viewHolder.label.setText(label)
viewHolder.itemView.setOnClickListener { onClick() }
}
}
List of such AdapterItemMedium objects is a data source for the adapter which actually accepts List<AdapterItem>. See below.
The important part of this solution is a view holder factory which will provide fresh instances of a specific ViewHolder:
class ViewHolderProvider {
private val viewHolderFactories = hashMapOf<Int, Pair<Int, Any>>()
fun provideViewHolder(viewGroup: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val (layoutId: Int, f: Any) = viewHolderFactories.get(viewType)
val viewHolderFactory = f as (View) -> RecyclerView.ViewHolder
val view = LayoutInflater.from(viewGroup.getContext()).inflate(layoutId, viewGroup, false)
return viewHolderFactory(view)
}
fun registerViewHolderFactory<T>(key: KClass<T>, layoutId: Int, viewHolderFactory: (View) -> T) {
viewHolderFactories.put(key.hashCode(), Pair(layoutId, viewHolderFactory))
}
}
And the simple adapter class looks like this:
public class MultitypeAdapter(val items: List<AdapterItem>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
val viewHolderProvider = ViewHolderProvider() // inject ex Dagger2
init {
viewHolderProvider!!.registerViewHolderFactory(ViewHolderMedium::class, R.layout.item_medium, { itemView ->
ViewHolderMedium(itemView)
})
}
override fun getItemViewType(position: Int): Int {
return items[position].viewType
}
override fun getItemCount(): Int {
return items.size()
}
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): RecyclerView.ViewHolder? {
return viewHolderProvider!!.provideViewHolder(viewGroup, viewType)
}
override fun onBindViewHolder(viewHolder: RecyclerView.ViewHolder, position: Int) {
items[position].bindViewHolder(viewHolder)
}
}
There are only three steps to create a new view type:
create a view holder class
create an adapter item class (extending from AdapterItemBase)
register the view holder class in ViewHolderProvider
Here is an example of this concept: android-drawer-template.
It goes even further - a view type which acts as a spinner component, with selectable adapter items.
It is very simple and straightforward.
Just override the getItemViewType() method in your adapter. On the basis of data return different itemViewType values. E.g., consider an object of type Person with a member isMale, if isMale is true, return 1 and isMale is false, return 2 in the getItemViewType() method.
Now coming to the createViewHolder (ViewGroup parent, int viewType), on the basis of different viewType yon can inflate the different layout file. Like the following:
if (viewType == 1){
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.male, parent, false);
return new AdapterMaleViewHolder(view);
}
else{
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.female, parent, false);
return new AdapterFemaleViewHolder(view);
}
in onBindViewHolder (VH holder,int position) check where holder is an instance of AdapterFemaleViewHolder or AdapterMaleViewHolder by instanceof and accordingly assign the values.
ViewHolder may be like this
class AdapterMaleViewHolder extends RecyclerView.ViewHolder {
...
public AdapterMaleViewHolder(View itemView){
...
}
}
class AdapterFemaleViewHolder extends RecyclerView.ViewHolder {
...
public AdapterFemaleViewHolder(View itemView){
...
}
}
I recommend this library from Hannes Dorfmann. It encapsulates all the logic related to a particular view type in a separate object called "AdapterDelegate".
https://github.com/sockeqwe/AdapterDelegates
public class CatAdapterDelegate extends AdapterDelegate<List<Animal>> {
private LayoutInflater inflater;
public CatAdapterDelegate(Activity activity) {
inflater = activity.getLayoutInflater();
}
#Override public boolean isForViewType(#NonNull List<Animal> items, int position) {
return items.get(position) instanceof Cat;
}
#NonNull #Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) {
return new CatViewHolder(inflater.inflate(R.layout.item_cat, parent, false));
}
#Override public void onBindViewHolder(#NonNull List<Animal> items, int position,
#NonNull RecyclerView.ViewHolder holder, #Nullable List<Object> payloads) {
CatViewHolder vh = (CatViewHolder) holder;
Cat cat = (Cat) items.get(position);
vh.name.setText(cat.getName());
}
static class CatViewHolder extends RecyclerView.ViewHolder {
public TextView name;
public CatViewHolder(View itemView) {
super(itemView);
name = (TextView) itemView.findViewById(R.id.name);
}
}
}
public class AnimalAdapter extends ListDelegationAdapter<List<Animal>> {
public AnimalAdapter(Activity activity, List<Animal> items) {
// DelegatesManager is a protected Field in ListDelegationAdapter
delegatesManager.addDelegate(new CatAdapterDelegate(activity))
.addDelegate(new DogAdapterDelegate(activity))
.addDelegate(new GeckoAdapterDelegate(activity))
.addDelegate(23, new SnakeAdapterDelegate(activity));
// Set the items from super class.
setItems(items);
}
}
I firstly recommend you to read Hannes Dorfmann's great article about this topic.
When a new view type comes, you have to edit your adapter and you have to handle so many messy things. Your adapter should be open for extension, but closed for modification.
You may check this two project, they can give the idea about how to handle different ViewTypes in Adapter:
https://github.com/sockeqwe/AdapterDelegates
https://github.com/ibrahimyilmaz/kiel
If anyone is interested to see the super simple solution written in Kotlin, check the blogpost I just created. The example in the blogpost is based on creating Sectioned RecyclerView:
https://brona.blog/2020/06/sectioned-recyclerview-in-three-steps/
Actually, I'd like to improve on Anton's answer.
Since getItemViewType(int position) returns an integer value, you can return the layout resource ID you'd need to inflate. That way you'd save some logic in onCreateViewHolder(ViewGroup parent, int viewType) method.
Also, I wouldn't suggest doing intensive calculations in getItemCount() as that particular function is called at least 5 times while rendering the list, as well as while rendering each item beyond the visible items. Sadly since notifyDatasetChanged() method is final, you can't really override it, but you can call it from another function within the adapter.
You can use the library: https://github.com/vivchar/RendererRecyclerViewAdapter
mRecyclerViewAdapter = new RendererRecyclerViewAdapter(); /* Included from library */
mRecyclerViewAdapter.registerRenderer(new SomeViewRenderer(SomeModel.TYPE, this));
mRecyclerViewAdapter.registerRenderer(...); /* You can use several types of cells */
For each item, you should to implement a ViewRenderer, ViewHolder, SomeModel:
ViewHolder - it is a simple view holder of recycler view.
SomeModel - it is your model with ItemModel interface
public class SomeViewRenderer extends ViewRenderer<SomeModel, SomeViewHolder> {
public SomeViewRenderer(final int type, final Context context) {
super(type, context);
}
#Override
public void bindView(#NonNull final SomeModel model, #NonNull final SomeViewHolder holder) {
holder.mTitle.setText(model.getTitle());
}
#NonNull
#Override
public SomeViewHolder createViewHolder(#Nullable final ViewGroup parent) {
return new SomeViewHolder(LayoutInflater.from(getContext()).inflate(R.layout.some_item, parent, false));
}
}
For more details you can look at the documentation.
View types implementation becomes easier with Kotlin. Here is a sample with this light library https://github.com/Link184/KidAdapter
recyclerView.setUp {
withViewType {
withLayoutResId(R.layout.item_int)
withItems(mutableListOf(1, 2, 3, 4, 5, 6))
bind<Int> { // this - is adapter view hoder itemView, it - current item
intName.text = it.toString()
}
}
withViewType("SECOND_STRING_TAG") {
withLayoutResId(R.layout.item_text)
withItems(mutableListOf("eight", "nine", "ten", "eleven", "twelve"))
bind<String> {
stringName.text = it
}
}
}
You can deal with multipleViewTypes RecyclerAdapter by making getItemViewType() return the expected viewType value for that position.
I prepared an MultipleViewTypeAdapter for constructing an MCQ list for examinations which may throw a question that may have two or more valid answers (checkbox options) and a single answer questions (radiobutton options).
For this I get the type of question from the API response and I used that for deciding which view I have to show for that question.
public class MultiViewTypeAdapter extends RecyclerView.Adapter {
Context mContext;
ArrayList<Question> dataSet;
ArrayList<String> questions;
private Object radiobuttontype1;
//Viewholder to display Questions with checkboxes
public static class Checkboxtype2 extends RecyclerView.ViewHolder {
ImageView imgclockcheck;
CheckBox checkbox;
public Checkboxtype2(#NonNull View itemView) {
super(itemView);
imgclockcheck = (ImageView) itemView.findViewById(R.id.clockout_cbox_image);
checkbox = (CheckBox) itemView.findViewById(R.id.clockout_cbox);
}
}
//Viewholder to display Questions with radiobuttons
public static class Radiobuttontype1 extends RecyclerView.ViewHolder {
ImageView clockout_imageradiobutton;
RadioButton clockout_radiobutton;
TextView sample;
public radiobuttontype1(View itemView) {
super(itemView);
clockout_imageradiobutton = (ImageView) itemView.findViewById(R.id.clockout_imageradiobutton);
clockout_radiobutton = (RadioButton) itemView.findViewById(R.id.clockout_radiobutton);
sample = (TextView) itemView.findViewById(R.id.sample);
}
}
public MultiViewTypeAdapter(ArrayList<QueDatum> data, Context context) {
this.dataSet = data;
this.mContext = context;
}
#NonNull
#Override
public RecyclerView.ViewHolder onCreateViewHolder(#NonNull ViewGroup viewGroup, int viewType) {
if (viewType.equalsIgnoreCase("1")) {
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.clockout_radio_list_row, viewGroup, false);
return new radiobuttontype1(view);
} else if (viewType.equalsIgnoreCase("2")) {
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.clockout_cbox_list_row, viewGroup, false);
view.setHorizontalFadingEdgeEnabled(true);
return new Checkboxtype2(view);
} else if (viewType.equalsIgnoreCase("3")) {
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.clockout_radio_list_row, viewGroup, false);
return new Radiobuttontype1(view);
} else if (viewType.equalsIgnoreCase("4")) {
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.clockout_radio_list_row, viewGroup, false);
return new Radiobuttontype1(view);
} else if (viewType.equalsIgnoreCase("5")) {
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.clockout_radio_list_row, viewGroup, false);
return new Radiobuttontype1(view);
}
return null;
}
#Override
public void onBindViewHolder(#NonNull RecyclerView.ViewHolder viewHolder, int viewType) {
if (viewType.equalsIgnoreCase("1")) {
options = dataSet.get(i).getOptions();
question = dataSet.get(i).getQuestion();
image = options.get(i).getValue();
((radiobuttontype1) viewHolder).clockout_radiobutton.setChecked(false);
((radiobuttontype1) viewHolder).sample.setText(question);
//Loading image bitmap in the ViewHolder's View
Picasso.with(mContext)
.load(image)
.into(((radiobuttontype1) viewHolder).clockout_imageradiobutton);
} else if (viewType.equalsIgnoreCase("2")) {
options = (ArrayList<Clockout_questions_Option>) dataSet.get(i).getOptions();
question = dataSet.get(i).getQuestion();
image = options.get(i).getValue();
//Loading image bitmap in the ViewHolder's View
Picasso.with(mContext)
.load(image)
.into(((Checkboxtype2) viewHolder).imgclockcheck);
} else if (viewType.equalsIgnoreCase("3")) {
//Fit data to viewHolder for ViewType 3
} else if (viewType.equalsIgnoreCase("4")) {
//Fit data to viewHolder for ViewType 4
} else if (viewType.equalsIgnoreCase("5")) {
//Fit data to viewHolder for ViewType 5
}
}
#Override
public int getItemCount() {
return dataSet.size();
}
/**
* Returns viewType for that position by picking the viewType value from the
* dataset
*/
#Override
public int getItemViewType(int position) {
return dataSet.get(position).getViewType();
}
}
You can avoid multiple conditionals based viewHolder data fillings in onBindViewHolder() by assigning same ids for the similar views across viewHolders which differ in their positioning.
If you want to use it in conjunction with Android Data Binding look into the https://github.com/evant/binding-collection-adapter - it is by far the best solution for the multiple view types RecyclerView I have even seen.
You may use it like
var items: AsyncDiffPagedObservableList<BaseListItem> =
AsyncDiffPagedObservableList(GenericDiff)
val onItemBind: OnItemBind<BaseListItem> =
OnItemBind { itemBinding, _, item -> itemBinding.set(BR.item, item.layoutRes) }
And then in the layout where the list is:
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
app:enableAnimations="#{false}"
app:scrollToPosition="#{viewModel.scrollPosition}"
app:itemBinding="#{viewModel.onItemBind}"
app:items="#{viewModel.items}"
app:reverseLayoutManager="#{true}"/>
Your list items must implement the BaseListItem interface which looks like this:
interface BaseListItem {
val layoutRes: Int
}
And the item view should look something like this:
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="item"
type="...presentation.somescreen.list.YourListItem"/>
</data>
...
</layout>
Where YourListItem implements BaseListItem.
First you must create two layout XML files. After that inside recyclerview adapter TYPE_CALL and TYPE_EMAIL are two static values with 1 and 2 respectively in the adapter class.
Now define two static values ​​at the Recycler view Adapter class level, for example: private static int TYPE_CALL = 1; private static int TYPE_EMAIL = 2;
Now create the view holder with multiple views like this:
class CallViewHolder extends RecyclerView.ViewHolder {
private TextView txtName;
private TextView txtAddress;
CallViewHolder(#NonNull View itemView) {
super(itemView);
txtName = itemView.findViewById(R.id.txtName);
txtAddress = itemView.findViewById(R.id.txtAddress);
}
}
class EmailViewHolder extends RecyclerView.ViewHolder {
private TextView txtName;
private TextView txtAddress;
EmailViewHolder(#NonNull View itemView) {
super(itemView);
txtName = itemView.findViewById(R.id.txtName);
txtAddress = itemView.findViewById(R.id.txtAddress);
}
}
Now code as below in onCreateViewHolder and onBindViewHolder method in the recyclerview adapter:
#NonNull
#Override
public RecyclerView.ViewHolder onCreateViewHolder(#NonNull ViewGroup viewGroup, int viewType) {
View view;
if (viewType == TYPE_CALL) { // for call layout
view = LayoutInflater.from(context).inflate(R.layout.item_call, viewGroup, false);
return new CallViewHolder(view);
} else { // for email layout
view = LayoutInflater.from(context).inflate(R.layout.item_email, viewGroup, false);
return new EmailViewHolder(view);
}
}
#Override
public void onBindViewHolder(#NonNull RecyclerView.ViewHolder viewHolder, int position) {
if (getItemViewType(position) == TYPE_CALL) {
((CallViewHolder) viewHolder).setCallDetails(employees.get(position));
} else {
((EmailViewHolder) viewHolder).setEmailDetails(employees.get(position));
}
}
I did something like this. I passed "fragmentType" and created two ViewHolders and on basis of this, I classified my Layouts accordingly in a single adapter that can have different Layouts and LayoutManagers
private Context mContext;
protected IOnLoyaltyCardCategoriesItemClicked mListener;
private String fragmentType;
private View view;
public LoyaltyCardsCategoriesRecyclerViewAdapter(Context context, IOnLoyaltyCardCategoriesItemClicked itemListener, String fragmentType) {
this.mContext = context;
this.mListener = itemListener;
this.fragmentType = fragmentType;
}
public class LoyaltyCardCategoriesFragmentViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
private ImageView lc_categories_iv;
private TextView lc_categories_name_tv;
private int pos;
public LoyaltyCardCategoriesFragmentViewHolder(View v) {
super(v);
view.setOnClickListener(this);
lc_categories_iv = (ImageView) v.findViewById(R.id.lc_categories_iv);
lc_categories_name_tv = (TextView) v.findViewById(R.id.lc_categories_name_tv);
}
public void setData(int pos) {
this.pos = pos;
lc_categories_iv.setImageResource(R.mipmap.ic_launcher);
lc_categories_name_tv.setText("Loyalty Card Categories");
}
#Override
public void onClick(View view) {
if (mListener != null) {
mListener.onLoyaltyCardCategoriesItemClicked(pos);
}
}
}
public class MyLoyaltyCardsFragmentTagViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
public ImageButton lc_categories_btn;
private int pos;
public MyLoyaltyCardsFragmentTagViewHolder(View v) {
super(v);
lc_categories_btn = (ImageButton) v.findViewById(R.id.lc_categories_btn);
lc_categories_btn.setOnClickListener(this);
}
public void setData(int pos) {
this.pos = pos;
lc_categories_btn.setImageResource(R.mipmap.ic_launcher);
}
#Override
public void onClick(View view) {
if (mListener != null) {
mListener.onLoyaltyCardCategoriesItemClicked(pos);
}
}
}
#NonNull
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (fragmentType.equalsIgnoreCase(Constants.LoyaltyCardCategoriesFragmentTag)) {
view = LayoutInflater.from(mContext).inflate(R.layout.loyalty_cards_categories_frag_item, parent, false);
return new LoyaltyCardCategoriesFragmentViewHolder(view);
} else if (fragmentType.equalsIgnoreCase(Constants.MyLoyaltyCardsFragmentTag)) {
view = LayoutInflater.from(mContext).inflate(R.layout.my_loyalty_cards_categories_frag_item, parent, false);
return new MyLoyaltyCardsFragmentTagViewHolder(view);
} else {
return null;
}
}
#Override
public void onBindViewHolder(#NonNull RecyclerView.ViewHolder holder, int position) {
if (fragmentType.equalsIgnoreCase(Constants.LoyaltyCardCategoriesFragmentTag)) {
((LoyaltyCardCategoriesFragmentViewHolder) holder).setData(position);
} else if (fragmentType.equalsIgnoreCase(Constants.MyLoyaltyCardsFragmentTag)) {
((MyLoyaltyCardsFragmentTagViewHolder) holder).setData(position);
}
}
#Override
public int getItemCount() {
return 7;
}
I see there are a lot of great answers, incredibly detailed and extensive. In my case, I always understand things better if I follow along the reasoning from almost scratch, step by step. I would recommend you check this link out and whenever you have similar questions, search for any codelabs that address the issue.
Android Kotlin Fundamentals: Headers in RecyclerView

How to add separator at particular position inside recyclerview?

I have a list of taskLists. To show the list I have used recyclerview. I have 1st 3 items as today , tomorrow and later in my list. I want to add one separator after 1st 3 items in recycler view. How can I do this?
Adapter :
public class ListAdapter extends RecyclerView.Adapter<ListAdapter.ItemViewHolder>{
ArrayList<ListData> item;
public static final int TYPE1=1;
Context conext;
public ListAdapter(Context context, ArrayList<ListData> item) {
this.conext=context;
this.item=item;
}
public interface OnItemClickListener {
void onItemClick(ListData listData);
}
#Override
public int getItemCount() {
return item.size();
}
public void remove(int position) {
item.remove(position);
notifyItemRemoved(position);
}
// #Override
// public int getItemViewType(int position) {
// return item.get(position).getExpenseType();// Assume that this return 1 0r 2
// }
#Override
public void onBindViewHolder(ItemViewHolder itemViewHolder,final int i) {
itemViewHolder.listName.setText(item.get(i).getTitle());
}
#Override
public ItemViewHolder onCreateViewHolder(ViewGroup viewGroup,int viewType) {
View itemView = LayoutInflater.
from(viewGroup.getContext()).
inflate(R.layout.list_layout, viewGroup, false);
return new ItemViewHolder(itemView,viewType);
}
public static class ItemViewHolder extends RecyclerView.ViewHolder {
TextView listName;
ItemViewHolder(View itemView, int viewType) {
super(itemView);
listName = (TextView)itemView.findViewById(R.id.listData);
}
}
#Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
}
}
Can anyone help with this how can I put separator after 3 items in list?
Thank you..
You should define 2 types of RecyclerView rows:
...YourRecyclerAdapter extends RecyclerView.Adapter<BaseViewHolder>
public static final int COMMON = 1;
public static final int SEPARATOR = 2;
Override getItemViewType method of your Adapter:
#Override
public int getItemViewType(int position) {
if (position%10 == 0) //each 10 row is separator (change it!)
return SEPARATOR;
else return COMMON;
}
Change onCreateViewHolder method of your Adapter:
#Override
public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == COMMON)
return new ItemViewHolder(LayoutInflater.from(activity).inflate(R.layout.list_layout, parent, false));
else
return new SeparatorHolder(LayoutInflater.from(activity).inflate(R.layout.separator_item, parent, false));
}
#Override
public void onBindViewHolder(BaseViewHolder holder, int position) {
if (getItemViewType(position) == COMMON) {
//do stuff
} else {
}
}
ItemViewHolder extends BaseViewHolder
SeparatorHolder extends BaseViewHolder
BaseViewHolder extends RecyclerView.ViewHolder
One solution is define 2 types of RecyclerView rows (one for normal row and one for separator)
Another solution is you should a Separator View in the bottom of your custom RecycleView row xml
<View
android:id="#+id/separatorView"
android:layout_width="match_parent"
android:layout_height="3dp"
android:visible="gone"
android:background="#android:color/darker_gray"/>
Then in bindViewHolder of your RecyclerView.Adapter, hide the separator in normal row and visible it in separator row
#Override
public void bindViewHolder(ViewHolder holder, int position) {
if(position == separatorPosition){
holder.separatorView.visible = View.VISIBLE;
}else{
holder.separatorView.visible = View.GONE;
}
}
Hope this help
You can create two ViewHolder class and Switch them in onCreateViewHolder. One containing your custom line, and others as your custom list items.
class ViewHolderLine extends RecyclerView.ViewHolder { //contains line
}
class ViewHolderItems extends RecyclerView.ViewHolder { //contains data
}
#Override
public int getItemViewType(int position) {
return item.get(position).getExpenseType();// Assume that this return 1 0r 2
}
#Override
public ItemViewHolder onCreateViewHolder(ViewGroup viewGroup,int viewType) {
switch (viewType) {
case 1: return new ViewHolderLine();
case 2:
View itemView = LayoutInflater.
from(viewGroup.getContext()).
inflate(R.layout.list_layout, viewGroup, false);
return new ItemViewHolder(itemView,viewType);
}
}
You can see the details description here for more info.
if you know that you are only going to add separator in the 1st three items, then you can put a condition based on the position of the item, inside onBindViewHolder.
ps: Please do not forget to add an else block after an if block
I have a recyclerview with section header and variable number of items in each section. The section should have a line separator across entire screen. and items must have a padded line between them.
So, my solution(in kotlin) was having two viewholders, one for header and one for item. in getItemViewType, return the type based on the item.
override fun getItemViewType(position: Int): Int {
if(dataList[position] is HeaderItem)
return Companion.TYPE_HEADER
return Companion.TYPE_ITEM
}
and create the corresponding viewholder in createviewholder, bind accordingly.
Used
parent.getChildViewHolder(parent.getChildAt(i))
to decide the padding(or color or width) for the item separator.
class ItemDivider(context: Context) : RecyclerView.ItemDecoration() {
private var mDivider: Drawable
private val mContext = context
companion object {
private val ATTRS = intArrayOf(android.R.attr.listDivider)
}
init {
val styledAttributes = context.obtainStyledAttributes(ATTRS)
mDivider = styledAttributes.getDrawable(0)
mDivider.setTint(ContextCompat.getColor(mContext, <color>))
styledAttributes.recycle()
}
override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State?) {
val right = parent.width - parent.paddingRight
for (i in 0 until parent.childCount - 1) {
val child = parent.getChildAt(i)
val params = child.layoutParams as RecyclerView.LayoutParams
val left = if (parent.getChildViewHolder(child) is HeaderViewHolder) {
0
} else {
child.left + params.leftMargin + mContext.resources.getDimension(<padding_in_dp>).toInt()
}
val top = child.bottom + params.bottomMargin
val bottom = top + mDivider.intrinsicHeight + <divider_height>
mDivider.setBounds(left, top, right, bottom)
mDivider.draw(c)
}
}
}
remember to replace the color, padding, divider height with valid values.
Hope it helps!

Android: invert RecyclerView positions

I'm setting a RecyclerView behaving like a list, I want a button in the bottom of the list that when clicked adds more views, I'm thinking the easier way to do it is to make the position 0 as the first one in the bottom, and increasing the position to the top, so I can add views when the the view in position 0 is clicked.
If there is a better aproach for this problem do share.
Here is my adapter:
public class AddEventsAdapter extends RecyclerView.Adapter<AddEventsAdapter.ViewHolder> {
public List<String> items = new ArrayList<>();
public void addItem(String name) {
notifyItemInserted(items.size() - 1);
items.add(name);
}
public void removeItem(int position) {
items.remove(position);
notifyItemRemoved(position);
notifyItemRangeChanged(position, items.size());
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View view = inflater.inflate(R.layout.add_event_item, parent, false);
return new ViewHolder(view);
}
#Override
public int getItemCount() {
return items.size();
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.setData(position);
holder.eventName.setText(i + "");
if(position == 0)
{
holder.theLayout.setBackgroundColor(Color.parseColor("#7F9099"));
holder.eventName.setText("Add");
}
}
static int i;
class ViewHolder extends RecyclerView.ViewHolder{
public TextView eventName;
public RelativeLayout theLayout;
public ViewHolder(final View itemView) {
super(itemView);
eventName = (TextView)itemView.findViewById(R.id.eventName);
theLayout = (RelativeLayout)itemView.findViewById(R.id.backgroundevent);
theLayout.setId(++i);
}
public void setData(final int position) {
theLayout.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (position == items.size() - 1) {
addItem("");
} else {
removeItem(position);
}
}
});
}
}
}
You may notice some errors in that, I've been over it for the last 10 hours and I'm having a logic breakdown
It's solved by addind this line to the LayoutManager .setReverseLayout(true);
you can add a footer view at the end of the list and inside that you can add your button. This is the link to create a footer in recycler view https://github.com/u3breeze/android-RecyclerView-WithHeaderAndFooter. You can add the views in the normal way

Categories

Resources