Android Custom Listview - java

I went through tutorials and searched, but still I can't understand how the,
getView(int position, View convertView, ViewGroup arg2)
method works when extends BaseAdapter to create a custom listView in my android application. Therefore I cant Edit the Custom list view exactly I want.
I need to know when this method invokes and the meanings of the parameters.
If someone can explain the following method its great. Thanks
#Override
public View getView(int position, View convertView, ViewGroup arg2)
{
ViewHolder holder;
LayoutInflater inflater = context.getLayoutInflater();
if (convertView == null)
{
convertView = inflater.inflate(R.layout.listitem_row, null);
holder = new ViewHolder();
holder.txtViewTitle = (TextView) convertView.findViewById(R.id.textView1);
holder.txtViewDescription = (TextView) convertView.findViewById(R.id.textView2);
convertView.setTag(holder);
}
else
{
holder = (ViewHolder) convertView.getTag();
}
holder.txtViewTitle.setText(title[position]);
holder.txtViewDescription.setText(description[position]);
return convertView;
}

getView() is called when you call setAdapter in your code. After that when you move focus over list or select any item or you call notifyDataSetChanged() , you get call in getView().
Position - The position of the item within the adapter's data set of the item whose view we want.
convertView - The old view to reuse, if possible. Note: You should check that this view is non-null and of an appropriate type before using. If it is not possible to convert this view to display the correct data, this method can create a new view. Heterogeneous lists can specify their number of view types, so that this View is always of the right type
The ViewGroup - that this view will eventually be attached to.

getView() : As mentioned in specs getView method displays the data at the specified position. So, when you setAdapter and when you scroll your listView getView method will be called.
The method you copied here is a part of EfficientAdapter to optimize your ListView performance and along with optimization you used ViewHolder pattern.
Copied from Specs : With little more explanation
position :The position of the item within the adapter's data set of the item whose view we want.
convertView: The old view to reuse, if possible. Note: You should check that this
view is non-null and of an appropriate type before using. If it is not possible to convert this view to display the correct data, this method can create a new view. Heterogeneous lists can specify their number of view types, so that this View is always of the right type (see getViewTypeCount() and getItemViewType(int)).
So, in above method when you are doing the following thing you are reusing your convertView.
if (convertView == null){
....
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
And by doing following thing you are avoiding lookup (findViewById), thats what the good thing about ViewHolder pattern
holder.txtViewTitle = (TextView) convertView.findViewById(R.id.textView1);
parent : The parent that this view will eventually be attached to
Edited
Question : How many times getView is called and how many convertView will be created ?
Answer: Lets take an example of Efficeint Adapter from ApiDemos. If your screen showing 10 Rows, then,
convertView Count : 10 + 1 = 11 (10 Rows what you are seeing on screen, one extra to show scrolling effect). That means statements in if(convertView == null){...} block will be called only 11 times.
getView Count: Initially count will be 10, but when you start scrolling count keep on increasing. getView called every time to update data.
Note: This is only true for getView method mentioned in question.

Here is the description of getView() parameters:
int position - the position of view in the list;
View convertView - IMHO, this is the most difficult parameter for understanding. At the beginning of list's work convertView = null. But when you start to scroll it down, when an item of list (which is instance of View) is hidden, it is stored in memory like convertView. This trick allows you not to create a new item when you scroll your list back, but use convertView stored in memmory. So the first item of list which becomes the convertView is the item at 0 position. Remember, that when you scroll your ListView down (from 0 position to bigger), convertView is situated on the top and on the bottom if you scroll the ListView up.
ViewGroup arg2 - this is your ListView (this class is derived from ViewGroup).
ViewHolder is the pattern, that makes comfortable communication with list's items. You make this object as item's tag and can use them for for indirect interaction with list's item, because it refers on item's fields (View.setTag(holder)).
getView() method is called every time when Android need to draw another one list's item.
Any questions?

Related

sticky or pinned sectioned list view in android with dynamic data

I want to achieve something like this. I am getting "survey titles"(type: string) and "different number of questions"(type:string) under every survey titles. I want to use survey titles as headers and questions of that survey should be display under that survey title header.
I have already tried https://github.com/emilsjolander/StickyListHeaders; and few other libraries. It is using first char of list data as header. Which in my case not possible.
This will be the look one header and its questions. When there are multiple survey with their questions, Sticky or pinned Header behaviour I want to achieve in my android app.
You can use the API for StickyListHeaders how you want and not use a char. I took this sample and changed a few items StickyListHeaders#getting-started indicated by my comments "//*** NOTE:" Just have to learn that you can change implementations however you want in Java if the API is provided. Just takes experimenting. Nothing in this API forces you to use their 'char' method or method at all. This is just the build in API I assume.
#Override
public View getHeaderView(int position, View convertView, ViewGroup parent) {
HeaderViewHolder holder;
if (convertView == null) {
holder = new HeaderViewHolder();
convertView = inflater.inflate(R.layout.header, parent, false);
holder.text = (TextView) convertView.findViewById(R.id.text);
convertView.setTag(holder);
} else {
holder = (HeaderViewHolder) convertView.getTag();
}
//*** NOTE: You can use the name here
//set header text as first char in name
String headerText = "" + countries[position].subSequence(0, 1).charAt(0);
holder.text.setText(headerText);
return convertView;
}
#Override
public long getHeaderId(int position) {
//*** NOTE: You could use the hashcode of the word here or your own implementation
//return the first character of the country as ID because this is what headers are based upon
return countries[position].subSequence(0, 1).charAt(0);
}

getItemViewType is returned only in onBindViewHolder but not in onCreateViewHolder

I have a RecyclerView adapter that should inflate 4 different layouts depending on what getItemViewType returns.
Each view type should be returned when the view is triggered, but the problem is one of the types does not return inside the onCreateViewHolder, but returns only in onBindViewHolder, thus preventing the ViewHolder from being created. Also I assure you getItemCount returns just the correct size of the data, so that should not be the problem.
I figure if the view types can be returned successfully they should show up in both methods when called. So this issue just doesn't make any sense to me.
#NonNull
#Override
public HorizontalViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int i) {
Log.d(SLIDER_TAG, "onCreateViewHolder: " + getItemViewType(i));
View cardView = LayoutInflater.from(parent.getContext()).inflate(
getItemViewType(i) == 0 ? R.layout.item_category_slider_viewed
: getItemViewType(i) == 1 ? R.layout.item_category_slider_added
: getItemViewType(i) == 2 ? R.layout.item_category_slider_browse_all
: R.layout.item_category_slider_regular
, parent, false);
return new HorizontalViewHolder(cardView, context);
}
When logging the getItemViewType(i) only 0, 1, and 3 are ever returned inside the onCreateViewHolder but not 2.
But strangely, logging that inside the onBindViewHolder returns all the view types from 0 - 3. Why is that the case?
EDIT
The RecyclerView displays a horizontal list of cards (about 20) while all but the last card (blank) uses the same layout, so only 2 view types are used in this specific list case, we can ignore the other 2 types for now. Here the last card is not inflated, thus was never called in the onCreateViewHolder. I'm suspecting that while the first many cards were inflated using the same layout, the layouts are not created again so it assumes that the last card uses the same layout.
The problem probably because you're rechecking for the itemViewType with getItemViewType(i)); inside of onCreateViewHolder. You shouldn't do that because onCreateViewHolder already giving you the itemViewType from its parameters. And you should use a switch case instead of if ? : to make your code more readable.
So, change your code to something like this:
#Override
public HorizontalViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
int layoutId;
switch(viewType) {
case 0:
layoutId = R.layout.item_category_slider_viewed;
break;
case 1:
layoutId = R.layout.item_category_slider_added;
break;
case 2:
layoutId = R.layout.item_category_slider_browse_all;
break;
default:
layoutId = R.layout.item_category_slider_regular;
}
View cardView = LayoutInflater.from(parent.getContext()).inflate(
layoutId, parent, false);
return new HorizontalViewHolder(cardView, context);
}

How to get ArrayList<String> image URLs into ImageView of ListView getView method

#Override
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView == null){
convertView = inflater.inflate(resource, null);
}
ImageView imageView;
imageView = (ImageView) convertView.findViewById(R.id.ivGallery);
for(HospitalModel.Images images: hospitalModelList.get(position).getImagesList()) {
Glide.with(getContext()).load(images).into(imageView);
}
return convertView;
}
// image URLs are stored in string ArrayList . I defined getter and setter for array list but still I don't know how to use get method for showing ArrayList images dynamically in ListView
Extend the class from BaseAdapter
Override getCount() method and return here the total amount of images you need to show.
In the getView load with Glide only ONE url (remove the for loop), the getView method will be called N times to show in the list the "total" amount of images you returned in the getCount method, the position parameter in the getView method will run from 0 to (total - 1).
You should map 1 position to 1 url, maybe you will need to change the way you access the objects that contain the urls.
Okay so first: stop using ListView, use RecyclerView.
Next, you need to override getItemCount(). You can either choose to do this using a for loop:
int count = 0;
for(HospitalModel.Images images: hospitalModelList.get(position).getImagesList()) {
count += images.size();
}
Or, what is likely preferable, flatten your model object into just what this adapter actually cares about (which is the image URL strings). Something like this:
ArrayList<String> imageUrls = new ArrayList<String>();
for(HospitalModel model : hospitalModelList) {
imageUrls.addAll(model.getImagesList().getImageUrls());
}
Then pass in the imageUrls ArrayList to the Adapter instead. You should only need to flatten this when the model is updated (whether that's initialization or changed). When that occurs, use the notify... methods on the adapter.
After that, it's just a simple Glide.with(getContext()).load(imageUrls.get(position)).into(imageView); inside getView (or onBindViewHolder if you're using a RecyclerView, which I HIGHLY recommend).

ListView OnLongClick strange behaviour

I have a ListView within my project. It has many elements, and it uses a custom adapter, since its populated dynamically from a rails server.
I want to change the content of a ListItem when the item is longpressed. In order to achieve this, I have 2 layouts inside the ListItem, with one visible and one hidden.
The issue is that when I longpress an item, the layout changes (As expected), but other ListItems are also affected, and changed in the same way. This appear to occur once for every 5 items, and I cant figure out why.
This is the LongClickListener I'm using, it is located inside de GetView method on the custom adapter:
View v = convertView;
if (v == null){
LayoutInflater vi =
(LayoutInflater)getActivity().getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(R.layout.list_item, null);
}
final LinearLayout placeInfo =
(LinearLayout) v.findViewById(R.id.list_item_info);
final RelativeLayout placeBrief =
(RelativeLayout)v.findViewById(R.id.list_item_brief);
v.setOnLongClickListener(new OnLongClickListener(){
#Override public boolean onLongClick(View v) {
placeInfo.setVisibility(View.GONE);
placeBrief.setVisibility(View.VISIBLE);
return false;
}});
I would appreciate any help, many thanks in advance.
ListViews recycle Views, so you only have a few views for all of your items. You're directly changing one of these view instances to switch between the info|brief. What you need is to save the status of the info|brief flag for the affected position somewhere else (e.g. a list of positions that should be "briefs" in the adapter). That way when you come back into getView() you can display the right one.

Android custom list slow

Is there a better way to create a custom list, for my code here seems to make for a less-responsive list. I can't use any of the standard Android lists because I need two ListView's in a ScrollView.
setContentView(R.layout.alertsview);
inflater = (LayoutInflater)this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
topRows = new ArrayList<View>();
topLayout = (LinearLayout)findViewById(R.id.topListLayout);
topLayout.removeAllViews();
topRows.clear();
List<Camera> camList = new ArrayList<Camera>(SitesOrchestrator.getInstance().currentSite.cameraList);
for (int i = 0; i < camList.size(); i++)
{
Camera cam = camList.get(i);
View row = inflater.inflate(R.layout.cameraalertrow, null);
TextView name = (TextView)row.findViewById(R.id.cameraNameAlerts);
CheckBox chk = (CheckBox)row.findViewById(R.id.camAlertChk);
name.setText(cam.name);
chk.setChecked(cam.alertsEnabled);
chk.setOnCheckedChangeListener(this);
String index = Integer.toString(i);
chk.setTag(index);
topRows.add(row);
topLayout.addView(row);
}
If you need to lists in the same layout, you should create your own Adapter (derive from base adapter might be good), and, suppose you have two arraylist:
ArrayList<TypeA> typeAList;
ArrayList<TypeB> typeBList;
#Override
int getViewTypeCount(){ return 2; } // means you have two different views from it
#Override
int getItemViewType(int position){
if (position>typeAList.size()) return 1;
return 0;
}
getView(int pos, View convertView, ViewGroup parent){
// Check the pos
if (getItemViewType(pos) == 0){
// Inflate view and bind for type A
}
else{
// Inflate view and bind for type B
}
}
In general, having two list view vertically is not encouraged in Android, but putting both of your content to one list do the trick. I also have a tutorial about this, though it is done in MVVM with Android-Binding.
Moreover, adding Views one by one to ScrollView to mimic the ListView would, of course, be very inefficient. The way Android ListView works (which should different from desktop frameworks) is with recycling views. Suppose you are scrolling up, once the child is scroll out of sight, the listview will recycle the topmost one and place it to the bottom, and the possibly recycled view will pass as convertView in the above getView code. Inflating is considered to be quite expensive process, and multiple child views are memory consuming as well, that's the reason why your code, compare to standard ListView, is very inefficient.

Categories

Resources