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
Related
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
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;
}
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!
I have a application that has an activity that shows message logs. The thing is the user must to be able to select some messages and then delete them as you can do when you want to remove some sms messages.
Which is the best way of doing that? Adding more details:
Here is a capture with the messages only.
Here is a capture with the selection layout.
I want to know how they do it to show the CheckBoxes. Did they use two layouts?
I wanna know how they do it to show the checkbox, does they use 2
layouts?
I doubt they use two layouts files. As the rows are very similar it very easy to switch from the layout that doesn't have the CheckBox to the layout that does have the CheckBox just by changing the visiblity. A way to do it would be to have a flag in your adapter that indicates the presence of the CheckBox in the rows and update the CheckBox visibility based on that flag:
boolean checkStatus = false;
//...
public void getView(int position, View convertView, ViewGroup parent) {
//...
if (checkStatus) {
checkBox.setVisibility(View.VISIBLE); //show the CheckBox for each row
} else {
checkBox.setVisibility(View.GONE); // hide the CheckBox for each row
}
//...
}
Then when it's time to show those CheckBoxes(on a Button click, menu click etc) you just have to set the checkStatus flag to the desired value(true for CheckBoxes present, false otherwise) and call notifyDataSetChanged() on your adapter.
Of course you could use two layouts, one that contains the CheckBox and one that doesn't have it. But, generally, you would use two layouts in a ListView when the two layouts are very(or substantial) different(which is not your case). To implement the two layouts you would have to use the methods getItemViewType and getViewTypeCount.
boolean checkStatus = false;
public int getViewTypeCount() {
return 2; // you have two layouts
}
public int getItemViewType(int position) {
if (checkStatus) {
return 1;
} else {
return 0;
}
}
Then in your getView method:
public void getView(int position, View convertView, ViewGroup parent) {
int which = getItemViewType(position)
// make a switch statement and inflate the correct layout file
// based on the which variable if the convertView is null
// do stuff;
}
Of course when it's time to show the CheckBoxes you would have to set the checkStatus flag again and call notifyDataSetChanged() on your adapter.
if you use a listView and an adapter , my guess is that the adapter holds a (pure java) list of the messages . so , when the user removes an item , remove it from the list , based on the item's location in the list .
right after that , call notifyDataSetChanged on the adapter . that's it .
I have achieved something similar for particular items within my ListView in this question.
However, now, I want the user to be able to click on items, which will essentially highlight any item the user has tapped. I keep a list of the tapped items in my application, and should be able to reference this from the ViewBinder when scrolling occurs, however, since I am never adapting anything to the LinearLayout that makes up the ListView itself, I am not sure how to get the LinearLayout object in order to properly set its background color.
I need to be able to do this, because as you may know, when scrolling, Android re-uses the list items, which is fine unless you are changing formatting (coloring, etc.) of individual list items. In that case, you end up with list items that are colored/formatted incorrectly. I am using a ViewBinder to solve the issue when it comes to the TextViews within my list items, but do not know how to do something similar for the background of the ListView itself.
Create a custom row layout- e.g. custom_row.xml, and arrange any views needed, just as you would in a normal layout for an activity (so in this case you would probably provide a textview for the text, and perhaps an icon to the left of it).
Then create your custom adapter by extending an existing adapter, and override the getView method as such. Here's an example that uses a layout custom_row with both a title and subtitle:
class CustomAdapter<T> extends ArrayAdapter<T> {
/** List item title */
protected TextView mTitle;
/** List item subtitle */
protected TextView mSubtitle;
/**
* #param context
* Current context
* #param items
* Items being added to the adapter
*/
public CustomAdapter(final Context context, final List<T> items) {
super(context, R.layout.custom_row, items);
}
/** Construct row */
#Override
public View getView(final int position, final View convertView, final ViewGroup parent) {
View view = convertView;
if (view == null) {
final LayoutInflater li = (LayoutInflater) getContext().getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
view = li.inflate(R.layout.custom_row, null);
}
mTitle = (TextView) view.findViewById(R.id.custom_row_title);
mSubtitle = (TextView) view.findViewById(R.id.custom_row_subtitle);
return view;
}
}
As demonstrated, you can grab the items specified in the custom_row layout you created through the inflater service. Then you can manipulate the objects as needed.
I believe one way is to use a state list where the default state has a transparent background, and the selected state has the background you want.
For example, see Romain Guy's answer here:
Changing background color of ListView items on Android
See also: Android ListView State List not showing default item background