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
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''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.
I am trying to do a endless listview with the Commonsware Endless Adapter (https://github.com/commonsguy/cwac-endless), which works fine by default.
What I am trying to do is a basic chat application.
New items should appear at the end and the chat history should be loadable by the endless adapter when the user scrolls to the top.
This by itself is not hard to do. If the ArrayAdapter contains the data s.t. newest items are at position 0, then simply using android:stackFromBottom in the XML declaration of the ListView will put the beginning of the list at the end.
To make sure that the 'load more' inicator is located at the top, I override getItem, getView etc. to provide it with the reversed positions (count-1-position).
My Problem: When the EndlessAdapter adds new data at the end of the List, the "load more" indicator remains visible, causing it to endlessly fetch more data until there is no more to fetch.
Expected is that it loads one batch and the user then needs to scroll down (here:up) to load further elements.
What am I missing?
Personally, I'd consider pull-to-refresh for this scenario, rather than EndlessAdapter.
That being said, if you are seeing additional rows appear, but the pending view is still there, then your modified getView() is not working properly. The additional rows appearing would indicated that notifyDataSetChanged() itself is functioning (otherwise, those rows would not show up). So, if the pending view is still shown, then getView() presumably is returning the pending view (position 0, I'd guess). In fact, I have no idea how you can get a reverse EndlessAdapter to work, as the first row should always be the pending view and should always be loading data, until you run out of data (and, in the case of a chat, that's possibly never the case).
Hence, again, I'd use pull-to-refresh, or you are going to have to switch to a different "endless" scheme that is paying attention to scroll events, rather than just waiting for the pending view to be displayed, as the trigger to fetch more data.
Do not care on notifyDataSetChanged() help.
I implemented an adapter to return Integer.MAX_VALUE as count of elements and especially calculate index in a cache.
Here is some snippet:
<ListView
android:id="#+id/logContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:stackFromBottom="true" />
......
logContainer.setOnScrollListener(new OnScrollListener() {
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if ((logAdapter.getCachedCount() - (Integer.MAX_VALUE - firstVisibleItem)) <= LINES_TO_TRIGGER_PRELOAD && !logAdapter.isPreloading()) {
logAdapter.preloadAtBackground();
}
}
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {}
});
class LogAdapter extends BaseAdapter {
private ArrayList<String> logList = new ArrayList<String>();
#Override
public int getCount() {
return Integer.MAX_VALUE;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
TextView logLineView = new TextView(LogViewDialog.this);
int idx = logList.size() - (Integer.MAX_VALUE - position);
logLineView.setText(logList.get(idx));
return logLineView;
}
.........
I know how to access a listview item through an onitemclicklistener but how do I do something like change the background color in code.
sudo code:
lv[0].setBackgroundResource(R.color.red); //change the background of the first listview item
Is there a way to access each view of the listview?
#Matt: It's not that it's weak, you just have to understand the way it works.
Yellavon, at any given time, your ListView only contains the visible items, so you can't access and change items directly. As they scroll out of view, the views are recycled, and populated with data by the ListAdapter. It's within this adapter that you need to handle the cases where items should differ. If you've created your own custom ListAdapter (e.g. ArrayAdapter, BaseAdapter), in your getView() method, simply add some logic within to handle the cases in which the background color should change.
Let's say for example, you have a list of integers, and you want any integer >= 50 to show up in red:
if(items.get(position) >= 50) {
myView.setBackgroundColor("#FF0000");
} else {
myView.setBackgroundColor("#000000");
}
(It's important to make sure to handle the else cases, as you may get a recycled view of an item that had been colored red. In this case, you'd have to reset it to whatever default background color you need.)
If you've never built a custom adapter, this excerpt from CommonsWare's book on creating custom ListAdapters is a great resource.
EDIT: Further thought, based on your comment:
In your custom ExpandableListAdapter
private Object lastSelectedObject;
public void setLastSelectedObject(Object obj) {
lastSelectedObject = obj;
}
public Object getLastSelectedObject() {
return lastSelectedObject;
}
Implement onListItemClick(ListView l, View v, int pos, long id) in your ListActivity
#Override
protected void onListItemClick(ListView l, View v, int pos, long id) {
super.onListItemClick(l, v, pos, id);
((CustomAdapter)l.getAdapter()).setLastSelectedObject(items.get(pos));
}
Now, back in getView()
Object obj = getLastSelectedObject();
if(obj != null) {
//handle background switching for your View here
} else {
//reset background to default for recycled views
}
Have a look at this example
int first = view.getFirstVisiblePosition();
int count = view.getChildCount();
for (int i=0; i<count; i++) {
TextView t = (TextView)view.getChildAt(i);
if (t.getTag() != null) {
t.setText(mStrings[first + i]);
t.setTag(null);
}
}
Seems like getChildAt is the method you are looking for.
The ListView class in android is weak.
The short answer to your question is no, not easily.
Someone asked about it at the Google I/O and the answer from the android team was to use a vertically filling LinearLayout and just add a stack of child views into it (which basically gives it the same functionality as a ListView).
You can do getChild(x) to get any of the views
I have list of checkboxes in list binded by Custom simpleCursorAdapter.
In my custom simpleCursorAdapter, I've overridden newView and bindView with my modifications.
I've managed somehow to do multichoice.
The wierd thing is, after I delete any item from my list, the first item's checkbox is being checked all of a sudden. How does that happen? How can I solve it?
My SimpleCursorAdapter class:
public class MyListCursorAdapter extends SimpleCursorAdapter
{
private Context context;
private int layout;
public MyCursorAdapter(Context context, int layout, Cursor c,
String[] from, int[] to)
{
super(context, layout, c, from, to);
this.context = context;
this.layout = layout;
}
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent)
{
Cursor c = getCursor();
final LayoutInflater inflater = LayoutInflater.from(context);
View v = inflater.inflate(layout, parent, false);
CheckBox chkBoxBtn = (CheckBox) v.findViewById (R.id.deleteTwittChkBox);
if (chkBoxBtn != null)
{
chkBoxBtn.setChecked(false);
}
return v;
}
#Override
public void bindView(View v, Context context, Cursor c)
{
--binding view to my textsview in my items
//now it's the importat part:
CheckBox chkBoxBtn = (CheckBox) v.findViewById(R.id.deleteTwittChkBox);
if (chkBoxBtn != null)
{
chkBoxBtn.setId(Integer.valueOf(c.getString(c
.getColumnIndex(MyUsers.User._ID))));
chkBoxBtn.setOnClickListener(new OnItemClickListener(chkBoxBtn, v));
chkBoxBtn.setChecked(false);
}
}
//i couldnt find another way of doing this, but this is how i set listeners to my checkboxses
static ArrayList<String> checkedItemsList = new ArrayList<String>();
private class OnItemClickListener implements OnClickListener
{
private int mPosition;
private CheckBox chkBox;
OnItemClickListener(CheckBox mChkBox, View v)
{
chkBox = mChkBox;
chkBox.setChecked(false);
}
#Override
public void onClick(View v)
{
if (chkBox.isChecked())
{
checkedItemsList.add(String.valueOf(chkBox.getId()));
}
else
{
checkedItemsList.remove(String.valueOf(chkBox.getId()));
}
}
}
}
Here is the code part from the ListActivity class which describes the button that deletes the checked box items:
OnClickListener btListener = new OnClickListener()
{
public void onClick(View view)
{
// long[] items = listView.getCheckItemIds();
int x = 0;
Uri myUri = Uri
.parse("content://com.idan.datastorageprovider/users");
String where = "_id" + "=?";
//here i am tatking all checkboxes which ive added from the adapter class
ArrayList<String> checkedItemsList = MySimpleCursorAdapter.checkedItemsList;
for (String itemID : checkedItemsList)
{
getContentResolver()
.delete(myUri, where, new String[] { itemID});
checkedItemsList.remove(itemID);
}
}
};
I doubt that SimpleCursorAdapter is the right class to extend here.
Is the "checked" state connected to the data XML in any way? No? So you need your own custom adapter!
Basically all adapters have to implement a way to generate a view from a given element (more precisely an element position!). This will be called at any time where the list wants to display an element. Now, the trick it uses is to re-use formerly created list view elements that cannot be seen on screen any more! Thus: when you scroll your list down and an element disappears at the top, EXACTLY this view object will be re-used for the next appearing item.
So, when this method is called with a given "old" view that should be re-used, all contained elements will have to be set according the elements data. If a checkbox is part of this game, you will have to have a storage for the checked state! It is not sufficient to have a checkbox as there will be less checkbox objects as there are list elements!
SimpleCursorAdapters are there to - yeah - represent SIMPLE things. An XML describing data (images and text, as the documentation states). Because of this simplicity all you have to do here is provide a method to create NEW element view objects - you are not intercepting the re-use process AT ALL! It basically only knows how to put the data into an existing view object - but it is lacking the knowledge of how to handle checked/unchecked boxes!
Your solution: write your own BaseAdapter extension and do what has to be done: implement "getView" (and some other methods like getItem, getItemId and getCount). It's not hard at all!
This API Demo uses a BaseAdapter and the mExpanded state here is basically identical to your checkbox states!
Good luck!
You might need to call notifyDataSetChanged when you modify the data.
The problem is probably that you're calling setChecked from within the onItemClickListener. One hacky way around this is to do the following before and after you call setChecked from within your listener:
chkBox.setClickable(false);
chkBox.setChecked(false);
checkBox.setClickable(true);
This will prevent your onItemClickListener from getting called when you manually call setChecked.