Hiding items in listview properly - android

I would like to show/hide items in my listview. Currently I am doing this by iterating through adapter's data. If item at certain index matches some condition, this line is called:
listView.getChildAt(index).setVisibility(View.GONE);
Item is not visible, but blank space remains (surprisingly View.GONE and View.INVISIBLE acts the same in this case).
I would like to achieve the same effect as if item was deleted from adapter and notifyDataSetChanged() was called, but I don't want to modify underlying data, just hide the item.

I have achieved this as Tonithy suggests here. Added an if clause at the begining of getView method in adapter, which checks if item matches condition to hide. If it does getView returns "null layout".
When I want to hide an item, I set a condition and call .invalidateViews() on my listview.

Do not return different views based on internal property without letting listview know about it, you should reuse views! Otherwise you make very slow apps. For example, take look at this tutorial with benchmark.
getView(...) - reuse convertView!, hide it conditionaly!
use Holder classes, make them "listen" to your adapter signals/evens and change their visual conditionally
Alternative: You can use multiple types of list items within single list:
define list item types (shown, hidden), override getViewTypeCount(): return 2
override getItemViewType(...),
is shown -> 0
is not shown -> 1
more complex getView(...)
get item view type or check if it's shown
dispatch view creation based on type, don't forget, that convertView argument is view for current item type, you should reuse it, if not null!
on change call notifyDataSetChanged()
I have tested approached like this for different stable types, but it should work even when type for item changes and you let observer know about it. This approach will be a lot of faster than one you have posted, because, you reuse views, don't need to always inflate new one!

Call .setVisibility(View.GONE) in override getView method of your CustomeListAdapter for all UI elements of your custom_list_item.
For Example:
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
Holder holder = new Holder();
View rowView;
rowView = inflater.inflate(R.layout.custom_list_item, null);
holder.tvName = (TextView) rowView.findViewById(R.id.textViewName);
holder.img = (ImageView) rowView.findViewById(R.id.imageView1);
if (items[position].NeedForCollapsed){
System.out.println("set Visibility.Gone");
holder.tvName.setVisibility(View.GONE);
holder.img.setVisibility(View.GONE);
return rowView;
}
//new values
holder.tvName.setText(items[position].name);
holder.img.setImageResource(GetImageID(items[position]));
return rowView;
}
public class Holder
{
TextView tvName;
ImageView img;
}
Or call .setVisibility(View.GONE) for your root container like a LinearLayout.
good luck!

Related

Two items get selected in listview android

I'm trying to implement a custom listview. Everything works fine until I use a if () statement inside the getView() method
Without the if() condition a single item gets selected when I select an item but when I add the if() condition, the views are displayed properly but two items (non-adjacent) get selected (1st and last 1st or and second-last, any such combination).
View getView(...){
....
if (!item.getPriceTo().equals(""))
priceToTV.setText(item.getPriceTo());
else
priceToTV.setText(item.getPriceFrom());
return view;
}
Also I'm using saving the previous view to show the selection so the current selection has a red_border and when it is selected a black_border is set to it.:
subItemsListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
Log.d("New Order", "........");
if (previousViewOfSubItems != null && previousViewOfSubItems != view) {
previousViewOfSubItems.setBackgroundResource(R.drawable.black_border);
if (quantity.getText().toString().equals("xx") || quantity.getText().toString().equals("0")) {
viewForVisibility.setVisibility(View.GONE);
layoutForQuantity.setVisibility(View.GONE);
}
}
if (previousViewOfSubItems == view)
return;
previousViewOfSubItems = view;
previousViewOfSubItems.setBackgroundResource(R.drawable.red_border);
viewForVisibility = previousViewOfSubItems.findViewById(R.id.viewForVisibility);
viewForVisibility.setVisibility(View.VISIBLE);
layoutForQuantity = (LinearLayout) previousViewOfSubItems.findViewById(R.id.layoutForQuantity);
layoutForQuantity.setVisibility(View.VISIBLE);
quantity = (TextView) previousViewOfSubItems.findViewById(R.id.subTypeQuantity);
}
});
previousViewOfSubItems = view; seems to be causing the problem,
In Listviews with adapter you should avoid saving view instances, because views are reused by adapters so view can be same for two rows so rather than saving view instance's reference use ViewHolder Design pattern and use view tagging
You need to use ViewHolder Pattern and view tagging to properly identify every view in different position. ListView always recycle the view instead of re-inflating the view again and again.
You can refer to Android Training documentation on how to implement ViewHolder pattern.
ListViews recycle the views in the list. so as you scroll, top views are reused and content replaced using the methods.
Where you set background to Red etc, use Else statements to set it back to your default black.
its beacause it listview reuses view to display items, once the first view is scrolled out the the same view is reused to display the view at bottom of the listview. instead of comparing the view try compairing the position of the view clicked
I have explained about this abnormal behavior in my blog on recyclerview you refer that to solve this problem.
use pojo class to get the status and update the view accordingly

How to create ListView with two (or more) sections and types of item?

Just like ListView in that Navigation Drawer:
Am I need to create three item-types for my custom adapter (Item with icon, separator item, item withhout icon), or I can achieve this in a simpler way?
Maybe I should to use two ListViews?
Thanks in advance.
At first use ViewHolder pattern.
At second make interfase of Item which will include two methods:
getType() - which return the type of your item;
getView(LayoutInflater inflater, View convertView) - which return the view of row. Inside this method create ViewHolder instance, then inflate View parameter to VH and do some action with result view.
. Then create 2 (or how much u need) classes which would implement Item. Define methods. Then in ListAdapter in getView() call getView() of items and return it to list.
And dont use 2ListView`. Always try to write pure code.
udenfox follow this tutorial:
http://www.android4devs.com/2014/12/how-to-make-material-design-navigation-drawer.html
I'm sure u'll get what you want.
You can try using the new recyclerview and the recyclerview adapter, which allows you to do that override the getItem method and return different items for different positions , also override getItemViewType and return differrent layouts for any position.
Pass A flag into your getview , This may not be the most efficient way i can currently think of but im pretty sure it will be of help.
A recyclerview is a really good option.
But ,
lets try to do the job with existing stuff just in case.
The following is just an example , it may not be what you want . You should look into it and grasp how it's done and implement it yourself.
Firstly how many types of list items are you gonna have ?
- let's take 3 for now.
Now Pass an integer value for each type of list item in your getview method either it goes in an array type or while parsing , for example you can have an if statement in your getview method which checks the int value in a given array item and inflates a different view depending on that value.
EXAMPLE CODE :
public View getView(final int position, View convertView, ViewGroup parent) {
// Declare Variables Globally , (here temporarily for answer)
ArrayList<HashMap<String, String>> data; // arraylist passed to custom adapter in my case
HashMap<String, String> resultp = new HashMap<String, String>(); // just a new hashmap which stores individual items from above arraylist
result = data.get(position);
inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if(result.get("type")==1){
View v = inflater.inflate(R.layout.type1, parent, false);
//declare variables in your view , lets take some textview
textview.setText(result.get("something"));
return v;
}else if(result.get("type")==2){
View v = inflater.inflate(R.layout.type2, parent, false);
//take an image for example here
imageview.displayimagewithurl(result.get(url));
return v;
}else{ //type 3
View v = inflater.inflate(R.layout.type3, parent, false);
//in here lets take a layout with a textview and an image
//You know what to do ..
return v;
}
}

How to add switch widget to a row in list view?

I have created a custom ListView by following a tutorial on Android Interview. Here's the link:
http://www.androidinterview.com/android-custom-listview-with-image-and-text-using-arrayadapter/
I want to add a switch widget to the first row of the ListView. How can do it on only 1 row or some specific rows like 1st, 4th, 7th, 16th etc?
If you are new to listViews, the easy answer is:
put a switch in your basic layout. The layout file is the xml file whose id is R.layout.mylist, in the example you linked.
hide the switch for every position that isn't position 0, show it otherwise. You can't rely on what the default is, because of view recycling, so you must either hide or show.
The more advanced answer is:
Use a Base Adapter as it has the methods getViewType and getViewTypeCount (an array adapter does not). You would have to code the relationship between your array and the view elements, which again if you are new to listViews/adapters, might be too daunting.
Create two layouts (xml files), one with the switch.
Override getViewType and getViewTypecount.
You have two view types now, so getViewTypeCount should return 2, and getViewType should return 0 for every position that is not position 0, and return 1 for the rest.
In getView, inflate convertView if null with correct layout. You can call getViewType(position) and switch on the result.
The advantage of doing it this way is the adapter will only pass the correct view type to the adapter so you don't have to hide/unhide the switch each time getView is called. Android only makes one layout that has a switch. Thus, it's more efficent.
You can Send another parameter to adapter of listview and make if condetion in the adapter
if (parameter == 1)
{ Show Switch }
I have modified my layout to add switch widget to all rows and then I have set the visibility to "GONE" for every switch in every row except 1st one.
public View getView(int position,View view,ViewGroup parent) {
LayoutInflater inflater = context.getLayoutInflater();
View rowView = inflater.inflate(R.layout.mylist, null,true);
Switch mSwitch = (Switch) rowView.findViewById(R.id.switch1);
if(position != 0) {
mSwitch.setVisibility(View.GONE);
}
return rowView;
};

Android multiple listviews with checkbox

I'm new to Android programing. I need to create a list of 1000 goods for users to click and check or the one they want to buy. I've created the array list and added it to my custom adapter and I have also added it to my list view. my problem is how to get the position for each item selected and I need clarification on the getView and the ViewHolder. I'm not working with toast
See the below tutorials,
http://sunil-android.blogspot.in/2013/04/android-listview-checkbox-example.html
http://developerandro.blogspot.in/2013/09/listview-with-checkbox-android-example.html
http://aboutyusata.blogspot.in/2013/11/how-to-make-listview-with-checkbox-in.html
Hope it helps.
You have to use ListView getCheckedItemPositions()
/**
* Returns the set of checked items in the list. The result is only valid if
* the choice mode has not been set to {#link #CHOICE_MODE_NONE}.
*
* #return A SparseBooleanArray which will return true for each call to
* get(int position) where position is a checked position in the
* list and false otherwise, or <code>null</code> if the choice
* mode is set to {#link #CHOICE_MODE_NONE}.
*/
public SparseBooleanArray getCheckedItemPositions() {
if (mChoiceMode != CHOICE_MODE_NONE) {
return mCheckStates;
}
return null;
}
getView() method is called when new list item that is adapted is being shown on your screen. That is why you need to take good care of the memory and setTag() for every item you inflate. Then when old item is viewed again you will not render it like a new one but get it by the tag you submitted for that item.
Example: If you have 1000 items only couple of those will be shown on the screen and your program will call getView() for those items that are visible plus for couple of items bellow those that are visible so you don't see the lag in inflation while scrolling.
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = convertView;
if(view==null)
{
LayoutInflater inflater=getLayoutInflater();
view=inflater.inflate(R.layout.list, parent, false);
TextView textView=(TextView)view.findViewById(R.id.text_hint);
ImageView imageView=(ImageView)view.findViewById(R.id.img);
// here we will setTag() our view later
}
//... (here we will manage the views and set some click listeners)
Now, what is a viewHolder?
The ViewHolder basically is the private class inside your adapter that is used to keep you inflated layout elements together and to let you manipulate those view after you got the using findViewById(int resId).
Some code:
private class ViewHolder{
public ImageView imageView;
public TextView textView;
public ViewHolder(ImageView imageView,TextView textView) {
this.imageView = imageView;
this.textView = textView;
}
}
Now to setTag() as promised.
view.setTag(new Holder(imageView,textView));
Now with this in mind you can get your ViewHolder using the code above and views that you got in the first part of my code. (this is what we will write instead of 3 dots)
ViewHolder h = (Holder) view.getTag();
h.textView.setText(ar[position]);
int resID = getResources().getIdentifier(pic[position], "drawable", getPackageName());
h.imageView.setImageResource(resID);
// here I did set some source to my imageView that is in my layout, but
// you can do whatever you want with these views. And that is what I'm
// going to explain later in this text.
return view;
}
Okay, what next?
After getting the ViewHolder from tag you have your entire layout as a View that can get his OnClickListener easily. Or just a checkBox can get OnCheckedChangeListener.
Inside methods for this listeners you can send the data to you controller (in case you are using MVC model) or to your activity that hosts this View, where you can save the state of the checkbox and title of the item that has been clicked.
For example you can do something like this on your corresponding listener method:
(MainActivity)context.markAsChecked(String title);
But in this case you will also need to have the opposite method for unchecking
(MainActivity)context.markAsUnchecked(String title);
and you will have to handle this in your MainActivity properly by browsing through the array of data that has been selected.
The second solution is to have :
(MainActivity)context.toggleState(String title);
And to handle both events checked and unchecked.
Your method in your Activity would need to do something similar to this:
public void toggleState(String title){
if (data.contains(title))
data.remove(title);
else data.add(title);
}
Then after your user checks what he wants you will have all the checked elements in your data array that in this case is ArrayList. You can also you HashMap for this if you like or something else too.
Hope this helps.
I will be more than happy to answer all of your questions if have some more.
Maybe controller implementation is something that you would like to consider in this case. That would mean that you would be using MVC model for better control of your app and data, and to delegate the tasks to responsible classes and methods. Not to put everything in one Activity :)
Bye

Async image loading, check if an image is recycled

This question came to me after reading this: Performance tips (specifically the part named "Async loading"). Basically he's trying to save info about a row to see if it's been recycled yet and only set the downloaded image if the row is still visible. This is how he saves the position:
holder.position = position;
new ThumbnailTask(position, holder)
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, null);
Where ThumbnailTask constructor is:
public ThumbnailTask(int position, ViewHolder holder) {
mPosition = position;
mHolder = holder;
}
In onPostExecute() he then does the before mentioned check:
if (mHolder.position == mPosition) {
mHolder.thumbnail.setImageBitmap(bitmap);
}
I just don't see how this gives any result. The Holder and the position are set in the constructor at the same time, to the same value (the position in the holder is the same as the position in mPosition). They don't get changed during the AsyncTask (it's true that the position might change in getView(), but the ones stored in the AsyncTask as private members are never manipulated). What am I missing here?
Also saving the position doesn't seem like a good option in the first place: I believe that it's not guaranteed to be unique, and if I recall correctly it resets itself to 0 after scrolling. Am I thinking in the right direction?
Background (you probably know this, but just in case): An adapter contains a collection of objects and uses info from these objects to populate Views (each view is a line item in the list). The list view is in charge of displaying those views. For performance reasons the ListView will recycle views that are no longer visible because they scrolled off the top or the bottom of the list. Here's how it does it:
When the ListView needs a new view to display it calls the Adapter's getView with an integer argument "position" to indicate which object in the Adapter's collection it wants to see (position is just a number from 1 to N -1) where N is the count of objects in the adapter.
If it has any views that are no longer visible, it will pass one of them in to the Adapter, too, as "convertView" This says "reuse this old view rather than creating a new one". A big performance win.
The code in the article attaches a ViewHolder object to each view it creates that, among other things, contains the position of the object requested by the ListView. In the article's code, this position is stashed away inside the ViewHolder along with a pointer to the field within the view that will contain the image. The ViewHolder is attached to the View as a tag (a separate topic).
If the view gets recycled to hold a different object (at a different position) then ListView will call Adapter.getView(newPosition, oldView...) The code in the article will store new position into the ViewHolder attached to the oldView. {make sense so far?) and start loading this new image to put into the view.
Now in the article, it is starting an AsyncTask to retrieve data that should go into the view) This task has the position (from the getView call) and the holder (from the oldView). The position tells it what data was requested. The holder tells it what data should currently be diplayed in this view and where to put it once it shows up.
If the view gets recycled again while the AsyncTask is still running, the position in the holder will have been changed so these numbers won't match and the AsyncTask knows it's data is no longer needed.
Does this make it clearer?
When AsyncTask is passed with ViewHolder and position it is given value of position (say 5) and value of reference (not a copy) to ViewHolder object. He also puts current position in ViewHolder (said 5), but the whole "trick" here is that for recycled views, the old ViewHolder object is also re-used (in linked article):
} else {
holder = convertView.getTag();
}
so whatever code references that particular ViewHolder object, will in fact check against its position member value at the moment of doing check, not at the moment of object creation. So the onPostExecute check makes sense, because position value passed to task constructor remains unchanged (in our case it has value of 5) as it is primitive, but ViewHolder object can change its properties, if view will be reused before we reach onPostExecute.
Please note we do NOT copy ViewHolder object in the constructor of the task, even it it looks so. It's not how Java works :) See this article for clarification.
Also saving the position doesn't seem like a good option: I believe
that it's not guaranteed to be unique, and it resets itself to zero
after scrolling. Is this true?
No. Position here means index in *source data set, not visible on the screen. So if you got 10 items to display, but your screen fits only 3 at the time, your position will be in range 0-9 and visibility of the rows does not matter.
As I understand you are trying to cancel the async-loading-task of the image when the view recycled, and no longer on screen.
To achieve that you can set up an RecyclerListener to the listview. It will be invoked when the listview don't need this view (when is not on screen), just before it passes it as a recycled view to the Adapter.
within this listener you can cancel your download task:
theListView.setRecyclerListener(new RecyclerListener() {
#Override
public void onMovedToScrapHeap(View view) {
for( ThumbnailTask task : listOfAllTasks )
task.viewRecycled(task);
}
});
and within ThumbnailTask :
public void viewRecycled(View v){
if(mHolder.theWholeView == v)
v.cancel();
}
Don't for to implement the cancel.
Note that its not the best approach since you should keep track of all your asynctask tasks. note that you could also cancel the task within the adapter where you also get the
public View getDropDownView (int position, View recycledView, ViewGroup parent){
//.. your logic
}
but note that this might require you to allocate the ThumbnailTask within the adapter with is not good practice.
note that you could also use image loading libraries that do eveything for you, from async download to chaching. for instance : https://github.com/nostra13/Android-Universal-Image-Loader
The accepted answer and Marcin's post already describe perfectly what's supposed to happen. However, the linked webpage does not and the google site on this topic is also very vague and only a reference for people who already know about the "trick". So here's the missing part, for future references, which shows the necessary additions to getView().
// The adapter's getView method
public View getView(int position, View convertView, ViewGroup parent) {
// Define View that is going to be returned by Adapter
View newViewItem;
ViewHolder holder;
// Recycle View if possible
if (convertView == null) {
// No view recycled, create a new one
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
newViewItem = inflater.inflate(R.layout.image_grid_view_item, null);
// Attach a new viewholder
holder = new ViewHolder();
holder.thumbnail = (ImageView) newViewItem.findViewById(R.id.imageGridViewItemThumbnail);
holder.position = position;
newViewItem.setTag(holder);
} else {
// Modify "recycled" viewHolder
holder = (ViewHolder) convertView.getTag();
holder.thumbnail = (ImageView) convertView.findViewById(R.id.imageGridViewItemThumbnail);
holder.position = position;
// Re-use convertView
newViewItem = convertView;
}
// Execute AsyncTask for image operation (load, decode, whatever)
new LoadThumbnailTask(position, holder).execute();
// Return the ImageView
return newViewItem;
}
// ViewHolder class, can be implemented inside adapter class
static class ViewHolder {
ImageView thumbnail;
int position;
}

Categories

Resources