I've got a custom listview, each entry contains spinning ProgressBar and single line textview. I'm always updating only the first item in the list. The problem is that every time I call notifyDataSetChanged all visible views are either recreated or recycled as convertView.
There are 2 problems with this approach:
all entries are recreated and that's slow - and that's not required as I'm updating only the textview in first entry
the ProgressBar animation restarts every time
So I though that I'll just keep the reference to first View in adapter and update it manually. It worked, but it randomly throws IllegalStateException ("The content of the adapter has changed but ListView did not receive a notification. Make sure the content of your adapter is not modified from a background thread, but only from the UI thread.")
How do I solve it? Is there a way to notify ListView that only first entry changed? Even if there is, ProgressBar animation will still fail. Is there any other way to update it manually?
Thanks
Whenever i need to update one single item on a listview i use this:
public void updateItemAt(int index) {
View v = listView.getChildAt(index - listView.getFirstVisiblePosition());
if (v != null) {
//updates the view
}
}
The downside is that you need to hold a reference to the listView in your adapter.
As this code is usually called from a background task, you have to use a handler to call it:
Handler myHandler = new Handler() {
public void handleMessage(Message msg) {
int index = msg.what;
myAdapter.updateItemAt(index);
};
};
//when task is complete:
myHandler.sendEmptyMessage(index);
Related
I have a recycler view and I want to perform click on one of its items.
Here is my code:
mRecyclerView.findViewHolderForAdapterPosition(2).itemView.performClick();
It works fine when the item is visible, but when it is not visible i'm getting a null pointer exception
also i tried scroll to position before perform click but i got same result
Any idea on how to solve this issue?
I have solved my problem with this code
mRecyclerView.getLayoutManager().scrollToPosition(17);
search_list.postDelayed(new Runnable() {
#Override
public void run() {
mRecyclerView.findViewHolderForAdapterPosition(17).itemView.performClick();
}
}, 50);
There is a slight delay for the viewholder to be created. Thus if the item is clicked before viewholder is created an NPE would occur
Unfortunately for you, this is working as intended. When a child View is scrolled out of the boundaries of a RecyclerView, the child View is often reused to display another item for another position in the list, hence you will get a null View for the position that is no longer displayed.
What you can do is implement a getItem() on the RecyclerView.Adapter to retrieve the item for that position. Not sure if that satisfies your requirements though.
It is recommended to use a listener to wait for the drawing to complete,
Then perform the operation you want.
recyclerView.getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
// At this point the layout is complete
recyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});
I have two listview, like listview_1 and listview_2. I wanna refresh the listview_2 while listview_1 is refreshed.
My code like this:
public void updateTwoListView() {
listview_1.getAdapter().notifyDataSetChanged();
listview_2.getAdapter().notifyDataSetChanged();
}
But it don't work, listview_1 can refresh but the listview_2 can't.
And at that moment what I found is that listview_1 was on focus.
And then I tried to set focus to other views before ran the method, both of them didn't refresh. It likes to refresh a listview only if the listview has focus.
What's more I found that when I called the method to refresh, listview_2 didn't, and then I set focus to listview_2, refreshed itself!
So, What all I want to ask is:
How to refresh two listview at one moment in Android?
What's more code:
//init two listview there
public void init() {
listview_1 = (ListView)findViewById(R.id.listView1);
listview_2 = (ListView)findViewById(R.id.listView2);
adapter1 = new MyListViewAdapter(mContext);
adapter2 = new MyListViewAdapter(mContext); //I have tried use different adapter, that also didn't work.
listview_1.setAdapter(adapter1);
listView_2.setAdapter(adapter2);
}
the real code of upside snippet is:
public void updateTwoListView(int currentPosition) {
adapter1.updateCurPos(currentPosition);
adapter2.updateCurPos(currentPosition);
}
and in MyListViewAdapter.java:
public void updateCurPos(int currentPosition) {
mCurrentPos = currentPosition;
notifyDataSetChanged();
}
And I will call method like listViewManager.updateTwoListView(1) outside to refresh.
Any reply is appreciated.
You have called listview_1 twice. Just change one of them to listview_2 as below:
public void updateTwoListView() {
listview_1.getAdapter().notifyDataSetChanged();
listview_2.getAdapter().notifyDataSetChanged();
}
It seems the problem of your code is that you call getAdapter().
Sets the data behind this ListView. The adapter passed to this method may be wrapped by a WrapperListAdapter, depending on the ListView features currently in use. For instance, adding headers and/or footers will cause the adapter to be wrapped.
Source: http://developer.android.com/reference/android/widget/ListView.html#setAdapter(android.widget.ListAdapter)
The solution is save your Adapter as member variable in your activity and call the notifyDataSetChanged()from there.
See more on this question's answer: https://stackoverflow.com/a/31893525/2742759
I have a ExpandableListView with some groups, and each group haves only one child, with a string on it.
I have a thread that get's some updated data and each second calls a handler that updates the string of one of the childs of the ExpandableListView and then calls this method to refresh the view and show the updated data to the user: ((BaseExpandableListAdapter) mAdapter).notifyDataSetChanged();
This is the update handler:
infoUpdateHandler = new Handler(){
#Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
children[2][0]=getUpdatedInfo();
((BaseExpandableListAdapter) mAdapter).notifyDataSetChanged();
}
};
The problem is that when i'm scrolling the expandable list view, i can see how the scroll is showing low performance, it stops sometimes. I think that it is because this call ((BaseExpandableListAdapter) mAdapter).notifyDataSetChanged(); is updating all the ExpandableListView.
Does not exist a better way to update a expandable list view? Maybe a way to update only the child that you want to update?
Thanks
I want to update the UI from a timer. That's not a problem at all but when it comes to Gallery/ListViews it gets difficult. I have a Gallery with custom BaseAdapter. I need a counter for every (gallery) item (every item counts different depending on the items data). That counter should run outside of the main thread. In addition I don't want to run 10 threads for 10 items of the gallery when just one item is visible. It's not a problem to define a Handler and start a thread (the counting) in the adapters getView()-method when a item/view gets visible. I can think of something like the following code:
public class MyAdapter extends BaseAdapter {
static class ViewHolder {
//...
public Handler myHandler;
}
public View getView(int position, View convertView, ViewGroup parent) {
//...
// getView() gets called indefinite so first remove callback because it may be added already
holder.myHandler.removeCallbacks(myRunnable);
holder.myHandler.postDelayed(myRunnable, 0);
//...
}
}
The problem is to remove the callback for a view thats not visible anymore because in getView() I get noticed when a view becomes visible but I have no clue how to get the view (and thereby the holder and it's handler) that became invisible to remove the callback.
Is there a(nother) approach to solve that?
Found a clean solution (if someone needs something like that). I needed to update a TextView to set the new counter value (every second).
In BaseAdapters getView I add the TextViews(s) to a WeakHashMap. The TextView is the key of the map. A key/value mapping will be removed if the key is no longer referenced. So I do not cause memory leaks. The GarbageCollector does the work.
A thread counts "counter objects" down and refreshes the TextViews with the corresponding values (GarbageCollector runs all the time so in my case the map consists mostly of 3 items because the gallery shows just one item. Due to GarbageCollection the map gets immediately cleared by "unused" TextViews specially when scrolling fast through the list/gallery)
BaseAdapter's getView():
private WeakHashMap<TextView, Integer> counterMap = new WeakHashMap<TextView,Integer>();
private Handler counterHandler = new Handler();
public View getView(...) {
//...
counterMap.put(holder.tvCounter, position);
//...
}
The counter:
// Thread decrementing the time of the counter objects and updating the UI
private final Runnable counterTimeTask = new Runnable() {
public void run() {
//...
// decrement the remaining time of all objects
for (CounterData data : counterDataList)
data.decrementTimeLeftInSeconds();
// iterate through the map and update the containing TextViews
for (Map.Entry<TextView, Integer> entry : counterMap.entrySet()) {
TextView tvCounter = entry.getKey();
Integer position = entry.getValue();
CounterData data = counterDataList.get(position);
long timeLeftInSeconds = data.getTimeLeftInSeconds();
tvCounter.setText(" " + timeLeftInSeconds);
}
if (yourCondition)
counterHandler.postDelayed(this, 1000);
}
};
Start/stop the counter:
public void startCounter() { counterHandler.post(counterTimeTask); }
public void stopCounter() { counterHandler.removeCallbacks(counterTimeTask); }
I have progress bar in every item inside my listview. When I tap an item on the list, another thread makes the progress bar move. For some reason the output says the progress bar is increasing but it's not. Below is some of the code.
mHandler.post(new Runnable() {
public void run() {
ViewHolder vh = (ViewHolder) adapter.getView(pro, null, null).getTag();
Log.d("Progress",String.valueOf(vh.progress.getProgress()));
vh.progress.setProgress((int) prog[pro]);
}
});
getView() on your Adapter is probably creating a brand new row, since that's what it is supposed to do when you call getView() with a null second parameter.
You need to have a better scheme for retrieving the ProgressBar that you wish to update, taking into account that the ProgressBar may have been recycled to be displaying some other row's progress.