I am creating a list which contains, different types of views. Like facebook does in showing their feeds in Mobile Application.
Example Some times scrollview or some times list inside list.
To do this what will be a good choice.
What if i add a fragment in each item of recyclerview. Like
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<fragment
android:layout_width="match_parent"
android:name="com.profile.EditProfileFragment"
android:layout_height="wrap_content"/>
and add this as a item in Reclycleview and keep different logic inside it.
Can any one suggest me how to go through this.
I don't think it's going to be possible using Fragment as a row. Fragment has its own lifecycle which I don't lies within what RecyclerView can control.
But you can do it with simple view.
Just override several methods in RecyclerView.Adapter
private ArrayList<Data> items = new ArrayList<>(); // data associated
// with each row
#Override
public int getItemViewType(int position) {
return items.get(position).getRowType();
// assuming this is the getter to get the type
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case Data.TYPE_A:
View convertView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.row_feed, parent, false);
return new ViewHolderTypeA(convertView);
case Data.TYPE_B:
...
default:
return null;
}
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
Data data = items.get(position);
if (holder instanceof ViewHolderTypeA) {
ViewHolderTypeA holderA= (ViewHolderTypeA) holder;
// manipulate the views in view holder for this type
...
}else if (holder instanceof ViewHolderTypeB){
}
}
Edit: You don't need different Adapter for each item. You just need item that includes all your logic. So if you want to have two row types, one for status, one for photo like Facebook feed then your item will be:
class Data {
String status, photoUrl;
}
Of course some members will be empty depending on the row.
Related
I am creating a chatting application where in I need to populate a different Xml file based on the messageType in the chats page where all the conversation with a particular user is displayed. Example if the messageType is an image I want to populate a different xml and if the messageType is a video I want to populate a different xml file, and similarly for a text and audio a different xml file each time. However in my code, I have populated a single common xml in the onCreateViewHolder method. The messageType is retrieved from the model class.
Any help how to do this ?
Thanks in advance !!
You can do this by using different "item view type" values for each messageType. The root of this logic is the adapter's getItemViewType() callback:
#Override
public int getItemViewType(int position) {
if (isImage(position)) {
return R.layout.layout_for_image;
} else if (isVideo(position) {
return R.layout.layout_for_video;
} else {
// and so on...
}
}
We're using a trick here: getItemViewType() only cares that you return an int; it doesn't care what the actual int values are, so we're using the layout ids as the return value. This is nice because it means you don't have to define extra constants or keep track of which view type goes with 0 and which goes with 1, etc.
You also need to create a different kind of ViewHolder for each view type. The viewType parameter passed to this method will have whatever value you returned from getItemViewType(), and since we returned layout ids, we can just inflate whatever value we get. Of course, we still have to pass it to the right ViewHolder, but still it makes things a little easier:
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View itemView = inflater.inflate(viewType, parent, false);
switch (viewType) {
case R.layout.layout_for_image: return new ImageViewHolder(itemView);
case R.layout.layout_for_video: return new VideoViewHolder(itemView);
...
}
}
The last piece is binding:
#Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) {
switch (holder.getItemViewType()) {
case R.layout.layout_for_image:
bindImage(holder, position);
break;
case R.layout.layout_for_video:
bindVideo(holder, position);
break;
...
}
}
Override getViewType(int index) and return different values for object types, then you get it at onCreateViewHolder and inflate the correct view with different ViewHolders, the same type of viewholder will be returned at onBindViewHolder for equals viewTypes.
In my Listview this code works:
for (int number = 0; number < matchingContacts.size(); number++) {
//if a phone number is in our array of matching contacts
if (matchingContacts.contains(selectPhoneContact.getPhone()))
{
//if a matching contact, no need to show the Invite button
viewHolder.invite.setVisibility(View.GONE);
//once a matching contact is found, no need to keep looping x number of time, move onto next contact
break;
} else {
//if not a matching contact, no need to show the check box
viewHolder.check.setVisibility(View.GONE);
}
}
If a phone number is in the matching arraylist then it should make the invite button invisible, if it is not in the matching arraylist it should make the checkbox invisible.
But not in my recyclerview, in which I am trying to make the code work.
On first load it looks ok but as soon as you start to scroll the views get messed up - checkboxes and buttons appear where they are not supposed to.
I've read that in Recyclerview you are supposed to implement this with case statements, and I've looked here Why RecyclerView items disappear with scrolling and here How to create RecyclerView with multiple view type? but for the life of me I cannot get it to work!
Can you help?
Here is my code:
public class PopulistoContactsAdapter extends RecyclerView.Adapter<PopulistoContactsAdapter.ViewHolder> {
//make a List containing info about SelectPhoneContact objects
public List<SelectPhoneContact> theContactsList;
Context context_type;
ArrayList<String> matchingContacts = new ArrayList<String>();
public static class ViewHolder extends RecyclerView.ViewHolder {
//In each recycler_blueprint show the items you want to have appearing
public TextView title, phone;
public CheckBox check;
public Button invite;
public ViewHolder(final View itemView) {
super(itemView);
//title is cast to the name id, in recycler_blueprint,
//phone is cast to the id called no etc
title = (TextView) itemView.findViewById(R.id.name);
phone = (TextView) itemView.findViewById(R.id.no);
invite = (Button) itemView.findViewById(R.id.btnInvite);
check = (CheckBox) itemView.findViewById(R.id.checkBoxContact);
}
}
public PopulistoContactsAdapter(List<SelectPhoneContact> selectPhoneContacts, Context context, int activity) {
theContactsList = selectPhoneContacts;
context_type = context;
matchingContacts.add("+3531234567");
matchingContacts.add("+3536789012");
matchingContacts.add("+3530987654");
matchingContacts.add("+3538765432");
}
#Override
public PopulistoContactsAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
Context context = parent.getContext();
LayoutInflater inflater = LayoutInflater.from(context);
View contactView = inflater.inflate(R.layout.recycler_blueprint, parent, false);
ViewHolder viewHolder = new ViewHolder(contactView);
return viewHolder;
}
#Override
public void onBindViewHolder(final PopulistoContactsAdapter.ViewHolder viewHolder, final int position) {
//bind the views into the ViewHolder
//selectPhoneContact is an instance of the SelectPhoneContact class.
//We will assign each row of the recyclerview to contain details of selectPhoneContact:
//The number of rows will match the number of contacts in our contacts list
final SelectPhoneContact selectPhoneContact = theContactsList.get(position);
//a text view for the name, set it to the matching selectPhoneContact
TextView title = viewHolder.title;
title.setText(selectPhoneContact.getName());
//a text view for the number, set it to the matching selectPhoneContact
TextView phone = viewHolder.phone;
phone.setText(selectPhoneContact.getPhone());
Button invite = viewHolder.invite;
CheckBox check = viewHolder.check;
for (int number = 0; number < matchingContacts.size(); number++) {
//if a phone number is in our array of matching contacts
if (matchingContacts.contains(selectPhoneContact.getPhone()))
{
//if a matching contact, no need to show the Invite button
viewHolder.invite.setVisibility(View.GONE);
//once a matching contact is found, no need to keep looping x number of time, move onto next contact
break;
} else {
//if not a matching contact, no need to show the check box
viewHolder.check.setVisibility(View.GONE);
}
}
}
#Override
public int getItemCount() {
return theContactsList.size();
}
}
Method 1: Actually in you case, using multiple View types is not necessary. This might be an easier way. Instead, I would recommend you amend your SelectPhoneContact class to include a simple boolean field (maybe called isMatching) that flags whether or not this phone number is a matching contact. You can then create a simple setter and getter methods like setIsMatchingContact(boolean) and isMatchingContact() to update/read this flag.
When you're initializing the list and adding the SelectPhoneContact, do the pre-processing of determining which instances belong or don't belong in the matching contacts by setting the setIsMatchingContact(boolean) method. In your onBindViewHolder method, rather than iterating over matchingContacts, you just check selectPhoneContact.isMatchingContact() and change the visibility accordingly. This is also more efficient as you don't have to perform a potentially expensive operation of iterating through a large list in a bind method which could cause the scrolling to stutter and have issues.
If your matchingContacts list changes throughout time, you could always write a method that iterates through the SelectPhoneContact list and resets the isMatching boolean.
Method 2: If you rather not extend the your SelectPhoneContact class to have those two methods I mentioned, you could alternatively create a private static wrapper class like this:
private static class SelectPhoneContactItem {
SelectPhoneContact selectPhoneContact;
boolean isMatching;
}
Then use this as the primary list in your adapter:
public List<SelectPhoneContactItem> theContactsList;
Like Method 1, you should the pre-processing of the figuring out which SelectPhoneContactItem is in the matchingContacts and assign the isMatching boolean as needed.
You should probably go with Method 1 unless there are some design constraints. Let me know if my answer makes sense, hope this helps!
RecyclerView can handle multiple view types with different view holders.
First of all you have to override the getItemViewType(int position) method on your adapter which will return type of the object according its position. Then create view holder class for each view type.
Handle onCreateViewHolder(ViewGroup parent, int viewType) method considering the view type:
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == YOUR_FIRST_TYPE) {
//inflate first type of view
return new FirstTypeViewHolder(view);
} else if (viewType == YOUR_SECOND_TYPE) {
//inflate second type of view
return new SecondTypeViewHolder(view);
}
}
Handle onBindViewHolder(ViewHolder viewHolder, int position) considering the view type:
#Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
int viewType = getItemViewType(position);
if (viewType == YOUR_FIRST_TYPE) {
FirstTypeViewHolder firstTypeViewHolder = (FirstTypeViewHolder) viewHolder;
//do your stuff
} else if (viewType == YOUR_SECOND_TYPE) {
SecondTypeViewHolder secondTypeViewHolder = (SecondTypeViewHolder) viewHolder;
//do your stuff
}
}
You can take a look on this tutorial.
I'm trying to implement an EmptyView on my RecyclerView Adapter but I'm not getting any result.
I've followed this tutorial and this tip, but noone worked for me.
I've implemented:
if (viewType == EMPTY_VIEW) {
v = LayoutInflater.from(parent.getContext()).inflate(R.layout.empty_view, parent, false);
EmptyViewHolder evh = new EmptyViewHolder(v);
return evh;
}
v = LayoutInflater.from(parent.getContext()).inflate(R.layout.data_row, parent, false);
ViewHolder vh = new ViewHolder(v);
return vh;
But it doesn't let me compile because they are differents ViewHolder, because I've created two ViewHolder classes but they extends Recycler.ViewHolder so I don't get it...
I'm trying to do this because I've got a SearchView and I want when the list is empty it shows an EmptyView, I've got it doing it programmatically but I prefer to add like a layout because I don't know that much how to put TextViews and Buttons programmatically.
Also if I put
return dataList.size() > 0 ? dataList.size() : 1;
It gives to me error because index is 0.
I've debugged the viewType and always is 1, then it won't join the if condition...
Deep on Android I found this :
/**
* Return the view type of the item at <code>position</code> for the purposes
* of view recycling.
*
* <p>The default implementation of this method returns 0, making the assumption of
* a single view type for the adapter. Unlike ListView adapters, types need not
* be contiguous. Consider using id resources to uniquely identify item view types.
*
* #param position position to query
* #return integer value identifying the type of the view needed to represent the item at
* <code>position</code>. Type codes need not be contiguous.
*/
public int getItemViewType(int position) {
return 0;
}
But the thing is that no changes the value.
EDIT
I almost done it, I did this :
#Override
public int getItemViewType(int position) {
return list.size() > 0 ? list.size() : 1;
}
But sometimes it returns 0 when the size() is 0... I don't get it, I'm using this SearchView, and sometimes when I type a letter that doesn't matches with any item of the list it doesn't show and sometimes it does...
Also other thing that happens is that when the layout popups it shows on the left of the screen when I put that is on center, but I think it's problem with RecyclerView because the layout puts inside of it.
RecyclerView layout :
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
android:id="#+id/rtpew"
android:layout_centerInParent="true"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
>
<LinearLayout android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/linearpew">
<android.support.v7.widget.RecyclerView
android:id="#+id/rv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>
</RelativeLayout>
And this is my emptylayout :
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/ImageViewSearchFail"
android:src="#drawable/sadface"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:layout_gravity="center"
android:textSize="#dimen/15dp"
android:layout_marginTop="4dp"
android:text="foo"
android:layout_below="#+id/ImageViewSearchFail"
android:layout_centerHorizontal="true" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/ButtonAddEntity"
android:text="foo"
android:background="?android:selectableItemBackground"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true" />
</RelativeLayout>
The other way that I thought is to implement it programmatically as follow :
#Override
public boolean onQueryTextChange(String query) {
final ArrayList<List> filteredModelList = filter(mModel, query);
mAdapter.animateTo(filteredModelList);
rv.scrollToPosition(0);
if(query.isEmpty()){
//Here
}
return true;
}
And :
private ArrayList<List> filter(ArrayList<List> models, String query) {
query = query.toLowerCase();
final ArrayList<List> filteredModelList = new ArrayList<List>();
for (List model : models) {
final String text = model.getRedName().toLowerCase();
if (text.contains(query)) {
filteredModelList.add(model);
}
}
if (filteredModelList.size()<0) {
//HERE
}
else{
//Delete the views added
}
return filteredModelList;
}
PROBLEMS
-I only add the view using the #Jimeux answer but I'd like to do this on the Adapter, I got it, but not always shows the view even if the list is empty.
-At the time to put the emptyview.xml it puts inside of the RecyclerView then since I've put all of this xml at the center it shows on the right. I've tried to add the xml programmatically but it's like a chaos....
Since you need to handle two different kind of views, it would be easier to use an intermediate list of business object for more easily binding them with views. Idea is to have a kind of placeholder in your list for representing empty state. Defining an intermediate layer is extremely useful in this sense for allowing you to consider eventual changes to be applied to your list in future (e.g. adding you element types). Moreover in this way you can more clearly separate your business model from ui representation (for example you can implement methods returning ui settings based on internal status of model objects).
You can proceed as follows:
Define a dedicated abstract type for List items (e.g. ListItem) to wrap your business objects. Its implementation could be something like this:
public abstract class ListItem {
public static final int TYPE_EMPTY = 0;
public static final int TYPE_MY_OBJ = 1;
abstract public int getType();
}
Define a class for each of your List element type:
public class EmptyItem extends ListItem {
#Override
public int getType() {
return TYPE_EMPTY;
}
}
public class MyObjItem extends ListItem {
private MyObj obj;
public ContactItem(MyObj obj) {
this.obj = obj;
}
public MyObj getMyObj() {
return obj;
}
// here you can also add methods for simplify
// objects rendering (e.g. get background color
// based on your object internal status)
#Override
public int getType() {
return TYPE_MY_OBJ;
}
}
Create your list.
List<ListItem> mItems = new ArrayList<>();
if (dataList != null && dataList.size() > 0) {
for (MyObj obj : dataList) {
mItems.add(new MyObjItem(obj));
}
} else {
mItems.add(new EmptyItem());
}
This is the most important part of code. You have many options for creating this list. You can do it inside your RecyclerView Adapter or outside, but it's extremely important to properly handle eventual modifications to it. This is essential for exploiting Adapter notify methods. For example, if you create list within the Adapter, it should probably provide also methods for adding or removing your model items. For example:
public void addObj(MyObj obj) {
if (mItems.size() == 1 && mItems.get(0).getType() == ListItem.EMPTY_TYPE) {
mItems.clear();
}
mItems.add(new MyObjItem(obj));
notifyDataSetChanged();
}
Define an adapter for your RecyclerView, working on List defined at point 3. Here what is important is to override getItemViewType method as follows:
#Override
public int getItemViewType(int position) {
return mItems.get(position).getType();
}
Moreover, type of ViewHolder should be RecyclerView.ViewHolder (unless you decide to create an intermediate class even in this case).
Then you need to have two layouts and ViewHolder for empty and business obj items. Adapter methods should take care of this accordingly:
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == ListItem.TYPE_EMPTY) {
View itemView = mLayoutInflater.inflate(R.layout.empty_layout, parent, false);
return new EmptyViewHolder(itemView);
} else {
View itemView = mLayoutInflater.inflate(R.layout.myobj_layout, parent, false);
return new MyObjViewHolder(itemView);
}
}
#Override
public void onBindViewHolder(final RecyclerView.ViewHolder viewHolder, final int position) {
int type = getItemViewType(position);
if (type == ListItem.TYPE_EMPTY) {
EmptyItem header = (EmptyItem) mItems.get(position);
EmptyViewHolder holder = (EmptyViewHolder) viewHolder;
// your logic here... probably nothing to do since it's empty
} else {
MyObjItem event = (MyObjItem) mItems.get(position);
MyObjViewHolder holder = (MyObjViewHolder) viewHolder;
// your logic here
}
}
Of course, as I wrote at the beginning you don't need to strictly define intermediate types for ui representation (EmptyItem and MyObjItem). You can even just use MyObj type and create a specific configuration for it that represent an empty placeholder. This approach is probably not the best in case in future you need to make your logic more complex by including for example new list item types.
Follow the below steps one by one
1). Since you have two types of views for your RecyclerView item, your adapter declaration should look like this a generic one
public class YourAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
and your ViewHolders for both listview item and empty view should extend RecyclerView.ViewHolder like this
static class ListItemViewHolder extends RecyclerView.ViewHolder {
public ListItemViewHolder(View itemView) {
super(itemView);
// initialize your views here for list items
}
}
static class EmptyViewViewHolder extends RecyclerView.ViewHolder {
public EmptyViewViewHolder(View itemView) {
super(itemView);
// initialize your views here for empty list
}
}
2). You have to Override getItemCount() and getItemViewType()
#Override
public int getItemCount() {
return yourList.size() > 0 ? yourList.size() : 1;// if size of your list is greater than 0, you will return your size of list otherwise 1 for the empty view.
}
#Override
public int getItemViewType(int position) {
if (yourList.size() == 0) {
return VIEW_TYPE_EMPTY;
}
return position;
}
3). Your onCreateViewHolder() will look alike this now
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == VIEW_TYPE_EMPTY) {
return new EmptyViewViewHolder(mLayoutInflater
.inflate(R.layout.empty_view_layout, parent, false));
} else {
return new ListItemViewHolder(mLayoutInflater
.inflate(R.layout.row_list_item, parent, false));
}
}
4). Same check you have to apply in your onBindViewHolder() as well
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (getItemViewType(position) == VIEW_TYPE_EMPTY) {
EmptyViewViewHolder emptyViewViewHolder = (EmptyViewViewHolder) holder;
// set values for your empty views
} else {
ListItemViewHolder listItemViewHolder = (ListItemViewHolder) holder;
// set values for your list items
}
}
5). At last Override your SearcView.setOnQueryTextListener()
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
#Override
public boolean onQueryTextSubmit(String query) {
return false;
}
#Override
public boolean onQueryTextChange(String newText) {
currentSearchKeyword = newText.trim();
if(currentSearchKeyword.iseEmpty()){
yourList.clear();
yourAdapter.notifyDataSetChanged();
}else{
// there will two cases again 1). If your currentSearchKeyword matchces with list results, add that items to your list and notify your adapter. 2) If the currentSearchKeyword doesn't matched with list results, clear your list and notify your adapter;
}
return false;
}
});
Hope it will help you, let me know if any issues.
The compilation error probably results because of you extending RecyclerView.Adapter with your main ViewHolder as the generic argument.
You should make it like
YourAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
And then cast your ViewHolders appropriately (you can reuse getViewType(position) here). Be sure to switch the ViewHolder type in your methods as well.
If I were you, I wouldn't put the empty view in the adapter at all. Put it under your linearpew layout that's holding the RecyclerView and hide/show it as your data changes. You can easily add a loading view, error view, etc. with this setup too.
Here's a bit of simplified code from one of my apps to give you some ideas. #Bind comes from Butter Knife if you're not familiar with it. You may also want to check out Jake Wharton's u2020 project for more RecyclerView ideas.
//fragment_layout.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:id="#+id/content">
</FrameLayout>
<include layout="#layout/status_views" />
</RelativeLayout>
//status_views.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<LinearLayout style="#style/ListStatusView"
android:id="#+id/empty_view"/>
<LinearLayout style="#style/ListStatusView"
android:id="#+id/error_view"/>
<LinearLayout style="#style/ListStatusView"
android:id="#+id/loading_view"
android:padding="30dp"/>
</LinearLayout>
//MyFragment.java
#Bind(R.id.content) protected ViewGroup contentView;
#Bind(R.id.loading_view) protected ViewGroup loadingView;
#Bind(R.id.empty_view) protected ViewGroup emptyView;
#Bind(R.id.error_view) protected ViewGroup errorView;
#Bind({R.id.loading_view, R.id.error_view, R.id.empty_view, R.id.content})
protected List<ViewGroup> stateViews;
protected void activateView(View view) {
for (ViewGroup vg : stateViews)
vg.setVisibility(View.GONE);
view.setVisibility(VISIBLE);
}
#Override
public void onActivityCreated(#Nullable Bundle state) {
super.onActivityCreated(state);
if (state == null) {
activateView(loadingView);
loadData();
} else if (data.isEmpty())
activateView(emptyView);
else
activateView(contentView);
}
Edit: Here's a simplified version without Butter Knife.
private ViewGroup contentView;
private ViewGroup emptyView;
#Override
protected void onCreate(#Nullable Bundle state) {
super.onCreate(state);
setContentView(R.layout.activity_main);
contentView = (ViewGroup) findViewById(R.id.content_view);
emptyView = (ViewGroup) findViewById(R.id.empty_view);
}
#Override
public boolean onQueryTextChange(String query) {
final ArrayList<List> filteredModelList = filter(mModel, query);
mAdapter.animateTo(filteredModelList);
rv.scrollToPosition(0);
if(query.isEmpty()){
contentView.setVisibility(View.GONE);
emptyView.setVisibility(View.VISIBLE);
} else {
contentView.setVisibility(View.VISIBLE);
emptyView.setVisibility(View.GONE);
}
return true;
}
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/rtpew"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true">
<LinearLayout
android:id="#+id/content_view"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.RecyclerView
android:id="#+id/rv"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
<RelativeLayout android:id="#+id/empty_view">
<ImageView android:src="#drawable/sadface"/>
<TextView android:text="foo"/>
<Button android:id="#+id/ButtonAddEntity"/>
</RelativeLayout>
</RelativeLayout>
Here is what you can try:
1. Replace
EmptyViewHolder evh = new EmptyViewHolder(v);
with
RecyclerView.ViewHolder evh = new EmptyViewHolder(v);
This is probably why the compilation fails.
2. Replace
#Override
public int getItemViewType(int position) {
return list.size() > 0 ? list.size() : 1;
}
with
#Override
public int getItemViewType(int position) {
return list.get(position) != null ? 1 : 0;
}
For this to work, you must insert a null object whenever you want to show an EmptyView:
int progressPosition = list.size();
list.add(null);
adapter.notifyItemInserted(progressPosition);
and remove the null object when you want to hide the EmptyView:
int progressPosition = existingList.size() - 1;
existingList.remove(progressPosition);
adapter.notifyItemRemoved(progressPosition);
Also, you must modify your onCreateViewHolder() method as follows:
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == 1) {
// inflate your default ViewHolder here ...
} else {
// inflate the EmptyViewHolder here
}
}
I believe we have discussed this before ... see this question for a detailed discussion on this.
3. Instead of using a SearchView, consider using an AutoCompleteTextView with a Filter. This may be easier to integrate with your RecyclerView's Adapter. See this answer for an example of this.
I will update this answer as I understand your question better ... do try this and update me.
I need to add a small strip in between items of a RecyclerView. This strip can come after different number of items in a list. This needs to be done dynamically.
I need to implement something like what FitBit has done:
I also need the first row i.e. the one saying "This Week" to stick on top even if the page scrolls down.
You should use the concept of different view types using getItemViewType(int). Then on onCreateViewHolder(ViewGroup, int) you can check which type you should inflate/create.
Example:
#Override
public int getItemViewType(int position) {
// you should return the view type, based on your own dynamic logic
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
// handle each view type accordingly
}
}
Use StickyHeaderRecyclerView library
It is very easy to use
You can use the concept of multiple view types in your RecyclerView, Just by using getItemViewType(), and take care of the viewType parameter in onCreateViewHolder().
For example you can use below model:
public class Data{
int field1;
float filed2;
int rowType // 1,2,2,...N this will fill by you whenever you will
//creating arraylist for your recyclerview
}
public class Custome Adapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
ArrayList<Data> mItems;
class ViewHolderRowType1 extends RecyclerView.ViewHolder {
...
}
class ViewHolderRowType2 extends RecyclerView.ViewHolder {
...
}
....
class ViewHolderRowTypeN extends RecyclerView.ViewHolder {
...
}
#Override
public int getItemViewType(int position) {
return mItems.get(position).rowType;
//or
//return positon%2; // This will based on your condition
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case 0: return new ViewHolderRowType0(...);
case 1: return new ViewHolderRowType1(...);
...
case N: return new ViewHolderRowTypeN(...);
}
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder vh, int position) {
//Just check which view type is going to bind and then fill the data accordingly in your rows
if(vh instanceof ViewHolderRowType1){
// Fill the data for first view type
} else if (vh instanceof ViewHolderRowType2) {
// Fill the data for second view type
} else if (vh instanceof ViewHolderRowTypeN){
// Fill the data for Nth view type
}
}
For your sticky "this view weak", you can add it at top of your RecyclerView and then handle it by scroll Event of RecyclerView
There are two ways to implement such RecyclerView
Add header in every layout and hide/show based on your requirement(preferable).
Or use two different layouts for header and content(not preferable because it can cause problem in total count of items in adapter).
In your custom POJO / GetterSetter class, add one field for headerStatus(either boolean or int), to identify whether to show header or not.
Now in adapter override public int getItemViewType(int position).
static final int TYPE_ITEM = 0;
static final int TYPE_SEPARATOR = 1;
#Override
public int getItemViewType(int position) {
if (mData.get(position).getHeaderStatus() == 0)
return TYPE_ITEM;
else
return TYPE_SEPARATOR;
}
Now at the time of inflating the layout in getView() you can check the row type by
int rowType = getItemViewType(position);
For case 1, you need to visible the header and set appropriate data in it.
For case 2, you need to inflate that header layout and add appropriate data in it.
If you want to do it in "proper" way, without hacks, you should write your own LayoutManager, and handle those cases by hands. It is not as hard as it sounds, but will take some efforts.
I am having a gridView and the data in the gridView is coming from server.
Now I am having some views in it that will show for some rows and will not show for some rows depends on the sever conditions.
Ex : I am having a LinearLayout which is having an imageView and 2 TextViews, this layout will be visible only for some rows based on server data.
First time it is coming fine but as I scroll down/up, the view of the rows get change.
For Ex: Like in the first row If I am not having this LinearLayout and in 2nd or 3rd row this layout is visible, the when I scroll down and then again scroll up, the first row also get that Layout exact same as the last scrolled position.
I am using Holder pattern, can you please help me here, I am stuck here.
Thank you so much in advanced.
The views are stateless so if you show the linearlayout on someviews you need to remember to hide it for the others.
onBindViewHolder will not give you a fresh view from xml but the view you mutated. Basically just remember to set the LinearLayout back to gone.
A better way would be to use multiple xml files and implement getItemViewType showing and hiding views can cause the scroll to gitter, although if heights remain the same you might get away with it.
public class ExampleAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
List<ContactsContract.Data> data;
private static final int TYPE_A = 0;
private static final int TYPE_B = 1;
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
RecyclerView.ViewHolder viewHolder;
if(viewType == TYPE_A) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.xml_a, parent, false);
viewHolder = new RecyclerView.ViewHolder(view);
} else {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.xml_b, parent, false);
viewHolder = new RecyclerView.ViewHolder(view);
}
return viewHolder;
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
holder.setData(data.get(position);
}
#Override
public int getItemViewType(int position) {
if(data.get(position).youCondition()) {
return TYPE_A;
} else {
return TYPE_B;
}
}
#Override
public int getItemCount() {
return data.size();
}
}
This is a basic example of how it could be done. Will need to implement your own ViewHolders i'd suggest making a different one for each view type from a base class that has the set data method.