android fastScroll only covers part of the list - android

I have a class which implements expandable list activity.
In the XML code (or I can do it in java), I set fastScrollEnabled to true. This does in deed enable fast scroll. BUT fast scroll only works in the top portion of the list. Like I can use the fastscroll thumb bar to scroll the whole list but only works in the top section of the scroll bar. It's not proportionate to the entire list. I can drag the thumb bar to the bottom of the list but it does no scrolling since the listview is already scrolled to the bottom due to the odd behaviour of it only working in the top portion of the list.
Confusing I know, I can try to clarify more if needed....
I do implement a custom BaseExpandableListAdapter.

I've just found a workaround to prevent the system to display this wrong behaviour.
There are two scenarios which use different code for the SectionIndexer to work.
The first scenario is the case that you use the FastScrollbar-Thumb to navigate to the next section. Assuming that the groups are your sections the overriden methods for implementing the SectionIndexer would look like that:
#Override
public int getPositionForSection(int section) {
return section;
}
// Gets called when scrolling the list manually
#Override
public int getSectionForPosition(int position) {
return ExpandableListView.getPackedPositionGroup(
expandableListView
.getExpandableListPosition(position));
}
The second scenario is the case that you scroll the list manually and the fast scrollbars move according to the sections, not to all items. The code therefore looks like that:
#Override
public int getPositionForSection(int section) {
return expandableListView.getFlatListPosition(
ExpandableListView.getPackedPositionForGroup(section));
}
// Gets called when scrolling the list manually
#Override
public int getSectionForPosition(int position) {
return ExpandableListView.getPackedPositionGroup(
expandableListView
.getExpandableListPosition(position));
}
As one can see these two behaviours can not play together without further adoption.
The workaround to make it both work is to catch the case when someone is scrolling per hand (i.e. scrolling via touch). This can be done with implementing the OnScrollListener interface with the adapter class and set it onto the ExpandableListView:
public class MyExpandableListAdapter extends BaseExpandableListAdapter
implements SectionIndexer, AbsListView.OnScrollListener {
// Your fields here
// ...
private final ExpandableListView expandableListView;
private boolean manualScroll;
public MyExpandableListAdapter(ExpandableListView expandableListView
/* Your other arguments */) {
this.expandableListView = expandableListView;
this.expandableListView.setOnScrollListener(this);
// Other initializations
}
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
this.manualScroll = scrollState == SCROLL_STATE_TOUCH_SCROLL;
}
#Override
public void onScroll(AbsListView view,
int firstVisibleItem,
int visibleItemCount,
int totalItemCount) {}
#Override
public int getPositionForSection(int section) {
if (manualScroll) {
return section;
} else {
return expandableListView.getFlatListPosition(
ExpandableListView.getPackedPositionForGroup(section));
}
}
// Gets called when scrolling the list manually
#Override
public int getSectionForPosition(int position) {
return ExpandableListView.getPackedPositionGroup(
expandableListView
.getExpandableListPosition(position));
}
// Your other methods
// ...
}
That fixed the bug for me.

This is a bug in the fast scroller. It does not play well with ExpandableListView.
See my code.
(it also includes a work-around for some cases)

Related

Material fastscroll thumb with GridView and ArrayAdapter

I have a GridView layout that makes use of an ArrayAdapter to populate its contents. I want to make use of fast-scrolling and as such have added the following attributed to the layout XML:
android:fastScrollAlwaysVisible="true"
android:fastScrollStyle="#android:style/Widget.Material.FastScroll"
I am now able to make use of fast scrolling to navigate but would now like to add a material thumb preview as such:
From my understanding, I would have to implement the SectionIndexer interface from my ArrayAdapter as so:
class exampleArrayAdapter extends ArrayAdapter<...> implements SectionIndexer
At this point, I have reached a bump and can't figure out how to get the thumb preview and fear I may be doing something wrong. Pointers as to how I can get this working or what I should look up would be appreciated.
I have finally had time to look back at this, and the solution turns out to be very trivial! This is what I did:
#Override
public Object[] getSections() {
ArrayList<String> labels = new ArrayList<>();
for (LaunchableActivity activity: mActivityInfos) {
labels.add(activity.getActivityLabel());
}
return labels.toArray();
}
#Override
public int getPositionForSection(int i) {
return i;
}
#Override
public int getSectionForPosition(int i) {
// We do not need this
return 0;
}
I had a list of LaunchableActivity and based of that created a sections array to be returned. For my needs, all I required was to implement getPositionForSection and not getSectionForPosition. Your use case may vary.
The source code where I implemented this is available here, specifically on commits:
a2c9ddd1c647919afbf24262ac1a7772a08e468c
08802e17f4c75835c28232353fed68964f5d7746
0d73a788c6b92d7b9c05a2871778da42af02afd8
f13a59f02690481801bd07ffc593648b8e71d036

in Android Which View to use and how to display n values per page

This is for example as I haven't coded it yet, since I don't know how to start.
Let's say in Android I have String array with 100 values:
String[] myString = new String[100];
for (int number = 0; number < 100; number++) {
myString[number] = "image " + number;
}
Which way should I display for example 5 values per page (which view to use (table, grid) and should I use Fragment replace for each page).
I would like to achieve something like this, but in Android. I just need some guidelines to start.
In general if you have noticed, explicit pagination is not so often done in android, instead, infinite scrolling is used, for example your news feed in Facebook is a list, when you reach the bottom, it loads more and you can scroll more, at the bottom of that it loads again.
To implement this, it is quite easy, just set an onScrollListener and override onScrollStateChanged() method.
Set onScrollListener after initializing your ListView:
//In onCreate()
ListView listView = (ListView)findViewById(R.id.list_view);
listView.setOnScrollListener(new ListScrollListener());
Make your listener class:
private class ListScrollListener implements OnScrollListener{
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (scrollState == SCROLL_STATE_IDLE) {
if (listView.getLastVisiblePosition() >= listView.getCount()-1) {
page_num++;
//TODO Load more list items.
//notify the adapter of the listview that data has changed
mAdapter.notifyDataSetChanged();
}
}
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
//This can be left blank
}
}
If you want you can add a footer with a ProgressBar to your ListView to display when you are loading more items with listView.addFooterView(View v);
You can find a nice tutorial about a custom ListView at Vogella tutorials.

ListView: grouping items by section

Taking for example Gmail App, on my Navigation Drawer, I want a ListView that is grouped by section, similar to inbox, all labels.
Is this behavior achieved by using multiple ListView separated by a "header" TextView (which I have to build manually obviously), or is this section-grouped behavior supported by the Adapter or ListView?
Don't use multiple ListViews, it will mess things up for the scroll.
What you describe can be achieve by using only one ListView + adapter with multiple item view types like this:
public class MyAdapter extends ArrayAdapter<Object> {
// It's very important that the first item have a value of 0.
// If not, the adapter won't work properly (I didn't figure out why yet)
private int TYPE_SEPARATOR = 0;
private int TYPE_DATA = 1;
class Separator {
String title;
}
public MyAdapter(Context context, int resource) {
super(context, resource);
}
#Override
public boolean areAllItemsEnabled() {
return false;
}
#Override
public int getItemViewType(int position) {
if (getItem(position).getClass().isAssignableFrom(Separator.class)) {
return TYPE_SEPARATOR;
}
return TYPE_DATA;
}
#Override
public int getViewTypeCount() {
// Assuming you have only 2 view types
return 2;
}
#Override
public boolean isEnabled(int position) {
// Mark separators as not enabled. That way, the onclick and onlongclik listener
// won't be triggered for those items.
return getItemViewType(position) != TYPE_SEPARATOR;
}
}
You just have to implement your own getView method for a correct rendering.
I am not sure exactly how the Gmail app achieves this behavior, but it seems as though you should work on a custom adapter. Using multiple list views would not be a productive way to approach this problem, as one wants to keep the rows of data (messages) together in single list items.

Android: how can I create a ListView header outside of a list?

This is my situation:
I would like the list to treat the "header" and tabs sections as a list header so that the "header" and tabs do not stay fixed on the screen and they all scroll together with the list.
I can't simply add the header and tabs as a headerView for the list via mListView.addHeaderView() because the tabs will swap out the "List" content area when pressed. The content area for the other tabs will contain other lists, and the header and tabs should scroll with the new list as well.
I'd appreciate any help.
You can override the getViewType method in order to precise how many kind of rows you listview has.
Have a look here.
Think of the entire thing as the ListView. You can call addHeaderView() as many times as you want so you can have two header views ("Header", and "Tab | Tab | Tab").
Re:
I can't simply add the header and tabs as a headerView for the list via mListView.addHeaderView() because the tabs will swap out the "List" content area when pressed.
Yes you can.
Update the reference to your list data and call mAdapter.notifyDataSetChanged().
I had the same need as you, I found this project that implements exactly what you need, plus some more eye-candy tricks.
https://github.com/kmshack/Android-ParallaxHeaderViewPager
The magic is binding the scrolling that happens in the fragment list to the header defined in the activity. There is also a fundamental binding between the selection of a tab and the scroll position in the list, to adjust lists when swiping.
This code is based upon that github repo but is simpler (therefore less robust), read it just to understand what happens, then read the source in the repo.
public class MyFragment extends Fragment implements OnScrollListener {
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
((MyParentActivity) getActivity()).onScrollFragment(view, mTabPosition);
}
public void adjustScroll(int scrollHeight) {
if (scrollHeight == 0 && fragmentListView.getFirstVisiblePosition() >= 1) {
return;
}
officesListView.setSelectionFromTop(1, scrollHeight);
}
}
then in the activity you just need these two specific methods
public class MyParentActivity implements ViewPager.OnPageChangeListener{
#Override
public void onPageSelected(int position)
myFragmentPagerAdapter.getFragmentAt(position)
.adjustScroll((int) (mHeader.getHeight() + ViewHelper.getTranslationY(mHeader)));
}
public void onScrollFragment(AbsListView view, int tabPosition) {
if (mViewPager.getCurrentItem() == tabPosition) {
int scrollY = getScrollY(view);
ViewHelper.setTranslationY(mHeader, Math.max(-scrollY, mMinHeaderTranslation));
}
}
}
this code is good up to API 8 thanks to NineOldAndroids' ViewHelper

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