diff --git a/pulltorefresh/default.properties b/pulltorefresh/default.properties index 6cd718f..e3ed280 100644 --- a/pulltorefresh/default.properties +++ b/pulltorefresh/default.properties @@ -1,12 +1,3 @@ -# This file is automatically generated by Android Tools. -# Do not modify this file -- YOUR CHANGES WILL BE ERASED! -# -# This file must be checked in Version Control Systems. -# -# To customize properties used by the Ant build system use, -# "build.properties", and override values to adapt the script to your -# project structure. - -# Project target. -target=android-10 android.library=true +# Project target. +target=android-14 diff --git a/pulltorefresh/project.properties b/pulltorefresh/project.properties new file mode 100644 index 0000000..a67d80b --- /dev/null +++ b/pulltorefresh/project.properties @@ -0,0 +1,12 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system use, +# "ant.properties", and override values to adapt the script to your +# project structure. + +android.library=true +# Project target. +target=android-10 diff --git a/pulltorefresh/src/com/markupartist/android/widget/PullToRefreshListView.java b/pulltorefresh/src/com/markupartist/android/widget/PullToRefreshListView.java index 40b9c75..b9f9774 100644 --- a/pulltorefresh/src/com/markupartist/android/widget/PullToRefreshListView.java +++ b/pulltorefresh/src/com/markupartist/android/widget/PullToRefreshListView.java @@ -31,6 +31,7 @@ public class PullToRefreshListView extends ListView implements OnScrollListener private int mRefreshState = PULL_TO_REFRESH; private OnRefreshListener mOnRefreshListener; + private OnEndOfListReachedListener mOnEndOfListListener; /** * Listener that will receive notifications every time the list scrolls. @@ -52,6 +53,7 @@ public class PullToRefreshListView extends ListView implements OnScrollListener private int mRefreshOriginalTopPadding; private int mLastMotionY; private int mHeight = -1; + private int mScrollPriorLast = -1; private boolean mBounceHack; private TextView mFooterView; @@ -149,46 +151,89 @@ protected void onDraw(Canvas canvas) { adaptFooterHeight(); } } - + /** - * Adapts the height of the footer view. + * Sets list view selection to first element in adapter unless refreshing where it will set it to the refresh view element */ - private void adaptFooterHeight() { - int itemHeight = getTotalItemHeight(); - int footerAndHeaderSize = mFooterView.getHeight() - + (mRefreshViewHeight - mRefreshOriginalTopPadding); - int actualItemsSize = itemHeight - footerAndHeaderSize; - if (mHeight < actualItemsSize) { - mFooterView.setHeight(0); - } else { - int h = mHeight - actualItemsSize; - mFooterView.setHeight(h); - setSelection(1); - } - } - - /** - * Calculates the combined height of all items in the adapter. - * - * Modified from http://iserveandroid.blogspot.com/2011/06/how-to-calculate-lsitviews-total.html - * - * @return - */ - private int getTotalItemHeight() { - ListAdapter adapter = getAdapter(); - int listviewElementsheight = 0; - for(int i =0; i < adapter.getCount(); i++) { - View mView = adapter.getView(i, null, this); - mView.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), - MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); - listviewElementsheight+= mView.getMeasuredHeight(); - } - return listviewElementsheight; - } + private void setSelectionToFirst() { + if (getAdapter() != null && mRefreshState != REFRESHING) { + // This allows for the divider to push the selection down from the top so the fading edge doesn't obscure the first view + setSelectionFromTop(1, getDividerHeight()); + } else { + // Refreshing or no adapter, display the refresh view + super.setSelection(0); + } + } + + @Override + public void setSelection(int position) { + // If the 0th or 1st element do special behavior + if (position <= 1) { + setSelectionToFirst(); + } else { + // Force to index 0 while refreshing, allow other indices while not + super.setSelection((mRefreshState == REFRESHING) ? 0 : position); + } + } + + /** + * Adapts the height of the footer view. + */ + private void adaptFooterHeight() { + if (mHeight == -1) { + return; + } + // We can fill up to the total height - header padding to get it off screen + int spaceToFill = mHeight - mRefreshOriginalTopPadding; + int itemHeight = getTotalItemHeight(spaceToFill); + spaceToFill -= itemHeight; + + if (spaceToFill <= 0) { + mFooterView.setHeight(0); + } else { + mFooterView.setHeight(spaceToFill); + setSelectionToFirst(); + } + } + + /** + * Calculates the combined height of all items in the adapter. Does not look at header and footer + * + * Modified from + * http://iserveandroid.blogspot.com/2011/06/how-to-calculate-lsitviews-total.html + * + * @param spaceToFill + * the maximum item height we care about + * @return total item height, capped to spaceToFill + */ + private int getTotalItemHeight(int spaceToFill) { + ListAdapter adapter = getAdapter(); + // If no adapter there is no item height + if (adapter == null) { + return 0; + } + int listviewElementsheight = 0; + // Need to constrain width for lists with variable height items or items with wrapping text + int desiredWidth = MeasureSpec.makeMeasureSpec(getWidth(), + MeasureSpec.AT_MOST); + // Skip header and footer + for (int i = 1; i < adapter.getCount() - 1 + && listviewElementsheight < spaceToFill; i++) { + View mView = adapter.getView(i, null, this); + mView.measure(desiredWidth, View.MeasureSpec.UNSPECIFIED); + listviewElementsheight += mView.getMeasuredHeight(); + } + // Add in dividers + listviewElementsheight += getDividerHeight() * (adapter.getCount() - 1); + if (listviewElementsheight > spaceToFill) { + listviewElementsheight = spaceToFill; + } + return listviewElementsheight; + } @Override protected void onAttachedToWindow() { - setSelection(1); + setSelectionToFirst(); } @Override @@ -198,8 +243,11 @@ public void setAdapter(ListAdapter adapter) { } super.setAdapter(adapter); - - setSelection(1); + // Ensure with new data the view is reinitialized to beginning + setSelectionToFirst(); + // With different data we may need to adjust footer height + adaptFooterHeight(); + mScrollPriorLast = -1; } /** @@ -221,6 +269,15 @@ public void setOnScrollListener(AbsListView.OnScrollListener l) { public void setOnRefreshListener(OnRefreshListener onRefreshListener) { mOnRefreshListener = onRefreshListener; } + + /** + * Register a callback to be invoked when the end of the list is reached. + * + * @param onEndOfListListener The callback to run. + */ + public void setOnEndOfListReachedListener(OnEndOfListReachedListener onEndOfListListener) { + mOnEndOfListListener = onEndOfListListener; + } /** * Set a text to represent when the list was last updated. @@ -256,7 +313,7 @@ public boolean onTouchEvent(MotionEvent event) { || mRefreshView.getTop() <= 0) { // Abort refresh and scroll down below the refresh view resetHeader(); - setSelection(1); + setSelectionToFirst(); } } break; @@ -346,7 +403,6 @@ private void measureView(View child) { child.measure(childWidthSpec, childHeightSpec); } - @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { // When the refresh view is completely visible, change the text to say @@ -376,10 +432,26 @@ public void onScroll(AbsListView view, int firstVisibleItem, } else if (mCurrentScrollState == SCROLL_STATE_FLING && firstVisibleItem == 0 && mRefreshState != REFRESHING) { - setSelection(1); + setSelectionToFirst(); mBounceHack = true; } else if (mBounceHack && mCurrentScrollState == SCROLL_STATE_FLING) { - setSelection(1); + setSelectionToFirst(); + } + + if(mOnEndOfListListener != null) + { + // what is the bottom item that is visible + int lastInScreen = firstVisibleItem + visibleItemCount; + + // is the bottom item visible + if (lastInScreen == totalItemCount) { + // Only do callback 1x when we reach the bottom + if (mScrollPriorLast != lastInScreen) { + mScrollPriorLast = lastInScreen; + // Do end of list reached callback + mOnEndOfListListener.onEndOfListReached(); + } + } } if (mOnScrollListener != null) { @@ -388,7 +460,6 @@ public void onScroll(AbsListView view, int firstVisibleItem, } } - @Override public void onScrollStateChanged(AbsListView view, int scrollState) { mCurrentScrollState = scrollState; @@ -440,7 +511,7 @@ public void onRefreshComplete() { // the next item. if (mRefreshView.getBottom() > 0) { invalidateViews(); - setSelection(1); + setSelectionToFirst(); } } @@ -450,8 +521,6 @@ public void onRefreshComplete() { * list. */ private class OnClickRefreshListener implements OnClickListener { - - @Override public void onClick(View v) { if (mRefreshState != REFRESHING) { prepareForRefresh(); @@ -474,4 +543,15 @@ public interface OnRefreshListener { */ public void onRefresh(); } + + /** + * Interface definition for a callback to be invoked when end of list is reached. + */ + public interface OnEndOfListReachedListener { + /** + * Called 1x when the end of list is reached. This might be used to implement an endless list which auto-loads more data as users scroll. + * It will only be called again if the adapter changes or the list grows/shrinks + */ + public void onEndOfListReached(); + } } diff --git a/pulltorefreshexample/default.properties b/pulltorefreshexample/default.properties index 2069d5a..563a0ff 100644 --- a/pulltorefreshexample/default.properties +++ b/pulltorefreshexample/default.properties @@ -1,12 +1,3 @@ -# This file is automatically generated by Android Tools. -# Do not modify this file -- YOUR CHANGES WILL BE ERASED! -# -# This file must be checked in Version Control Systems. -# -# To customize properties used by the Ant build system use, -# "build.properties", and override values to adapt the script to your -# project structure. - -# Project target. -target=android-10 android.library.reference.1=../pulltorefresh/ +# Project target. +target=android-14 diff --git a/pulltorefreshexample/project.properties b/pulltorefreshexample/project.properties new file mode 100644 index 0000000..826299c --- /dev/null +++ b/pulltorefreshexample/project.properties @@ -0,0 +1,12 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system use, +# "ant.properties", and override values to adapt the script to your +# project structure. + +android.library.reference.1=../pulltorefresh/ +# Project target. +target=android-10 diff --git a/pulltorefreshexample/src/com/markupartist/android/example/pulltorefresh/PullToRefreshActivity.java b/pulltorefreshexample/src/com/markupartist/android/example/pulltorefresh/PullToRefreshActivity.java index 5dd6bad..f736968 100644 --- a/pulltorefreshexample/src/com/markupartist/android/example/pulltorefresh/PullToRefreshActivity.java +++ b/pulltorefreshexample/src/com/markupartist/android/example/pulltorefresh/PullToRefreshActivity.java @@ -9,8 +9,10 @@ import android.view.View; import android.widget.ArrayAdapter; import android.widget.ListView; +import android.widget.Toast; import com.markupartist.android.widget.PullToRefreshListView; +import com.markupartist.android.widget.PullToRefreshListView.OnEndOfListReachedListener; import com.markupartist.android.widget.PullToRefreshListView.OnRefreshListener; public class PullToRefreshActivity extends ListActivity { @@ -25,12 +27,17 @@ public void onCreate(Bundle savedInstanceState) { // Set a listener to be invoked when the list should be refreshed. PullToRefreshListView listView = (PullToRefreshListView) getListView(); listView.setOnRefreshListener(new OnRefreshListener() { - @Override public void onRefresh() { // Do work to refresh the list here. new GetRobotTalkTask().execute(); } }); + listView.setOnEndOfListReachedListener(new OnEndOfListReachedListener() { + public void onEndOfListReached() { + // Post a toast, could load more data here to extend the list + Toast.makeText(getApplicationContext(), "End of list reached", Toast.LENGTH_SHORT).show(); + } + }); mAdapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1,