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.
Related
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.
i Have Two Database
first one Contain All The Items, and the ListView Display it
and the second db contain the the Favorite item , [selected from the first database]
what i want is that when the listview display all the items
check if the item is already exist in Favoritelist then make that textview background RED for this item
i have this code that work fine
public static void ChangeMyFavoritesBG(){
for (int i = 0; i < Items.size(); i++) {
if(db.verification(Items.get(i).toString())){
try {
TextView favtextview = (TextView) listview.getChildAt(i-listview.getFirstVisiblePosition()).findViewById(R.id.item_name);
favtextview.setBackgroundResource(R.drawable.redcolor);
}catch (NullPointerException e) {
}}}}
db.verification check if item exist in favorites database
if true . then it should change the background of this item to red
this code work fine but only if i put it in button click
i need to make the code work automatically
but if i made it start automatically when the activity is loaded i get NullPointer Error
i guess because the function ChangeMyFavoritesBG(); work before the listview display items
any idea guys? and sorry for my bad english
Do this control inside the getView(int position, View convertView, ViewGroup parent) method of the Adapter used by the listView.
If your favorite is not currently visible in the ListView then getChildAt() will return null.
You are looping over all items in the list view and my guess is that it holds more items than can fit on the screen. When your favorite item is one of them then this fragment of your code
listview.getChildAt(i-listview.getFirstVisiblePosition())
will return null. And that will cause the NullPointerException when you call findViewById(R.id.item_name) on it.
Just add a check for null on the result of getChildAt(). If it is null then do nothing, if it is non-null then call the second part. This will protect against the exception when your favorite item is not on the screen, and will allow it to be colored red when your favorite is visible on the screen.
update
My apologies, I read to quickly and misunderstood your problem to be about the NullPointerException but you say that your code works fine when you call it from a button click handler but not when you call it automatically at start-up.
You are right, the ListView does not yet have any items loaded when you are still in onCreate(). You can add a delay before running you code. The following works for me:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my);
// initialize the ListView with data for the list items. (I'm using a string array in this
// example. You are loading it from a database table, but that is the same in principle.)
ListAdapter adapter = new ArrayAdapter<String>(this, R.layout.item_list, R.id.item_name, Items);
ListView listview = (ListView) findViewById(R.id.listview);
listview.setAdapter(adapter);
// ask the system to wait before setting the background color of the favorite item so that
// the ListView has time to load the items.
final int DELAY_IN_MILLISECONDS = 100;
listview.postDelayed(new Runnable() {
#Override
public void run() {
ChangeMyFavoritesBG();
}
}, DELAY_IN_MILLISECONDS);
}
As you can see in the above example, after initializing the ListView, you ask the system to wait 100 milliseconds before calling ChangeMyFavoritesBG(). Hopefully that is enough time to load the items from the database into the ListView. If it is not enough time then you can, of course, use a longer delay.
The alternative
The above should work, but to be honest I would not write it this way. The above code is very brittle because it depends on the timing of how long it takes to load the items. I recommend that you put your background coloring into a customized adapter.
Because you want the items displayed in a customized way -- you want them to have a red background when it is the favorite one -- you should use a customized adapter. Override the bindView() function to make the background red when it is the favorite one or give it a normal background when it is not the favorite.
I don't know how you currently get the items from the database into your ListView, but inheriting from SimpleCursorAdaptor would work pretty well.
public class FavoritesItemAdapter extends SimpleCursorAdapter {
public FavoritesItemAdapter(Context context, int layout, Cursor c, String[] from, int[] to, int flags) {
super(context, layout, c, from, to, flags);
}
#Override
public void bindView(View view, Context context, Cursor cursor) {
super.bindView(view, context, cursor);
// read the name from the database
int nameColumnIndex = cursor.getColumnIndexOrThrow("name");
String name = cursor.getString(nameColumnIndex);
// write the name to the TextView
TextView nameText = (TextView) view.findViewById(R.id.item_name);
nameText.setText(name);
// set the background to normal or to red, depending on if it is the favorite one or not
boolean isFavorite = db_verification(name);
if (isFavorite) {
nameText.setBackgroundResource(R.drawable.redcolor);
} else {
nameText.setBackgroundResource(android.R.color.transparent);
}
}
public boolean db_verification(String name) {
// this is a stub. You must use your own code here
return name.equals("the favorite one");
}
}
You can then throw away ChangeMyFavoritesBG() and initialize your ListView with the adapter in onCreate() like this.
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my);
Cursor cursor = readItemsFromDatabase();
String[] from = new String[] { "name_column" }; // mapping from database column name ...
int[] to = new int[] { R.id.item_name }; // ... to View ID in the item's layout.
FavoritesItemAdapter adapter = new FavoritesItemAdapter(this, R.layout.item_list, cursor, from, to, 0);
ListView listview = (ListView) findViewById(R.id.listview);
listview.setAdapter(adapter);
}
Good luck!
I have an activity which has a TextView button (btnChangeMode) that toggles the mode from "admin" to "guest". Depending on the mode chosen, I need to hide/show a button (btnAddListItems) within my listview row. The code i have, currently doesn't seem to be cutting it.
Code speaks easier, so here's the gist of my code:
My activity layout:
<FrameLayout>
<ListView> ... </ListView> <!-- which has its items populated from myCustomAdapter -->
<TextView> ... </TextView> <!-- this is my btnChangeMode -->
<FrameLayout>
nothing fancy in my activity:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.my_activity);
List listItems = ... // some method that gets objects from Database
ListView listView = (ListView) findViewById(R.id.list_view_id);
listView.setAdapter(new MyCustomAdapter(this, listItems));
}
I have a custom adapter that basically has two types of rows header & item. In the "header" row, I have button "btnAddListItems" that allows me to add items into the list view. I want this button to be visible only in admin mode.
I've overridden the necessary methods in myCustomAdapter (getviewTypeCount, getItemViewType, getCount and getView). here's the getView method:
#Override
public View getView(int position, View row, ViewGroup parent) {
if (row == null) {
if (getItemViewType(position) == ITEM_VIEW_TYPE_HEADER) {
return getHeaderRow();
} else {
return getItemRow();
}
}
if (getItemViewType(position) == ITEM_VIEW_TYPE_ROW) {
MyHolder holder = (MyHolder) row.getTag();
holder.populateNewContent();
}
return row;
}
....
private View getHeaderRow() {
View lRow = LayoutInflater.from(getContext()).inflate(R.layout.my_header, null);
mViewMode = new DetailsViewMode((Activity) getContext(), getChangeViewModeListener(lRow));
return lRow;
}
So mViewMode here is a convenience POJO class i wrote that contains btnChangeMode, a boolean variable that indicates current mode (isAdmin) and the caller activity. I don't believe there's anything specific to my problem, so i'm not including that code here. Will be glad to if someone thinks that'll help.
private ChangeViewModeFragment.ChangeViewModeListener getChangeViewModeListener(final View headerRow) {
return new ChangeViewModeFragment.ChangeViewModeListener() {
#Override
public void onViewModeChanged(boolean isViewModeAdmin) {
mViewMode.changeViewModeButtonText(isViewModeAdmin);
toggleAdminFeatures(isViewModeAdmin, headerRow);
}
};
}
private void toggleAdminFeatures(boolean isViewModeAdmin, View headerRow) {
TextView btnAddListItems = (TextView) headerRow.findViewById(R.id.add_button_id);
if (isViewModeAdmin) {
btnAddListItems.setVisibility(View.VISIBLE);
} else {
btnAddListItems.setVisibility(View.GONE);
}
}
This is the part that's not working as it should. btnAddListItems is always visible within my listview.
btnChangeMode is within my activity so to speak, while the btnAddListItems is within my Adapter (ListView). But my requirement necessitates this behavior of having the listner of an activity, change the row state of my listview.
I suspect that when i change the visibility of my header row's button, I don't have hold of the correct header row instance, if that makes sense :P.
NitroNbg's suggestion of having a private button didn't work, which leads me to believe that maybe the ListView just needs a kickstart to get refreshed?
But I've tried calling notifyDataSetChanged() at the end of my toggleAdminFeatures method but that doesn't seem to be doing the trick.
I'd try the following - create a private Button within your adapter class and within your getView() method, put a reference to btnAddListItems to it.
private Button buttonToHide;
//...
public View getView(...) {
//...
buttonToHide = (Button) row.findViewById(R.id.add_button_id);
//...
}
Then, inside your ChangeViewModeListener() simply refer to a method of your adapter class (of course you'll have to write it) that sets the buttonToHide.setVisibility(View.INVISIBLE)
Hopefully, since it's within the adapter it would be accessible.
EDIT: Just to point out if it isn't obvious - only refer to the button that's in the header row.
You don't need to notifyDataSetChanged(), coz dataset hasn't changed, try setting the visiblity to INVISIBLE
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.
I'm running into a problem with a ListActivity.
The onListItemClick method needs to access the child views of the ListView in order to highlight the correct answer if an incorrect one was chosen. This logic usually works fine. But the problem is that if I go too fast, i.e. just keep banging away indiscriminately at the display as the lists are presented, before too long the ListView will return 0 children in onListItemClick, and the program will crash on the resulting empty view. My debug statements show that when this occurs, the array used to populate the ListView is correctly initialized, containing all four items as expected.
Additional info: When the user responds, I'm writing data to a DB inside of an AsyncTask. When I disable this, the problem seems to go away. I'm passing a subclass of the ApplicationContext to it, but no other data is common to the threads.
This is the array used to populate the ListView:
ArrayList<String> myArrayList = new ArrayList<String>();
That array list is populated in the code and is declared as follows:
String[] myAnswerArray = new String[4];
The ListView adapter is set in the follow code extract:
myArrayList.clear();
myArrayList.addAll(Arrays.asList(myAnswerArray));
// attach the adapter to the ListView
setListAdapter(myStringArrayAdapter);
The MyStringArrayAdapter extends ArrayAdapter, and shown here for completeness:
public class MyStringArrayAdapter extends ArrayAdapter<String> {
private Typeface font;
private static final String TAG = "MyStringArrayAdapter";
public MyStringArrayAdapter(Context context, int textViewResourceId,
ArrayList<String> answerArray, Typeface font) {
super(context, textViewResourceId, answerArray);
this.font = font;
}
#Override
public View getView(int position, View view, ViewGroup viewGroup) {
View v = super.getView(position, view, viewGroup);
((TextView) v).setTypeface(font);
return v;
}
}
Once the problem started showing up, I added code to check for zero children. Here's how I check for that:
#Override
protected void onListItemClick(ListView listView, View listItemView,
int position, long id) {
super.onListItemClick(listView, listItemView, position, id);
int childCount = listView.getChildCount();
// Workaround on bug raised by monkey exerciser
if (childCount == 0) {
Log.d(TAG, "No children found in ListView")
}
// etc...
Again, this problem only happens if I bang away rapidly on the touch screen, and also the exerciser monkey has occasionally but not always generated this I've tried waiting for it to be populated, using Thread.sleep in a loop and re-retrieving the child count, but it makes no difference.
Anyone have any ideas how to resolve this?
Try with ViewHolder... http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/view/List14.html