A reversed endless listview - android

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;
}
.........

Related

Get visible position of listview item

I have a custom cursor adapter to populate a list view in my fragment.
I want to set the visibility of certain elements within the list view items, depending on whether the item is the first visible one in the list or not. Is it possible to get that info in the bindView() method of the Cursor adapter?
Adapter's purpose plan:
Create views
Attaching data to them
Return the view for the ListView to use.
Conclusion: Adapter doesn't know where the view it's creating will be shown.
However, the ListView does know about this and it's probably the only way you can get this working.
Example code to get you started:
listView.setOnScrollListener(new AbsListView.OnScrollListener() {
int previousFirst = -1;
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (previousFirst != firstVisibleItem) {
previousFirst = firstVisibleItem;
TextView textView = (TextView) view.findViewById(R.id.title);
if (textView != null) textView.setText("First: " + firstVisibleItem);
}
}
});
Problems with this code:
Once the first item changes, you need to set it's text back to the previous one.
Same goes with the view hierarchy. If you change how this view looks, after it's not the first one anymore you need to change it back.
ListView doesn't scroll upon creation. So the first item will not have the text changed until you scroll manually.
ListView doesn't include the options to customize the first visible item internally, that's why you have to use all these dirty hacks. However, it is possible :). I leave you to overcome these problems yourself.
Good luck!

How to update listview as user scroll down?

I want to make a listview that as user scroll to bottom of list view other items of list fill automatically from internet.I wrote code to that in the adapter of that expandable list view (within getGroupView() method)as like this,
public View getGroupView(final int arg0, final boolean arg1, View arg2, ViewGroup arg3) {
//if(arg2==null){
//arg2=act.getLayoutInflater().inflate(R.layout.layout_exlistview_group, null);
}
//((TextView)arg2.findViewById(R.id.nameText)).setText(item.getItemName());
if(arg0>=getGroupCount()-1){//chech is near for end
/*here i run a asynctask to download data and add items to SparseArray of this class.That sparsearray is the source to the adapter to view them in listview*/
}
//return arg2;
}
So is this correct way to do this or is there any good way to do that?
From the fact that you're using getGroupView I assume you're using an ExpandableListView, not a regular ListView, which should probably be stated in your question.
Either way, the best way to do this would be to assign a OnScrollListener onto your list and then do your check there, rather than in getGroupView.
I'd recommend you put something along the lines of the following into your onScroll method:
if (firstVisibleItem + visibleItemCount > (totalItemCount - NUM_BEFORE_LOAD)) {
loadMore()
}
where NUM_BEFORE_LOAD based on your example would be 1, but you could make it anything you want to make the list load quicker than when it hits the bottom.

Android listview item update every time it is scroll back into screen

I have a listView which has its content as random generated numbers. If one item in the listView is scrolled out of the screen and then scrolled back in, the random generated number is changed (it generates a new number).
Is there any way I can prevent this from happening?
A example code is here
This is because every time your item goes off the screen and again come to front it calls getview() method. To solve this you can store all random generated numbers in an array in starting and use it to show the items.
OR
i think using view holder class (i didn't try).
Just try this code for listview implement listener and/or your getview() method calling every time on scroll up and/or down,
mListView.setOnScrollListener(new OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// write down your code here
// i think notifyDataSetChanged() this method calling whatever u require.
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
// write down your code here
}
}); // mListView.setOnScrollListener close

How to get the view of a ListView item?

I have two ListViews (A and B) with items of the same type (a class I created)
When I click on an item from A, it adds this object on B and if I click again it removes it.
Only that when an item is selected, I change its background using view.setBackgroundColor(myColor).
I want to be able to remove the item from list B (it works), but I want also to reset the background color. I can't figure out how to get the view of this item I'm removing.
Any ideas?
There's no guarantee that any specific ListView item will even have a view at any given time. If the item is currently off-screen, then it may not have a view. Since a specific item might not have a view, it might not make any sense to try to get the item's view.
Beyond that, because of the way ListView creates and reuses views, you'll see some odd, undesirable effects if you simply modify the views directly. As the user scrolls through the list, items that become visible will incorrectly end up with the same backgrounds as other items that have fallen outside the visible portion.
I don't know whether what follows is the best way to implement your functionality because I don't know the cost of rebuilding the list after a change. Here's the (probably naive) way I would do this:
Add another boolean member to your data object, something like isInSecondList.
Override getView() in the Adapter. In getView(), set the background to either normal or highlighted depending on the the value of the item's isInSecondList.
When an item is added or removed from the second list, update the data object to reflect the change, then call the Adapter's notifyDataSetChanged().
int position = 0;
listview.setItemChecked(position, true);
View wantedView = adapter.getView(position, null, listview);
Here is what i did
private View oldSelection;
#Override
public void onItemClick(AdapterView<?> arg0, View view, int position,
long arg3) {
highlightSelectdListItem(position);
}
public void highlightSelectdListItem(int position) {
clearPreviousSelection();
View newsItemView = mGridVIew.getChildAt(position);
oldSelection = newsItemView;
newsItemView.setBackgroundColor(Color.GRAY);
}
public void clearPreviousSelection() {
if (oldSelection != null) {
oldSelection.setBackgroundColor(Color.TRANSPARENT);
}
}

Android Endless List

How can I create a list where when you reach the end of the list I am notified so I can load more items?
One solution is to implement an OnScrollListener and make changes (like adding items, etc.) to the ListAdapter at a convenient state in its onScroll method.
The following ListActivity shows a list of integers, starting with 40, adding items when the user scrolls to the end of the list.
public class Test extends ListActivity implements OnScrollListener {
Aleph0 adapter = new Aleph0();
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setListAdapter(adapter);
getListView().setOnScrollListener(this);
}
public void onScroll(AbsListView view,
int firstVisible, int visibleCount, int totalCount) {
boolean loadMore = /* maybe add a padding */
firstVisible + visibleCount >= totalCount;
if(loadMore) {
adapter.count += visibleCount; // or any other amount
adapter.notifyDataSetChanged();
}
}
public void onScrollStateChanged(AbsListView v, int s) { }
class Aleph0 extends BaseAdapter {
int count = 40; /* starting amount */
public int getCount() { return count; }
public Object getItem(int pos) { return pos; }
public long getItemId(int pos) { return pos; }
public View getView(int pos, View v, ViewGroup p) {
TextView view = new TextView(Test.this);
view.setText("entry " + pos);
return view;
}
}
}
You should obviously use separate threads for long running actions (like loading web-data) and might want to indicate progress in the last list item (like the market or gmail apps do).
Just wanted to contribute a solution that I used for my app.
It is also based on the OnScrollListener interface, but I found it to have a much better scrolling performance on low-end devices, since none of the visible/total count calculations are carried out during the scroll operations.
Let your ListFragment or ListActivity implement OnScrollListener
Add the following methods to that class:
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
//leave this empty
}
#Override
public void onScrollStateChanged(AbsListView listView, int scrollState) {
if (scrollState == SCROLL_STATE_IDLE) {
if (listView.getLastVisiblePosition() >= listView.getCount() - 1 - threshold) {
currentPage++;
//load more list items:
loadElements(currentPage);
}
}
}
where currentPage is the page of your datasource that should be added to your list, and threshold is the number of list items (counted from the end) that should, if visible, trigger the loading process. If you set threshold to 0, for instance, the user has to scroll to the very end of the list in order to load more items.
(optional) As you can see, the "load-more check" is only called when the user stops scrolling. To improve usability, you may inflate and add a loading indicator to the end of the list via listView.addFooterView(yourFooterView). One example for such a footer view:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/footer_layout"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:padding="10dp" >
<ProgressBar
android:id="#+id/progressBar1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_gravity="center_vertical" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toRightOf="#+id/progressBar1"
android:padding="5dp"
android:text="#string/loading_text" />
</RelativeLayout>
(optional) Finally, remove that loading indicator by calling listView.removeFooterView(yourFooterView) if there are no more items or pages.
You can detect end of the list with help of onScrollListener, working code is presented below:
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (view.getAdapter() != null && ((firstVisibleItem + visibleItemCount) >= totalItemCount) && totalItemCount != mPrevTotalItemCount) {
Log.v(TAG, "onListEnd, extending list");
mPrevTotalItemCount = totalItemCount;
mAdapter.addMoreData();
}
}
Another way to do that (inside adapter) is as following:
public View getView(int pos, View v, ViewGroup p) {
if(pos==getCount()-1){
addMoreData(); //should be asynctask or thread
}
return view;
}
Be aware that this method will be called many times, so you need to add another condition to block multiple calls of addMoreData().
When you add all elements to the list, please call notifyDataSetChanged() inside yours adapter to update the View (it should be run on UI thread - runOnUiThread)
At Ognyan Bankov GitHub i found a simple and working solution!
It makes use of the Volley HTTP library that makes networking for Android apps easier and most importantly, faster. Volley is available through the open AOSP repository.
The given code demonstrates:
ListView which is populated by HTTP paginated requests.
Usage of NetworkImageView.
"Endless" ListView pagination with read-ahead.
For future consistence i forked Bankov's repo.
Here is a solution that also makes it easy to show a loading view in the end of the ListView while it's loading.
You can see the classes here:
https://github.com/CyberEagle/OpenProjects/blob/master/android-projects/widgets/src/main/java/br/com/cybereagle/androidwidgets/helper/ListViewWithLoadingIndicatorHelper.java
- Helper to make it possible to use the features without extending from SimpleListViewWithLoadingIndicator.
https://github.com/CyberEagle/OpenProjects/blob/master/android-projects/widgets/src/main/java/br/com/cybereagle/androidwidgets/listener/EndlessScrollListener.java
- Listener that starts loading data when the user is about to reach the bottom of the ListView.
https://github.com/CyberEagle/OpenProjects/blob/master/android-projects/widgets/src/main/java/br/com/cybereagle/androidwidgets/view/SimpleListViewWithLoadingIndicator.java
- The EndlessListView. You can use this class directly or extend from it.
May be a little late but the following solution happened very useful in my case.
In a way all you need to do is add to your ListView a Footer and create for it addOnLayoutChangeListener.
http://developer.android.com/reference/android/widget/ListView.html#addFooterView(android.view.View)
For example:
ListView listView1 = (ListView) v.findViewById(R.id.dialogsList); // Your listView
View loadMoreView = getActivity().getLayoutInflater().inflate(R.layout.list_load_more, null); // Getting your layout of FooterView, which will always be at the bottom of your listview. E.g. you may place on it the ProgressBar or leave it empty-layout.
listView1.addFooterView(loadMoreView); // Adding your View to your listview
...
loadMoreView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
#Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
Log.d("Hey!", "Your list has reached bottom");
}
});
This event fires once when a footer becomes visible and works like a charm.
The key of this problem is to detect the load-more event, start an async request for data and then update the list. Also an adapter with loading indicator and other decorators is needed. In fact, the problem is very complicated in some corner cases. Just a OnScrollListener implementation is not enough, because sometimes the items do not fill the screen.
I have written a personal package which support endless list for RecyclerView, and also provide a async loader implementation AutoPagerFragment which makes it very easy to get data from a multi-page source. It can load any page you want into a RecyclerView on a custom event, not only the next page.
Here is the address: https://github.com/SphiaTower/AutoPagerRecyclerManager
Best solution so far that I have seen is in FastAdapter library for recycler views. It has a EndlessRecyclerOnScrollListener.
Here is an example usage: EndlessScrollListActivity
Once I used it for endless scrolling list I have realised that the setup is a very robust. I'd definitely recommend it.
I've been working in another solution very similar to that, but, I am using a footerView to give the possibility to the user download more elements clicking the footerView, I am using a "menu" which is shown above the ListView and in the bottom of the parent view, this "menu" hides the bottom of the ListView, so, when the listView is scrolling the menu disappear and when scroll state is idle, the menu appear again, but when the user scrolls to the end of the listView, I "ask" to know if the footerView is shown in that case, the menu doesn't appear and the user can see the footerView to load more content. Here the code:
Regards.
listView.setOnScrollListener(new OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// TODO Auto-generated method stub
if(scrollState == SCROLL_STATE_IDLE) {
if(footerView.isShown()) {
bottomView.setVisibility(LinearLayout.INVISIBLE);
} else {
bottomView.setVisibility(LinearLayout.VISIBLE);
} else {
bottomView.setVisibility(LinearLayout.INVISIBLE);
}
}
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
}
});
I know its an old question and the Android world has mostly moved on to RecyclerViews, but for anyone interested, you may find this library very interesting.
It uses the BaseAdapter used with the ListView to detect when the list has been scrolled to the last item or when it is being scrolled away from the last item.
It comes with an example project(barely 100 lines of Activity code) that can be used to quickly understand how it works.
Simple usage:
class Boy{
private String name;
private double height;
private int age;
//Other code
}
An adapter to hold Boy objects would look like:
public class BoysAdapter extends EndlessAdapter<Boy>{
ViewHolder holder = null;
if (convertView == null) {
LayoutInflater inflater = LayoutInflater.from(parent
.getContext());
holder = new ViewHolder();
convertView = inflater.inflate(
R.layout.list_cell, parent, false);
holder.nameView = convertView.findViewById(R.id.cell);
// minimize the default image.
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
Boy boy = getItem(position);
try {
holder.nameView.setText(boy.getName());
///Other data rendering codes.
} catch (Exception e) {
e.printStackTrace();
}
return super.getView(position,convertView,parent);
}
Notice how the BoysAdapter's getView method returns a call to the EndlessAdapter superclass's getView method. This is 100% essential.
Now to create the adapter, do:
adapter = new ModelAdapter() {
#Override
public void onScrollToBottom(int bottomIndex, boolean moreItemsCouldBeAvailable) {
if (moreItemsCouldBeAvailable) {
makeYourServerCallForMoreItems();
} else {
if (loadMore.getVisibility() != View.VISIBLE) {
loadMore.setVisibility(View.VISIBLE);
}
}
}
#Override
public void onScrollAwayFromBottom(int currentIndex) {
loadMore.setVisibility(View.GONE);
}
#Override
public void onFinishedLoading(boolean moreItemsReceived) {
if (!moreItemsReceived) {
loadMore.setVisibility(View.VISIBLE);
}
}
};
The loadMore item is a button or other ui element that may be clicked to fetch more data from the url.
When placed as described in the code, the adapter knows exactly when to show that button and when to disable it. Just create the button in your xml and place it as shown in the adapter code above.
Enjoy.

Categories

Resources