My app is all about performance, so I would really like to optimize this RecyclerView as much as possible. I have measured how long every part takes to complete, and the whole thing needs about 150ms to load. Here is the RELEVANT code:
public class AppAdapter extends RecyclerView.Adapter<AppAdapter.TabViewHolder> {
private Context context;
private MenuActivity menuActivity;
private Intent intent;
private ArrayList<String> stringys = new ArrayList<>();
public void setUpAdapter(Context mContext, MenuActivity mMenuActivity, Intent mIntent, ArrayList<String> mString) {
this.menuActivity = mMenuActivity;
this.context = mContext;
this.intent = mIntent;
stringys.addAll(mString);
}
#Override
public TabViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(context);
Log.d(TAG, "CreatingViewholder " + "Time: " + menuActivity.deltaTime());
View view = inflater.inflate(R.layout.item_tab, parent, false); //Here is where the wait happens
Log.d(TAG, "ViewHolderCreated " + "Time: " + menuActivity.deltaTime());
return new TabViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull TabViewHolder holder, int position) {
holder.bind(position, holder);
}
#Override
public int getItemCount() {
return stringys.size();
}
class TabViewHolder extends RecyclerView.ViewHolder {
TextView text;
ImageView image;
TabViewHolder(View itemView) {
super(itemView);
text = itemView.findViewById(R.id.label);
image = itemView.findViewById(R.id.icon);
}
void bind(int position, TabViewHolder holder) {
new LongOperation(text, image).execute(stringys.get(position));
}
}
private class LongOperation extends AsyncTask<String, Void, Void> {
TextView text;
ImageView image;
CharSequence textToSet;
Drawable imageToSet;
public LongOperation(TextView text, ImageView image) {
super();
this.text = text;
this.image = image;
}
#Override
protected Void doInBackground(String... params) {
textToSet = params[0].getTitle(context.getDefaultSharedPreferences());
imageToSet = params[0].getIcon(context.getDefaultSharedPreferences());
return null;
}
#Override
protected void onPostExecute(Void result) {
text.setText(textToSet);
image.setImageDrawable(imageToSet);
}
}
}
I have several ideas/questions about this:
Is it possible to reuse this ViewHolder? I'm using it every time, and each inflation takes about 5ms, which adds up quickly because this is a grid, and I have about 40 holders loading when I launch this.
If it helps, I am also ready to use another kind of view. I took recyclerview as it made the most sense imo, but if there is a better-performing view I can change to that.
Would it help if I used a Linear Layout, and put 4 of the ViewHolders I currently use next to each other? Would my time be then reduced by 4?
In the asynctask, I call context.getDefaultSharedPreferences() twice. Would it load faster (the async part) if I did it once and had it as a variable to pass?
The asynctask it might leak if it isn't static. I assume that's not a problem because it finished very quickly anyways, right?
RecyclerView is used for long lists, implementation of ViewHolder pattern improves performance by re-using Views after they leave the screen. If you have don't have enough items to fill the viewport there's no reason to use it.
Also keep in mind while debugging setting bitmaps is much slower, if possible try building as "release" and see how it performs.
Your ViewHolder will be reused as you scroll down, but you need at least enough view holders to display what is currently on screen, plus a few more that are just off screen. So when you first load this are there 40 views displayed on the screen? If not, you have an issue.
It depends if you can scroll your view a lot.
You might get a slight improvement but I doubt it.
Shared preferences calls are cached. You don't have to worry about them taking a long time after the first access.
Not sure
Other improvements:
You could move your LayoutInflater creation to setUpAdapter()
What is in R.layout.item_tab? Make sure your hierarchy of views is as flat as possible. Use Constraint Layout if you can.
Related
I need to display some images in a RecyclerView.
These images should be rendered "on the fly" as user is scrolling the list. Rendering each image takes a long time: 50-500ms. Before the image is displayed to the user, a progress bar is displayed.
Due to the long rendering time this part is placed into an AsyncTask.
See the code below:
class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
class ViewHolder extends RecyclerView.ViewHolder {
ImageView myImg;
ProgressBar myProgressBar;
public ViewHolder(View view) {
super(itemView);
myImg = view.findViewById(R.id.myImg);
myProgressBar = view.findViewById(R.id.myProgressBar);
}
}
#Override
public void onBindViewHolder(#NonNull ViewHolder holder, int position) {
holder.myProgressBar.setVisibility(View.VISIBLE);
AsyncTask.execute(() -> {
Bitmap bitmap = longRenderingFunction();
holder.myImg.post(() -> {
holder.myImg.setImageBitmap(bitmap);
holder.myProgressBar.setVisibility(View.GONE);
If the user is scrolling the list slowly, e.g. 1-2 screens per second, the images are loaded correctly. Sometimes one can notice the progress bar, which quickly disappears.
But if the user is scrolling very fast, the images appear and then are replaced, sometimes two times:
In general it is clear, that on fast scrolling:
Many onBindViewHolder are called and multiple AsyncTasks per recyclable item ViewHolder are started.
Even a new onBindViewHolder is triggered for the same item, the old AsyncTask keep running
Then AsyncTasks for the same ViewHolder are completing one after another.
Each AsyncTask puts its own resulting bitmap to the ImageView.
My intentions would be:
minimum: do not setImageBitmap from the outdated AsyncTasks
maximum: to stop already outdated AsyncTasks as soon as possible to save system resources
I would appreciate to hear some hints or maybe solutions for this problem.
Here is the solution I came up with.
Stop flickering
The first part is pretty simple:
A simple index was introduced:
int loadingPosition;
At the beginning of onBindViewHolder the current position is saved:
holder.loadingPosition = position;
Before switching the bitmap the current position is checked against the last started. If a new task has been already started, the index was changed beforehand, then the image will not be updated:
if (holder.loadingPosition == position) {
holder.myImg.setImageBitmap(bitmap);
Improve performance
To prevent the task to run further when not needed a Future object was introduced. It allows to manage the started task, while AsyncTask does not:
Future<?> future;
Start the async task via ExecutorService instead of AsyncTask:
holder.future = executor.submit(() -> { ...
Stopping the running task, if it is still running:
if ((holder.future != null) && (!holder.future.isDone()))
holder.future.cancel(true);
Complete code
The whole code looks like that:
class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
private ExecutorService executor = Executors.newSingleThreadExecutor();
class ViewHolder extends RecyclerView.ViewHolder {
ImageView myImg;
ProgressBar myProgressBar;
int loadingPosition;
Future<?> future;
public ViewHolder(View view) {
super(itemView);
myImg = view.findViewById(R.id.myImg);
myProgressBar = view.findViewById(R.id.myProgressBar);
}
}
#Override
public void onBindViewHolder(#NonNull ViewHolder holder, int position) {
holder.loadingPosition = position;
if ((holder.future != null) && (!holder.future.isDone()))
holder.future.cancel(true);
holder.myProgressBar.setVisibility(View.VISIBLE);
holder.future = executor.submit(() -> {
Bitmap bitmap = longRenderingFunction();
holder.myImg.post(() -> {
if (holder.loadingPosition == position) {
holder.myImg.setImageBitmap(bitmap);
holder.myProgressBar.setVisibility(View.GONE);
Maybe using the both methods is a little bit overkill, but it provides some level of insurance.
EDIT: I've solved this issue, if interested, please take a look at my answer to see how I did it!
I am currently working in Android Studio. I have a ListView that I populate with several items. Within each of these items is an ImageButton that has a "+" as the image. What I want to do is, whenever that image is clicked (not the entire ListView item, just the image), I want that image of "+" to become another image. Any help would be appreciated, as this has been an ongoing issue for a while!
Here is the current code that I attempt to use to achieve this:
final ImageButton movieSeen = (ImageButton convertView.findViewById(R.id.movieWatched);
movieSeen.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
movieSeen.setImageResource(R.drawable.ic_check_circle_black_24dp);
}
});
Currently this does update the image that I click correctly, BUT it also updates images that are not yet rendered on the screen, so when I scroll the list view down, other objects are also changed to ic_check_circle_black_24dp.
What I want is pretty straightforward, I just don't know how to achieve it. I just want to click an ImageButton, that's inside an item on a ListView, and have that ImageButton change its image resource.
Here is my custom array adapter as requested!
private class MovieScrollAdapter extends ArrayAdapter<Movie> {//custom array adapter
private Context context;
private List<Movie> movies;
public MovieScrollAdapter(Context context, List<Movie> movies){
super(context, -1, movies);
this.context = context;
this.movies = movies;
if(this.movies.isEmpty()){//if no results were returned after all processing, display a toast letting the user know
Toast.makeText(getApplicationContext(), R.string.no_matches, Toast.LENGTH_SHORT).show();
}
}
#Override
public View getView(final int position, View convertView, ViewGroup parent){
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) context.
getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.movie_layout, parent, false);
}
TextView title = (TextView) convertView.findViewById(R.id.title);
title.setText(movies.get(position).getTitle());
TextView plot = (TextView) convertView.findViewById(R.id.plot);
plot.setText(movies.get(position).getPlot());
TextView genre = (TextView) convertView.findViewById(R.id.genre);
genre.setText(movies.get(position).getGenre());
TextView metaScore = (TextView) convertView.findViewById(R.id.metascore);
if(movies.get(position).getMetaScore() == -1){//if the metaScore is set to -1, that means movie has not been rated, which by inference means it is not yet released
metaScore.setText(R.string.movie_not_released);
metaScore.setTextSize(TypedValue.COMPLEX_UNIT_SP, 9.5f);//smaller text so it fits without breaking anything
metaScore.setTextColor(getColor(R.color.colorAccent));
} else {
metaScore.setText(" " + Integer.valueOf(movies.get(position).getMetaScore()).toString() + " ");//using white space for minor formatting, instead of altering margins each time this is rendered
metaScore.setTextSize(TypedValue.COMPLEX_UNIT_SP, 25);
//setting up a "highlighted" background to achieve metacritic square effect
Spannable spanText = Spannable.Factory.getInstance().newSpannable(metaScore.getText());
spanText.setSpan(new BackgroundColorSpan(getColor(R.color.metaScore)), 3, 7, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
metaScore.setText(spanText);
metaScore.setTextColor(getColor(android.R.color.primary_text_dark));
}
ImageView image = (ImageView) convertView.findViewById(R.id.imageView);
new ImageDownloadTask((ImageView)image).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, movies.get(position).getPosterURL());//because there are several images to load here, we let these threads run parallel
title.setOnClickListener(new View.OnClickListener() {//setting up a simple onClickListener that will open a link leading to more info about the movie in question!
#Override
public void onClick(View v) {
Uri uri = Uri.parse(movies.get(position).getMovieURL());
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
}
});
final ImageButton movieSeen = (ImageButton) convertView.findViewById(R.id.movieWatched);
movieSeen.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
movieSeen.setImageResource(R.drawable.ic_check_circle_black_24dp);
}
});
return convertView;
}
}
The problem is on a ListView, the views are being reused to save memory and avoid creating a lot of views, so when you change a view it keeps the state while it's being reused to show another item.
For example, you have 100 elements, you touch the first element ImageButton and that button is changed. Maybe on the screen there are 5 elements of the list showing, and you changed the first one. But if you scroll to the element number 15 the system is not creating 15 views, is taking the first one you clicked before and is changing the content.
So, you are expecting to have a view with a "+" ImageButton icon but you see another icon, that's because you must keep the view state inside a model object and set the state every time 'getView' is called.
Post your list adapter to see how is implemented.
UPDATE:
Now I see your adapter implementation I suggest you to add an int field inside Movie class to save the resource id you want to show on the ImageButton. Then inside the onClickListener you must set to this field the resource you want to show on the view when its clicked, and call notifyDataSetChanged(). After that you must do inside getView():
movieSeen.setImageResource(movies.get(position).getButtonImageResource());
Use RecyclerView and set the OnItemClickListener on your ImageButton within your view holder.
This already answered question should help.
The adapted code below is coming from this nice tutorial. Using ReciclerView with an adapter like this will solve your concern.
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
private ArrayList<String> mDataset;
public class ViewHolder extends RecyclerView.ViewHolder {
public ImageView imageView;
public TextView txtHeader;
public ViewHolder(View v) {
super(v);
txtHeader = (TextView) v.findViewById(R.id.xxx);
imageView = (ImageView) v.findViewById(R.id.yyy);
}
}
public MyAdapter(ArrayList<String> myDataset) {
mDataset = myDataset;
}
#Override
public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.rowlayout, parent, false);
ViewHolder vh = new ViewHolder(v);
return vh;
}
// Replace the contents of a view (invoked by the layout manager)
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
final String name = mDataset.get(position);
holder.txtHeader.setText(mDataset.get(position));
holder.imageView.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
// Do here what you need to change the image content
}
});
holder.itemView.setBackground(....); // Initialize your image content here...
}
//...
}
Here is my suggestion to achieve what you want :
Create An Interface in your adapter :
public interface YourInterface{
void selectedImage(int position,ImageView iamgeView);
}
Create variable interface in your adapter that you just created :
private YourInterface yourInterface;
and make your adapter constructor like this :
public YourAdapterConstructor(YourInterface yourInterface){
this.yourInterface = yourInterface;
}
in your ImageView onClickListener :
final ImageButton movieSeen = (ImageButton) convertView.findViewById(R.id.movieWatched);
movieSeen.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
yourInterface.selectedImage(position, imageView);
}
});
and then finally in your class activity, Implements YourInterface and change you ImageView there :
#Override
public void selectedImage(final int position,final ImageView imageView) {
//change your image view here
}
I'd like to thank everyone for their support. Unfortunately, with the way my code is written (rather messily and without much regard for what my professors taught me), I was unable to get most of these solutions to work. I did however, find a solution that falls in line with my own framework that I've had going into this. Unfortunately I could not redo my entire adapter method, or implement various interfaces that would cause me to have to rewrite a huge chunk of code for something seemingly trivial.
So, if anyone finds themselves in this situation in the future, here is my solution:
In the Movie class, I add a boolean value that represents my values, along with some getters and setters:
private boolean watchedStatus;
public boolean hasSeen() {return watchedStatus;}
public void toggleWatchedStatus(){
watchedStatus = !watchedStatus;
}
In the getView method, I simply get a reference to the ImageButton, and then based on the boolean value returned by "hasSeen," I set the ImageResource to one of two states:
#Override
public View getView(final int position, View convertView, ViewGroup parent){
ImageButton movieSeen = (ImageButton) convertView.findViewById(R.id.movieSeen);
if(movies.get(position).hasSeen()){
movieSeen.setImageResource(R.drawable.ic_check_circle_black_24dp);
} else {
movieSeen.setImageResource(R.drawable.ic_add_circle_black_24dp);
}
}
Next, I override the OnClickListener, and make it so that whenever the button is clicked, the boolean value in the Movie.java class is toggled. The key here was using the ArrayAdapter's method "notifyDataSetChanged()" This completes the process, and lets the ListView know that it should update itself:
final ImageButton movieSeenForClick = (ImageButton) convertView.findViewById(R.id.movieSeen);
movieSeen.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//movieSeenForClick.setImageResource(R.drawable.ic_check_circle_black_24dp);
movies.get(position).toggleWatchedStatus();
System.out.println(movies.get(position).hasSeen() + " ------- position: " + position);
notifyDataSetChanged();
}
});
Thanks again for the time taken to provide information, a lot of it really did steer me int he right direction, I just had to use the information correctly with the way my code was structured.
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 making an app with 100 list items and was wondering if I could get away with not implementing the RecyclerView as I find it hard to implement it.
Quite frankly it depends up to you, Listview makes it easy for you by taking a lot of responsibility which makes it slow at time when you have to show a lot of data, on other hand RecyclerView does what it is best at make's things fast by taking care or bare minimum structure.
RecyclerView is quite easy to implement and you will get chance to learn some of the touch framework of Android because of it.
And performing Animation on RecyclerView is quite easy as well and way better than Listview
Making a custom listview is piece of cake with RecyclerView
here's an example for RecyclerView
private RecyclerView recyclerView;
recyclerView = (RecyclerView)findViewById(R.id.recycler);
MyViewComplainAdapter adapter = new MyViewComplainAdapter(getApplicationContext(), createComplainList());
recyclerView.setAdapter(adapter);
recyclerView.setLayoutManager(new LinearLayoutManager(getApplicationContext()));
in XML
<android.support.v7.widget.RecyclerView
android:id="#+id/recycler"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:scrollbars="vertical" />
your Adapter (Whatever you want to call this thing... lol )
private class MyViewComplainAdapter extends RecyclerView.Adapter<MyViewComplainAdapter.MyViewComplainViewHolder>{
private Context _Context;
private ArrayList<ViewMyComplainData> _List;
private LayoutInflater _Inflater;
public MyViewComplainAdapter(Context context, ArrayList<ViewMyComplainData> list){
_Context = context;
_List = list;
_Inflater = LayoutInflater.from(_Context);
}
#Override
public MyViewComplainViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View layout = _Inflater.inflate(R.layout.single_item_view_my_complain,parent,false);
MyViewComplainViewHolder holder = new MyViewComplainViewHolder(layout);
return holder;
}
#Override
public void onBindViewHolder(MyViewComplainViewHolder holder, int position) {
ViewMyComplainData data = _List.get(position);
holder.complaint_number.setText(data.getComplaint_Number()+"");
holder.complaint_type.setText(data.getComplaint_Type()+"");
holder.status.setText(data.getStatus()+"");
}
#Override
public int getItemCount() {
return _List.size();
}
public class MyViewComplainViewHolder extends RecyclerView.ViewHolder{
TextView complaint_number;
TextView complaint_type;
TextView status;
public MyViewComplainViewHolder(View itemView) {
super(itemView);
complaint_number = (TextView)itemView.findViewById(R.id.textView_complaint_number_single_item_view_my_complain);
complaint_type = (TextView)itemView.findViewById(R.id.textView_complaint_type_single_item_view_my_complain);
status= (TextView)itemView.findViewById(R.id.textView_status_single_item_view_my_complain);
}
}
}
yes you will have to make a ArrayList<ViewMyComplainData> using this method createComplainList(), you should figure this out
Technically speaking, RecyclerView doesn't need anything like "notifyDataSetChanged()" when an item is added or deleted from your List, which is a huge improvement performance-wise.
I have a ListView that has a TextView and in each TextView several items, each with a brief description. In order to apply different styles to the items and description I am using multiples span objects: ClickableSpan, ForegroundColorSpan, BackgroundColorSpan, AbsoluteSizeSpan and StyleSpan.
On average the ListView has 10 TextViews, and each TextView has 10 pairs of item/description and each pair uses around 6 SpanObjects so a total of ~600 SpanObjects per ListView. And when the user clicks one of the items the ListView is cleared and redraw again generating another set of 600 SpanObjects.
So I am wondering if it would be a better idea to create one TextView for each item and each description and format it through XML rather than styling the TextViews with span, this way there would be around 200 TextViews and no SpanObjects.
As a note, I did not start with this approach because the hierarchy tree of views was going to be more complex and TextViews are more expensive to create that the SpanObjects but now that I see the number of SpanObjects I am creating I am not sure what is better. I am also thinking to start writing a recycling mechanism but not sure if it is worth or this should not be a problem for Android GC.
I know its been a couple years since you've asked this question, and I hope you've found an answer by now, but I thought I'd answer your question anyhow.
The amount of TextViews and SpanObjects you have are incredibly expensive for any android device, and loading them each time can not only cause lag, but possibly a OOM-related crash (out of memory).
The approach to solving this is using a RecyclerView and CardView. First, write your RecyclerView adapter. Within this class, write a static class that loads all of the TextViews and other items within each card (a card is essentially a list item). This way, all of your items are loaded on OnCreate, and are not continuously generated. This is especially helpful if you're using fragments.
This link may be of use to you: http://www.binpress.com/tutorial/android-l-recyclerview-and-cardview-tutorial/156
Here is an example of a RecyclerView adapter class with the static class:
public class ContactAdapter extends RecyclerView.Adapter<ContactAdapter.ContactViewHolder> {
private List<ContactInfo> contactList;
public ContactAdapter(List<ContactInfo> contactList) {
this.contactList = contactList;
}
#Override
public int getItemCount() {
return contactList.size();
}
#Override
public void onBindViewHolder(ContactViewHolder contactViewHolder, int i) {
ContactInfo ci = contactList.get(i);
contactViewHolder.vName.setText(ci.name);
contactViewHolder.vSurname.setText(ci.surname);
contactViewHolder.vEmail.setText(ci.email);
contactViewHolder.vTitle.setText(ci.name + " " + ci.surname);
}
#Override
public ContactViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
View itemView = LayoutInflater.
from(viewGroup.getContext()).
inflate(R.layout.card_layout, viewGroup, false);
return new ContactViewHolder(itemView);
}
//Static class that loads all card item components
public static class ContactViewHolder extends RecyclerView.ViewHolder {
protected TextView vName;
protected TextView vSurname;
protected TextView vEmail;
protected TextView vTitle;
public ContactViewHolder(View v) {
super(v);
vName = (TextView) v.findViewById(R.id.txtName);
vSurname = (TextView) v.findViewById(R.id.txtSurname);
vEmail = (TextView) v.findViewById(R.id.txtEmail);
vTitle = (TextView) v.findViewById(R.id.title);
}
}
}