update single item view in listview scroll issue - android

A little difficult problem to describe so please be patient
i have a progress bar in every listitem of listview used to show downloading
now to update a single view i get view object of single item
now this view is passed to async task to update only that view for which downloading is in progress
it works fine untill i scroll listview
when i scroll list view progress bar is shown for some random listitem of listview
code to invoke asynctask
ListView lv = getListView();
int visiblePosition = lv.getFirstVisiblePosition();
View v = lv.getChildAt(position - visiblePosition);
v.setClickable(true);
task = new TestLoadingTask();
task.v = lv.getChildAt(position - visiblePosition);
executeAsyncTask(task, null, null, null);
on progressupdate of async task
#Override
protected void onProgressUpdate(Object... values) {
super.onProgressUpdate(values);
progress[currentposition] = (Integer)values[0];
max[currentposition] = 100;
updateprogress(currentposition,v);
}
public void updateprogress(int position,View v)
{
ProgressBar update = (ProgressBar)v.findViewById(R.id.downloadbar);
if(max!=null && max[position]!=0)
update.setMax(max[position]);
if(progress!=null)
update.setProgress(progress[position]);
TextView tv_per = (TextView) v.findViewById(R.id.percentage);
if(progress[position]==max[position])
{
tv_per.setVisibility(View.GONE);
update.setVisibility(View.GONE);
}
else
{
tv_per.setVisibility(View.VISIBLE);
tv_per.setText(progress[position]+"/"+max[position]);
update.setVisibility(View.VISIBLE);
}
}
any reference/advice/example would be greatly appreciated.

Okay, if I understood you right, your problem is that ListView's adapter doesn't create new list-items every time you scroll it, it just converts previous. So if it doesn't have special directions about some views of new visible list-item, it will draw them like in previous list-item which was at this position.
The solution is to add some flag to your list-items (like boolean isUpdating) and show progress bar at your adapter's getView method if it's true, and hide progress bar otherwise

Related

Progress visibility not working in list view footer

I am using pagination in my list view and i am displaying a progress bar(small) in list view footer when user scroll down to end of the list view.
LayoutInflater inflater = (LayoutInflater)getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(R.layout.list_footer, null);
myListView.addFooter(view).
Then i am setting my adapter as follows
ProgressBar progressBar = (ProgressBar) getFooterView().findViewById(R.id.progressBar);
mAdapter = new MyAdapter(BaseActivity.getActivity(), 0, progressBar, myArrayList);
mList.setAdapter(mAdapter);
In my adapter class i am settings progress bar visibility in getView() method as follows.
if(position == MAX_RECORDS)
{
progressBar.setVisibility(View.VISIBLE);
// Some code goes here.
} else
progressBar.setVisibility(View.GONE);
But ProgressBar not disappears when list has no data to fetch. Please help me.
Set progressBar to visible state initially and use the same code itself.
try this one.Changes in this line will help you
ProgressBar progressBar = (ProgressBar)view.findViewById(R.id.progressBar);
instead of
ProgressBar progressBar = (ProgressBar) getFooterView().findViewById(R.id.progressBar);
try :
if(position == MAX_RECORDS-1)
position starts with 0
UPDATE
Just trying to understand your code here:
if(position == MAX_RECORDS-1)
{
progressBar.setVisibility(View.VISIBLE);
// Some code goes here.
}
above block will get executed when the last row is displayed. since getview is called for all rows before, and hopefully you are using convertview, the else block will not be executed again.
Net result is : dialog will be visible indefinitely.
If I am right you want to show the dialog when loading data and hide it when load completes. There are two scenarios:
you fetch data in one go
you fetch data when last row is displayed.
Can you shed some light which one of above is true?

Update TextView from ListView android row

I am retrieving some data in an Async class called from a Custom ArrayAdapter. When i add a new comment, i update the comment text view and that works ok, but after i reload the entire list the updates don't appear anymore. I can see in the logcat that there is a new comment nr, but not on the UI.
Shouldn't this : answersListView.invalidateViews be enough? I am trying to update that single row from the listview, to escape the issue with not updating the comment nr after a while.
private void updateView(int index) {
System.out.println("index: " + index);
View v = answersListView.getChildAt(index - answersListView.getFirstVisiblePosition());
if (v == null)
return;
final TextView nrComments = (TextView) v.findViewById(com.dub.mobile.R.id.showCommentsTxt);
if (nrComments != null) {
if (nrComments.getText().toString().trim().length() == 0) {
// first comment
nrComments.setText("1 comments");
} else {
// comments exist already
int newNr = Integer.parseInt(nrComments.getText().toString().trim()
.substring(0, nrComments.getText().toString().trim().indexOf("comments")).trim()) + 1;
nrComments.setText(newNr + " comments");
}
System.out.println("final nr of comments: " + nrComments.getText());
nrComments.setVisibility(View.VISIBLE);
}
answersListView.getAdapter().getView(position, v, answersListView);
v.setBackgroundColor(Color.GREEN);
answersListView.invalidateViews();
}
And :
updateView(position);
adapter.notifyDataSetChanged();
The answer is pretty much what #Rami said in a comment to your question. You don't update directly the views, you just need to update the data. In your adapter, you override the getView method, in there is where you make all this changes, you don't need the updateViews method.
Let me try to explain how it works.
The listView uses an Adapter.
The List view ask the Adapter "give me the view in X position" with the getView method.
The adapter creates that view, is returned to the ListView, and that what is shown.
The Adapter itself, should contain a List with the data you want to show.
Those views (rows) are created and destroyed everytime one of those views become visible or invisible in the screen, or if you call the notifyDataSetChanged method.
So now, the thing is, if you change, let's say, the object in the position 5, of the adapter List for a different object with new data, then next time the ListView ask the Adapter to give me the view in the position 5, the adapter is going to create the View (row) with the new data. That's it.
you need to update the data in the UI thread.
if you have context in your Async class use
runOnUiThread(new Runnable() {
#Override
public void run() {
//update here
}
});
or
android.os.Handler handler = new android.os.Handler();
handler.post(new Runnable() {
#Override
public void run() {
//update here
}
});
That's not how you change data for an item in a list view. You must have used some array of strings to fill data in textview in the getView function of the adapter. You only need to change that array and then call notifydatasetchanged on adapter. Views are recycled by adapter so above does not make sense
getView (...)
{
TextView tv = new TextView(context);
tv.setText(myData[index]); // You need to change myData array contents
}

Change Specific TextView Color in Listview

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!

Android Listview.getChildAt() points to two items

I have an activity that has a single listview in it with enough items to extend of the page.
I want to set a certain listView item at position i to a different drawable.
To go this I use the line of code..
listView.getChildAt(selector).setBackgroundResource(R.drawable.main_button_shape_pressed);
There is a very confusing problem going in. This line of code is setting two listView items to the specified drawable.
When i = 0 item 0 and item 11 are set to that drawable. It turns out that when I call this line of code with i both item i and item i+11 are set to that drawable. This is rather baffling. Then to mix EVERYTHING when I start the activity in landscape, it is a different second listview that gets set to that drawable. And in certain scenarios when I change from portrait to landscape, the current highlight listview item on screen will change to a different one.
WTF is going on with the listview class? Are the indexes to it children constantly pointing to different things?
Here is my entire activity.
public class SelectorActivity extends Activity {
private ListView listView;
private int selector;
private boolean set;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.selector_layout);
set=false;
Bundle extras = getIntent().getExtras();
if(extras!=null)
{
selector=extras.getInt("selector");
}
listView=(ListView)findViewById(R.id.selector_layout);
//set the string array for the listview
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this,
R.array.sounds_array, android.R.layout.simple_list_item_1);
adapter.setDropDownViewResource(android.R.layout.simple_list_item_1);
listView.setBackgroundResource(R.drawable.listview_background);
listView.setAdapter(adapter);
highlightSelected();
}
//this method will highlight a selected listview once that listview is drawn
private void highlightSelected()
{
if(!set)
{
new Thread(
new Runnable()
{
#Override
public void run() {
// TODO Auto-generated method stub
boolean trigger=true;
while(trigger)
{
if(listView.getChildAt(selector)!=null)
{
set=true;
trigger=false;
listView.getChildAt(selector).setBackgroundResource(R.drawable.main_button_shape_pressed);
}
}
}
}
).start();
}
}
}
ListViews recycle their children. While drawing itself, the ListView will create a new view for every visible child. When you scroll, it will then re-use the last view that became non-visible (scrolled off the screen) as the next view in the list. That is why it's a different view index in landscape and that is why it would probably be a different view index on a device with a different screen size.
The solution should be to reset the view background in the Adapter's getView() method.
Additionally, touching views on anything other than the UI (main) thread is a bad practice. Check the selected item index in the getView() method and set the background right there. You'll also need to handle the case where the selected index changes (unless it never changes after this activity is created) by iterating over visible views in the listview and setting their backgrounds to the appropriate values.
// Must be final to use inside the ArrayAdapter
final int selector = extras == null ? -1 : extras.getInt("selector");
ArrayAdapter<CharSequence> adapter = new ArrayAdapter<CharSequence>(
this,
R.array.sounds_array,
android.R.layout.simple_list_item_1) {
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View newView = super.getView(position, convertView, parent);
// set the background according to whether this is the selected item
if (position == selector) {
// this is the selected item
newView.setBackgroundResource(R.drawable.main_button_shape_pressed);
} else {
// default background for simple_list_item_1 is nothing
newView.setBackground(null);
}
return newView;
}
};

keep Scroll position with every refresh in list view

I set a timer in my app, I could get some information from a web service and resulted in a list view to display. Now my problem is that every time the timer runs, scroll back to the beginning ...
how can i keep Scroll position with every refresh in list view ?
part of my code:
runOnUiThread(new Runnable() {
public void run() {
/**
* Updating parsed JSON data into ListView
* */
ListAdapter adapter = new SimpleAdapter(DashboardActivity.this,
all_chat,
R.layout.list_item,
new String[] { TAG_FULLNAME,
TAG_DATE,
TAG_MESSAGE },
new int[] { R.id.fullname,
R.id.date,
R.id.message }
);
// updating listview
setListAdapter(adapter);
}
});
TNx.
Do not call setAdapter(). Do something like this:
ListAdapter adapter; // declare as class level variable
runOnUiThread(new Runnable() {
public void run() {
/**
* Updating parsed JSON data into ListView
*/
if (adapter == null) {
adapter = new SimpleAdapter(
DashboardActivity.this, all_chat, R.layout.list_item, new String[]{TAG_FULLNAME, TAG_DATE, TAG_MESSAGE},
new int[]{R.id.fullname, R.id.date, R.id.message});
setListAdapter(adapter);
} else {
//update only dataset
allChat = latestetParedJson;
((SimpleAdapter) adapter).notifyDataSetChanged();
}
// updating listview
}
});
You can add the following attribute to your ListView in xml.
android:stackFromBottom="true"
android:transcriptMode="alwaysScroll"
Add these attributes and your ListView will always be drawn at bottom like you want it to be in a chat.
or if you want to keep it at the same place it was before, replace alwaysScroll to normal
in the android:transcriptMode attribute.
Cheers!!!
I had the same issue, tried a lot of things to prevent the list from changing its scroll position, including:
android:stackFromBottom="true"
android:transcriptMode="alwaysScroll"
and not calling listView.setAdapter();
None of it worked until I found this answer:
Which looks like this:
// save index and top position
int index = mList.getFirstVisiblePosition();
View v = mList.getChildAt(0);
int top = (v == null) ? 0 : (v.getTop() - mList.getPaddingTop());
// ...
// restore index and position
mList.setSelectionFromTop(index, top);
Explanation:
ListView.getFirstVisiblePosition() returns the top visible list item. But this item may be partially scrolled out of view, and if you want to restore the exact scroll position of the list you need to get this offset. So ListView.getChildAt(0) returns the View for the top list item, and then View.getTop() - mList.getPaddingTop() returns its relative offset from the top of the ListView. Then, to restore the ListView's scroll position, we call ListView.setSelectionFromTop() with the index of the item we want and an offset to position its top edge from the top of the ListView.
There's a good article by Chris Banes. For the first part, just use ListView#setSelectionFromTop(int) to keep the ListView at the same visible position. To keep the ListView from flickering, the solution is to simply block the ListView from laying out it's children.

Categories

Resources