I have anEditTextin eachView, and inonViewRecycled(.)I update the item in theAdapterwith any user input. For the purpose of maintaining the information when scrollingViewsout of and back into view.
However, I realised that with this solution it's a bit of a hassle to retrieve the information that has been modified but not recycled. I solved this by adding eachViewHolderto a separate list in theAdapter(added inonCreateViewHolder(.)), since I couldn't find a simpler way to update theViewson the screen.
Is there any reason not to do this; let theAdapterhave direct access to theViewHolders? I.e. are there any better ways to call a function on allViewscurrently in existance?
Edit:
public class AdapterRv extends Adapter<AdapterRv.MyViewHolder> {
private List<Item> items;
private List<MyViewHoders> viewHolders;
public AdapterRv(List<Item> inItems) {
...
viewHolders = new ArrayList<>();
}
public class MyViewHolder extends RecyclerView.ViewHolder {
private EditText text;
private Item item;
private MyViewHolder(View inView) {
...
}
public void bindItem(Item inItem) {
...
}
}
#Override
public MyViewHolder onCreateViewHolder(ViewGroup inParent, int inViewType) {
...
if (!viewHolders.contains(tmpHolder)) {
viewHolders.add(tmpHolder);
}
...
}
....
#Override
public void onViewRecycled(MyViewHolder inViewHolder) {
saveView(inViewHolder.item, inViewHolder.text.getText().toString());
}
private void saveViews() {
for (MyViewHolder tmpViewHolder : viewHolders) {
saveView(tmpViewHolder.item, tmpViewHolder.text.getText().toString());
}
}
private void saveView(Item inItem, String inNewText) {
if (inItem.getText().equals(inNewText)) {
return;
}
inItem.setText(inNewText);
}
public List<Item> fetchTexts() {
saveViews();
return items;
}
}
The purpose of the RecyclerView.Adapter is only to create ViewHolder instances and provide them with data, but it is the RecyclerView itself that requests holders and manages them.
The purpose of the ViewHolder is, as it name suggests, to hold the item's view hierarchy so the Views can be cached and reused by the RecyclerView. Hence, the adapter should only create and bind the correct data to holders, and it is recommended to not store any references to holders inside the adapter, since you only need the reference to holder in the onBindViewHolder(ViewHolder holder, int position method, where it is provided by the RecyclerView. It's also vice-versa, the view holders don't need a reference to the adapter, so your MyViewHolder should be marked static.
If you need to operate on recycler's views, the RecyclerView has plenty of methods in it's API, such as findViewHolderForLayoutPosition(int position), getChildViewHolder(View child) etc. You can also set listeners for observing scroll, item touch, view attach state etc. changes.
See the documentation for the RecyclerView
So, if you need to access and manipulate the views (ie. call the function on all of recycler's views), do it through the RecyclerView and not the adapter, because it's the recycler that manages them - adapter only provides data.
Related
My app has more than 4 lists of different data models.
I want to create a more generic CommonAdapter that extends PagedListAdapter
Here is my current code
public abstract class CommonPagedListAdapter<T, VH extends RecyclerView.ViewHolder>
extends PagedListAdapter<T, VH> {
private Context mContext;
private ArrayList<T> mArrayList;
public abstract void onItemClick(T model, int position);
public abstract int getLayoutResId();
protected CommonPagedListAdapter(Context context, ArrayList<T> arrayList,
#NonNull DiffUtil.ItemCallback<T> diffCallback) {
super(diffCallback);
this.mContext = context;
this.mArrayList = arrayList;
}
#NonNull
#Override
public VH onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
//what should i return here?
View view = LayoutInflater.from(mContext).inflate(getLayoutResId(),parent,false);
return (VH) new ItemViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull VH holder, int position) {
//what should i do here?
// holder
}
class ItemViewHolder extends RecyclerView.ViewHolder {
public ItemViewHolder(#NonNull View itemView) {
super(itemView);
}
}
}
I'm using PagelListAdapter from the Android Paging Library
I would like to know a few things:
- What should be set in the onCreateViewHolder as I'll be having different ViewHolders?
- What should be set in onBindViewHolder?
- Is this really the right way that makes the CommonPagedListAdapter extensible and maintainable?
I had a similar issue where I tried to create a single adapter to use for multiple different list types. I ultimately came to the conclusion that it is best to use a separate adapter for each list type solely because it avoids having to make a very big "common" adapter class which goes against the "single-responsibility" principle. That way, each adapter is a lot smaller, more maintainable, and flexible.
However, if you really want to use a single adapter for similar items, the way I usually do it is by creating unique ViewHolders for each item type and then binding them accordingly in onBindViewHolder using a switch statement or something similar. In order to do this, you would need to override an additional method called getItemViewType in your adapter.
There's a really good guide that goes over how to create an adapter that can handle different view types on Codepath: https://guides.codepath.com/android/Heterogenous-Layouts-inside-RecyclerView
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
I have fragment "Incoming" on one slide of a viewpager. This fragment contains a RecyclerView populated with custom-relative-layouts. The LinearLayoutManager orientation is Vertical.
I have a second fragment "Find" on the next slide of the said viewpager. "Find" will consist of two recyclerviews. It will have a Horizontal recyclerview filled with cardviews (fast loading of profile pictures). Underneath that, I am loading more slowly another recyclerview with a custom-relative-layout, the same as in the "incoming" fragment.
Does that make sense? I'll elaborate some more:
The question is for these three recyclerviews, should I declare a new RecyclerAdapter for each one? The reason I ask is that they'll all have unknown variable item_counts.
Here is the RecyclerAdapter I have for "Incoming":
public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.ViewHolder>{
private Context mContext;
public RecyclerAdapter(Context context, List<Incoming> items) {
mContext = context;
}
public static class ViewHolder extends RecyclerView.ViewHolder {
public ViewHolder(View v) {
super(v);
// Define all of the components in the view
}
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater mInf = LayoutInflater.from(mContext);
View customView = mInf.inflate(R.layout.item_layout_incoming, parent, false);
final ViewHolder vh = new ViewHolder(customView);
return vh;
}
#Override
public int getItemCount(){ return 6; } // THIS IS TEMPORARY; WILL BE VARIABLE
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
// Replace contents
}
For my criteria, should I create another Adapter for my horizontal-cardview-recyclerview? It seems repetitive, but otherwise, how would I handle either inflating cardview or item_layout_incoming?
Seems like there should be a DRY way to do this, without hits to performance. Thanks
You are using fragments so you will create 2 objects of that class. So it's the same thing. you just reduce compiler load by reducing the task of loading the new class into memory and then create its object.
It's better to use two different Adapter because of 2 reasons.
Your code will become ugly I mean so much congested and so many if
else condition.
In future, if you need to change something in layouts then again it
will affect all objects if same adapter class.
So my advice do developer friendly code and create two classes.
I'm trying to create a UI similar to Google Keep. I know how to create a staggered View using a Recycler View. If i click a specific Card. Then it has to open a activity.
I can achieve this using onclick method.
This same scenario happens in atleast 5 different Activities in my App.
My question is that
Can I use this single Adapter in all those 5 places ?
If yes, where should i place the onclick actions ?
If no, How can I Create a staggered layout like Keep?
Thanks in Advance!
(See application for RecyclerView below in edits)
Like I mentioned in my comment, it's certainly fine to have separate adapters for all your Activities which use different data and views. As your app data and layouts get more complex, so does your code...that's just the way it is.
But if some of your Activities used similar data in their ListViews -- maybe, for example, two TextViews and an ImageButton -- you could save some effort by defining a single adapter that can be used for multiple Activities. You would then instantiate separate objects for each Activity, similar to the way you would create several ArrayAdapter<String> objects to populate multiple ListViews.
The BaseAdapter is a great class to extend when writing a custom adapter. It's flexible and allows you complete control over the data that's getting shown in your ListView. Here's a minimal example:
public class CustomBaseAdapter extends BaseAdapter {
private Context context;
private ArrayList<String> listData;
public CustomBaseAdapter(Context context, ArrayList<String> listData) {
this.context = context;
this.listData = listData;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView == null) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.your_list_item_layout, parent, false);
//populate the view with your data -- some examples...
TextView textData = (TextView) convertView.findViewById(R.id.yourTextView);
textData.setText(listData.get(position));
ImageButton button = (ImageButton) convertView.findViewById(R.id.yourImageButton);
button.setOnClickListener(new View.OnClickListener() {
//...
//...
});
}
return convertView;
}
#Override
public Object getItem(int position) {
return 0;
}
#Override
public long getItemId(int position) {
return 0;
}
#Override
public int getCount() {
return listData.size();
}
}
So the key part of this code is obviously the getView() method, which is called every time the ListView needs some data to display. For efficiency, views are stored in something called a convertView so they may be re-used and not have to be inflated every time a view appears on the screen.
So what we do in getView() is first find out if the convertView exists. If it does, we just pass that back to the calling ListView because everything should already be instantiated and ready to go. If the convertView is null, it means the data hasn't been instantiated (or needs to be re-instantiated for whatever reason), and so we inflate a brand new view and populate it with our data.
So in the case of this example adapter above, if several of your Activities all displayed a single list of Strings, you could reuse this adapter for each one, passing in a different ArrayList<String> through the constructor each time you created a new object. But obviously you could pass in more than just Strings if you had more data to show. The level of complexity is up to you. And if the difference among your Activities was too great, I would just create custom versions of this class for all of them and just instantiate them and populate them however you'd like. It will keep all your data very organized and encapsulated.
Hope this helps! Feel free to ask questions for more clarification if you need it.
EDIT IN RESPONSE TO COMMENTS
Since you are using a RecyclerView instead of just plain ListViews (which I, for some reason, totally forgot) you could still do something very similar using a RecyclerView.Adapter<YourViewHolder> instead. The difference would be that instead of inflating the views in a getView() method, they are inflated inside your custom ViewHolder, which I assume you already have. The code might look something like this:
public class CustomRecyclerViewAdapter extends RecyclerView.Adapter<StringViewHolder> {
private final List<String> items;
public CustomRecyclerViewAdapter(ArrayList<String> items) {
this.items = items;
}
#Override
public StringViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//So instead of inflating the views here or in a getView() like in
//in the BaseAdapter, you would instead inflate them in your custom
//ViewHolder.
return new StringViewHolder(parent);
}
#Override
public void onBindViewHolder(StringViewHolder holder, int position) {
holder.setModel(items.get(position));
}
#Override
public long getItemId(int position) {
return items.get(position).hashCode();
}
#Override
public int getItemCount() {
return items.size();
}
}
I have been playing around with RecyclerView for a little bit. Is there any easy way to put OnClickListener for items in RecyclerView? I have tried implementing it in ViewHolder. The onClick event never got triggered.
And I have used notifyItemInserted(position) for adding new value into RecyclerView. The UI does not got refreshed automatically. Needed to pull up and down to refresh. But when I invoke notifyDatasetChanged(..), it is ok.
I have applied DefaultItemAnimator to RecyclerView. But, not seeing any animation when new item added.
Thanks advance for any idea.
This is the first Android L component I have tested out and I am stucking there.
Here is my Adapter class:
public class AdapterRecyclerView extends RecyclerView.Adapter<AdapterRecyclerView.MyViewHolder> {
private List<String> arrExperiences;
//Provide a reference to the type of views that you are using - Custom ViewHolder
public class MyViewHolder extends RecyclerView.ViewHolder {
public TextView tvExperienceTitle;
public TextView tvExperienceDesc;
public MyViewHolder(RelativeLayout itemView) {
super(itemView);
tvExperienceTitle = (TextView) itemView.findViewById(R.id.tv_experience_title);
tvExperienceDesc = (TextView) itemView.findViewById(R.id.tv_experience_desc);
}
}
//Provide a suitable constructor : depending on the kind of dataset.
public AdapterRecyclerView(List<String> arrExperiences){
this.arrExperiences = arrExperiences;
}
//Create new view : invoke by a Layout Manager
#Override
public AdapterRecyclerView.MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
RelativeLayout view = (RelativeLayout) LayoutInflater.from(parent.getContext()).inflate(R.layout.view_item_recycler, parent, false);
MyViewHolder myViewHolder = new MyViewHolder(view);
return myViewHolder;
}
#Override
public void onBindViewHolder(AdapterRecyclerView.MyViewHolder viewHolder, int position) {
//get element from your dataset at this position.
//replace the content of the view with this element.
viewHolder.tvExperienceTitle.setText(arrExperiences.get(position));
}
#Override
public int getItemCount() {
return arrExperiences.size();
}
public void addExperience(String experience, int position){
arrExperiences.add(position, experience);
notifyItemInserted(position);
//notifyDataSetChanged();
}
public void removeExperience(){
int index = (int) (Math.random() * arrExperiences.size());
arrExperiences.remove(index);
notifyItemRemoved(index);
//notifyDataSetChanged();
}
}
Simply add this in your Adapter:
#Override
public void onBindViewHolder(AdapterRecyclerView.MyViewHolder viewHolder, int position) {
yourItems.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
//do your stuff
}
});
}
Please see my answer here. You do need an extra class (which may be included as part of the full release) but it will allow you to create OnItemClickListeners the way you are used to for ListViews.
Since you still didn't mark correct any answer, and even if it's an old question, I will try to provide the way I do. I think it is very clean and professional. The functionalities are taken from different blogs (I still have to mention them in the page), merged and methods have been improved for speed and scalability, for all activities that use a RecycleView.
https://github.com/davideas/FlexibleAdapter
At lower class there is SelectableAdapter that provides selection functionalities and it's able to maintain the state after the rotation, you just need to call the onSave/onRestore methods from the activity.
Then the class FlexibleAdapter handles the content with the support of the animation (calling notify only for the position. Note: you still need to set your animation to the RecyclerView when you create it the activity).
Then you need to extend over again this class. Here you add and implements methods as you wish for your own ViewHolder and your Domain/Model class (data holder). Note: I have provided an example which does not compile because you need to change the classes with the ones you have in your project.
I think that, it's the ViewHolder that should keep the listeners of the clicks and that it should be done at the creation and not in the Binding method (that is called at each invalidate from notify...() ).
Also note that this adapter handles the basic clicks: single and long clicks, if you need a double tap you need to use the way Jabob Tabak does in its answer.
I still have to improve it, so keep an eye on it. I also want to add some new functionalities like the Undo.
Here you get a simple Adapter class which can perform onItemClick event on each list row for the recyclerview.