I want to remove textview from specific items in recycler view. I wrote the below code to do that
#Override
public void onBindViewHolder(#NonNull final Myholder holder, final int possion) {
final String n = names.get(possion);
if(possion==3){
holder.textView.setVisibility(View.GONE);}}
but this changes all items and i want to make gone only position number 3 item view.
this is my view holder
public static class Myholder extends RecyclerView.ViewHolder {
TextView textView;
public Myholder(#NonNull View itemView) {
super(itemView);
textView = itemView.findViewById(R.id.textfolder);
}
}
what should i do?
Always remember that RecyclerView recycles view holders. That is, the same ViewHolder instance will be re-used for different views. This means that it is almost always a bad idea to have an if statement that modifies a view withouth having a corresponding else.
So, try this instead:
if (possion == 3) {
holder.textView.setVisibility(View.GONE);
} else {
holder.textView.setVisibility(View.VISIBLE);
}
Note also that just checking the position argument is not necessarily a good idea. If you use notifyItemInserted() or notifyItemRemoved(), this can lead to problems.
It would be better to set something about the item at that position to indicate that its text should not be shown.
Related
My problem is: I have a video streaming happening on one of the views inside the RecyclerView.
When the user scrolls, the view gets recycled and other cameras starts their own streaming on that recycled viewholder. This is bad for user interface since the streaming process takes some seconds to start.
How can I say to the RecyclerView: "Hey Recycler, please, do not recycle that exact position x and give that position ALWAYS the same viewholder you gave it the first time, instead of random one"?
Please someone help me =(
In your getItemViewType(int position) method of adapter, assign unique values for each video, so it will always return same ViewHolder for same video as you wish.
return unique positive number as type for each video type (here i used the adapter position as unique key)
return negative numbers for any non-video items. (nothing special here, just to avoid conflicts with video items, we use negative numbers for non-video items)
I hope you get the idea. cheers :)
#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
if(dataList.get(position).isVideo()){
return position;
}else{
return -1;//indicates general type, if you have more types other than video, you can use -1,-2,-3 and so on.
}
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case -1: View view1 = LayoutInflater.from(parent.getContext())
.inflate(R.layout.general_item, parent, false);
return new GeneralViewHolder(view1);
default:View view2 = LayoutInflater.from(parent.getContext())
.inflate(R.layout.video_item, parent, false);
return new VideoViewHolder(view2);
}
}
Perform viewHolder.setIsRecyclable(false) on the ViewHolder you want not to be recycled.
From docs of ViewHolder#setIsRecyclable(boolean):
Informs the recycler whether this item can be recycled. Views which are not recyclable will not be reused for other items until setIsRecyclable() is later set to true.
This will cause only one ViewHolder to be created.
public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
...
#Override
public void onViewAttachedToWindow(final RecyclerView.ViewHolder holder) {
if (holder instanceof VideoViewHolder) {
holder.setIsRecyclable(false);
}
super.onViewAttachedToWindow(holder);
}
#Override
public void onViewDetachedFromWindow(final RecyclerView.ViewHolder holder) {
if (holder instanceof VideoViewHolder){
holder.setIsRecyclable(true);
}
super.onViewDetachedFromWindow(holder);
}
...
}
RecyclerView uses one view multiple times, when it contains the list which is not displaying on the screen at a time(means a list contain large amount of items which is not displaying on screen at same time you need to scroll up and down). When user scroll the list the offscreen items are reused to display the remaining list items which is called recycling.
To Stop recycling the items call this method in your onBindViewHolder method:
viewHolder.setIsRecyclable(false);
This statement stop the recycling the views.
To Start recycling the items call this method in your onBindViewHolder method:
viewHolder.setIsRecyclable(true);
I hope this will solve your problem.
Thanks
Your problem comes from the viewholder itself. Viewholders keep reference to views, while the adapter don't. The adapter keeps the data collection only. So, add a field to the viewholder to keep a reference of the data element you used to populate the view in the viewholder. In other words:
public class SomeViewHolder extends RecyclerView.ViewHolder{
private View view;
private Data data;
public SomeViewHolder(View itemView) {
super(itemView);
view = itemView;
}
public void bindData(Data data){
view.setData(data);
this.data = data;
}
public void setData(Data data){
this.data = data;
}
public Data getData(){
return data;
}
public View getView(){
return view;
}
}
Now, the viewholder know which element of the adapter is using. Therefore, when overriding the binding method in the adapter, you can check if the holder has already bonded with some data, and, if the data contains video, you can avoid the binding and forcefully set an already loaded view.
#Override
public void onBindViewHolder(SomeViewHolder holder, int position) {
//videoViewData is a data field you have to put into the adapter.
//videoView is a view field you have to put into the adapter.
if(adapterData.get(position).equals(videoViewData)){
holder.setView(videoView);
holder.setData(adapterData.get(position));
}else{
holder.bindData(adapterData.get(position));
if(adapterData.get(position).isVideo()){
videoViewData = adapterData.get(position);
videoView = holder.getView();
}
}
}
Finally, you'll have to override the onViewRecycled method in the adapter, so, when a view containing a video gets recycled, you can get the view and put it somewhere else.
public void onViewRecycled(SomeViewHolder holder){
if(holder.getData().isVideo()){
videoViewData = holder.getData().
videoView = holder.getView();
videoView.pauseVideo();
}
}
keep in mind, this can cause some serious leaks if you don't manage the stored view. Also, you have to define methods for telling when your data is video, and a properly defined equals method.
Best way to handle item not to recycle in recyclerview this answer will resolve your problem.
Not to recycle item
Try using this for that particular position:
holder.setIsRecyclable(false);
Hope this may help.
If You are using query, you can use
query.limit(//no of items you want to show in your RecyclerView)
give it a try.
or Plese post your QueryCode
in my app, I'm implementing a recyclerview. My dataset for this recyclerview will have varying sizes according to the options that I set for the data to be displayed on the recyclerview.
One of the actions that I take with my recyclerview is to "expand" an item when a click is done on it, displaying further options in it. When pressing on this "expanded" item, I perform the action of "closing" it. Also, there can only be on "expanded" item at maximum at any moment.
The thing is that I understand that recyclerview recycles its row-views when they get out of sight for improved performance. However, because I am trying to have only one "expanded" item at a time, this recycling messes it up quite a lot.
What happens right now is that when I "expand", say the item related to position 1 of my dataset, as shown in the image below.
When I scroll down, I will see that the rowview for this item being recycled at a random chance since I will see this "expanded" view on items that I have not set to be "expanded", as shown in the image below.
And of course, when this happens, then when I scroll back to the item that I have selected to "expand", it will be "closed" as you would have expected.
So I have been thinking that I could resolve this problem by setting the possible number of views to be something like 80% of my dataset size will decrease the possibility of this problem occurring while still reduced, but enjoy some improved performance.
Another solution I thought about was disabling this "expanded" view from being recycled for other views and when this "expanded" item's position comes into screen, it gets bounded to this specific view. I thought of this solution after seeing that there is a concept of "scrap" and "recycle" for recyclerview, but I am not so sure if this method is even possible because I think I have only vaguely understood this side of recyclerview.
That being said, my question is are there ways for me to set the number of views to be recycled for a recycled view? Or even better, having one view from being recycled for items other than the "expanded" item?
Thanks in advance.
EDIT:
here's my (I know it's very messy I'm sorry...) code for my adapter:
public class DrinkMenuItem extends RecyclerView.Adapter<DrinkMenuItem.ViewHolder> {
private Context context;
private ViewGroup parent;
private ArrayList<Drink> menu;
private ArrayList<DrinkSelected> selected;
private DrinkMenuBasketItem selectedAdapter;
public int expanded = -1;
public boolean expandedVisible = false;
private DrinkMenuDropdownItem dropdownAdapter;
public static class ViewHolder extends RecyclerView.ViewHolder {
public RelativeLayout layout;
public TextView name, price;
public ListView dropdown;
public RelativeLayout basket;
public boolean tabbed = false;
public ViewHolder(View itemView) {
super(itemView);
layout = (RelativeLayout)itemView.findViewById(R.id.drink_menu_layout);
name = (TextView)itemView.findViewById(R.id.drink_menu_name);
price = (TextView)itemView.findViewById(R.id.drink_menu_price);
dropdown = (ListView)itemView.findViewById(R.id.drink_menu_dropdown_list);
basket = (RelativeLayout)itemView.findViewById(R.id.drink_menu_basket_button);
}
}
public DrinkMenuItem(Context context, ArrayList<Drink> menu, ArrayList<DrinkSelected> selected, DrinkMenuBasketItem selectedAdapter) {
this.context = context;
this.menu = menu;
this.selected = selected;
this.selectedAdapter = selectedAdapter;
this.dropdownAdapter = null;
}
public void updateDropdown(int requestedOption, int responsedOptionitem) {
dropdownAdapter.updateSelectedOption(requestedOption, responsedOptionitem);
notifyDataSetChanged();
}
// Create new views (invoked by the layout manager)
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
this.parent = parent;
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_drink_menu, parent, false);
return new ViewHolder(itemView);
}
// Replace the contents of a view (invoked by the layout manager)
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
Drink d = menu.get(position);
holder.name.setText(d.getName());
holder.price.setText(d.getPrice() + d.totalAdditionalPrice() + "원");
if(position == expanded) {
//delete dropdown
holder.dropdown.setAdapter(null);
menu.get(position).returnToUnselected();
holder.price.setText(menu.get(position).getPrice() + "원");
setListViewHeight(holder.dropdown);
//reset dropdown-related stuff
holder.tabbed = false;
holder.basket.setVisibility(View.GONE);
}
setOnClickEvent(holder, position, parent);
}
private void setOnClickEvent(final ViewHolder holder, final int position, final ViewGroup parent) {
holder.layout.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (!holder.tabbed) {
//close dropdown of expanded view
if(expanded != -1) notifyItemChanged(expanded);
//make dropdown
dropdownAdapter = new DrinkMenuDropdownItem(context, menu, position, holder.price);
holder.dropdown.setAdapter(dropdownAdapter);
setListViewHeight(holder.dropdown);
//set dropdown-related stuff
holder.tabbed = true;
holder.basket.setVisibility(View.VISIBLE);
expanded = position;
expandedVisible = true;
((RecyclerView) parent).smoothScrollToPosition(position);
} else {
//delete dropdown
holder.dropdown.setAdapter(null);
menu.get(position).returnToUnselected();
holder.price.setText(menu.get(position).getPrice() + "원");
setListViewHeight(holder.dropdown);
expanded = -1;
expandedVisible = false;
//reset dropdown-related stuff
holder.tabbed = false;
holder.basket.setVisibility(View.GONE);
}
}
});
...
}
}
I have exactly same issue in my project. I did not succeed solving it with recyclerView. But the solution would be one of the following:
Create an expandableListView instead of recyclerView and everything will work great.
Create a ScrollView, and put a LinearLayout with android:orientation="vertical". Then, create a loop and insert all your custom views, and set click listener where you wish to expand.
Use an Expandable RecyclerView Library like one of these:
https://github.com/h6ah4i/android-advancedrecyclerview
https://github.com/bignerdranch/expandable-recycler-view
I'm using a custom CompoundView which extends LinearLayout to display items of a RecyclerView. Each item displays an article which contains multiple paragraphs and images. The CompoundView adds TextView or ImageView dynamically based on the data attached by CompoundView.setData(List<DataPiece> pieces), the number of which is unknown before data is attached. Each DataPiece object tells CompoundView whether it's a piece of text or an image. And here is the code for CompoundView.setData(List<DataPiece> pieces):
public void setData(List<DataPiece> pieces) {
removeAllViews();
for (DataPiece dataPiece : pieces) {
switch (dataPiece.getType()) {
case IMAGE:
ImageView imageView = new ImageView(getContext());
...
addView(imageView);
break;
case TEXT:
TextView textView = new TextView(getContext());
...
addView(textView);
break;
}
}
}
In the RecyclerView.Adapter.onBindViewHolder(), the data is attached to CompoundView by calling MyViewHolder.compoundView.setData(...). And it works fine when the RecyclerView is created.
However, for a CompoundView item with multiple ImageViews and TextViews, when I scroll away from it and then scroll back, the scroll becomes heavily unsmooth.
I guess it's because removeAllViews() in setData() is called, and the CompoundView creation for-loop is executed again by the recycler. But I don't know how to avoid this.
And I also wonder why the scroll is always smooth when using TextView(with Images) in a RecyclerView even it's recycled too.
Thanks in advance!
There are multiple considerations that could go into deciding what the best approach might be.
First, do you have an idea about the maximum number of items in the recycler's list? If it is just a handful, maybe you could ditch the RecyclerView approach and just add your CompoundView into a container hosted by a ScrollView.
Secondly - is the layout of each item fairly complicated (a.k.a. are there many TextViews, ImageViews etc. in it)? If yes, maybe you could take an approach that would resemble an ExpandableListView - show a summary as each list item and expand to the full layout of the item on click.
Thirdly - if none of the above is acceptable and you still want to go the current approach - don't construct/add your view in the binding method. Do it in the onCreateViewHolder, when the system expects you to construct your view (I don't know for sure but by the time you're called on onBindViewHolder your view might have been already added to the hierarchy and any hierarchical change to it has a ripple effect on its containers - but don't take my word for it, I don't actually know the view is already added, it is just an assumption). You will have to assign each item a different type, so that in onCreateViewHolder you could match the view type with the supporting data (for the addition of the corresponding number of child views); create the view from scratch each time - this way you don't need to call on removeAllViews. Something like(I left out parts of the adapter that are not relevant to the case):
public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
ArrayList<DataPiecesList> mItems;
public RecyclerViewAdapter(ArrayList<DataPiecesList> items) {
mItems = items;
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
CompoundView compoundView = new CompoundView();
List<DataPiece> dataPieces = mItems.get(viewType);
for (int i = 0; i < dataPieces.size(); i++)
{
// construct TextView or ImageView or whatever
compoundView.add(child);
}
MyViewHolder view = new MyViewHolder(compoundView);
return view;
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int i) {
CompoundView compoundView = viewHolder.itemView;
DataPiece dataPiece = mItems.get(i);
for (int j = 0; j < compoundView.getChildCount(); j++)
{
compoundView.getChildAt(j) <- dataPiece.get(j);
}
}
#Override
public int getItemViewType(int position) {
return position;
}
#Override
public int getItemCount() {
return mItems.size();
}
public class MyViewHolder extends RecyclerView.ViewHolder {
...
public MyViewHolder(View itemView) {
super(itemView);
}
}
}
RecyclerView is supposed to reuse the views. It will be slow if you throw away the already created TextView / ImageView objects and create new ones every time.
It sounds like you need a RecyclerView with multiple view types. The idea is to create multiple view holders - some of them with ImageView, the others with TextView. You'll have to override the getItemViewType(int position) method of your adapter - it should return different values for the IMAGE items and the TEXT items. The onCreateViewHolder(ViewGroup parent, int viewType) receives a viewType parameter so you know which type of ViewHolder to create there. In the onBindViewHolder(VH holder, int position) you could assume that the holder passed to you is the correct type (i.e. the type with TextView for TEXT items and the type with ImageView for IMAGE items), so there is no need to remove its child views and create them again.
There is nice article about RecyclerView's Adapters with multiple view types here.
i'm new to android,
I've been working on a project, and in my news feeds page, I'm trying to include a modular feed RecyclerView, which shows a question with different answer forms, varrying according to the Question type. The way I was doing it so far was by using the include and turning the forms visible when needed. recently since i added more modules, the app started to slowdown segnificantly, so i'm trying to implement ViewStubs.
This is my RecyclerView adapter:
public class ReQuestionAdapter extends RecyclerView.Adapter<FeedItem> {
private ArrayList<Question> myQuestions;
public ReQuestionAdapter(Context context, ArrayList<Question> questions) {
myQuestions = questions ;
}
#Override
public FeedItem onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_item_re_question, parent, false);
return new FeedItem(view);
}
#Override
public void onBindViewHolder(FeedItem holder, int position) {
Question q = myQuestions.get(position);
holder.bindQuestion(q);
}
#Override
public int getItemViewType(int position) {
return 0;
}
#Override
public int getItemCount() {
return myQuestions.size();
}
}
And this is the ViewHolder class for the adapter:
public class FeedItem extends RecyclerView.ViewHolder{
private Question mQuestion;
public TextView tvName;
public TextView tvTime;
public TextView tvContent;
public ProfilePictureView profilePictureView;
public ViewStub moduleView;
private int moduleType;
public FeedItem(View itemView) {
super(itemView);
}
public void bindQuestion(Question question) {
mQuestion = question;
tvTime = (TextView) itemView.findViewById(R.id.li_q_date);
tvContent = (TextView) itemView.findViewById(R.id.li_q_content);
moduleView = (ViewStub) itemView.findViewById(R.id.module_viewstub);
tvTime.setText(TimeHandler.When(mQuestion.publish_time));
tvContent.setText(mQuestion.content);
moduleType = question.type;
switch (moduleType) {
case Question.TYPE_YN:
moduleView.setLayoutResource(R.layout.module_yes_no);
moduleView.inflate();
break;
case Question.TYPE_CUSTOM:
moduleView.setLayoutResource(R.layout.module_custom);
moduleView.inflate();
break;
default:
break;
}
}
}
Now, the problem is that the ViewStub which contains a certain layout, cannot be reinflated with a new one, the reason for that is that it gets removed from the view hirarchy as soon as it leaves the screen, the symptoms:
When scrolling down the RecyclerView, the first list items that fill the screen are working perfect, but others to load when the previous leave the screen cause the FeedItem binding to bring a NullPointerException. (It canno't find it in the list item layout).
I'm looking for a solution as efficiant as ViewStubs, or a way to make them work properly, since I got many modules and inflating them all in each item as invisible would make my app slow.
In your bindQuestion() method you are referencing two different layouts to inflate, so in essence you have two different view types.
Adapter views have an efficient way way to handle this built right in.
Start by overriding getItemViewType(). When the item at position gets the module_yes_no layout, return 0. When it gets the module_custom layout, return 1.
Then in onCreateViewHolder(), when the viewType parameter is 0, inflate a list_item_re_question view complete with the module_yes_no layout. When viewType == 1, inflate the module_custom version of the view.
Now when you get a view in onBindViewHolder(), it will already have the correct subview, so you proceed to fill out that view as needed. By using getItemViewType(), the RecyclerView is working with you to recycle the exact view you need.
You can even have two FeedItem subclasses, one for module_yes_no and one for module_custom, so in onBindViewHolder(), you just check the class of the ViewHolder and branch accordingly.
That should help improve the performance of your app.
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.