I am developing an application which heavily relies on the usage of RecyclerView.
I really need to know how to use the same RecyclerView for different item layouts. An e
I came across similar situation and here is the model i followed.
First of all, Fragment layout file.
fragment layout file is not going to change for all 3 fragments(basically it is similar to list fragment), so I created a template file for list fragment.
list_fragment_template.xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout>
Now fragment code :
In my case all the 3 fragments do almost same stuff (get recycler view, get adapter, recycler view decoration and some more operations etc).
Created an AbstrctFragment which extends fragment and overrided onCreate onAttach onDestroy etc. Since only type of data recyclerview consumes and adapters to push data to recycelrview would change for each of the fragment, create an abstract function to getAdapter and templatize data. Each of the three fragments will be derived from this AbstractFragment.
public abstract class AbstractFragment<E> extends Fragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.template_list_fragment, container, false);
mRecyclerView = (RecyclerView) view.findViewById(R.id.list);
// get adapter to show item list
// and fetch data.
mRecyclerAdapter = getAdapter();
mRecyclerView.setAdapter(mRecyclerAdapter);
// show it as vertical list
mRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
// add seperator between list items.
mRecyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL_LIST));
return view;
}
... onAttach onDetach and whatever common memberfunctions and member variables that comes for each fragment.
Now RecyclerView layout files. Since all of them are different in layout, obviously they must be different.
RecyclerViewAdapters : Again here common code would be to member declarations, CreateViewHolder (here only layout name changes rest all code is same) and any other function which all of these adapters would share. (something like filtering list items).
similar to how we did for fragments, you can keep this in AbstractRecyclerViewAdapter and make bindViewholder etc as abstract functions and have 3 different recyclerAdapters which would derive from this AbstractRecyclerViewAdapter..
//To setViewType maybe is a solution for you.Sample below:
private static final int TYPE_DESC = 0;
private static final int TYPE_IMAGE = TYPE_DESC + 1;
private static final int TYPE_THREE_TEXT = TYPE_IMAGE + 1;
public int getItemViewType(int position) {
int type = super.getItemViewType(position);
try
{
type = Integer.parseInt(data.get(position).get("type"));
} catch (Exception e)
{
e.printStackTrace();
}
return type;
}
public int getViewTypeCount() {
return 3;
}
public View getView(int position, View convertView, ViewGroup parent) {
int type = TYPE_DESC;
try
{
type = Integer.parseInt(data.get(position).get("type"));
} catch (Exception e)
{
e.printStackTrace();
}
ViewHolder holder = null;
if (convertView == null)
{
System.out.println("getView::convertView is null");
holder = new ViewHolder();
switch (type)
{
case TYPE_DESC:
convertView = View.inflate(getBaseContext(),
R.layout.listitem_1, null);
break;
case TYPE_IMAGE:
convertView = View.inflate(getBaseContext(),
R.layout.listitem_2, null);
break;
case TYPE_THREE_TEXT:
convertView = View.inflate(getBaseContext(),
R.layout.listitem_3, null);
break;
}
convertView.setTag(holder);
}
else
{
holder = (ViewHolder) convertView.getTag();
}
//TODO
return convertView;
}
Since the recyclerview item in xml doesn't depend on the kind of items that you will inflating in it, you can go ahead and use the same recycler view layout file for the three fragments.
As for the adapters, your lists seem to be homogeneous (i.e., single kind of view item). Its best that you use 3 different adapters for each case. You can customise the constructor, add custom helper methods for each kind of adapter as per your convenience.
Now if you have a heterogeneous list, you will have to override getItemViewType()
in your adapter and use this appropriately in onCreateViewHolder()
and onBindViewHolder()
Hope this helps! :)
its too late but, might be helpful for someone needy developer Your adapter should look like this, also you can add header and footer using this sample
public class SampleAdapter extends RecyclerView.Adapter<SampleAdapter.ViewHolder> {
// Declaring Variable to Understand which View is being worked on
// IF the view under inflation and population is header or Item
private static final int TYPE_HEADER = 0;
private static final int TYPE_ITEM = 1;
private static final int TYPE_FOOTER = 2;
private Activity mContext;
private ArrayList<DataModel> _mItems;
private int mLayout;
private String mProductHeadingTitle="Heading";
private String mProductHeadingSubTitle="SubHeading";
private String loadingText="LOADING";
private int visibility= View.VISIBLE;
public interface SampleAdapterInterface {
void itemClicked(int position);
}
SampleAdapterInterface mCallBack;
public SampleAdapter(Activity context, ArrayList<DataModel> items, int item_layout) {
if (_mItems != null) {
_mItems.clear();
}
this.mContext = context;
this._mItems = items;
this.mLayout = item_layout;
mCallBack = (SampleAdapterInterface) context;
}
@Override
public int getItemCount() {
return _mItems.size()+2; // +2 for header and footer
}
@Override
public int getItemViewType(int position) {
if (position==0)
return TYPE_HEADER;
else if(position==(_mItems.size()+1))
return TYPE_FOOTER;
return TYPE_ITEM;
}
public void setHeaderData(String title,String subTitle)
{
this.mProductHeadingTitle=title;
this.mProductHeadingSubTitle=subTitle;
Log.d("LOG", "ProductHeadingTitle: " + mProductHeadingTitle);
Log.d("LOG", "ProductHeadingSubTitle: " + mProductHeadingSubTitle);
notifyDataSetChanged();
}
public void setFooterData(String loadingText,int visibility)
{
this.loadingText=loadingText;
this.visibility=visibility;
Log.d("LOG", "LoadingText: " + loadingText);
Log.d("LOG", "Visibility: " + visibility);
notifyDataSetChanged();
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == TYPE_HEADER)
{
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.header_layout,parent,false);
ViewHolder vhHeader = new ViewHolder(view,viewType);
return vhHeader;
}
else if (viewType == TYPE_ITEM)
{
View view = LayoutInflater.from(parent.getContext()).inflate(mLayout,parent,false);
//Creating ViewHolder and passing the object of type view
ViewHolder vhItem = new ViewHolder(view,viewType);
return vhItem;
}
else if (viewType == TYPE_FOOTER)
{
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.footer_lyout,parent,false);
ViewHolder vhFooter = new ViewHolder(view,viewType);
return vhFooter;
}
return null;
}
@Override
public void onBindViewHolder(ViewHolder viewHolder, int pos) {
if(viewHolder.Holderid ==0)
{
// header view
Log.d("LOG", "in header binder");
viewHolder.mProductCatalogTitle.setText(mProductHeadingTitle);
viewHolder.mProductCatalogSubTitle.setText(mProductHeadingSubTitle);
}
else if(viewHolder.Holderid==1)
{
final int position=pos-1; // -1 to substract header number
// your code
}
else if(viewHolder.Holderid==2)
{
// footer
Log.d("LOG", "in footer binder");
viewHolder.mProgressBar.setVisibility(visibility);
viewHolder.mLoading.setText(loadingText);
}
}
class ViewHolder extends RecyclerView.ViewHolder {
int Holderid;
// header
TextView mProductCatalogTitle;
TextView mProductCatalogSubTitle;
//list
// item type variable declaration
// footer
ProgressBar mProgressBar;
TextView mLoading;
public ViewHolder(View itemView, int viewType) {
super(itemView);
// Here we set the appropriate view in accordance with the the view type as passed when the holder object is created
if(viewType == TYPE_HEADER)
{
Holderid = 0;
mProductCatalogTitle = (TextView) itemView.findViewById(R.id.tv_title);
mProductCatalogSubTitle = (TextView) itemView.findViewById(R.id.tv_subtitle);
}
else if(viewType == TYPE_ITEM)
{
Holderid = 1;
itemView.setClickable(true);
itemView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mCallBack.itemClicked(getAdapterPosition()-1);
}
});
// initialize the view holder
}
else if(viewType == TYPE_FOOTER)
{
Holderid = 2;
mLoading = (TextView) itemView.findViewById(R.id.tv_loading);
mProgressBar = (ProgressBar) itemView.findViewById(R.id.progress_bar);
}
}
}
in your activity
// Adding to adapter as gridview when grid button clicked
private void setProductGridAdapter() {
mListViewTab.setVisibility(View.VISIBLE);
mGridViewTab.setVisibility(View.INVISIBLE);
mSampleAdapter = new SampleAdapter(YourActicity.this,
yourlist,R.layout.item_product_grid);
mRecyclerView.setAdapter(mSampleAdapter);
mRecyclerView.setHasFixedSize(true);
final GridLayoutManager layoutManager = new GridLayoutManager(getActivity(),
2, //number of columns
LinearLayoutManager.VERTICAL, // orientation
false); //reverse layout
mRecyclerView.setLayoutManager(layoutManager);
layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
// to show header and footer in full row
if(position==0 || position==(yourlist.size()+1))
return layoutManager.getSpanCount();
else
return 1;
}
});
mRecyclerView.scrollToPosition(AppController.currentPosition);
mSampleAdapter.notifyDataSetChanged();
// Scroll listener for RecyclerView to call load more products
mRecyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
int visibleItemCount = layoutManager.getChildCount();
int totalItemCount = layoutManager.getItemCount();
int firstVisibleItem = layoutManager.findFirstVisibleItemPosition();
int lastInScreen = firstVisibleItem + visibleItemCount;
if ((lastInScreen >= totalItemCount) && !isLoading) {
//last item
// do something after last item like load more code or
// show No more items string
mSampleAdapter.setFooterData("NO More Items",View.INVISIBLE);
}
AppController.currentPosition = firstVisibleItem;
}
});
}
// Adding to adapter as listview when list button clicked
private void setProductListAdapter() {
mListViewTab.setVisibility(View.INVISIBLE);
mGridViewTab.setVisibility(View.VISIBLE);
mSampleAdapter = new SampleAdapter(YourActicity.this, yourlist,R.layout.item_product_list);
mRecyclerView.setAdapter(mSampleAdapter);
mRecyclerView.setHasFixedSize(true);
final LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity(),
LinearLayoutManager.VERTICAL, // orientation
false); //reverse layout
mRecyclerView.setLayoutManager(layoutManager);
mRecyclerView.scrollToPosition(AppController.currentPosition);
mSampleAdapter.notifyDataSetChanged();
// Scroll listener for RecyclerView to call load more products
mRecyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
int visibleItemCount = layoutManager.getChildCount();
int totalItemCount = layoutManager.getItemCount();
int firstVisibleItem = layoutManager.findFirstVisibleItemPosition();
int lastInScreen = firstVisibleItem + visibleItemCount;
if ((lastInScreen >= totalItemCount) && !isLoading) {
//last item
// do something after last item like load more code or
// show No more items string
mSampleAdapter.setFooterData("NO MOre Items",View.INVISIBLE);
}
AppController.currentPosition = firstVisibleItem;
}
});
}
and your activity must implement SampleAdapterInterface in activity to get callback from adapter. Call those methods when toggling button from grid to list and vice versa