How to identify reused widgets in ListViews - android

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.

Related

OnClickListener for Buttons in GridView

I have a GridView adapter displaying a grid of Buttons. Now I want to set up an OnClickListener for my buttons but of course they don't have their own R.id I can access as they are added to the grid via the adapter, rather than a layout.xml.
I tried to use OnItemClickListener as follows:
m_onItemClickListener = new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> adapterView, View view, int pos, long l) {
switch(pos) {
case MyConstants.POS_OF_BUTTON_1:
// Do stuff...
break;
case MyConstants.POS_OF_BUTTON_2:
// Do stuff...
break;
}
}
};
But to my understanding you can't use a clickable or focusable item with OnItemClickListener. How do I get round this? Thanks!
There are more elegant ways to do this whole thing (starting from using a RecyclerView with a GridLayoutManager instead of a GridView), but if you're looking for the quick and easy solution to use with what you already have, this is what you can do:
First of all, you should set some ID on your buttons, they don't have to come from R.id (although it would be preferable if you inflated the views from a layout, with an ID defined there, and used a ViewHolder).
Worst case, you can just define constants in your adapter for the IDs you want to use for each kind of button (e.g. static final int DELETE_BUTTON = 1;), and then set these IDs on the buttons manually, in code.
Then you can pass a simple OnClickListener (not OnItemClickListener), which handles clicks of all these different buttons in a single item, to your adapter, and make the adapter set the listener on each of these buttons, for each of the item views in the grid.
You will also need to set the position of the item as a tag on the button view itself, so that when the click happens, you can determine for which item the click happened.
Sample code as follows:
View.OnClickListener listener = new View.OnClickListener() {
#Override
public void onClick(View v) {
Object tag = v.getTag();
if (!(tag instanceof Integer)) {
// Show error message or just throw an exception.
}
int position = (Integer) tag;
// We get the item at this position, to know which one to use
Item item = adapter.getItem(position);
switch (v.getId()) {
case DELETE_BUTTON:
// Delete stuff here
break;
case EDIT_BUTTON:
// Edit stuff here
break;
...
}
}
};
adapter.setOnClickListener(listener);
Then, in the getView method of the adapter, you need to set this listener on each of the buttons and also set the position of the item as a tag on the buttons. This way, you will be able to figure out to which item the button belongs to, in the listener code above.
#Override
public View getView(int i, View view, ViewGroup viewGroup) {
...
deleteButton.setId(DELETE_BUTTON);
deleteButton.setOnClickListener(listener);
deleteButton.setTag(i);
...
}
In general, I sincerely urge you to also look into the ViewHolder pattern, and RecyclerView and GridLayoutManager when you have time. Most of this will translate there as well.
EDIT
In order to make multiple Views clickable/focusable inside a list/grid item, you need to set the descendantFocusability attribute to blocksDescendants on the root view of the item, either simply in the XML, or in code via:
viewGroup.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);

listview getting new instance when swiping the listview

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();
}

Getting references to views in ListView

I''m struggling a little with the hierarchy here. I'd like to get references to every ImageButton view with the id delete_img in my listView. The imagebutton is added via the XML in the row layout xml.
Essentially i want to be able to set the visibility of a certain element within every row but i cant figure out how to get that sort of reference. Is there an alternative way of doing this? The method deleteShow() is my attempt to get at it so far but its obviously wrong as i am getting a Null Pointer when trying to set the Visibility.
NotesFragment
public class NotesFragment extends ListFragment {
private CommentsDataSource datasource;
private View v = null;
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Cursor theNotes = (Cursor) returnNotes();
String[] projection = { MySQLiteHelper.COLUMN_ID,
MySQLiteHelper.COLUMN_COMMENT,
MySQLiteHelper.COLUMN_COMMENTNAME,
MySQLiteHelper.COLUMN_FOLDERFK };
int[] to = new int[] { R.id.id_txt, R.id.content_txt, R.id.title_text };
#SuppressWarnings("deprecation")
SimpleCursorAdapter sca = new SimpleCursorAdapter(getActivity(),
R.layout.notes_list_layout, theNotes, projection, to);
setListAdapter(sca);
View v = inflater.inflate(R.layout.notesfragment, container, false);
deleteShow();
return v;
}
#Override
public void onListItemClick(ListView parent, View v, int position, long id) {
Intent intentView = new Intent(getActivity().getApplicationContext(),
ViewNote.class);
intentView.putExtra("id", id);
startActivity(intentView);
}
public Cursor returnNotes() {
Cursor theNotesCursor = null;
datasource = new CommentsDataSource(getActivity());
datasource.open();
theNotesCursor = datasource.getAllCommentsAsCursor();
return theNotesCursor;
}
public void deleteShow() {
ImageButton b = (ImageButton) getActivity().findViewById(R.id.delete_img);
b.setVisibility(View.INVISIBLE);
}
public void onPause() {
super.onPause();
datasource.close();
}
}
The hierarchy for dealing with ListView is not that complicated once you understand what's going on. Think of the ListView as the framwork that holds a bunch of child views or Items. Those Items each have child views that consist of the individual elements that make up a row in the ListView. To modify a list Item you either need to (1) change the data backing that item and update your ArrayAdapter or (2) find the individual Item you are trying to modify from within the ListView and then act on the child views for that individual item.
The easiest way to do this is to modify the data in the adapter backing the list and then call notifyDataSetChanged() on your ArrayAdapter to update the ListView. I don't know how your adapter is set up so to give you direct advice is difficult but the general idea is that you want to change the data backing the Item you want to modify, change that data, and then call notifyDataSetChanged() on the ArrayAdapter so that the ListView reflects the changes.
To modify an individual item directly is much more complicated. You cannot do it in one step as your code proposes - finding the individual view by id and then changing its visibility - will not operate accross the entire list as you suspect. findViewById is likley returning null because it is not looking within an indvidual list element but within the whole list - i.e. the outer list structure - for a view that is not there.
To do what you want programatically you need to (1) get a reference to the ListView itself; (2) find the first displayed view within the list by calling getFirstVisiblePosition(); (3) figure out how far down from that first visibile item is the item you want to modify; (4) get that item; (5) modify it
This ends up just being a pain in the ass. Its much easier to modify the data backing the list and update than to find single view.

accessing an item in listview

I have a list of players whos name are displayed listview. and each row of listview contains button, textview and imageview. How can I get the value of textview?
If you are using Custom Adapter class to populate the listview,then in your adapter,you can use HashMap for saving key-value pair,saving position of listitem with the data into that textview.and then you can easily retrieve it on OnItemClickListener of listview.
Depending on the specifics of your implementation, I would go with one of the following approaches.
Option A.
Use setOnItemClickListener to register a click listener with the list (or if you're using a ListActivity or ListFragment simply use [onListItemClick](http://developer.android.com/reference/android/app/ListActivity.html#onListItemClick%28android.widget.ListView, android.view.View, int, long%29)). onItemClick gets passed in the View that was clicked and can be used to retrieve nested views, e.g. the TextView you're looking for.
list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
TextView tv = (TextView) view.findViewById(R.id.textview);
String tvText = tv.getText();
}
}
Option B
Assuming you fill your list from some sort of data collection, you may be able to do something similar to above, but use the passed position parameter as an index to directly get the text from the objects in your collection; i.e.:
list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
SomeObject so = myCollection.get(position);
String tvText = so.getTextViewText();
}
}
There are lots of more options though. I kind of like creating my own extension of ArrayAdapter to hold the models for the views of the items in the list. That way you could also call getItemAtPosition(int position) or getItem(int position) and cast the returned object to your data type.
Is it a static list or a dynamically generated one? If its a static one you can assign a different id to each textview in the xml itself, and then use FindViewById to access it. If it's not this is what you've got to do: you will obviously have one row and display it many times. So multiple textviews will have same ID. Use a for loop, inside which use FindViewById(Remember FindViewByID will only access the first element with mentioned Id, set its Id to something else, in the next iteration the next textview is selected, set its Id to something) then use these new ids to access them, thus you can access each textview

display spinner2 if item xy in spinner1 is selected not working correctly

i have two spinners.
If in the first one the Item "Diesel" is selected i want to display the second one.
sKraftstoffArt.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
if(sKraftstoffArt.getSelectedItem().toString() == "Diesel"){
sPartikelfilterArt.setVisibility(sPartikelfilterArt.VISIBLE);
}
}
public void onNothingSelected(AdapterView<?> adapterView) {
return;
}
});
I've implemented this code in the onCreate method. When i select a item during the runtime i'm not getting the selected item text... It works only if the activity gets created and the default value gets selected....
Where else do i have to implement it?
Regards,
float
Unless your sKraftstoffArt object is not a final one, the check against it's selected item text inside an anonymous class won't work.
The adapterView among the parameters is your ListView instance to which you've assigned the AdapterView.OnItemClickListener.
The view parameter is the actual item (renderer) inside your ListView that has been clicked. This item is provided by your adapter's getView(int position, View convertView, ViewGroup parent) method.
Also, you should use the equals method of String to check whether two String values are equal.
So this won't work:
if(sKraftstoffArt.getSelectedItem().toString() == "Diesel")
Use insetad
if (adapterView.getSelectedItem().toString().equals("Diesel"))
You might also want to add an else clause after this if, to hide the sPartikelfilterArt spinner when the selected item in the previous spinner is not "Diesel".
Please note, that every time you assign a new adapter to this list (which probably you don't, i still mention it just in case...), you should add the AdapterView.OnItemClickListener to it again.

Categories

Resources