listview getting new instance when swiping the listview - android

I have one listview in my application,it contains two rows one for task and another one for alarm,date,severity. Initially first row of the list item only displayed for all list item and the second one is invisible. When i click the list item the second row displayed for that item as well as click another list item at that time the above list item closed that second row. Its working fine for me...My problem is if i open one list item and then swipe the listview at then i click the another list item at that time the above one cannot be closed because the above list item instance will be chnaged.please any one help me how to solve this problem...
int lastselectedPosition == -1
#Override
public void onItemClick(AdapterView<?> arg0, View view, int position,
long id) {
TextView textviewDate=(TextView)view.findViewById(R.id.taskTimeidDaytoDay);
selectedtaskDate=textviewDate.getText().toString().trim();
if (lastselectedPosition == -1) {
Log.i(TAG,"Loopif:"+lastselectedPosition);
TextView twTaskTime = (TextView) view
.findViewById(R.id.taskTimeidDaytoDay);
TextView twSeverity = (TextView) view
.findViewById(R.id.severityidDaytoDay);
TextView twAlarm = (TextView) view
.findViewById(R.id.alarmidDaytoDay);
twAlarm.setVisibility(view.VISIBLE);
twSeverity.setVisibility(view.VISIBLE);
twTaskTime.setVisibility(view.VISIBLE);
lastselectedPosition = position;
lastSelectedItem = arg0.getChildAt(position);
} else {
// Log.i(TAG,"LoopElse:"+lastselectedPosition);
lastSelectedItem.findViewById(R.id.taskTimeidDaytoDay)
.setVisibility(lastSelectedItem.GONE);
lastSelectedItem.findViewById(R.id.severityidDaytoDay)
.setVisibility(lastSelectedItem.GONE);
lastSelectedItem.findViewById(R.id.alarmidDaytoDay).setVisibility(
lastSelectedItem.GONE);
if (lastselectedPosition != position) {
view.findViewById(R.id.taskTimeidDaytoDay).setVisibility(
view.VISIBLE);
view.findViewById(R.id.severityidDaytoDay).setVisibility(
view.VISIBLE);
view.findViewById(R.id.alarmidDaytoDay).setVisibility(
view.VISIBLE);
lastselectedPosition = position;
lastSelectedItem = arg0.getChildAt(position);
} else {
lastselectedPosition = -1;
lastSelectedItem = null;
}
}
GetView():
#Override
public View getView(int position, View view, ViewGroup parent) {
Log.i("XXXX", "Inside getView");
final DaytoDayTaskGetterSetter objDaytoDaygetset=getItem(position);
TextView textviewTask;
TextView txtviewAlarm ,txtviewTaskTime ,txtviewSeverity;
Log.i(TAG,"InsideGetView:"+position);
LayoutInflater inflater=(LayoutInflater)context.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
if(view==null)
{
view=inflater.inflate(R.layout.daytodaylistlayout,null);
}
Log.i("XXXX", "before first test");
textviewTask=(TextView)view.findViewById(R.id.tasknameidDaytoDay);
txtviewAlarm=(TextView)view.findViewById(R.id.alarmidDaytoDay);
txtviewSeverity=(TextView)view.findViewById(R.id.severityidDaytoDay);
txtviewTaskTime=(TextView)view.findViewById(R.id.taskTimeidDaytoDay);
return view;
}
In first i click the "gdfgdtet" list item it show another row and then i click the second list item "dfgsdgsd" at that time the above list item "gdfgdtet" closed the second row.This is a normal output.Suppose if i open the "gdfgdtet" list item and then swipe the listview at that time both of "gdfgdtet" "dfgsdgsd" will be opened and crashed...because the above one list item reference changed when i am swiping please how to solve this problem...

I'll try to provide you a good answer that explains why you are having this problems, but the general idea is that you have to see this video - http://www.youtube.com/watch?v=wDBM6wVEO70
please take my words kindly - you don't seems to understand what ListView + BaseAdapter recycling mechanism is all about, and I strongly recommend you see the full video I linked you to, and read more about that.
in general, the specific problem in your code is that you are holding reference to listview item (lastSelectedItem), then trying to use it latter assuming it's still representing same list item. that's wrong. in that stage (after scrolling) the view already been recycled to represent another item in the list (based on the adapter implementation).
listView's number of childs is not the size of adapter.getCount()!!!!!!!!
listViews's number of childs = number of visible list items on screen + 1 + headers + footers
let's say you have the 5 first items visible on screen, then you are scrolling down. when you see the 7 item you actually see the same view instance that used to show the first list item and been recycled.
getView will call in this stage with convertView != null and position in the adapter to let you reuse the item by putting new values such different text/image to the same instance
this mechanism provides ability to display list of "infinite" number of items in the list, and holding in memory only a few number of views. imagine that you have list of 5000 items in the list, and each one of them have different view instance - you would get outOfMemory exception in a sec!
complete explanation about that would be hard to write in stackoverflow answer's context.
it just too long trying to explain one of the most important and complex UI components in android, but this links would be a good start:
http://www.youtube.com/watch?v=wDBM6wVEO70
How ListView's recycling mechanism works
http://mobile.cs.fsu.edu/the-nuance-of-android-listview-recycling-for-n00bs/
if you are interstead in "quick" fix for your specific problem, the solution would be:
hold in the data structure represents your list item additional field indicating if it in "close" or "open state. when item been clicked - change the data accordinly and call notifyDatasetChanged(). inside the getView() check if item is open or close and populate it accordinly
by the way - it's not only "quick fix" solution, but also the right thing to do anyway

You should pay attention to Tal Kanel's answer and consider this one to be an extension to it. His advice will help you in the long run.
Add a boolean field to DaytoDayTaskGetterSetter class:
public class DaytoDayTaskGetterSetter {
....
....
boolean open;
public DaytoDayTaskGetterSetter (.., .., boolean o) {
....
....
open = o;
}
....
....
public boolean shouldOpen() {
return open;
}
public void setOpen(boolean o) {
open = o;
}
}
In your getView(), check if the object has its open value set:
DaytoDayTaskGetterSetter obj = (DaytoDayTaskGetterSetter) getItem(position);
if (obj.shouldOpen()) {
// Set visibility to true for the items
} else {
// Set visibility to false for the items
}
On list item click, traverse the list and set open for all list items to false. Use the position to retrieve DaytoDayTaskGetterSetter and set its open to true:
#Override
public void onItemClick(AdapterView<?> arg0, View view, int position, long id) {
for (DaytoDayTaskGetterSetter obj : listContainingObjects) {
obj.setOpen(false);
}
DaytoDayTaskGetterSetter clickedItem = (DaytoDayTaskGetterSetter)
yourAdapter.getItem(position);
clickedItem.setOpen(true);
yourAdapter.notifyDataSetChanged();
}
Edit 1:
#Override
public void onItemClick(AdapterView<?> arg0, View view, int position, long id) {
DaytoDayTaskGetterSetter clickedItem = (DaytoDayTaskGetterSetter)
yourAdapter.getItem(position);
if (clickedItem.shouldOpen()) {
clickedItem.setOpen(false);
} else {
for (DaytoDayTaskGetterSetter obj : listContainingObjects) {
obj.setOpen(false);
}
clickedItem.setOpen(true);
}
yourAdapter.notifyDataSetChanged();
}

Related

Listview data model updated but items not displaying

I have searched these forums for nearly 3 hours and seen several similar questions but none of the answers works for me.
I have a single Activity, with several card views. One of the card views has a Spinner with string values and a very simple ListView. The user selects a value from the Spinner, between 1 and 12. The ListView should then display a number of strings equal to the value selected, based on the position in the spinner list. For example, user selects 2nd item in spinner list and the ListView displays 2 strings. I have a custom adapter on the listview. The ListView itself initially displays a single row, which is correct. However, after the user selects a value from the spinner, the listview is not displaying the extra rows, it still only displays one row. The data for the ListView comes from an ArrayList. I have checked the data model of the adapter after the user selects a value and it has the correct number of entries, as does the ArrayList itself, yet no matter what I try the ListView itself still only display the first row. I have tried NotifyDataSetChanged and every variation of Invalidate without success.
The various code samples:
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
if (parent == spDoseFrequency){
Toast.makeText(this,String.valueOf(position),Toast.LENGTH_SHORT).show();
rebuildReminderTimesList(position + 1);
}
}
private void rebuildReminderTimesList(int numberOfTimes){
Toast.makeText(this,"yup",Toast.LENGTH_SHORT).show();
//reset selected item to position 1
myApp.iSelectedReminderTimeIndex = 0;
//clear array and list, then rebuild with hourly timeslots
iarrTimes = new int[numberOfTimes][2];
liReminderTimes.clear();
int startTime = 8;
for (int i = 0; i < numberOfTimes; i++){
iarrTimes[i][0] = startTime + i;
iarrTimes[i][1] = 0;
liReminderTimes.add(pad(startTime + i) + ":00");
}
//refresh the listview
myAdapter.notifyDataSetChanged();
}
public class ReminderListAdapter extends ArrayAdapter {
List<String> liTimes;
Context ctx;
LayoutInflater inf;
public ReminderListAdapter(Context ctx, List<String> liTimes) {
super(ctx, R.layout.reminder_time_listview, liTimes);
this.liTimes = liTimes;
this.ctx = ctx;
inf = LayoutInflater.from(ctx);
}
public void setLiTimes(List<String> liTimes){
this.liTimes = liTimes;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = convertView;
ViewHolder viewHolder;
if (view == null){
view = inf.inflate(R.layout.reminder_time_listview,parent,false);
viewHolder = new ViewHolder();
viewHolder.sTime = (TextView) view.findViewById(R.id.tvTime);
view.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) view.getTag();
}
viewHolder.sTime.setText(liTimes.get(position));
return view;
}
private static class ViewHolder{
TextView sTime;
}
}
Any help would be appreciated as this is driving me crazy.
Quick update to this question: I have just tested supplying the initial list more than one value but even then it only displays the first item. Is there perhaps a problem with using ListView inside a CardView object? All my other cards work fine, only the ListView one fails to display properly.
Also, I have tried amending the code so that instead of changing the number of elements in the list, it just changes the text in the string of the first element and this works fine. So the notifyDataSetChanged appears to be working, but it just won't display more than one item. A quick check of the Adapter.getCount() method also gives the correct number of elements back, but won't display them.
A lot of folks forget to do the notifyDataSetChanged() call, but you've got that. Are you using a custom adapter? If so, that makes this sound like an issue with one or more of the adapter's methods. In particular, it sounds like getCount or getView might not be returning what they should be. That could either be because of a flawed logic issue, the underlying data source isn't being updated correctly, or the underlying data source isn't the same object you think it is. Without seeing your adapter though, it's hard to diagnose.
I found the problem. I had several CardView objects inside a LinearLayout, which itself was inside a ScrollView. As soon as I removed the ScrollView, the List inside the Card displayed properly. This has unfortunately created another problem in that I can no longer scroll the page to see later cards, which I have not yet found a solution for, but that is a different topic.

How to change other items in a Android ListView when one item is clicked

I have a ListView that contains items with checkboxes that should behave sometimes like a CHOICE_MODE_MULTIPLE and sometimes like a CHOICE_MODE_SINGLE. What I mean is for certain items in the list, when selected certain other items needs to be deselected whilst other can remain selected.
So when item A is checked I can find in my data the item B that needs to be unchecked but how do I get the UI to refresh to show this as I (I believe) cannot find the actual View that represents B but just it's data?
It sounds like you're off to a good start. You're right that you should be manipulating the underlying data source for item B when A is clicked.
Two tips that may help you:
Your getView() method in the Adapter should be looking at your data source and changing convertView based on what it finds. You cannot find the actual View that represents B because in a ListView, the Views are recycled and get reused as different data needs to be displayed. Basically, when an item is scrolled off the list, the View that was used gets passed to the getView() function as convertView, ready to handle the next element's data. For this reason, you should probably never directly change a View in a ListView based on user input, but rather the underlying data.
You can call notifyDataSetChanged() from within your adapter to signal that somewhere the underlying data has been changed and getView() should be called again for the elements currently displayed in your list.
If you're still having trouble, feel free to post some code that illustrates the specific problem that you're having. It's much easier to provide concrete advice when the problem is better defined. Hope this helps!
you can use singleChoice alartDialog, i have used like:
private int i = 0; // this is global
private final CharSequence[] items = {"Breakfast", "Lunch", "Dinner"}; // this is global
Button settings = (Button)view.findViewById(R.id.settings);
settings.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(final View v) {
AlertDialog.Builder builder = new AlertDialog.Builder(v.getContext());
//Title of Popup
builder.setTitle("Settings");
builder.setSingleChoiceItems(items, i,
new DialogInterface.OnClickListener() {
// When you click the radio button
public void onClick(DialogInterface dialog, int item){
i=item;
}
});
builder.setPositiveButton("Confirm",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int item) {
if (i == 0) {
//it means 1st item is checked, so do your code
}
if (i == 1) {
//it means 2nd item is checked, so do your code
} /// for more item do if statement
}
});
//When you click Cancel, Leaves PopUp.
builder.setNegativeButton("Cancel", null);
builder.create().show();
}
});
i have initialized i=0, so that for the very first time when user click on settings button, the first item is selected. and after then when user select other item, i have saved the i value so that next time when user click settings button, i can show user his/her previously selected item is selected.
I come across and solve this question today.
public class ItemChooceActivity extends Activity implements OnItemClickListener {
private int chosenOne = -1;
class Madapter extends BaseAdapter {
.....
.....
#Override
public View getView(final int position, View convertView,
ViewGroup parent) {
// TODO Auto-generated method stub
if (chosenOne != position) {
set the view in A style
} else {
set the view in B style
}
return convertView;
}
}
#Override
public void onItemClick(AdapterView<?> arg0, View view, int position,
long arg3) {
,,,,
chosenOne = position;
adapter.notifyDataSetChanged();
,,,
}
}

Getting data from custom list view on click

I have a custom ListView with two TextViews both containing different values. What I want to be able to do it get the contents from one of these TextViews when an item is clicked.
This is the code I have so far:
listView.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
String value;
// value = (get value of TextView here)
}
});
I want to be able to assign value to the text of one of the TextView's.
Although #Sam's suggestions will work fine in most scenarios, I actually prefer using the supplied AdapterView in onItemClick(...) for this:
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Person person = (Person) parent.getItemAtPosition(position);
// ...
}
I consider this to be a slightly more fool-proof approach, as the AdapterView will take into account any header views that may potentially be added using ListView.addHeaderView(...).
For example, if your ListView contains one header, tapping on the first item supplied by the adapter will result in the position variable having a value of 1 (rather than 0, which is the default case for no headers), since the header occupies position 0. Hence, it's very easy to mistakenly retrieve the wrong data for a position and introduce an ArrayIndexOutOfBoundsException for the last list item. By retrieving the item from the AdapterView, the position is automatically correctly offset. You can of course manually correct it too, but why not use the tools provided? :)
Just FYI and FWIW.
You have a few options. I reference the code from your previous question.
You can access this data from the row's layout view:
ViewHolder holder = (ViewHolder) view.getTag();
// Now use holder.name.getText().toString() and holder.description as you please
You can access the Adapter with position:
Person person = mAdapter.getItem(position);
// Now use person.name and person.description as you please
(By the way in your Person class, name and description are public so you don't need the get methods.)
Override following method in adaterclass.
public String[] getText() {
return text;
}

How to identify reused widgets in ListViews

I have what I consider to be a strange dilemma, although YMMV.
I'm using a layout file that describes each line/row in a ListView (nothing too exotic about that). I have an id assigned to each one, such as:
android:id="#+id/checkBox1"
android:id="#+id/checkBox2"
android:id="#+id/checkBox3"
android:id="#+id/contactLabel" // a TextView
Now this doesn't seem to make sense, as these ids should be unique, so what is the id of the second
row? That is, if "row 1" honors the specified ids of checkbox1, checkbox2, checkbox3, and contactLabel, what would the "row 2" ids be?
I'm curious, but also I need to know because I want to save the values of the checkboxes to a SharedPreferences object.
Who has a clue about how to get around this?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Update
The first thing I need to solve is how to respond to a click on the ListView. This is my current conundrum related to all of this:
ListView doesn't know it's been clicked, or won't tell me
I've added this event handler to my ListActivity:
#Override
protected void onListItemClick(ListView l, View v, int position, long id) {
String item = (String) getListAdapter().getItem(position);
Toast.makeText(this, item + " selected", Toast.LENGTH_LONG).show();
}
...but it's not getting called. I click on the Contacts that display, but no go.
I also tried it this way:
lv.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
Toast.makeText(getApplicationContext(),
"Click ListItem Number " + position, Toast.LENGTH_LONG)
.show();
}
});
...still no joy...I put breakpoints on both "Toast" lines, and they never get reached.
I read here:
http://www.vogella.de/articles/AndroidListView/article.html#listsactivity_layout
...that, "In case you need more the just a ListView in your Activity, you can use you own layout for
ListActivity."
...which I do, because I add a header and a footer in addition to the listview.
It goes on to say, "In this case your layout must have an ListView element with the android:id
attribute set to #android:id/list."
So I added this to my layout:
...but it makes no difference one way or the other.
The ID's for the items within the ListView widget are referenced through their parent view when you inflate it in your getView() method.
To elaborate, you would have something like this is you ListView adapter.
public View getView(int position, View convertView, ViewGroup viewGroup) {
if (convertView == null) {
convertView = LayoutInflater.from(ctx).inflate(R.layout.list_view_item, null);
}
Nown, a new view instance exists as the convertView. You can access your widgets using covertView.findViewById(R.id.checkBox1), convertView.findViewById(R.id.checkBox2), etc.
Each of these views is a child of your ListView. You can reference each individual view from your ListView using the getChildCount() and getChildAt() methods from the ListView object.
However, since it is recommended to use the convertView view to recycle views, in that case you will only have reference to the views on screen at a time.
Also, with regards to the SharedPreferences, all the views in your ListView are populated by an Adapter subclass which would be the actual object that puts the values in the Checkbox and TextView widgets. This Adapter has a dataset that you provide it. Why not reference the values from the dataset directly, instead of trying to find them from the list items which are populated from the dataset in any case ? You can write to the dataset from the ListView when someone clicks a CheckBox so you have an easy ordered reference to all the items in the ListView.
UPDATE: Added dummy source code
OK. Let's start with a hypothical list. We want to display say five items on the list. For simplicity, I'll assume each has a TextView and a Checkbox. So my container class is:
class Item {
String textView;
boolean checked;
}
Now in my Activity where I want to display this list, I put an ArrayList of items (you can use just about any datastructure) as a class variable. Then I get the ListView reference and assign it an adapter.
class MyActivity extends Activity {
ArrayList<Item> listItems;
.....
onCreate(Bundle icicle) {
.....
ListView listView = (ListView) findViewById(R.id.listView1); // this will be you list view
MyAdapter listAdapter = new MyAdapter();
listView.setAdapter(listAdapter);
....
// Rest of your Activity
....
MyAdapter extends BaseAdapter {
int getItemCount() {
return listItems.size();
}
Item getItem(int position) {
return listItems.get(position);
}
View getView(int position, View convertView, ViewGroup parent) {
// Here's the important part
Item currentItem = listItems.getItem(position); // Since the array is a class variable, you can do either get or getItem
..... // do the standard individual item inflating ....
checkbox = convertView.findViewById(R.id.checkbox);
checkbox.OnItemSelectedListener( new OnItemSelectedListener() { // or whatever listener there should be... I didn't check
... // do whatever...
currentItem.setChecked(true);
}
When you want to retrieve what items were clicked, just iterate through the Item class and find which ones are true or you perform whatever action you want within the Listener since you have a reference identifying individual members of the ListView dataset (here listItems ArrayList).
Apologies for any errors. Didn't do any checking.

How to Set Background Color or Drawable to a Specific Item and when the listview is scrolled to have those colors stay

I have a ListView that is populated with 50 items.
This is the xml that I use for my ArrayAdapter.
<?xml version="1.0" encoding="utf-8"?>
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#android:id/text1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:gravity="center_vertical"
android:paddingLeft="6dip"
android:minHeight="?android:attr/listPreferredItemHeight"
android:background="#drawable/listview_background"
android:textColor="#ff000000">
</TextView>
I type
ArrayAdapter<String> list = new ArrayAdapter<String>(this, R.layout.my_simple_list);
to create a new ArrayAdapter. Then I add a String[] items to the list. Then I call
setListAdapter(list);
To add Strings from the String[] items to list, I use a for loop to loop through the String array and add each String to list using command: list.add(items.get(i)); where i is my for loop counter.
for(int i=0;i<items.size();i++){
list.add(items.get(i));
}
This gets my ListView created and populated. I also have an onListItemClick function.
public void onListItemClick(ListView parent, View v, int position, long id) {
String Select = this.getListAdapter().getItem(position).toString();
if(copyitems.contains(Select)){
v.setBackgroundResource(R.drawable.listview_background);
copyitems.remove(Select);
}else{
copyitems.add(Select);
v.setBackgroundColor(Color.GRAY);
}
}
In the onListItemClick function copyitems is an ArrayAdapter variable that I declare as a global variable. So what this function does is store the text from the selected items of the listview so later I can copy that selected text. So if an item is selected it's text is added to copyitems and if the same item is selected it will be removed form copyitems. Every time an item is added to copyitems the background of that item is changed to Gray and if an item is removed from copyitems the background of that item is set back to original. All this work fine. But when I have lots of items(like 50) they don't all fit to the screen so if I want to select an item that is not on screen then I have to scroll down or up to get to the item. The wierd this is that when I scroll away from a selected item and comeback to it the Background changes to original and a different item has the Gray background. So basically if my screen fits 10 items and I select the 1st one and scroll to select others that are passed 10(for example 11). When the 1st item leaves the screen and the 11 comes in, it is already colored Gray but I didn't selected. When I scroll back up to see the 1st item, the background may or may not be Gray. If its not Gray so other item's Background is set to Gray like the 2nd one. Is this happening because the items are recycled when they go off the screen and because of it the positions change of the items? If it is how can I disable that so I only have items background being gray if I have selected that item. If there is a better approach to what I'm trying to do please tell me. I'm new to android.
You should write your own Adapter. I would recommend you to extend from BaseAdapter. Code would look like this:
// These are members of your activity
private List<String> mList = null;
private int mNewlyAddedItem;
private class Adapter extends BaseAdapter {
#Override
public int getCount() {
return mList.size();
}
#Override
public Object getItem(int position) {
return mList.get(position);
}
#Override
public long getItemId(int position) {
return (long)position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if(position == mNewlyAddedItem) {
convertView.setBackgroundColor(Color.GRAY);
} else {
convertView.setBackgroundResource(R.drawable.listview_background);
}
// Set text to our TextView
TextView nameText = (TextView)convertView.findViewById(R.id.text1);
nameText.setText(mList.get(position));
return convertView;
}
}
Instead of background changing inside your onListItemClick handler you should just assign mNewlyAddedItem:
public void onListItemClick(ListView parent, View v, int position, long id) {
String Select = this.getListAdapter().getItem(position).toString();
if(copyitems.contains(Select)){
copyitems.remove(Select);
mNewlyAddedItem = -1;
}else{
copyitems.add(Select);
mNewlyAddedItem = position;
}
}
mNewlyAddedItem = position;
I hope I understood your task right. Anyway, you got the point: you should change adapter code to draw your views based on some state (which you can set using Activity member variables), change the state and your views will update (you can also call getListView().invalidateViews() to force redrawal of all views if it is not happening at some point).

Categories

Resources