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.
Related
I am dynamically adding items to my listview my code works fine but my problem is when the listview is updated it is going to the starting position (items are added but scroll view begins from initial position).I am using listview inside fragment.I want to avoid that scrolling to initial position.
CODE
ListAdapter adapter =
new SimpleAdapter(getContext(), productsList, R.layout.list_notify, new String[]{"id","title","des"},
new int[]{R.id.id, R.id.title,R.id.des});
lv.setAdapter(adapter);
lv.invalidateViews();
Reference : How to refresh Android listview?
ListView Refresh in Android
Refresh Listview in android
Android refresh listview in fragment
How to refresh Android listview?
ListView is officially legacy. Try to use RecyclerView then you will be able to tell that you don't update whole list with methods like notifyItemChanged(position)...
In your case you will call notifyItemRangeInserted(position, count)
https://developer.android.com/guide/topics/ui/layout/recyclerview
Try smoothScrollToPosition on your listview.
See this, pretty similar if I understand correct what you want.
So in order to get that scrolling to stop you basically have to block the listview from laying out its children so first off you have to create a custom listview something like
public class BlockingListView extends ListView {
private boolean mBlockLayoutChildren;
public BlockingListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setBlockLayoutChildren(boolean block) {
mBlockLayoutChildren = block;
}
#Override
protected void layoutChildren() {
if (!mBlockLayoutChildren) {
super.layoutChildren();
}
}
}
then you can use it like this for example
int firstVisPos = mListView.getFirstVisiblePosition();
View firstVisView = mListView.getChildAt(0);
int top = firstVisView != null ? firstVisView.getTop() : 0;
// Block children layout for now
mListView.setBlockLayoutChildren(true);
// Number of items added before the first visible item
int itemsAddedBeforeFirstVisible = ...;
// Change the cursor, or call notifyDataSetChanged() if not using a Cursor
mAdapter.swapCursor(...);
// Let ListView start laying out children again
mListView.setBlockLayoutChildren(false);
// Call setSelectionFromTop to change the ListView position
mListView.setSelectionFromTop(firstVisPos + itemsAddedBeforeFirstVisible, top);
the setBlockLayoutChildren being true is what will stop your listview from scrolling and of course you can set whatever else you would like it to do
you may also just want to look into recyclerview though may make your life easier
I used a gridview instead of my listview and it solved my problem
set 1 item per row can act as listview in my code
<GridView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/grid"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
**android:numColumns="1"**
android:verticalSpacing="10dp"
android:horizontalSpacing="10dp"
android:scrollbarStyle="outsideOverlay"
android:verticalScrollbarPosition="right"
android:scrollbars="vertical">
reference : Custom layout as an item for a grid view
I'm using a ListView with a custom ArrayAdapter.
The List is an infinite scroll of tweets.
Updates to the list are inserted from the top.
I want to obtain an effect as the Twitter application. I'm not talking about the "scroll to update", but to maintain the position after the update.
I've just implemented some code that works in that way. Here it is:
// get the position of the first visible tweet.
// pausedCounter traces the number of tweets in the waiting line
final int idx = listView.getFirstVisiblePosition() + pausedCounter;
View first = listView.getChildAt(0);
int position = 0;
if (first != null)
position = first.getTop();
// here I update the listView with the new elements
for (Tweet[] tweets1 : pausedTweets)
super.updateTweets(tweets1);
final int finalPosition = position;
// this code maintain the position
listView.post(new Runnable() {
#Override
public void run() {
listView.setSelectionFromTop(idx, finalPosition);
}
});
The problem of this code is that for an instant the listView goes to the first element of the list, then kick in the setSelectionFromTop and it goes to the correct position.
This sort of "flickering" is annoying, and I want to remove it.
I found out only this solution:
// add the new elements to the current ArrayAdapter
for (Tweet[] tweets1 : pausedTweets)
super.updateTweets(tweets1);
// create a NEW ArrayAdapter using the data of the current used ArrayAdapter
// (this is a custom constructor, creates an ArrayAdapter using the data from the passed)
TweetArrayAdapter newTweetArrayAdapter =
new TweetArrayAdapter(context, R.layout.tweet_linearlayout, (TweetArrayAdapter)listView.getAdapter());
// change the ArrayAdapter of the listView with the NEW ArrayAdapter
listView.setAdapter(newTweetArrayAdapter);
// set the position. Remember to add as offset the number of new elements inserted
listView.setSelectionFromTop(idx, position);
In this way I have no "flickering" at all!
I have created a dynamic ListView where objects are added from top.
When the user press a button the listView is updated from the contents of an array, then notifyDataSetChanged() is called on the custom arrayAdapter.
Now I want to mantain the list position when adding, so I added this code:
// pausedCounter trace the number of objects(lines) to add to the listView
int idx = listView.getFirstVisiblePosition() + pausedCounter;
View first = listView.getChildAt(0);
int position = 0;
if (first != null)
position = first.getTop();
// cycle to add the new objects to the listView
for (Tweet[] tweets1 : pausedTweets)
super.updateTweets(tweets1);
listView.setSelectionFromTop(idx, position);
// reset of counter and accumulator
pausedTweets = new ArrayList<Tweet[]>();
pausedCounter = 0;
This code behave in this way: if the getFirstVisiblePosition returns 2, and the pausedCounter is 5, after the update the list will be set to the 3th of the new five elements.
What I want is to have the first visible element of the list set to the 8th.
After further tests I found out that the number of childrens of the listView doesn't change during the run of this piece of code, so it updates the size of the listView after I called setSelectionFromTop. Could be this the problem?
The trick was this:
listView.post(new Runnable() {
#Override
public void run() {
listView.setSelectionFromTop(idx, finalPosition);
}
});
Using the post method permits to wait the update of the ListView before change position.
I have a ListView with 10 items in it, when i set the 4th item in the ListView as selected then my ListView gets scrolled and 4th item in the ListView gets placed at the top of the screen.
I don't want to scroll the ListView when i call setSelected() for the list item which is not in the view.
I have also tried to scroll the ListView programmatically using scrollTo(0, 0); but it is not working.
An old question, but it may be useful to somebody. You can use setSelectionFromTop. It's rather smart, and you can use it for example like that:
getListView().setSelectionFromTop(selection, getListView().getHeight() / 2);
It begins to scroll only after middle of the list:
Here's what I did:
int top = listView.getChildAt(fixedChildIndex).getTop();
//set selected item, keep scroll position
listView.setSelectionFromTop(newIndex, top);
This keeps the ListViews Child with view index (ListView reuses views!) fixedChildIndex in place.
For example if you want the highlighting to stay use this:
int first = listView.getFirstVisiblePosition();
int fixedChildIndex = listView.getSelectedItemPosition() - first;
View newSelectedChild = listView.getChildAt(selectedViewIndex);
If you want to make sure the selected item is fully visible use this line
//set selected item, keep scroll position and make sure it is completely visible
listView.setSelectionFromTop(newIndex, Math.min(Math.max(0,top), listView.getHeight()-newSelectedChild.getHeight()));
Where newSelectedChild is the view of the child you want to select. You can get it by doing this:
I think it's hardcoded and thus not possible without scrolling
you can use listview.setChoiceMode(ListView.CHOICE_MODE_SINGLE); option with
mListView.setItemChecked(pos, true);
Setup some selectors may be required. Also look for google example IOSchedule App --great example about listview with multi selection controller (have a few bugs :) )
setSelection() may be of your work.
Do NOT use setSelection()!
I do that in another way.
private AdapterView.OnItemClickListener oiclFolders = new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> adapterView, View view, int posi, long id) {
// clear original selected item
if ( mViewOrg!=null ) mViewOrg.setBackgroundDrawable(null);
if ( view!=null ) {
// define your own drawable to set as background
view.setBackgroundResource(R.drawable.item_bg);
mViewOrg = view;
}
curSel = posi; // to store which one is selected
// Do NOT setSelection -> will auto scroll to top
// mlvFolders.setSelection(posi);
}
};
This worked for me -
Set CHOICE_MODE_SINGLE when setting up the custom listview -
Use setSelected() inside OnItemClickListener() as under -
private void setup_view(View rootView) {
listview = (ListView) rootView.findViewById(R.id.list);
if(listview != null) {
listview.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
listview.setAdapter(new MyAdapter(getActivity().getApplicationContext()));
listview.setOnItemClickListener(onitemclicklistener);
}
}
private OnItemClickListener onitemclicklistener = new OnItemClickListener() {
public void onItemClick(AdapterView parent, View v, int position,
long id) {
v.setSelected(true);
}
};
You can override requestChildRectangleOnScreen like this to get rid of the scrolling:
#Override
public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
return false;
}
Use setSelection(4) to set the Selection without scrolling if the new selection is visible.
Android has the transcript mode to allow to automatically scroll a list view to the bottom when new data is added to the adapter.
Can this be somehow reversed so that new items are automatically added at the top of the list ("inverse transcript mode")
Method stackFromBottom seems about right, but does not do the auto-scrolling on input change.
Does anyone have some example code where a list is constantly adding stuff that gets always inserted at the top? Am I on the right track here?
Update
Thanks for the answers, that made me think more. Actually. I want to have new entries to appear at the top, but the screen still show the item the user is looking at. The user should actively scroll to the top to view the new items. So I guess that transcript mode is not what I want.
Hmm, well, if I was going to try this, I'd do something like the following:
List items = new ArrayList();
//some fictitious objectList where we're populating data
for(Object obj : objectList) {
items.add(0, obj);
listAdapter.notifyDataSetChanged();
}
listView.post(new Runnable() {
#Override
public void run() {
listView.smoothScrollToPosition(0);
}
}
I don't know for certain that this will work, but it seems logical. Basically, just make sure to add the item at the beginning of the list (position 0), refresh the list adapter, and scroll to position (0, 0).
instead of this:
items.add(edittext.getText().toString());
adapter.notifyDataSetChanged();
you should try that (works for me):
listview.post(new Runnable() {
#Override
public void run() {
items.add(0, edittext.getText().toString());
adapter.notifyDataSetChanged();
listview.smoothScrollToPosition(0);
}
});
Shouldn't it be enough to just add a smoothScrollToPosition(0) whenever stuff gets added to the ListView? Don't think there's an automatic scroll option.
I spent several hours attempting to accomplish the same thing. Essentially, this acts like a chat app where the user scrolls up to view older messages at the top of the list.
The problem is that, you want to dynamically add another 50 or 100
records to the top but the scrolling should be continuous from where
the prepended items were added.
The moment you do a notifyDataSetChanged, the ListView will automatically position itself at the first item in your data set and NOT at the position that preceded the position where the new items got inserted.
This makes it look like your list just jumped 50 or 100 records. I believe TranscriptMode set to normal is not the solution. The listview needs to function as a normal listview and you need to programmatically scroll to the bottom of the list to simulate the TranscriptMode as it functions under "normal".
Try to use
LinkedList items = new LinkedList<Object>();
//some fictitious objectList where we're populating data
for(Object obj : objectList) {
items.addFirst(obj);
}
listAdapter.notifyDataSetChanged();
This resolves the problem:
...
ListView lv;
ArrayAdapter<String> adapter;
ArrayList<String> aList;
...
lv = (ListView) findViewById(R.id.mylist);
aList = new ArrayList<String>();
adapter = new ArrayAdapter<String>(getApplicationContext(), android.R.layout.simple_spinner_item, aList);
lv.setAdapter(aAdapter);
...
adapter.insert ("Some Text 1", 0);
adapter.insert ("Some Text 2", 0);
adapter.insert ("Some Text 3", 0);
...
you should try that (list position and refresh adapter)
list.add(0,editTextSName.getText().toString());
adapter.notifyDataSetChanged();