瀏覽代碼

集成下拉刷新框架

316044749 7 年之前
父節點
當前提交
0c6af7a837
共有 41 個文件被更改,包括 5000 次插入0 次删除
  1. 57 0
      library/src/main/java/com/common/library/pulltorefresh/ILoadingLayout.java
  2. 246 0
      library/src/main/java/com/common/library/pulltorefresh/IPullToRefresh.java
  3. 73 0
      library/src/main/java/com/common/library/pulltorefresh/LoadingLayoutProxy.java
  4. 178 0
      library/src/main/java/com/common/library/pulltorefresh/OverscrollHelper.java
  5. 476 0
      library/src/main/java/com/common/library/pulltorefresh/PullToRefreshAdapterViewBase.java
  6. 1654 0
      library/src/main/java/com/common/library/pulltorefresh/PullToRefreshBase.java
  7. 103 0
      library/src/main/java/com/common/library/pulltorefresh/PullToRefreshExpandableListView.java
  8. 103 0
      library/src/main/java/com/common/library/pulltorefresh/PullToRefreshGridView.java
  9. 112 0
      library/src/main/java/com/common/library/pulltorefresh/PullToRefreshHorizontalScrollView.java
  10. 338 0
      library/src/main/java/com/common/library/pulltorefresh/PullToRefreshListView.java
  11. 111 0
      library/src/main/java/com/common/library/pulltorefresh/PullToRefreshScrollView.java
  12. 166 0
      library/src/main/java/com/common/library/pulltorefresh/PullToRefreshWebView.java
  13. 134 0
      library/src/main/java/com/common/library/pulltorefresh/extras/PullToRefreshWebView2.java
  14. 97 0
      library/src/main/java/com/common/library/pulltorefresh/extras/SoundPullEventListener.java
  15. 43 0
      library/src/main/java/com/common/library/pulltorefresh/internal/EmptyViewMethodAccessor.java
  16. 146 0
      library/src/main/java/com/common/library/pulltorefresh/internal/FlipLoadingLayout.java
  17. 147 0
      library/src/main/java/com/common/library/pulltorefresh/internal/IndicatorLayout.java
  18. 393 0
      library/src/main/java/com/common/library/pulltorefresh/internal/LoadingLayout.java
  19. 110 0
      library/src/main/java/com/common/library/pulltorefresh/internal/RotateLoadingLayout.java
  20. 13 0
      library/src/main/java/com/common/library/pulltorefresh/internal/Utils.java
  21. 70 0
      library/src/main/java/com/common/library/pulltorefresh/internal/ViewCompat.java
  22. 21 0
      library/src/main/res/anim/slide_in_from_bottom.xml
  23. 21 0
      library/src/main/res/anim/slide_in_from_top.xml
  24. 21 0
      library/src/main/res/anim/slide_out_to_bottom.xml
  25. 21 0
      library/src/main/res/anim/slide_out_to_top.xml
  26. 二進制
      library/src/main/res/drawable-hdpi/default_ptr_flip.png
  27. 二進制
      library/src/main/res/drawable-hdpi/default_ptr_rotate.png
  28. 二進制
      library/src/main/res/drawable-hdpi/indicator_arrow.png
  29. 二進制
      library/src/main/res/drawable-mdpi/default_ptr_flip.png
  30. 二進制
      library/src/main/res/drawable-mdpi/default_ptr_rotate.png
  31. 二進制
      library/src/main/res/drawable-mdpi/indicator_arrow.png
  32. 二進制
      library/src/main/res/drawable-xhdpi/default_ptr_flip.png
  33. 二進制
      library/src/main/res/drawable-xhdpi/default_ptr_rotate.png
  34. 二進制
      library/src/main/res/drawable-xhdpi/indicator_arrow.png
  35. 18 0
      library/src/main/res/drawable/indicator_bg_bottom.xml
  36. 18 0
      library/src/main/res/drawable/indicator_bg_top.xml
  37. 29 0
      library/src/main/res/layout/pull_to_refresh_header_horizontal.xml
  38. 59 0
      library/src/main/res/layout/pull_to_refresh_header_vertical.xml
  39. 1 0
      library/src/main/res/values/attrs.xml
  40. 8 0
      library/src/main/res/values/ids.xml
  41. 13 0
      library/src/main/res/values/pull_refresh_strings.xml

+ 57 - 0
library/src/main/java/com/common/library/pulltorefresh/ILoadingLayout.java

@@ -0,0 +1,57 @@
+package com.common.library.pulltorefresh;
+
+import android.graphics.Typeface;
+import android.graphics.drawable.Drawable;
+
+public interface ILoadingLayout {
+
+	/**
+	 * Set the Last Updated Text. This displayed under the main label when
+	 * Pulling
+	 * 
+	 * @param label - Label to set
+	 */
+	public void setLastUpdatedLabel(CharSequence label);
+
+	/**
+	 * Set the drawable used in the loading layout. This is the same as calling
+	 * <code>setLoadingDrawable(drawable, Mode.BOTH)</code>
+	 * 
+	 * @param drawable - Drawable to display
+	 */
+	public void setLoadingDrawable(Drawable drawable);
+
+	/**
+	 * Set Text to show when the Widget is being Pulled
+	 * <code>setPullLabel(releaseLabel, Mode.BOTH)</code>
+	 * 
+	 * @param pullLabel - CharSequence to display
+	 */
+	public void setPullLabel(CharSequence pullLabel);
+
+	/**
+	 * Set Text to show when the Widget is refreshing
+	 * <code>setRefreshingLabel(releaseLabel, Mode.BOTH)</code>
+	 * 
+	 * @param refreshingLabel - CharSequence to display
+	 */
+	public void setRefreshingLabel(CharSequence refreshingLabel);
+
+	/**
+	 * Set Text to show when the Widget is being pulled, and will refresh when
+	 * released. This is the same as calling
+	 * <code>setReleaseLabel(releaseLabel, Mode.BOTH)</code>
+	 * 
+	 * @param releaseLabel - CharSequence to display
+	 */
+	public void setReleaseLabel(CharSequence releaseLabel);
+
+	/**
+	 * Set's the Sets the typeface and style in which the text should be
+	 * displayed. Please see
+	 * {@link android.widget.TextView#setTypeface(Typeface)
+	 * TextView#setTypeface(Typeface)}.
+	 */
+	public void setTextTypeface(Typeface tf);
+
+}

+ 246 - 0
library/src/main/java/com/common/library/pulltorefresh/IPullToRefresh.java

@@ -0,0 +1,246 @@
+/*******************************************************************************
+ * Copyright 2011, 2012 Chris Banes.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *******************************************************************************/
+package com.common.library.pulltorefresh;
+
+import android.view.View;
+import android.view.animation.Interpolator;
+
+import com.common.library.pulltorefresh.PullToRefreshBase.Mode;
+import com.common.library.pulltorefresh.PullToRefreshBase.OnPullEventListener;
+import com.common.library.pulltorefresh.PullToRefreshBase.OnRefreshListener;
+import com.common.library.pulltorefresh.PullToRefreshBase.OnRefreshListener2;
+import com.common.library.pulltorefresh.PullToRefreshBase.State;
+
+public interface IPullToRefresh<T extends View> {
+
+	/**
+	 * Demos the Pull-to-Refresh functionality to the user so that they are
+	 * aware it is there. This could be useful when the user first opens your
+	 * app, etc. The animation will only happen if the Refresh View (ListView,
+	 * ScrollView, etc) is in a state where a Pull-to-Refresh could occur by a
+	 * user's touch gesture (i.e. scrolled to the top/bottom).
+	 * 
+	 * @return true - if the Demo has been started, false if not.
+	 */
+	public boolean demo();
+
+	/**
+	 * Get the mode that this view is currently in. This is only really useful
+	 * when using <code>Mode.BOTH</code>.
+	 * 
+	 * @return Mode that the view is currently in
+	 */
+	public Mode getCurrentMode();
+
+	/**
+	 * Returns whether the Touch Events are filtered or not. If true is
+	 * returned, then the View will only use touch events where the difference
+	 * in the Y-axis is greater than the difference in the X-axis. This means
+	 * that the View will not interfere when it is used in a horizontal
+	 * scrolling View (such as a ViewPager).
+	 * 
+	 * @return boolean - true if the View is filtering Touch Events
+	 */
+	public boolean getFilterTouchEvents();
+
+	/**
+	 * Returns a proxy object which allows you to call methods on all of the
+	 * LoadingLayouts (the Views which show when Pulling/Refreshing).
+	 * <p />
+	 * You should not keep the result of this method any longer than you need
+	 * it.
+	 * 
+	 * @return Object which will proxy any calls you make on it, to all of the
+	 *         LoadingLayouts.
+	 */
+	public ILoadingLayout getLoadingLayoutProxy();
+
+	/**
+	 * Returns a proxy object which allows you to call methods on the
+	 * LoadingLayouts (the Views which show when Pulling/Refreshing). The actual
+	 * LoadingLayout(s) which will be affected, are chosen by the parameters you
+	 * give.
+	 * <p />
+	 * You should not keep the result of this method any longer than you need
+	 * it.
+	 * 
+	 * @param includeStart - Whether to include the Start/Header Views
+	 * @param includeEnd - Whether to include the End/Footer Views
+	 * @return Object which will proxy any calls you make on it, to the
+	 *         LoadingLayouts included.
+	 */
+	public ILoadingLayout getLoadingLayoutProxy(boolean includeStart, boolean includeEnd);
+
+	/**
+	 * Get the mode that this view has been set to. If this returns
+	 * <code>Mode.BOTH</code>, you can use <code>getCurrentMode()</code> to
+	 * check which mode the view is currently in
+	 * 
+	 * @return Mode that the view has been set to
+	 */
+	public Mode getMode();
+
+	/**
+	 * Get the Wrapped Refreshable View. Anything returned here has already been
+	 * added to the content view.
+	 * 
+	 * @return The View which is currently wrapped
+	 */
+	public T getRefreshableView();
+
+	/**
+	 * Get whether the 'Refreshing' View should be automatically shown when
+	 * refreshing. Returns true by default.
+	 * 
+	 * @return - true if the Refreshing View will be show
+	 */
+	public boolean getShowViewWhileRefreshing();
+
+	/**
+	 * @return - The state that the View is currently in.
+	 */
+	public State getState();
+
+	/**
+	 * Whether Pull-to-Refresh is enabled
+	 * 
+	 * @return enabled
+	 */
+	public boolean isPullToRefreshEnabled();
+
+	/**
+	 * Gets whether Overscroll support is enabled. This is different to
+	 * Android's standard Overscroll support (the edge-glow) which is available
+	 * from GINGERBREAD onwards
+	 * 
+	 * @return true - if both PullToRefresh-OverScroll and Android's inbuilt
+	 *         OverScroll are enabled
+	 */
+	public boolean isPullToRefreshOverScrollEnabled();
+
+	/**
+	 * Returns whether the Widget is currently in the Refreshing mState
+	 * 
+	 * @return true if the Widget is currently refreshing
+	 */
+	public boolean isRefreshing();
+
+	/**
+	 * Returns whether the widget has enabled scrolling on the Refreshable View
+	 * while refreshing.
+	 * 
+	 * @return true if the widget has enabled scrolling while refreshing
+	 */
+	public boolean isScrollingWhileRefreshingEnabled();
+
+	/**
+	 * Mark the current Refresh as complete. Will Reset the UI and hide the
+	 * Refreshing View
+	 */
+	public void onRefreshComplete();
+
+	/**
+	 * Set the Touch Events to be filtered or not. If set to true, then the View
+	 * will only use touch events where the difference in the Y-axis is greater
+	 * than the difference in the X-axis. This means that the View will not
+	 * interfere when it is used in a horizontal scrolling View (such as a
+	 * ViewPager), but will restrict which types of finger scrolls will trigger
+	 * the View.
+	 * 
+	 * @param filterEvents - true if you want to filter Touch Events. Default is
+	 *            true.
+	 */
+	public void setFilterTouchEvents(boolean filterEvents);
+
+	/**
+	 * Set the mode of Pull-to-Refresh that this view will use.
+	 * 
+	 * @param mode - Mode to set the View to
+	 */
+	public void setMode(Mode mode);
+
+	/**
+	 * Set OnPullEventListener for the Widget
+	 * 
+	 * @param listener - Listener to be used when the Widget has a pull event to
+	 *            propogate.
+	 */
+	public void setOnPullEventListener(OnPullEventListener<T> listener);
+
+	/**
+	 * Set OnRefreshListener for the Widget
+	 * 
+	 * @param listener - Listener to be used when the Widget is set to Refresh
+	 */
+	public void setOnRefreshListener(OnRefreshListener<T> listener);
+
+	/**
+	 * Set OnRefreshListener for the Widget
+	 * 
+	 * @param listener - Listener to be used when the Widget is set to Refresh
+	 */
+	public void setOnRefreshListener(OnRefreshListener2<T> listener);
+
+	/**
+	 * Sets whether Overscroll support is enabled. This is different to
+	 * Android's standard Overscroll support (the edge-glow). This setting only
+	 * takes effect when running on device with Android v2.3 or greater.
+	 * 
+	 * @param enabled - true if you want Overscroll enabled
+	 */
+	public void setPullToRefreshOverScrollEnabled(boolean enabled);
+
+	/**
+	 * Sets the Widget to be in the refresh state. The UI will be updated to
+	 * show the 'Refreshing' view, and be scrolled to show such.
+	 */
+	public void setRefreshing();
+
+	/**
+	 * Sets the Widget to be in the refresh state. The UI will be updated to
+	 * show the 'Refreshing' view.
+	 * 
+	 * @param doScroll - true if you want to force a scroll to the Refreshing
+	 *            view.
+	 */
+	public void setRefreshing(boolean doScroll);
+
+	/**
+	 * Sets the Animation Interpolator that is used for animated scrolling.
+	 * Defaults to a DecelerateInterpolator
+	 * 
+	 * @param interpolator - Interpolator to use
+	 */
+	public void setScrollAnimationInterpolator(Interpolator interpolator);
+
+	/**
+	 * By default the Widget disables scrolling on the Refreshable View while
+	 * refreshing. This method can change this behaviour.
+	 * 
+	 * @param scrollingWhileRefreshingEnabled - true if you want to enable
+	 *            scrolling while refreshing
+	 */
+	public void setScrollingWhileRefreshingEnabled(boolean scrollingWhileRefreshingEnabled);
+
+	/**
+	 * A mutator to enable/disable whether the 'Refreshing' View should be
+	 * automatically shown when refreshing.
+	 * 
+	 * @param showView
+	 */
+	public void setShowViewWhileRefreshing(boolean showView);
+
+}

+ 73 - 0
library/src/main/java/com/common/library/pulltorefresh/LoadingLayoutProxy.java

@@ -0,0 +1,73 @@
+package com.common.library.pulltorefresh;
+
+import java.util.HashSet;
+
+import android.graphics.Typeface;
+import android.graphics.drawable.Drawable;
+
+import com.common.library.pulltorefresh.internal.LoadingLayout;
+
+public class LoadingLayoutProxy implements ILoadingLayout {
+
+	private final HashSet<LoadingLayout> mLoadingLayouts;
+
+	LoadingLayoutProxy() {
+		mLoadingLayouts = new HashSet<LoadingLayout>();
+	}
+
+	/**
+	 * This allows you to add extra LoadingLayout instances to this proxy. This
+	 * is only necessary if you keep your own instances, and want to have them
+	 * included in any
+	 * {@link PullToRefreshBase#createLoadingLayoutProxy(boolean, boolean)
+	 * createLoadingLayoutProxy(...)} calls.
+	 * 
+	 * @param layout - LoadingLayout to have included.
+	 */
+	public void addLayout(LoadingLayout layout) {
+		if (null != layout) {
+			mLoadingLayouts.add(layout);
+		}
+	}
+
+	@Override
+	public void setLastUpdatedLabel(CharSequence label) {
+		for (LoadingLayout layout : mLoadingLayouts) {
+			layout.setLastUpdatedLabel(label);
+		}
+	}
+
+	@Override
+	public void setLoadingDrawable(Drawable drawable) {
+		for (LoadingLayout layout : mLoadingLayouts) {
+			layout.setLoadingDrawable(drawable);
+		}
+	}
+
+	@Override
+	public void setRefreshingLabel(CharSequence refreshingLabel) {
+		for (LoadingLayout layout : mLoadingLayouts) {
+			layout.setRefreshingLabel(refreshingLabel);
+		}
+	}
+
+	@Override
+	public void setPullLabel(CharSequence label) {
+		for (LoadingLayout layout : mLoadingLayouts) {
+			layout.setPullLabel(label);
+		}
+	}
+
+	@Override
+	public void setReleaseLabel(CharSequence label) {
+		for (LoadingLayout layout : mLoadingLayouts) {
+			layout.setReleaseLabel(label);
+		}
+	}
+
+	public void setTextTypeface(Typeface tf) {
+		for (LoadingLayout layout : mLoadingLayouts) {
+			layout.setTextTypeface(tf);
+		}
+	}
+}

+ 178 - 0
library/src/main/java/com/common/library/pulltorefresh/OverscrollHelper.java

@@ -0,0 +1,178 @@
+/*******************************************************************************
+ * Copyright 2011, 2012 Chris Banes.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *******************************************************************************/
+package com.common.library.pulltorefresh;
+
+import android.annotation.TargetApi;
+import android.util.Log;
+import android.view.View;
+
+import com.common.library.pulltorefresh.PullToRefreshBase.Mode;
+import com.common.library.pulltorefresh.PullToRefreshBase.State;
+
+@TargetApi(9)
+public final class OverscrollHelper {
+
+	static final String LOG_TAG = "OverscrollHelper";
+	static final float DEFAULT_OVERSCROLL_SCALE = 1f;
+
+	/**
+	 * Helper method for Overscrolling that encapsulates all of the necessary
+	 * function.
+	 * <p/>
+	 * This should only be used on AdapterView's such as ListView as it just
+	 * calls through to overScrollBy() with the scrollRange = 0. AdapterView's
+	 * do not have a scroll range (i.e. getScrollY() doesn't work).
+	 * 
+	 * @param view - PullToRefreshView that is calling this.
+	 * @param deltaX - Change in X in pixels, passed through from from
+	 *            overScrollBy call
+	 * @param scrollX - Current X scroll value in pixels before applying deltaY,
+	 *            passed through from from overScrollBy call
+	 * @param deltaY - Change in Y in pixels, passed through from from
+	 *            overScrollBy call
+	 * @param scrollY - Current Y scroll value in pixels before applying deltaY,
+	 *            passed through from from overScrollBy call
+	 * @param isTouchEvent - true if this scroll operation is the result of a
+	 *            touch event, passed through from from overScrollBy call
+	 */
+	public static void overScrollBy(final PullToRefreshBase<?> view, final int deltaX, final int scrollX,
+			final int deltaY, final int scrollY, final boolean isTouchEvent) {
+		overScrollBy(view, deltaX, scrollX, deltaY, scrollY, 0, isTouchEvent);
+	}
+
+	/**
+	 * Helper method for Overscrolling that encapsulates all of the necessary
+	 * function. This version of the call is used for Views that need to specify
+	 * a Scroll Range but scroll back to it's edge correctly.
+	 * 
+	 * @param view - PullToRefreshView that is calling this.
+	 * @param deltaX - Change in X in pixels, passed through from from
+	 *            overScrollBy call
+	 * @param scrollX - Current X scroll value in pixels before applying deltaY,
+	 *            passed through from from overScrollBy call
+	 * @param deltaY - Change in Y in pixels, passed through from from
+	 *            overScrollBy call
+	 * @param scrollY - Current Y scroll value in pixels before applying deltaY,
+	 *            passed through from from overScrollBy call
+	 * @param scrollRange - Scroll Range of the View, specifically needed for
+	 *            ScrollView
+	 * @param isTouchEvent - true if this scroll operation is the result of a
+	 *            touch event, passed through from from overScrollBy call
+	 */
+	public static void overScrollBy(final PullToRefreshBase<?> view, final int deltaX, final int scrollX,
+			final int deltaY, final int scrollY, final int scrollRange, final boolean isTouchEvent) {
+		overScrollBy(view, deltaX, scrollX, deltaY, scrollY, scrollRange, 0, DEFAULT_OVERSCROLL_SCALE, isTouchEvent);
+	}
+
+	/**
+	 * Helper method for Overscrolling that encapsulates all of the necessary
+	 * function. This is the advanced version of the call.
+	 * 
+	 * @param view - PullToRefreshView that is calling this.
+	 * @param deltaX - Change in X in pixels, passed through from from
+	 *            overScrollBy call
+	 * @param scrollX - Current X scroll value in pixels before applying deltaY,
+	 *            passed through from from overScrollBy call
+	 * @param deltaY - Change in Y in pixels, passed through from from
+	 *            overScrollBy call
+	 * @param scrollY - Current Y scroll value in pixels before applying deltaY,
+	 *            passed through from from overScrollBy call
+	 * @param scrollRange - Scroll Range of the View, specifically needed for
+	 *            ScrollView
+	 * @param fuzzyThreshold - Threshold for which the values how fuzzy we
+	 *            should treat the other values. Needed for WebView as it
+	 *            doesn't always scroll back to it's edge. 0 = no fuzziness.
+	 * @param scaleFactor - Scale Factor for overscroll amount
+	 * @param isTouchEvent - true if this scroll operation is the result of a
+	 *            touch event, passed through from from overScrollBy call
+	 */
+	public static void overScrollBy(final PullToRefreshBase<?> view, final int deltaX, final int scrollX,
+			final int deltaY, final int scrollY, final int scrollRange, final int fuzzyThreshold,
+			final float scaleFactor, final boolean isTouchEvent) {
+
+		final int deltaValue, currentScrollValue, scrollValue;
+		switch (view.getPullToRefreshScrollDirection()) {
+			case HORIZONTAL:
+				deltaValue = deltaX;
+				scrollValue = scrollX;
+				currentScrollValue = view.getScrollX();
+				break;
+			case VERTICAL:
+			default:
+				deltaValue = deltaY;
+				scrollValue = scrollY;
+				currentScrollValue = view.getScrollY();
+				break;
+		}
+
+		// Check that OverScroll is enabled and that we're not currently
+		// refreshing.
+		if (view.isPullToRefreshOverScrollEnabled() && !view.isRefreshing()) {
+			final Mode mode = view.getMode();
+
+			// Check that Pull-to-Refresh is enabled, and the event isn't from
+			// touch
+			if (mode.permitsPullToRefresh() && !isTouchEvent && deltaValue != 0) {
+				final int newScrollValue = (deltaValue + scrollValue);
+
+				if (PullToRefreshBase.DEBUG) {
+					Log.d(LOG_TAG, "OverScroll. DeltaX: " + deltaX + ", ScrollX: " + scrollX + ", DeltaY: " + deltaY
+							+ ", ScrollY: " + scrollY + ", NewY: " + newScrollValue + ", ScrollRange: " + scrollRange
+							+ ", CurrentScroll: " + currentScrollValue);
+				}
+
+				if (newScrollValue < (0 - fuzzyThreshold)) {
+					// Check the mode supports the overscroll direction, and
+					// then move scroll
+					if (mode.showHeaderLoadingLayout()) {
+						// If we're currently at zero, we're about to start
+						// overscrolling, so change the state
+						if (currentScrollValue == 0) {
+							view.setState(State.OVERSCROLLING);
+						}
+
+						view.setHeaderScroll((int) (scaleFactor * (currentScrollValue + newScrollValue)));
+					}
+				} else if (newScrollValue > (scrollRange + fuzzyThreshold)) {
+					// Check the mode supports the overscroll direction, and
+					// then move scroll
+					if (mode.showFooterLoadingLayout()) {
+						// If we're currently at zero, we're about to start
+						// overscrolling, so change the state
+						if (currentScrollValue == 0) {
+							view.setState(State.OVERSCROLLING);
+						}
+
+						view.setHeaderScroll((int) (scaleFactor * (currentScrollValue + newScrollValue - scrollRange)));
+					}
+				} else if (Math.abs(newScrollValue) <= fuzzyThreshold
+						|| Math.abs(newScrollValue - scrollRange) <= fuzzyThreshold) {
+					// Means we've stopped overscrolling, so scroll back to 0
+					view.setState(State.RESET);
+				}
+			} else if (isTouchEvent && State.OVERSCROLLING == view.getState()) {
+				// This condition means that we were overscrolling from a fling,
+				// but the user has touched the View and is now overscrolling
+				// from touch instead. We need to just reset.
+				view.setState(State.RESET);
+			}
+		}
+	}
+
+	static boolean isAndroidOverScrollEnabled(View view) {
+		return view.getOverScrollMode() != View.OVER_SCROLL_NEVER;
+	}
+}

+ 476 - 0
library/src/main/java/com/common/library/pulltorefresh/PullToRefreshAdapterViewBase.java

@@ -0,0 +1,476 @@
+/*******************************************************************************
+ * Copyright 2011, 2012 Chris Banes.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *******************************************************************************/
+package com.common.library.pulltorefresh;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.widget.AbsListView;
+import android.widget.AbsListView.OnScrollListener;
+import android.widget.Adapter;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.ListAdapter;
+
+import com.common.library.R;
+import com.common.library.pulltorefresh.internal.EmptyViewMethodAccessor;
+import com.common.library.pulltorefresh.internal.IndicatorLayout;
+
+public abstract class PullToRefreshAdapterViewBase<T extends AbsListView> extends PullToRefreshBase<T> implements
+		OnScrollListener {
+
+	private static FrameLayout.LayoutParams convertEmptyViewLayoutParams(ViewGroup.LayoutParams lp) {
+		FrameLayout.LayoutParams newLp = null;
+
+		if (null != lp) {
+			newLp = new FrameLayout.LayoutParams(lp);
+
+			if (lp instanceof LinearLayout.LayoutParams) {
+				newLp.gravity = ((LinearLayout.LayoutParams) lp).gravity;
+			} else {
+				newLp.gravity = Gravity.CENTER;
+			}
+		}
+
+		return newLp;
+	}
+
+	private boolean mLastItemVisible;
+	private OnScrollListener mOnScrollListener;
+	private OnLastItemVisibleListener mOnLastItemVisibleListener;
+	private View mEmptyView;
+
+	private IndicatorLayout mIndicatorIvTop;
+	private IndicatorLayout mIndicatorIvBottom;
+
+	private boolean mShowIndicator;
+	private boolean mScrollEmptyView = true;
+
+	public PullToRefreshAdapterViewBase(Context context) {
+		super(context);
+		mRefreshableView.setOnScrollListener(this);
+	}
+
+	public PullToRefreshAdapterViewBase(Context context, AttributeSet attrs) {
+		super(context, attrs);
+		mRefreshableView.setOnScrollListener(this);
+	}
+
+	public PullToRefreshAdapterViewBase(Context context, Mode mode) {
+		super(context, mode);
+		mRefreshableView.setOnScrollListener(this);
+	}
+
+	public PullToRefreshAdapterViewBase(Context context, Mode mode, AnimationStyle animStyle) {
+		super(context, mode, animStyle);
+		mRefreshableView.setOnScrollListener(this);
+	}
+
+	/**
+	 * Gets whether an indicator graphic should be displayed when the View is in
+	 * a state where a Pull-to-Refresh can happen. An example of this state is
+	 * when the Adapter View is scrolled to the top and the mode is set to
+	 * {@link Mode#PULL_FROM_START}. The default value is <var>true</var> if
+	 * {@link PullToRefreshBase#isPullToRefreshOverScrollEnabled()
+	 * isPullToRefreshOverScrollEnabled()} returns false.
+	 * 
+	 * @return true if the indicators will be shown
+	 */
+	public boolean getShowIndicator() {
+		return mShowIndicator;
+	}
+
+	public final void onScroll(final AbsListView view, final int firstVisibleItem, final int visibleItemCount,
+			final int totalItemCount) {
+
+		if (DEBUG) {
+			Log.d(LOG_TAG, "First Visible: " + firstVisibleItem + ". Visible Count: " + visibleItemCount
+					+ ". Total Items:" + totalItemCount);
+		}
+
+		/**
+		 * Set whether the Last Item is Visible. lastVisibleItemIndex is a
+		 * zero-based index, so we minus one totalItemCount to check
+		 */
+		if (null != mOnLastItemVisibleListener) {
+			mLastItemVisible = (totalItemCount > 0) && (firstVisibleItem + visibleItemCount >= totalItemCount - 1);
+		}
+
+		// If we're showing the indicator, check positions...
+		if (getShowIndicatorInternal()) {
+			updateIndicatorViewsVisibility();
+		}
+
+		// Finally call OnScrollListener if we have one
+		if (null != mOnScrollListener) {
+			mOnScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
+		}
+	}
+
+	public final void onScrollStateChanged(final AbsListView view, final int state) {
+		/**
+		 * Check that the scrolling has stopped, and that the last item is
+		 * visible.
+		 */
+		if (state == OnScrollListener.SCROLL_STATE_IDLE && null != mOnLastItemVisibleListener && mLastItemVisible) {
+			mOnLastItemVisibleListener.onLastItemVisible();
+		}
+
+		if (null != mOnScrollListener) {
+			mOnScrollListener.onScrollStateChanged(view, state);
+		}
+	}
+
+	/**
+	 * Pass-through method for {@link PullToRefreshBase#getRefreshableView()
+	 * getRefreshableView()}.
+	 * {@link AdapterView#setAdapter(Adapter)}
+	 * setAdapter(adapter)}. This is just for convenience!
+	 * 
+	 * @param adapter - Adapter to set
+	 */
+	public void setAdapter(ListAdapter adapter) {
+		((AdapterView<ListAdapter>) mRefreshableView).setAdapter(adapter);
+	}
+
+	/**
+	 * Sets the Empty View to be used by the Adapter View.
+	 * <p/>
+	 * We need it handle it ourselves so that we can Pull-to-Refresh when the
+	 * Empty View is shown.
+	 * <p/>
+	 * Please note, you do <strong>not</strong> usually need to call this method
+	 * yourself. Calling setEmptyView on the AdapterView will automatically call
+	 * this method and set everything up. This includes when the Android
+	 * Framework automatically sets the Empty View based on it's ID.
+	 * 
+	 * @param newEmptyView - Empty View to be used
+	 */
+	public final void setEmptyView(View newEmptyView) {
+		FrameLayout refreshableViewWrapper = getRefreshableViewWrapper();
+
+		if (null != newEmptyView) {
+			// New view needs to be clickable so that Android recognizes it as a
+			// target for Touch Events
+			newEmptyView.setClickable(true);
+
+			ViewParent newEmptyViewParent = newEmptyView.getParent();
+			if (null != newEmptyViewParent && newEmptyViewParent instanceof ViewGroup) {
+				((ViewGroup) newEmptyViewParent).removeView(newEmptyView);
+			}
+
+			// We need to convert any LayoutParams so that it works in our
+			// FrameLayout
+			FrameLayout.LayoutParams lp = convertEmptyViewLayoutParams(newEmptyView.getLayoutParams());
+			if (null != lp) {
+				refreshableViewWrapper.addView(newEmptyView, lp);
+			} else {
+				refreshableViewWrapper.addView(newEmptyView);
+			}
+		}
+
+		if (mRefreshableView instanceof EmptyViewMethodAccessor) {
+			((EmptyViewMethodAccessor) mRefreshableView).setEmptyViewInternal(newEmptyView);
+		} else {
+			mRefreshableView.setEmptyView(newEmptyView);
+		}
+		mEmptyView = newEmptyView;
+	}
+
+	/**
+	 * Pass-through method for {@link PullToRefreshBase#getRefreshableView()
+	 * getRefreshableView()}.
+	 * {@link AdapterView#setOnItemClickListener(OnItemClickListener)
+	 * setOnItemClickListener(listener)}. This is just for convenience!
+	 * 
+	 * @param listener - OnItemClickListener to use
+	 */
+	public void setOnItemClickListener(OnItemClickListener listener) {
+		mRefreshableView.setOnItemClickListener(listener);
+	}
+
+	public final void setOnLastItemVisibleListener(OnLastItemVisibleListener listener) {
+		mOnLastItemVisibleListener = listener;
+	}
+
+	public final void setOnScrollListener(OnScrollListener listener) {
+		mOnScrollListener = listener;
+	}
+
+	public final void setScrollEmptyView(boolean doScroll) {
+		mScrollEmptyView = doScroll;
+	}
+
+	/**
+	 * Sets whether an indicator graphic should be displayed when the View is in
+	 * a state where a Pull-to-Refresh can happen. An example of this state is
+	 * when the Adapter View is scrolled to the top and the mode is set to
+	 * {@link Mode#PULL_FROM_START}
+	 * 
+	 * @param showIndicator - true if the indicators should be shown.
+	 */
+	public void setShowIndicator(boolean showIndicator) {
+		mShowIndicator = showIndicator;
+
+		if (getShowIndicatorInternal()) {
+			// If we're set to Show Indicator, add/update them
+			addIndicatorViews();
+		} else {
+			// If not, then remove then
+			removeIndicatorViews();
+		}
+	}
+
+	;
+
+	@Override
+	protected void onPullToRefresh() {
+		super.onPullToRefresh();
+
+		if (getShowIndicatorInternal()) {
+			switch (getCurrentMode()) {
+				case PULL_FROM_END:
+					mIndicatorIvBottom.pullToRefresh();
+					break;
+				case PULL_FROM_START:
+					mIndicatorIvTop.pullToRefresh();
+					break;
+				default:
+					// NO-OP
+					break;
+			}
+		}
+	}
+
+	protected void onRefreshing(boolean doScroll) {
+		super.onRefreshing(doScroll);
+
+		if (getShowIndicatorInternal()) {
+			updateIndicatorViewsVisibility();
+		}
+	}
+
+	@Override
+	protected void onReleaseToRefresh() {
+		super.onReleaseToRefresh();
+
+		if (getShowIndicatorInternal()) {
+			switch (getCurrentMode()) {
+				case PULL_FROM_END:
+					mIndicatorIvBottom.releaseToRefresh();
+					break;
+				case PULL_FROM_START:
+					mIndicatorIvTop.releaseToRefresh();
+					break;
+				default:
+					// NO-OP
+					break;
+			}
+		}
+	}
+
+	@Override
+	protected void onReset() {
+		super.onReset();
+
+		if (getShowIndicatorInternal()) {
+			updateIndicatorViewsVisibility();
+		}
+	}
+
+	@Override
+	protected void handleStyledAttributes(TypedArray a) {
+		// Set Show Indicator to the XML value, or default value
+		mShowIndicator = a.getBoolean(R.styleable.PullToRefresh_ptrShowIndicator, !isPullToRefreshOverScrollEnabled());
+	}
+
+	protected boolean isReadyForPullStart() {
+		return isFirstItemVisible();
+	}
+
+	protected boolean isReadyForPullEnd() {
+		return isLastItemVisible();
+	}
+
+	@Override
+	protected void onScrollChanged(int l, int t, int oldl, int oldt) {
+		super.onScrollChanged(l, t, oldl, oldt);
+		if (null != mEmptyView && !mScrollEmptyView) {
+			mEmptyView.scrollTo(-l, -t);
+		}
+	}
+
+	@Override
+	protected void updateUIForMode() {
+		super.updateUIForMode();
+
+		// Check Indicator Views consistent with new Mode
+		if (getShowIndicatorInternal()) {
+			addIndicatorViews();
+		} else {
+			removeIndicatorViews();
+		}
+	}
+
+	private void addIndicatorViews() {
+		Mode mode = getMode();
+		FrameLayout refreshableViewWrapper = getRefreshableViewWrapper();
+
+		if (mode.showHeaderLoadingLayout() && null == mIndicatorIvTop) {
+			// If the mode can pull down, and we don't have one set already
+			mIndicatorIvTop = new IndicatorLayout(getContext(), Mode.PULL_FROM_START);
+			FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+					ViewGroup.LayoutParams.WRAP_CONTENT);
+			params.rightMargin = getResources().getDimensionPixelSize(R.dimen.indicator_right_padding);
+			params.gravity = Gravity.TOP | Gravity.RIGHT;
+			refreshableViewWrapper.addView(mIndicatorIvTop, params);
+
+		} else if (!mode.showHeaderLoadingLayout() && null != mIndicatorIvTop) {
+			// If we can't pull down, but have a View then remove it
+			refreshableViewWrapper.removeView(mIndicatorIvTop);
+			mIndicatorIvTop = null;
+		}
+
+		if (mode.showFooterLoadingLayout() && null == mIndicatorIvBottom) {
+			// If the mode can pull down, and we don't have one set already
+			mIndicatorIvBottom = new IndicatorLayout(getContext(), Mode.PULL_FROM_END);
+			FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+					ViewGroup.LayoutParams.WRAP_CONTENT);
+			params.rightMargin = getResources().getDimensionPixelSize(R.dimen.indicator_right_padding);
+			params.gravity = Gravity.BOTTOM | Gravity.RIGHT;
+			refreshableViewWrapper.addView(mIndicatorIvBottom, params);
+
+		} else if (!mode.showFooterLoadingLayout() && null != mIndicatorIvBottom) {
+			// If we can't pull down, but have a View then remove it
+			refreshableViewWrapper.removeView(mIndicatorIvBottom);
+			mIndicatorIvBottom = null;
+		}
+	}
+
+	private boolean getShowIndicatorInternal() {
+		return mShowIndicator && isPullToRefreshEnabled();
+	}
+
+	private boolean isFirstItemVisible() {
+		final Adapter adapter = mRefreshableView.getAdapter();
+
+		if (null == adapter || adapter.isEmpty()) {
+			if (DEBUG) {
+				Log.d(LOG_TAG, "isFirstItemVisible. Empty View.");
+			}
+			return true;
+
+		} else {
+
+			/**
+			 * This check should really just be:
+			 * mRefreshableView.getFirstVisiblePosition() == 0, but PtRListView
+			 * internally use a HeaderView which messes the positions up. For
+			 * now we'll just add one to account for it and rely on the inner
+			 * condition which checks getTop().
+			 */
+			if (mRefreshableView.getFirstVisiblePosition() <= 1) {
+				final View firstVisibleChild = mRefreshableView.getChildAt(0);
+				if (firstVisibleChild != null) {
+					return firstVisibleChild.getTop() >= mRefreshableView.getTop();
+				}
+			}
+		}
+
+		return false;
+	}
+
+	private boolean isLastItemVisible() {
+		final Adapter adapter = mRefreshableView.getAdapter();
+
+		if (null == adapter || adapter.isEmpty()) {
+			if (DEBUG) {
+				Log.d(LOG_TAG, "isLastItemVisible. Empty View.");
+			}
+			return true;
+		} else {
+			final int lastItemPosition = mRefreshableView.getCount() - 1;
+			final int lastVisiblePosition = mRefreshableView.getLastVisiblePosition();
+
+			if (DEBUG) {
+				Log.d(LOG_TAG, "isLastItemVisible. Last Item Position: " + lastItemPosition + " Last Visible Pos: "
+						+ lastVisiblePosition);
+			}
+
+			/**
+			 * This check should really just be: lastVisiblePosition ==
+			 * lastItemPosition, but PtRListView internally uses a FooterView
+			 * which messes the positions up. For me we'll just subtract one to
+			 * account for it and rely on the inner condition which checks
+			 * getBottom().
+			 */
+			if (lastVisiblePosition >= lastItemPosition - 1) {
+				final int childIndex = lastVisiblePosition - mRefreshableView.getFirstVisiblePosition();
+				final View lastVisibleChild = mRefreshableView.getChildAt(childIndex);
+				if (lastVisibleChild != null) {
+					return lastVisibleChild.getBottom() <= mRefreshableView.getBottom();
+				}
+			}
+		}
+
+		return false;
+	}
+
+	private void removeIndicatorViews() {
+		if (null != mIndicatorIvTop) {
+			getRefreshableViewWrapper().removeView(mIndicatorIvTop);
+			mIndicatorIvTop = null;
+		}
+
+		if (null != mIndicatorIvBottom) {
+			getRefreshableViewWrapper().removeView(mIndicatorIvBottom);
+			mIndicatorIvBottom = null;
+		}
+	}
+
+	private void updateIndicatorViewsVisibility() {
+		if (null != mIndicatorIvTop) {
+			if (!isRefreshing() && isReadyForPullStart()) {
+				if (!mIndicatorIvTop.isVisible()) {
+					mIndicatorIvTop.show();
+				}
+			} else {
+				if (mIndicatorIvTop.isVisible()) {
+					mIndicatorIvTop.hide();
+				}
+			}
+		}
+
+		if (null != mIndicatorIvBottom) {
+			if (!isRefreshing() && isReadyForPullEnd()) {
+				if (!mIndicatorIvBottom.isVisible()) {
+					mIndicatorIvBottom.show();
+				}
+			} else {
+				if (mIndicatorIvBottom.isVisible()) {
+					mIndicatorIvBottom.hide();
+				}
+			}
+		}
+	}
+}

文件差異過大導致無法顯示
+ 1654 - 0
library/src/main/java/com/common/library/pulltorefresh/PullToRefreshBase.java


+ 103 - 0
library/src/main/java/com/common/library/pulltorefresh/PullToRefreshExpandableListView.java

@@ -0,0 +1,103 @@
+/*******************************************************************************
+ * Copyright 2011, 2012 Chris Banes.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *******************************************************************************/
+package com.common.library.pulltorefresh;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ExpandableListView;
+
+import com.common.library.pulltorefresh.internal.EmptyViewMethodAccessor;
+
+public class PullToRefreshExpandableListView extends PullToRefreshAdapterViewBase<ExpandableListView> {
+
+	public PullToRefreshExpandableListView(Context context) {
+		super(context);
+	}
+
+	public PullToRefreshExpandableListView(Context context, AttributeSet attrs) {
+		super(context, attrs);
+	}
+
+	public PullToRefreshExpandableListView(Context context, Mode mode) {
+		super(context, mode);
+	}
+
+	public PullToRefreshExpandableListView(Context context, Mode mode, AnimationStyle style) {
+		super(context, mode, style);
+	}
+
+	@Override
+	public final Orientation getPullToRefreshScrollDirection() {
+		return Orientation.VERTICAL;
+	}
+
+	@Override
+	protected ExpandableListView createRefreshableView(Context context, AttributeSet attrs) {
+		final ExpandableListView lv;
+		if (VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD) {
+			lv = new InternalExpandableListViewSDK9(context, attrs);
+		} else {
+			lv = new InternalExpandableListView(context, attrs);
+		}
+
+		// Set it to this so it can be used in ListActivity/ListFragment
+		lv.setId(android.R.id.list);
+		return lv;
+	}
+
+	class InternalExpandableListView extends ExpandableListView implements EmptyViewMethodAccessor {
+
+		public InternalExpandableListView(Context context, AttributeSet attrs) {
+			super(context, attrs);
+		}
+
+		@Override
+		public void setEmptyView(View emptyView) {
+			PullToRefreshExpandableListView.this.setEmptyView(emptyView);
+		}
+
+		@Override
+		public void setEmptyViewInternal(View emptyView) {
+			super.setEmptyView(emptyView);
+		}
+	}
+
+	@TargetApi(9)
+	final class InternalExpandableListViewSDK9 extends InternalExpandableListView {
+
+		public InternalExpandableListViewSDK9(Context context, AttributeSet attrs) {
+			super(context, attrs);
+		}
+
+		@Override
+		protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX,
+				int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {
+
+			final boolean returnValue = super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX,
+					scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
+
+			// Does all of the hard work...
+			OverscrollHelper.overScrollBy(PullToRefreshExpandableListView.this, deltaX, scrollX, deltaY, scrollY,
+					isTouchEvent);
+
+			return returnValue;
+		}
+	}
+}

+ 103 - 0
library/src/main/java/com/common/library/pulltorefresh/PullToRefreshGridView.java

@@ -0,0 +1,103 @@
+/*******************************************************************************
+ * Copyright 2011, 2012 Chris Banes.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *******************************************************************************/
+package com.common.library.pulltorefresh;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.GridView;
+
+import com.common.library.R;
+import com.common.library.pulltorefresh.internal.EmptyViewMethodAccessor;
+
+public class PullToRefreshGridView extends PullToRefreshAdapterViewBase<GridView> {
+
+	public PullToRefreshGridView(Context context) {
+		super(context);
+	}
+
+	public PullToRefreshGridView(Context context, AttributeSet attrs) {
+		super(context, attrs);
+	}
+
+	public PullToRefreshGridView(Context context, Mode mode) {
+		super(context, mode);
+	}
+
+	public PullToRefreshGridView(Context context, Mode mode, AnimationStyle style) {
+		super(context, mode, style);
+	}
+
+	@Override
+	public final Orientation getPullToRefreshScrollDirection() {
+		return Orientation.VERTICAL;
+	}
+
+	@Override
+	protected final GridView createRefreshableView(Context context, AttributeSet attrs) {
+		final GridView gv;
+		if (VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD) {
+			gv = new InternalGridViewSDK9(context, attrs);
+		} else {
+			gv = new InternalGridView(context, attrs);
+		}
+
+		// Use Generated ID (from res/values/ids.xml)
+		gv.setId(R.id.gridview);
+		return gv;
+	}
+
+	class InternalGridView extends GridView implements EmptyViewMethodAccessor {
+
+		public InternalGridView(Context context, AttributeSet attrs) {
+			super(context, attrs);
+		}
+
+		@Override
+		public void setEmptyView(View emptyView) {
+			PullToRefreshGridView.this.setEmptyView(emptyView);
+		}
+
+		@Override
+		public void setEmptyViewInternal(View emptyView) {
+			super.setEmptyView(emptyView);
+		}
+	}
+
+	@TargetApi(9)
+	final class InternalGridViewSDK9 extends InternalGridView {
+
+		public InternalGridViewSDK9(Context context, AttributeSet attrs) {
+			super(context, attrs);
+		}
+
+		@Override
+		protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX,
+				int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {
+
+			final boolean returnValue = super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX,
+					scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
+
+			// Does all of the hard work...
+			OverscrollHelper.overScrollBy(PullToRefreshGridView.this, deltaX, scrollX, deltaY, scrollY, isTouchEvent);
+
+			return returnValue;
+		}
+	}
+}

+ 112 - 0
library/src/main/java/com/common/library/pulltorefresh/PullToRefreshHorizontalScrollView.java

@@ -0,0 +1,112 @@
+/*******************************************************************************
+ * Copyright 2011, 2012 Chris Banes.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *******************************************************************************/
+package com.common.library.pulltorefresh;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.HorizontalScrollView;
+
+import com.common.library.R;
+
+public class PullToRefreshHorizontalScrollView extends PullToRefreshBase<HorizontalScrollView> {
+
+	public PullToRefreshHorizontalScrollView(Context context) {
+		super(context);
+	}
+
+	public PullToRefreshHorizontalScrollView(Context context, AttributeSet attrs) {
+		super(context, attrs);
+	}
+
+	public PullToRefreshHorizontalScrollView(Context context, Mode mode) {
+		super(context, mode);
+	}
+
+	public PullToRefreshHorizontalScrollView(Context context, Mode mode, AnimationStyle style) {
+		super(context, mode, style);
+	}
+
+	@Override
+	public final Orientation getPullToRefreshScrollDirection() {
+		return Orientation.HORIZONTAL;
+	}
+
+	@Override
+	protected HorizontalScrollView createRefreshableView(Context context, AttributeSet attrs) {
+		HorizontalScrollView scrollView;
+
+		if (VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD) {
+			scrollView = new InternalHorizontalScrollViewSDK9(context, attrs);
+		} else {
+			scrollView = new HorizontalScrollView(context, attrs);
+		}
+
+		scrollView.setId(R.id.scrollview);
+		return scrollView;
+	}
+
+	@Override
+	protected boolean isReadyForPullStart() {
+		return mRefreshableView.getScrollX() == 0;
+	}
+
+	@Override
+	protected boolean isReadyForPullEnd() {
+		View scrollViewChild = mRefreshableView.getChildAt(0);
+		if (null != scrollViewChild) {
+			return mRefreshableView.getScrollX() >= (scrollViewChild.getWidth() - getWidth());
+		}
+		return false;
+	}
+
+	@TargetApi(9)
+	final class InternalHorizontalScrollViewSDK9 extends HorizontalScrollView {
+
+		public InternalHorizontalScrollViewSDK9(Context context, AttributeSet attrs) {
+			super(context, attrs);
+		}
+
+		@Override
+		protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX,
+				int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {
+
+			final boolean returnValue = super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX,
+					scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
+
+			// Does all of the hard work...
+			OverscrollHelper.overScrollBy(PullToRefreshHorizontalScrollView.this, deltaX, scrollX, deltaY, scrollY,
+					getScrollRange(), isTouchEvent);
+
+			return returnValue;
+		}
+
+		/**
+		 * Taken from the AOSP ScrollView source
+		 */
+		private int getScrollRange() {
+			int scrollRange = 0;
+			if (getChildCount() > 0) {
+				View child = getChildAt(0);
+				scrollRange = Math.max(0, child.getWidth() - (getWidth() - getPaddingLeft() - getPaddingRight()));
+			}
+			return scrollRange;
+		}
+	}
+}

+ 338 - 0
library/src/main/java/com/common/library/pulltorefresh/PullToRefreshListView.java

@@ -0,0 +1,338 @@
+/*******************************************************************************
+ * Copyright 2011, 2012 Chris Banes.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *******************************************************************************/
+package com.common.library.pulltorefresh;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+
+import com.common.library.R;
+import com.common.library.pulltorefresh.internal.EmptyViewMethodAccessor;
+import com.common.library.pulltorefresh.internal.LoadingLayout;
+
+public class PullToRefreshListView extends PullToRefreshAdapterViewBase<ListView> {
+
+	private LoadingLayout mHeaderLoadingView;
+	private LoadingLayout mFooterLoadingView;
+
+	private FrameLayout mLvFooterLoadingFrame;
+
+	private boolean mListViewExtrasEnabled;
+
+	public PullToRefreshListView(Context context) {
+		super(context);
+	}
+
+	public PullToRefreshListView(Context context, AttributeSet attrs) {
+		super(context, attrs);
+	}
+
+	public PullToRefreshListView(Context context, Mode mode) {
+		super(context, mode);
+	}
+
+	public PullToRefreshListView(Context context, Mode mode, AnimationStyle style) {
+		super(context, mode, style);
+	}
+
+	@Override
+	public final Orientation getPullToRefreshScrollDirection() {
+		return Orientation.VERTICAL;
+	}
+
+	@Override
+	protected void onRefreshing(final boolean doScroll) {
+		/**
+		 * If we're not showing the Refreshing view, or the list is empty, the
+		 * the header/footer views won't show so we use the normal method.
+		 */
+		ListAdapter adapter = mRefreshableView.getAdapter();
+		if (!mListViewExtrasEnabled || !getShowViewWhileRefreshing() || null == adapter || adapter.isEmpty()) {
+			super.onRefreshing(doScroll);
+			return;
+		}
+
+		super.onRefreshing(false);
+
+		final LoadingLayout origLoadingView, listViewLoadingView, oppositeListViewLoadingView;
+		final int selection, scrollToY;
+
+		switch (getCurrentMode()) {
+			case MANUAL_REFRESH_ONLY:
+			case PULL_FROM_END:
+				origLoadingView = getFooterLayout();
+				listViewLoadingView = mFooterLoadingView;
+				oppositeListViewLoadingView = mHeaderLoadingView;
+				selection = mRefreshableView.getCount() - 1;
+				scrollToY = getScrollY() - getFooterSize();
+				break;
+			case PULL_FROM_START:
+			default:
+				origLoadingView = getHeaderLayout();
+				listViewLoadingView = mHeaderLoadingView;
+				oppositeListViewLoadingView = mFooterLoadingView;
+				selection = 0;
+				scrollToY = getScrollY() + getHeaderSize();
+				break;
+		}
+
+		// Hide our original Loading View
+		origLoadingView.reset();
+		origLoadingView.hideAllViews();
+
+		// Make sure the opposite end is hidden too
+		oppositeListViewLoadingView.setVisibility(View.GONE);
+
+		// Show the ListView Loading View and set it to refresh.
+		listViewLoadingView.setVisibility(View.VISIBLE);
+		listViewLoadingView.refreshing();
+
+		if (doScroll) {
+			// We need to disable the automatic visibility changes for now
+			disableLoadingLayoutVisibilityChanges();
+
+			// We scroll slightly so that the ListView's header/footer is at the
+			// same Y position as our normal header/footer
+			setHeaderScroll(scrollToY);
+
+			// Make sure the ListView is scrolled to show the loading
+			// header/footer
+			mRefreshableView.setSelection(selection);
+
+			// Smooth scroll as normal
+			smoothScrollTo(0);
+		}
+	}
+
+	@Override
+	protected void onReset() {
+		/**
+		 * If the extras are not enabled, just call up to super and return.
+		 */
+		if (!mListViewExtrasEnabled) {
+			super.onReset();
+			return;
+		}
+
+		final LoadingLayout originalLoadingLayout, listViewLoadingLayout;
+		final int scrollToHeight, selection;
+		final boolean scrollLvToEdge;
+
+		switch (getCurrentMode()) {
+			case MANUAL_REFRESH_ONLY:
+			case PULL_FROM_END:
+				originalLoadingLayout = getFooterLayout();
+				listViewLoadingLayout = mFooterLoadingView;
+				selection = mRefreshableView.getCount() - 1;
+				scrollToHeight = getFooterSize();
+				scrollLvToEdge = Math.abs(mRefreshableView.getLastVisiblePosition() - selection) <= 1;
+				break;
+			case PULL_FROM_START:
+			default:
+				originalLoadingLayout = getHeaderLayout();
+				listViewLoadingLayout = mHeaderLoadingView;
+				scrollToHeight = -getHeaderSize();
+				selection = 0;
+				scrollLvToEdge = Math.abs(mRefreshableView.getFirstVisiblePosition() - selection) <= 1;
+				break;
+		}
+
+		// If the ListView header loading layout is showing, then we need to
+		// flip so that the original one is showing instead
+		if (listViewLoadingLayout.getVisibility() == View.VISIBLE) {
+
+			// Set our Original View to Visible
+			originalLoadingLayout.showInvisibleViews();
+
+			// Hide the ListView Header/Footer
+			listViewLoadingLayout.setVisibility(View.GONE);
+
+			/**
+			 * Scroll so the View is at the same Y as the ListView
+			 * header/footer, but only scroll if: we've pulled to refresh, it's
+			 * positioned correctly
+			 */
+			if (scrollLvToEdge && getState() != State.MANUAL_REFRESHING) {
+				mRefreshableView.setSelection(selection);
+				setHeaderScroll(scrollToHeight);
+			}
+		}
+
+		// Finally, call up to super
+		super.onReset();
+	}
+
+	@Override
+	protected LoadingLayoutProxy createLoadingLayoutProxy(final boolean includeStart, final boolean includeEnd) {
+		LoadingLayoutProxy proxy = super.createLoadingLayoutProxy(includeStart, includeEnd);
+
+		if (mListViewExtrasEnabled) {
+			final Mode mode = getMode();
+
+			if (includeStart && mode.showHeaderLoadingLayout()) {
+				proxy.addLayout(mHeaderLoadingView);
+			}
+			if (includeEnd && mode.showFooterLoadingLayout()) {
+				proxy.addLayout(mFooterLoadingView);
+			}
+		}
+
+		return proxy;
+	}
+
+	protected ListView createListView(Context context, AttributeSet attrs) {
+		final ListView lv;
+		if (VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD) {
+			lv = new InternalListViewSDK9(context, attrs);
+		} else {
+			lv = new InternalListView(context, attrs);
+		}
+		return lv;
+	}
+
+	@Override
+	protected ListView createRefreshableView(Context context, AttributeSet attrs) {
+		ListView lv = createListView(context, attrs);
+
+		// Set it to this so it can be used in ListActivity/ListFragment
+		lv.setId(android.R.id.list);
+		return lv;
+	}
+
+	@Override
+	protected void handleStyledAttributes(TypedArray a) {
+		super.handleStyledAttributes(a);
+
+		mListViewExtrasEnabled = a.getBoolean(R.styleable.PullToRefresh_ptrListViewExtrasEnabled, true);
+
+		if (mListViewExtrasEnabled) {
+			final FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,
+					FrameLayout.LayoutParams.WRAP_CONTENT, Gravity.CENTER_HORIZONTAL);
+
+			// Create Loading Views ready for use later
+			FrameLayout frame = new FrameLayout(getContext());
+			mHeaderLoadingView = createLoadingLayout(getContext(), Mode.PULL_FROM_START, a);
+			mHeaderLoadingView.setVisibility(View.GONE);
+			frame.addView(mHeaderLoadingView, lp);
+			mRefreshableView.addHeaderView(frame, null, false);
+
+			mLvFooterLoadingFrame = new FrameLayout(getContext());
+			mFooterLoadingView = createLoadingLayout(getContext(), Mode.PULL_FROM_END, a);
+			mFooterLoadingView.setVisibility(View.GONE);
+			mLvFooterLoadingFrame.addView(mFooterLoadingView, lp);
+
+			/**
+			 * If the value for Scrolling While Refreshing hasn't been
+			 * explicitly set via XML, enable Scrolling While Refreshing.
+			 */
+			if (!a.hasValue(R.styleable.PullToRefresh_ptrScrollingWhileRefreshingEnabled)) {
+				setScrollingWhileRefreshingEnabled(true);
+			}
+		}
+	}
+
+	@TargetApi(9)
+	final class InternalListViewSDK9 extends InternalListView {
+
+		public InternalListViewSDK9(Context context, AttributeSet attrs) {
+			super(context, attrs);
+		}
+
+		@Override
+		protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX,
+				int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {
+
+			final boolean returnValue = super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX,
+					scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
+
+			// Does all of the hard work...
+			OverscrollHelper.overScrollBy(PullToRefreshListView.this, deltaX, scrollX, deltaY, scrollY, isTouchEvent);
+
+			return returnValue;
+		}
+	}
+
+	protected class InternalListView extends ListView implements EmptyViewMethodAccessor {
+
+		private boolean mAddedLvFooter = false;
+
+		public InternalListView(Context context, AttributeSet attrs) {
+			super(context, attrs);
+		}
+
+		@Override
+		protected void dispatchDraw(Canvas canvas) {
+			/**
+			 * This is a bit hacky, but Samsung's ListView has got a bug in it
+			 * when using Header/Footer Views and the list is empty. This masks
+			 * the issue so that it doesn't cause an FC. See Issue #66.
+			 */
+			try {
+				super.dispatchDraw(canvas);
+			} catch (IndexOutOfBoundsException e) {
+				e.printStackTrace();
+			}
+		}
+
+		@Override
+		public boolean dispatchTouchEvent(MotionEvent ev) {
+			/**
+			 * This is a bit hacky, but Samsung's ListView has got a bug in it
+			 * when using Header/Footer Views and the list is empty. This masks
+			 * the issue so that it doesn't cause an FC. See Issue #66.
+			 */
+			try {
+				return super.dispatchTouchEvent(ev);
+			} catch (IndexOutOfBoundsException e) {
+				e.printStackTrace();
+				return false;
+			}
+		}
+
+		@Override
+		public void setAdapter(ListAdapter adapter) {
+			// Add the Footer View at the last possible moment
+			if (null != mLvFooterLoadingFrame && !mAddedLvFooter) {
+				addFooterView(mLvFooterLoadingFrame, null, false);
+				mAddedLvFooter = true;
+			}
+
+			super.setAdapter(adapter);
+		}
+
+		@Override
+		public void setEmptyView(View emptyView) {
+			PullToRefreshListView.this.setEmptyView(emptyView);
+		}
+
+		@Override
+		public void setEmptyViewInternal(View emptyView) {
+			super.setEmptyView(emptyView);
+		}
+
+	}
+
+}

+ 111 - 0
library/src/main/java/com/common/library/pulltorefresh/PullToRefreshScrollView.java

@@ -0,0 +1,111 @@
+/*******************************************************************************
+ * Copyright 2011, 2012 Chris Banes.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *******************************************************************************/
+package com.common.library.pulltorefresh;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ScrollView;
+
+import com.common.library.R;
+
+public class PullToRefreshScrollView extends PullToRefreshBase<ScrollView> {
+
+	public PullToRefreshScrollView(Context context) {
+		super(context);
+	}
+
+	public PullToRefreshScrollView(Context context, AttributeSet attrs) {
+		super(context, attrs);
+	}
+
+	public PullToRefreshScrollView(Context context, Mode mode) {
+		super(context, mode);
+	}
+
+	public PullToRefreshScrollView(Context context, Mode mode, AnimationStyle style) {
+		super(context, mode, style);
+	}
+
+	@Override
+	public final Orientation getPullToRefreshScrollDirection() {
+		return Orientation.VERTICAL;
+	}
+
+	@Override
+	protected ScrollView createRefreshableView(Context context, AttributeSet attrs) {
+		ScrollView scrollView;
+		if (VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD) {
+			scrollView = new InternalScrollViewSDK9(context, attrs);
+		} else {
+			scrollView = new ScrollView(context, attrs);
+		}
+
+		scrollView.setId(R.id.scrollview);
+		return scrollView;
+	}
+
+	@Override
+	protected boolean isReadyForPullStart() {
+		return mRefreshableView.getScrollY() == 0;
+	}
+
+	@Override
+	protected boolean isReadyForPullEnd() {
+		View scrollViewChild = mRefreshableView.getChildAt(0);
+		if (null != scrollViewChild) {
+			return mRefreshableView.getScrollY() >= (scrollViewChild.getHeight() - getHeight());
+		}
+		return false;
+	}
+
+	@TargetApi(9)
+	final class InternalScrollViewSDK9 extends ScrollView {
+
+		public InternalScrollViewSDK9(Context context, AttributeSet attrs) {
+			super(context, attrs);
+		}
+
+		@Override
+		protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX,
+				int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {
+
+			final boolean returnValue = super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX,
+					scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
+
+			// Does all of the hard work...
+			OverscrollHelper.overScrollBy(PullToRefreshScrollView.this, deltaX, scrollX, deltaY, scrollY,
+					getScrollRange(), isTouchEvent);
+
+			return returnValue;
+		}
+
+		/**
+		 * Taken from the AOSP ScrollView source
+		 */
+		private int getScrollRange() {
+			int scrollRange = 0;
+			if (getChildCount() > 0) {
+				View child = getChildAt(0);
+				scrollRange = Math.max(0, child.getHeight() - (getHeight() - getPaddingBottom() - getPaddingTop()));
+			}
+			return scrollRange;
+		}
+	}
+}

+ 166 - 0
library/src/main/java/com/common/library/pulltorefresh/PullToRefreshWebView.java

@@ -0,0 +1,166 @@
+/*******************************************************************************
+ * Copyright 2011, 2012 Chris Banes.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *******************************************************************************/
+package com.common.library.pulltorefresh;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.webkit.WebChromeClient;
+import android.webkit.WebView;
+
+import com.common.library.R;
+
+public class PullToRefreshWebView extends PullToRefreshBase<WebView> {
+
+	private static final OnRefreshListener<WebView> defaultOnRefreshListener = new OnRefreshListener<WebView>() {
+
+		@Override
+		public void onRefresh(PullToRefreshBase<WebView> refreshView) {
+			refreshView.getRefreshableView().reload();
+		}
+
+	};
+
+	private final WebChromeClient defaultWebChromeClient = new WebChromeClient() {
+
+		@Override
+		public void onProgressChanged(WebView view, int newProgress) {
+			if (newProgress == 100) {
+				onRefreshComplete();
+			}
+		}
+
+	};
+
+	public PullToRefreshWebView(Context context) {
+		super(context);
+
+		/**
+		 * Added so that by default, Pull-to-Refresh refreshes the page
+		 */
+		setOnRefreshListener(defaultOnRefreshListener);
+		mRefreshableView.setWebChromeClient(defaultWebChromeClient);
+	}
+
+	public PullToRefreshWebView(Context context, AttributeSet attrs) {
+		super(context, attrs);
+
+		/**
+		 * Added so that by default, Pull-to-Refresh refreshes the page
+		 */
+		setOnRefreshListener(defaultOnRefreshListener);
+		mRefreshableView.setWebChromeClient(defaultWebChromeClient);
+	}
+
+	public PullToRefreshWebView(Context context, Mode mode) {
+		super(context, mode);
+
+		/**
+		 * Added so that by default, Pull-to-Refresh refreshes the page
+		 */
+		setOnRefreshListener(defaultOnRefreshListener);
+		mRefreshableView.setWebChromeClient(defaultWebChromeClient);
+	}
+
+	public PullToRefreshWebView(Context context, Mode mode, AnimationStyle style) {
+		super(context, mode, style);
+
+		/**
+		 * Added so that by default, Pull-to-Refresh refreshes the page
+		 */
+		setOnRefreshListener(defaultOnRefreshListener);
+		mRefreshableView.setWebChromeClient(defaultWebChromeClient);
+	}
+
+	@Override
+	public final Orientation getPullToRefreshScrollDirection() {
+		return Orientation.VERTICAL;
+	}
+
+	@Override
+	protected WebView createRefreshableView(Context context, AttributeSet attrs) {
+		WebView webView;
+		if (VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD) {
+			webView = new InternalWebViewSDK9(context, attrs);
+		} else {
+			webView = new WebView(context, attrs);
+		}
+
+		webView.setId(R.id.webview);
+		return webView;
+	}
+
+	@Override
+	protected boolean isReadyForPullStart() {
+		return mRefreshableView.getScrollY() == 0;
+	}
+
+	@Override
+	protected boolean isReadyForPullEnd() {
+		float exactContentHeight = (float) Math.floor(mRefreshableView.getContentHeight() * mRefreshableView.getScale());
+		return mRefreshableView.getScrollY() >= (exactContentHeight - mRefreshableView.getHeight());
+	}
+
+	@Override
+	protected void onPtrRestoreInstanceState(Bundle savedInstanceState) {
+		super.onPtrRestoreInstanceState(savedInstanceState);
+		mRefreshableView.restoreState(savedInstanceState);
+	}
+
+	@Override
+	protected void onPtrSaveInstanceState(Bundle saveState) {
+		super.onPtrSaveInstanceState(saveState);
+		mRefreshableView.saveState(saveState);
+	}
+
+	@TargetApi(9)
+	final class InternalWebViewSDK9 extends WebView {
+
+		// WebView doesn't always scroll back to it's edge so we add some
+		// fuzziness
+		static final int OVERSCROLL_FUZZY_THRESHOLD = 2;
+
+		// WebView seems quite reluctant to overscroll so we use the scale
+		// factor to scale it's value
+		static final float OVERSCROLL_SCALE_FACTOR = 1.5f;
+
+		public InternalWebViewSDK9(Context context, AttributeSet attrs) {
+			super(context, attrs);
+		}
+
+		@Override
+		protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX,
+				int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {
+
+			final boolean returnValue = super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX,
+					scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
+
+			// Does all of the hard work...
+			OverscrollHelper.overScrollBy(PullToRefreshWebView.this, deltaX, scrollX, deltaY, scrollY,
+					getScrollRange(), OVERSCROLL_FUZZY_THRESHOLD, OVERSCROLL_SCALE_FACTOR, isTouchEvent);
+
+			return returnValue;
+		}
+
+		private int getScrollRange() {
+			return (int) Math.max(0, (float) Math.floor(mRefreshableView.getContentHeight() * mRefreshableView.getScale())
+					- (getHeight() - getPaddingBottom() - getPaddingTop()));
+		}
+	}
+}

+ 134 - 0
library/src/main/java/com/common/library/pulltorefresh/extras/PullToRefreshWebView2.java

@@ -0,0 +1,134 @@
+/*******************************************************************************
+ * Copyright 2011, 2012 Chris Banes.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *******************************************************************************/
+package com.common.library.pulltorefresh.extras;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.webkit.WebView;
+
+import com.common.library.pulltorefresh.PullToRefreshWebView;
+
+/**
+ * An advanced version of {@link PullToRefreshWebView} which delegates the
+ * triggering of the PullToRefresh gesture to the Javascript running within the
+ * WebView. This means that you should only use this class if:
+ * <p/>
+ * <ul>
+ * <li>{@link PullToRefreshWebView} doesn't work correctly because you're using
+ * <code>overflow:scroll</code> or something else which means
+ * {@link WebView#getScrollY()} doesn't return correct values.</li>
+ * <li>You control the web content being displayed, as you need to write some
+ * Javascript callbacks.</li>
+ * </ul>
+ * <p/>
+ * <p/>
+ * The way this call works is that when a PullToRefresh gesture is in action,
+ * the following Javascript methods will be called:
+ * <code>isReadyForPullDown()</code> and <code>isReadyForPullUp()</code>, it is
+ * your job to calculate whether the view is in a state where a PullToRefresh
+ * can happen, and return the result via the callback mechanism. An example can
+ * be seen below:
+ * <p/>
+ * 
+ * <pre>
+ * function isReadyForPullDown() {
+ *   var result = ...  // Probably using the .scrollTop DOM attribute
+ *   ptr.isReadyForPullDownResponse(result);
+ * }
+ * 
+ * function isReadyForPullUp() {
+ *   var result = ...  // Probably using the .scrollBottom DOM attribute
+ *   ptr.isReadyForPullUpResponse(result);
+ * }
+ * </pre>
+ * 
+ * @author Chris Banes
+ */
+public class PullToRefreshWebView2 extends PullToRefreshWebView {
+
+	static final String JS_INTERFACE_PKG = "ptr";
+	static final String DEF_JS_READY_PULL_DOWN_CALL = "javascript:isReadyForPullDown();";
+	static final String DEF_JS_READY_PULL_UP_CALL = "javascript:isReadyForPullUp();";
+
+	public PullToRefreshWebView2(Context context) {
+		super(context);
+	}
+
+	public PullToRefreshWebView2(Context context, AttributeSet attrs) {
+		super(context, attrs);
+	}
+
+	public PullToRefreshWebView2(Context context, Mode mode) {
+		super(context, mode);
+	}
+
+	private JsValueCallback mJsCallback;
+	private final AtomicBoolean mIsReadyForPullDown = new AtomicBoolean(false);
+	private final AtomicBoolean mIsReadyForPullUp = new AtomicBoolean(false);
+
+	@SuppressLint("JavascriptInterface")
+	@Override
+	protected WebView createRefreshableView(Context context, AttributeSet attrs) {
+		WebView webView = super.createRefreshableView(context, attrs);
+
+		// Need to add JS Interface so we can get the response back
+		mJsCallback = new JsValueCallback();
+		webView.addJavascriptInterface(mJsCallback, JS_INTERFACE_PKG);
+
+		return webView;
+	}
+
+	@Override
+	protected boolean isReadyForPullStart() {
+		// Call Javascript...
+		getRefreshableView().loadUrl(DEF_JS_READY_PULL_DOWN_CALL);
+
+		// Response will be given to JsValueCallback, which will update
+		// mIsReadyForPullDown
+
+		return mIsReadyForPullDown.get();
+	}
+
+	@Override
+	protected boolean isReadyForPullEnd() {
+		// Call Javascript...
+		getRefreshableView().loadUrl(DEF_JS_READY_PULL_UP_CALL);
+
+		// Response will be given to JsValueCallback, which will update
+		// mIsReadyForPullUp
+
+		return mIsReadyForPullUp.get();
+	}
+
+	/**
+	 * Used for response from Javascript
+	 * 
+	 * @author Chris Banes
+	 */
+	final class JsValueCallback {
+
+		public void isReadyForPullUpResponse(boolean response) {
+			mIsReadyForPullUp.set(response);
+		}
+
+		public void isReadyForPullDownResponse(boolean response) {
+			mIsReadyForPullDown.set(response);
+		}
+	}
+}

+ 97 - 0
library/src/main/java/com/common/library/pulltorefresh/extras/SoundPullEventListener.java

@@ -0,0 +1,97 @@
+/*******************************************************************************
+ * Copyright 2011, 2012 Chris Banes.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *******************************************************************************/
+package com.common.library.pulltorefresh.extras;
+
+import android.content.Context;
+import android.media.MediaPlayer;
+import android.view.View;
+
+import com.common.library.pulltorefresh.PullToRefreshBase;
+import com.common.library.pulltorefresh.PullToRefreshBase.Mode;
+import com.common.library.pulltorefresh.PullToRefreshBase.State;
+
+import java.util.HashMap;
+
+
+public class SoundPullEventListener<V extends View> implements PullToRefreshBase.OnPullEventListener<V> {
+
+	private final Context mContext;
+	private final HashMap<State, Integer> mSoundMap;
+
+	private MediaPlayer mCurrentMediaPlayer;
+
+	/**
+	 * Constructor
+	 * 
+	 * @param context - Context
+	 */
+	public SoundPullEventListener(Context context) {
+		mContext = context;
+		mSoundMap = new HashMap<State, Integer>();
+	}
+
+	@Override
+	public final void onPullEvent(PullToRefreshBase<V> refreshView, State event, Mode direction) {
+		Integer soundResIdObj = mSoundMap.get(event);
+		if (null != soundResIdObj) {
+			playSound(soundResIdObj.intValue());
+		}
+	}
+
+	/**
+	 * Set the Sounds to be played when a Pull Event happens. You specify which
+	 * sound plays for which events by calling this method multiple times for
+	 * each event.
+	 * <p/>
+	 * If you've already set a sound for a certain event, and add another sound
+	 * for that event, only the new sound will be played.
+	 * 
+	 * @param event - The event for which the sound will be played.
+	 * @param resId - Resource Id of the sound file to be played (e.g.
+	 *            <var>R.raw.pull_sound</var>)
+	 */
+	public void addSoundEvent(State event, int resId) {
+		mSoundMap.put(event, resId);
+	}
+
+	/**
+	 * Clears all of the previously set sounds and events.
+	 */
+	public void clearSounds() {
+		mSoundMap.clear();
+	}
+
+	/**
+	 * Gets the current (or last) MediaPlayer instance.
+	 */
+	public MediaPlayer getCurrentMediaPlayer() {
+		return mCurrentMediaPlayer;
+	}
+
+	private void playSound(int resId) {
+		// Stop current player, if there's one playing
+		if (null != mCurrentMediaPlayer) {
+			mCurrentMediaPlayer.stop();
+			mCurrentMediaPlayer.release();
+		}
+
+		mCurrentMediaPlayer = MediaPlayer.create(mContext, resId);
+		if (null != mCurrentMediaPlayer) {
+			mCurrentMediaPlayer.start();
+		}
+	}
+
+}

+ 43 - 0
library/src/main/java/com/common/library/pulltorefresh/internal/EmptyViewMethodAccessor.java

@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * Copyright 2011, 2012 Chris Banes.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *******************************************************************************/
+package com.common.library.pulltorefresh.internal;
+
+import android.view.View;
+
+/**
+ * Interface that allows PullToRefreshBase to hijack the call to
+ * AdapterView.setEmptyView()
+ * 
+ * @author chris
+ */
+public interface EmptyViewMethodAccessor {
+
+	/**
+	 * Calls upto AdapterView.setEmptyView()
+	 * 
+	 * @param emptyView - to set as Empty View
+	 */
+	public void setEmptyViewInternal(View emptyView);
+
+	/**
+	 * Should call PullToRefreshBase.setEmptyView() which will then
+	 * automatically call through to setEmptyViewInternal()
+	 * 
+	 * @param emptyView - to set as Empty View
+	 */
+	public void setEmptyView(View emptyView);
+
+}

+ 146 - 0
library/src/main/java/com/common/library/pulltorefresh/internal/FlipLoadingLayout.java

@@ -0,0 +1,146 @@
+/*******************************************************************************
+ * Copyright 2011, 2012 Chris Banes.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *******************************************************************************/
+package com.common.library.pulltorefresh.internal;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Matrix;
+import android.graphics.drawable.Drawable;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.Animation;
+import android.view.animation.RotateAnimation;
+import android.widget.ImageView.ScaleType;
+
+import com.common.library.R;
+import com.common.library.pulltorefresh.PullToRefreshBase.Mode;
+import com.common.library.pulltorefresh.PullToRefreshBase.Orientation;
+
+@SuppressLint("ViewConstructor")
+public class FlipLoadingLayout extends LoadingLayout {
+
+	static final int FLIP_ANIMATION_DURATION = 150;
+
+	private final Animation mRotateAnimation, mResetRotateAnimation;
+
+	public FlipLoadingLayout(Context context, final Mode mode, final Orientation scrollDirection, TypedArray attrs) {
+		super(context, mode, scrollDirection, attrs);
+
+		final int rotateAngle = mode == Mode.PULL_FROM_START ? -180 : 180;
+
+		mRotateAnimation = new RotateAnimation(0, rotateAngle, Animation.RELATIVE_TO_SELF, 0.5f,
+				Animation.RELATIVE_TO_SELF, 0.5f);
+		mRotateAnimation.setInterpolator(ANIMATION_INTERPOLATOR);
+		mRotateAnimation.setDuration(FLIP_ANIMATION_DURATION);
+		mRotateAnimation.setFillAfter(true);
+
+		mResetRotateAnimation = new RotateAnimation(rotateAngle, 0, Animation.RELATIVE_TO_SELF, 0.5f,
+				Animation.RELATIVE_TO_SELF, 0.5f);
+		mResetRotateAnimation.setInterpolator(ANIMATION_INTERPOLATOR);
+		mResetRotateAnimation.setDuration(FLIP_ANIMATION_DURATION);
+		mResetRotateAnimation.setFillAfter(true);
+	}
+
+	@Override
+	protected void onLoadingDrawableSet(Drawable imageDrawable) {
+		if (null != imageDrawable) {
+			final int dHeight = imageDrawable.getIntrinsicHeight();
+			final int dWidth = imageDrawable.getIntrinsicWidth();
+
+			/**
+			 * We need to set the width/height of the ImageView so that it is
+			 * square with each side the size of the largest drawable dimension.
+			 * This is so that it doesn't clip when rotated.
+			 */
+			ViewGroup.LayoutParams lp = mHeaderImage.getLayoutParams();
+			lp.width = lp.height = Math.max(dHeight, dWidth);
+			mHeaderImage.requestLayout();
+
+			/**
+			 * We now rotate the Drawable so that is at the correct rotation,
+			 * and is centered.
+			 */
+			mHeaderImage.setScaleType(ScaleType.MATRIX);
+			Matrix matrix = new Matrix();
+			matrix.postTranslate((lp.width - dWidth) / 2f, (lp.height - dHeight) / 2f);
+			matrix.postRotate(getDrawableRotationAngle(), lp.width / 2f, lp.height / 2f);
+			mHeaderImage.setImageMatrix(matrix);
+		}
+	}
+
+	@Override
+	protected void onPullImpl(float scaleOfLayout) {
+		// NO-OP
+	}
+
+	@Override
+	protected void pullToRefreshImpl() {
+		// Only start reset Animation, we've previously show the rotate anim
+		if (mRotateAnimation == mHeaderImage.getAnimation()) {
+			mHeaderImage.startAnimation(mResetRotateAnimation);
+		}
+	}
+
+	@Override
+	protected void refreshingImpl() {
+		mHeaderImage.clearAnimation();
+		mHeaderImage.setVisibility(View.INVISIBLE);
+		mHeaderProgress.setVisibility(View.VISIBLE);
+	}
+
+	@Override
+	protected void releaseToRefreshImpl() {
+		mHeaderImage.startAnimation(mRotateAnimation);
+	}
+
+	@Override
+	protected void resetImpl() {
+		mHeaderImage.clearAnimation();
+		mHeaderProgress.setVisibility(View.GONE);
+		mHeaderImage.setVisibility(View.VISIBLE);
+	}
+
+	@Override
+	protected int getDefaultDrawableResId() {
+		return R.drawable.default_ptr_flip;
+	}
+
+	private float getDrawableRotationAngle() {
+		float angle = 0f;
+		switch (mMode) {
+			case PULL_FROM_END:
+				if (mScrollDirection == Orientation.HORIZONTAL) {
+					angle = 90f;
+				} else {
+					angle = 180f;
+				}
+				break;
+
+			case PULL_FROM_START:
+				if (mScrollDirection == Orientation.HORIZONTAL) {
+					angle = 270f;
+				}
+				break;
+
+			default:
+				break;
+		}
+
+		return angle;
+	}
+
+}

+ 147 - 0
library/src/main/java/com/common/library/pulltorefresh/internal/IndicatorLayout.java

@@ -0,0 +1,147 @@
+/*******************************************************************************
+ * Copyright 2011, 2012 Chris Banes.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *******************************************************************************/
+package com.common.library.pulltorefresh.internal;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.graphics.Matrix;
+import android.graphics.drawable.Drawable;
+import android.view.View;
+import android.view.animation.Animation;
+import android.view.animation.Animation.AnimationListener;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+import android.view.animation.LinearInterpolator;
+import android.view.animation.RotateAnimation;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.ImageView.ScaleType;
+
+import com.common.library.R;
+import com.common.library.pulltorefresh.PullToRefreshBase;
+
+@SuppressLint("ViewConstructor")
+public class IndicatorLayout extends FrameLayout implements AnimationListener {
+
+	static final int DEFAULT_ROTATION_ANIMATION_DURATION = 150;
+
+	private Animation mInAnim, mOutAnim;
+	private ImageView mArrowImageView;
+
+	private final Animation mRotateAnimation, mResetRotateAnimation;
+
+	public IndicatorLayout(Context context, PullToRefreshBase.Mode mode) {
+		super(context);
+		mArrowImageView = new ImageView(context);
+
+		Drawable arrowD = getResources().getDrawable(R.drawable.indicator_arrow);
+		mArrowImageView.setImageDrawable(arrowD);
+
+		final int padding = getResources().getDimensionPixelSize(R.dimen.indicator_internal_padding);
+		mArrowImageView.setPadding(padding, padding, padding, padding);
+		addView(mArrowImageView);
+
+		int inAnimResId, outAnimResId;
+		switch (mode) {
+			case PULL_FROM_END:
+				inAnimResId = R.anim.slide_in_from_bottom;
+				outAnimResId = R.anim.slide_out_to_bottom;
+				setBackgroundResource(R.drawable.indicator_bg_bottom);
+
+				// Rotate Arrow so it's pointing the correct way
+				mArrowImageView.setScaleType(ScaleType.MATRIX);
+				Matrix matrix = new Matrix();
+				matrix.setRotate(180f, arrowD.getIntrinsicWidth() / 2f, arrowD.getIntrinsicHeight() / 2f);
+				mArrowImageView.setImageMatrix(matrix);
+				break;
+			default:
+			case PULL_FROM_START:
+				inAnimResId = R.anim.slide_in_from_top;
+				outAnimResId = R.anim.slide_out_to_top;
+				setBackgroundResource(R.drawable.indicator_bg_top);
+				break;
+		}
+
+		mInAnim = AnimationUtils.loadAnimation(context, inAnimResId);
+		mInAnim.setAnimationListener(this);
+
+		mOutAnim = AnimationUtils.loadAnimation(context, outAnimResId);
+		mOutAnim.setAnimationListener(this);
+
+		final Interpolator interpolator = new LinearInterpolator();
+		mRotateAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
+				0.5f);
+		mRotateAnimation.setInterpolator(interpolator);
+		mRotateAnimation.setDuration(DEFAULT_ROTATION_ANIMATION_DURATION);
+		mRotateAnimation.setFillAfter(true);
+
+		mResetRotateAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f,
+				Animation.RELATIVE_TO_SELF, 0.5f);
+		mResetRotateAnimation.setInterpolator(interpolator);
+		mResetRotateAnimation.setDuration(DEFAULT_ROTATION_ANIMATION_DURATION);
+		mResetRotateAnimation.setFillAfter(true);
+
+	}
+
+	public final boolean isVisible() {
+		Animation currentAnim = getAnimation();
+		if (null != currentAnim) {
+			return mInAnim == currentAnim;
+		}
+
+		return getVisibility() == View.VISIBLE;
+	}
+
+	public void hide() {
+		startAnimation(mOutAnim);
+	}
+
+	public void show() {
+		mArrowImageView.clearAnimation();
+		startAnimation(mInAnim);
+	}
+
+	@Override
+	public void onAnimationEnd(Animation animation) {
+		if (animation == mOutAnim) {
+			mArrowImageView.clearAnimation();
+			setVisibility(View.GONE);
+		} else if (animation == mInAnim) {
+			setVisibility(View.VISIBLE);
+		}
+
+		clearAnimation();
+	}
+
+	@Override
+	public void onAnimationRepeat(Animation animation) {
+		// NO-OP
+	}
+
+	@Override
+	public void onAnimationStart(Animation animation) {
+		setVisibility(View.VISIBLE);
+	}
+
+	public void releaseToRefresh() {
+		mArrowImageView.startAnimation(mRotateAnimation);
+	}
+
+	public void pullToRefresh() {
+		mArrowImageView.startAnimation(mResetRotateAnimation);
+	}
+
+}

+ 393 - 0
library/src/main/java/com/common/library/pulltorefresh/internal/LoadingLayout.java

@@ -0,0 +1,393 @@
+/*******************************************************************************
+ * Copyright 2011, 2012 Chris Banes.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *******************************************************************************/
+package com.common.library.pulltorefresh.internal;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.TypedArray;
+import android.graphics.Typeface;
+import android.graphics.drawable.AnimationDrawable;
+import android.graphics.drawable.Drawable;
+import android.text.TextUtils;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.Interpolator;
+import android.view.animation.LinearInterpolator;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import com.common.library.R;
+import com.common.library.pulltorefresh.ILoadingLayout;
+import com.common.library.pulltorefresh.PullToRefreshBase.Mode;
+import com.common.library.pulltorefresh.PullToRefreshBase.Orientation;
+
+@SuppressLint("ViewConstructor")
+public abstract class LoadingLayout extends FrameLayout implements ILoadingLayout {
+
+	static final String LOG_TAG = "PullToRefresh-LoadingLayout";
+
+	static final Interpolator ANIMATION_INTERPOLATOR = new LinearInterpolator();
+
+	private FrameLayout mInnerLayout;
+
+	protected final ImageView mHeaderImage;
+	protected final ProgressBar mHeaderProgress;
+
+	private boolean mUseIntrinsicAnimation;
+
+	private final TextView mHeaderText;
+	private final TextView mSubHeaderText;
+
+	protected final Mode mMode;
+	protected final Orientation mScrollDirection;
+
+	private CharSequence mPullLabel;
+	private CharSequence mRefreshingLabel;
+	private CharSequence mReleaseLabel;
+
+	public LoadingLayout(Context context, final Mode mode, final Orientation scrollDirection, TypedArray attrs) {
+		super(context);
+		mMode = mode;
+		mScrollDirection = scrollDirection;
+
+		switch (scrollDirection) {
+			case HORIZONTAL:
+				LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header_horizontal, this);
+				break;
+			case VERTICAL:
+			default:
+				LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header_vertical, this);
+				break;
+		}
+
+		mInnerLayout = (FrameLayout) findViewById(R.id.fl_inner);
+		mHeaderText = (TextView) mInnerLayout.findViewById(R.id.pull_to_refresh_text);
+		mHeaderProgress = (ProgressBar) mInnerLayout.findViewById(R.id.pull_to_refresh_progress);
+		mSubHeaderText = (TextView) mInnerLayout.findViewById(R.id.pull_to_refresh_sub_text);
+		mHeaderImage = (ImageView) mInnerLayout.findViewById(R.id.pull_to_refresh_image);
+
+		LayoutParams lp = (LayoutParams) mInnerLayout.getLayoutParams();
+
+		switch (mode) {
+			case PULL_FROM_END:
+				lp.gravity = scrollDirection == Orientation.VERTICAL ? Gravity.TOP : Gravity.LEFT;
+
+				// Load in labels
+				mPullLabel = context.getString(R.string.pull_to_refresh_from_bottom_pull_label);
+				mRefreshingLabel = context.getString(R.string.pull_to_refresh_from_bottom_refreshing_label);
+				mReleaseLabel = context.getString(R.string.pull_to_refresh_from_bottom_release_label);
+				break;
+
+			case PULL_FROM_START:
+			default:
+				lp.gravity = scrollDirection == Orientation.VERTICAL ? Gravity.BOTTOM : Gravity.RIGHT;
+
+				// Load in labels
+				mPullLabel = context.getString(R.string.pull_to_refresh_pull_label);
+				mRefreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label);
+				mReleaseLabel = context.getString(R.string.pull_to_refresh_release_label);
+				break;
+		}
+
+		if (attrs.hasValue(R.styleable.PullToRefresh_ptrHeaderBackground)) {
+			Drawable background = attrs.getDrawable(R.styleable.PullToRefresh_ptrHeaderBackground);
+			if (null != background) {
+				ViewCompat.setBackground(this, background);
+			}
+		}
+
+		if (attrs.hasValue(R.styleable.PullToRefresh_ptrHeaderTextAppearance)) {
+			TypedValue styleID = new TypedValue();
+			attrs.getValue(R.styleable.PullToRefresh_ptrHeaderTextAppearance, styleID);
+			setTextAppearance(styleID.data);
+		}
+		if (attrs.hasValue(R.styleable.PullToRefresh_ptrSubHeaderTextAppearance)) {
+			TypedValue styleID = new TypedValue();
+			attrs.getValue(R.styleable.PullToRefresh_ptrSubHeaderTextAppearance, styleID);
+			setSubTextAppearance(styleID.data);
+		}
+
+		// Text Color attrs need to be set after TextAppearance attrs
+		if (attrs.hasValue(R.styleable.PullToRefresh_ptrHeaderTextColor)) {
+			ColorStateList colors = attrs.getColorStateList(R.styleable.PullToRefresh_ptrHeaderTextColor);
+			if (null != colors) {
+				setTextColor(colors);
+			}
+		}
+		if (attrs.hasValue(R.styleable.PullToRefresh_ptrHeaderSubTextColor)) {
+			ColorStateList colors = attrs.getColorStateList(R.styleable.PullToRefresh_ptrHeaderSubTextColor);
+			if (null != colors) {
+				setSubTextColor(colors);
+			}
+		}
+
+		// Try and get defined drawable from Attrs
+		Drawable imageDrawable = null;
+		if (attrs.hasValue(R.styleable.PullToRefresh_ptrDrawable)) {
+			imageDrawable = attrs.getDrawable(R.styleable.PullToRefresh_ptrDrawable);
+		}
+
+		// Check Specific Drawable from Attrs, these overrite the generic
+		// drawable attr above
+		switch (mode) {
+			case PULL_FROM_START:
+			default:
+				if (attrs.hasValue(R.styleable.PullToRefresh_ptrDrawableStart)) {
+					imageDrawable = attrs.getDrawable(R.styleable.PullToRefresh_ptrDrawableStart);
+				} else if (attrs.hasValue(R.styleable.PullToRefresh_ptrDrawableTop)) {
+					Utils.warnDeprecation("ptrDrawableTop", "ptrDrawableStart");
+					imageDrawable = attrs.getDrawable(R.styleable.PullToRefresh_ptrDrawableTop);
+				}
+				break;
+
+			case PULL_FROM_END:
+				if (attrs.hasValue(R.styleable.PullToRefresh_ptrDrawableEnd)) {
+					imageDrawable = attrs.getDrawable(R.styleable.PullToRefresh_ptrDrawableEnd);
+				} else if (attrs.hasValue(R.styleable.PullToRefresh_ptrDrawableBottom)) {
+					Utils.warnDeprecation("ptrDrawableBottom", "ptrDrawableEnd");
+					imageDrawable = attrs.getDrawable(R.styleable.PullToRefresh_ptrDrawableBottom);
+				}
+				break;
+		}
+
+		// If we don't have a user defined drawable, load the default
+		if (null == imageDrawable) {
+			imageDrawable = context.getResources().getDrawable(getDefaultDrawableResId());
+		}
+
+		// Set Drawable, and save width/height
+		setLoadingDrawable(imageDrawable);
+
+		reset();
+	}
+
+	public final void setHeight(int height) {
+		ViewGroup.LayoutParams lp = (ViewGroup.LayoutParams) getLayoutParams();
+		lp.height = height;
+		requestLayout();
+	}
+
+	public final void setWidth(int width) {
+		ViewGroup.LayoutParams lp = (ViewGroup.LayoutParams) getLayoutParams();
+		lp.width = width;
+		requestLayout();
+	}
+
+	public final int getContentSize() {
+		switch (mScrollDirection) {
+			case HORIZONTAL:
+				return mInnerLayout.getWidth();
+			case VERTICAL:
+			default:
+				return mInnerLayout.getHeight();
+		}
+	}
+
+	public final void hideAllViews() {
+		if (View.VISIBLE == mHeaderText.getVisibility()) {
+			mHeaderText.setVisibility(View.INVISIBLE);
+		}
+		if (View.VISIBLE == mHeaderProgress.getVisibility()) {
+			mHeaderProgress.setVisibility(View.INVISIBLE);
+		}
+		if (View.VISIBLE == mHeaderImage.getVisibility()) {
+			mHeaderImage.setVisibility(View.INVISIBLE);
+		}
+		if (View.VISIBLE == mSubHeaderText.getVisibility()) {
+			mSubHeaderText.setVisibility(View.INVISIBLE);
+		}
+	}
+
+	public final void onPull(float scaleOfLayout) {
+		if (!mUseIntrinsicAnimation) {
+			onPullImpl(scaleOfLayout);
+		}
+	}
+
+	public final void pullToRefresh() {
+		if (null != mHeaderText) {
+			mHeaderText.setText(mPullLabel);
+		}
+
+		// Now call the callback
+		pullToRefreshImpl();
+	}
+
+	public final void refreshing() {
+		if (null != mHeaderText) {
+			mHeaderText.setText(mRefreshingLabel);
+		}
+
+		if (mUseIntrinsicAnimation) {
+			((AnimationDrawable) mHeaderImage.getDrawable()).start();
+		} else {
+			// Now call the callback
+			refreshingImpl();
+		}
+
+		if (null != mSubHeaderText) {
+			mSubHeaderText.setVisibility(View.GONE);
+		}
+	}
+
+	public final void releaseToRefresh() {
+		if (null != mHeaderText) {
+			mHeaderText.setText(mReleaseLabel);
+		}
+
+		// Now call the callback
+		releaseToRefreshImpl();
+	}
+
+	public final void reset() {
+		if (null != mHeaderText) {
+			mHeaderText.setText(mPullLabel);
+		}
+		mHeaderImage.setVisibility(View.VISIBLE);
+
+		if (mUseIntrinsicAnimation) {
+			((AnimationDrawable) mHeaderImage.getDrawable()).stop();
+		} else {
+			// Now call the callback
+			resetImpl();
+		}
+
+		if (null != mSubHeaderText) {
+			if (TextUtils.isEmpty(mSubHeaderText.getText())) {
+				mSubHeaderText.setVisibility(View.GONE);
+			} else {
+				mSubHeaderText.setVisibility(View.VISIBLE);
+			}
+		}
+	}
+
+	@Override
+	public void setLastUpdatedLabel(CharSequence label) {
+		setSubHeaderText(label);
+	}
+
+	public final void setLoadingDrawable(Drawable imageDrawable) {
+		// Set Drawable
+		mHeaderImage.setImageDrawable(imageDrawable);
+		mUseIntrinsicAnimation = (imageDrawable instanceof AnimationDrawable);
+
+		// Now call the callback
+		onLoadingDrawableSet(imageDrawable);
+	}
+
+	public void setPullLabel(CharSequence pullLabel) {
+		mPullLabel = pullLabel;
+	}
+
+	public void setRefreshingLabel(CharSequence refreshingLabel) {
+		mRefreshingLabel = refreshingLabel;
+	}
+
+	public void setReleaseLabel(CharSequence releaseLabel) {
+		mReleaseLabel = releaseLabel;
+	}
+
+	@Override
+	public void setTextTypeface(Typeface tf) {
+		mHeaderText.setTypeface(tf);
+	}
+
+	public final void showInvisibleViews() {
+		if (View.INVISIBLE == mHeaderText.getVisibility()) {
+			mHeaderText.setVisibility(View.VISIBLE);
+		}
+		if (View.INVISIBLE == mHeaderProgress.getVisibility()) {
+			mHeaderProgress.setVisibility(View.VISIBLE);
+		}
+		if (View.INVISIBLE == mHeaderImage.getVisibility()) {
+			mHeaderImage.setVisibility(View.VISIBLE);
+		}
+		if (View.INVISIBLE == mSubHeaderText.getVisibility()) {
+			mSubHeaderText.setVisibility(View.VISIBLE);
+		}
+	}
+
+	/**
+	 * Callbacks for derivative Layouts
+	 */
+
+	protected abstract int getDefaultDrawableResId();
+
+	protected abstract void onLoadingDrawableSet(Drawable imageDrawable);
+
+	protected abstract void onPullImpl(float scaleOfLayout);
+
+	protected abstract void pullToRefreshImpl();
+
+	protected abstract void refreshingImpl();
+
+	protected abstract void releaseToRefreshImpl();
+
+	protected abstract void resetImpl();
+
+	private void setSubHeaderText(CharSequence label) {
+		if (null != mSubHeaderText) {
+			if (TextUtils.isEmpty(label)) {
+				mSubHeaderText.setVisibility(View.GONE);
+			} else {
+				mSubHeaderText.setText(label);
+
+				// Only set it to Visible if we're GONE, otherwise VISIBLE will
+				// be set soon
+				if (View.GONE == mSubHeaderText.getVisibility()) {
+					mSubHeaderText.setVisibility(View.VISIBLE);
+				}
+			}
+		}
+	}
+
+	private void setSubTextAppearance(int value) {
+		if (null != mSubHeaderText) {
+			mSubHeaderText.setTextAppearance(getContext(), value);
+		}
+	}
+
+	private void setSubTextColor(ColorStateList color) {
+		if (null != mSubHeaderText) {
+			mSubHeaderText.setTextColor(color);
+		}
+	}
+
+	private void setTextAppearance(int value) {
+		if (null != mHeaderText) {
+			mHeaderText.setTextAppearance(getContext(), value);
+		}
+		if (null != mSubHeaderText) {
+			mSubHeaderText.setTextAppearance(getContext(), value);
+		}
+	}
+
+	private void setTextColor(ColorStateList color) {
+		if (null != mHeaderText) {
+			mHeaderText.setTextColor(color);
+		}
+		if (null != mSubHeaderText) {
+			mSubHeaderText.setTextColor(color);
+		}
+	}
+
+}

+ 110 - 0
library/src/main/java/com/common/library/pulltorefresh/internal/RotateLoadingLayout.java

@@ -0,0 +1,110 @@
+/*******************************************************************************
+ * Copyright 2011, 2012 Chris Banes.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *******************************************************************************/
+package com.common.library.pulltorefresh.internal;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Matrix;
+import android.graphics.drawable.Drawable;
+import android.view.animation.Animation;
+import android.view.animation.RotateAnimation;
+import android.widget.ImageView.ScaleType;
+
+import com.common.library.R;
+import com.common.library.pulltorefresh.PullToRefreshBase.Mode;
+import com.common.library.pulltorefresh.PullToRefreshBase.Orientation;
+
+public class RotateLoadingLayout extends LoadingLayout {
+
+	static final int ROTATION_ANIMATION_DURATION = 1200;
+
+	private final Animation mRotateAnimation;
+	private final Matrix mHeaderImageMatrix;
+
+	private float mRotationPivotX, mRotationPivotY;
+
+	private final boolean mRotateDrawableWhilePulling;
+
+	public RotateLoadingLayout(Context context, Mode mode, Orientation scrollDirection, TypedArray attrs) {
+		super(context, mode, scrollDirection, attrs);
+
+		mRotateDrawableWhilePulling = attrs.getBoolean(R.styleable.PullToRefresh_ptrRotateDrawableWhilePulling, true);
+
+		mHeaderImage.setScaleType(ScaleType.MATRIX);
+		mHeaderImageMatrix = new Matrix();
+		mHeaderImage.setImageMatrix(mHeaderImageMatrix);
+
+		mRotateAnimation = new RotateAnimation(0, 720, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
+				0.5f);
+		mRotateAnimation.setInterpolator(ANIMATION_INTERPOLATOR);
+		mRotateAnimation.setDuration(ROTATION_ANIMATION_DURATION);
+		mRotateAnimation.setRepeatCount(Animation.INFINITE);
+		mRotateAnimation.setRepeatMode(Animation.RESTART);
+	}
+
+	public void onLoadingDrawableSet(Drawable imageDrawable) {
+		if (null != imageDrawable) {
+			mRotationPivotX = Math.round(imageDrawable.getIntrinsicWidth() / 2f);
+			mRotationPivotY = Math.round(imageDrawable.getIntrinsicHeight() / 2f);
+		}
+	}
+
+	protected void onPullImpl(float scaleOfLayout) {
+		float angle;
+		if (mRotateDrawableWhilePulling) {
+			angle = scaleOfLayout * 90f;
+		} else {
+			angle = Math.max(0f, Math.min(180f, scaleOfLayout * 360f - 180f));
+		}
+
+		mHeaderImageMatrix.setRotate(angle, mRotationPivotX, mRotationPivotY);
+		mHeaderImage.setImageMatrix(mHeaderImageMatrix);
+	}
+
+	@Override
+	protected void refreshingImpl() {
+		mHeaderImage.startAnimation(mRotateAnimation);
+	}
+
+	@Override
+	protected void resetImpl() {
+		mHeaderImage.clearAnimation();
+		resetImageRotation();
+	}
+
+	private void resetImageRotation() {
+		if (null != mHeaderImageMatrix) {
+			mHeaderImageMatrix.reset();
+			mHeaderImage.setImageMatrix(mHeaderImageMatrix);
+		}
+	}
+
+	@Override
+	protected void pullToRefreshImpl() {
+		// NO-OP
+	}
+
+	@Override
+	protected void releaseToRefreshImpl() {
+		// NO-OP
+	}
+
+	@Override
+	protected int getDefaultDrawableResId() {
+		return R.drawable.default_ptr_rotate;
+	}
+
+}

+ 13 - 0
library/src/main/java/com/common/library/pulltorefresh/internal/Utils.java

@@ -0,0 +1,13 @@
+package com.common.library.pulltorefresh.internal;
+
+import android.util.Log;
+
+public class Utils {
+
+	static final String LOG_TAG = "PullToRefresh";
+
+	public static void warnDeprecation(String depreacted, String replacement) {
+		Log.w(LOG_TAG, "You're using the deprecated " + depreacted + " attr, please switch over to " + replacement);
+	}
+
+}

+ 70 - 0
library/src/main/java/com/common/library/pulltorefresh/internal/ViewCompat.java

@@ -0,0 +1,70 @@
+/*******************************************************************************
+ * Copyright 2011, 2012 Chris Banes.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *******************************************************************************/
+package com.common.library.pulltorefresh.internal;
+
+import android.annotation.TargetApi;
+import android.graphics.drawable.Drawable;
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
+import android.view.View;
+
+@SuppressWarnings("deprecation")
+public class ViewCompat {
+
+	public static void postOnAnimation(View view, Runnable runnable) {
+		if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
+			SDK16.postOnAnimation(view, runnable);
+		} else {
+			view.postDelayed(runnable, 16);
+		}
+	}
+
+	public static void setBackground(View view, Drawable background) {
+		if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
+			SDK16.setBackground(view, background);
+		} else {
+			view.setBackgroundDrawable(background);
+		}
+	}
+
+	public static void setLayerType(View view, int layerType) {
+		if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) {
+			SDK11.setLayerType(view, layerType);
+		}
+	}
+
+	@TargetApi(11)
+	static class SDK11 {
+
+		public static void setLayerType(View view, int layerType) {
+			view.setLayerType(layerType, null);
+		}
+	}
+
+	@TargetApi(16)
+	static class SDK16 {
+
+		public static void postOnAnimation(View view, Runnable runnable) {
+			view.postOnAnimation(runnable);
+		}
+
+		public static void setBackground(View view, Drawable background) {
+			view.setBackground(background);
+		}
+
+	}
+
+}

+ 21 - 0
library/src/main/res/anim/slide_in_from_bottom.xml

@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<translate xmlns:android="http://schemas.android.com/apk/res/android"
+    android:duration="@android:integer/config_longAnimTime"
+    android:fromYDelta="100%p"
+    android:toYDelta="0" />

+ 21 - 0
library/src/main/res/anim/slide_in_from_top.xml

@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<translate xmlns:android="http://schemas.android.com/apk/res/android"
+    android:duration="@android:integer/config_longAnimTime"
+    android:fromYDelta="-100%p"
+    android:toYDelta="0" />

+ 21 - 0
library/src/main/res/anim/slide_out_to_bottom.xml

@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<translate xmlns:android="http://schemas.android.com/apk/res/android"
+    android:duration="@android:integer/config_longAnimTime"
+    android:fromYDelta="0"
+    android:toYDelta="100%p" />

+ 21 - 0
library/src/main/res/anim/slide_out_to_top.xml

@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<translate xmlns:android="http://schemas.android.com/apk/res/android"
+    android:duration="@android:integer/config_longAnimTime"
+    android:fromYDelta="0"
+    android:toYDelta="-100%p" />

二進制
library/src/main/res/drawable-hdpi/default_ptr_flip.png


二進制
library/src/main/res/drawable-hdpi/default_ptr_rotate.png


二進制
library/src/main/res/drawable-hdpi/indicator_arrow.png


二進制
library/src/main/res/drawable-mdpi/default_ptr_flip.png


二進制
library/src/main/res/drawable-mdpi/default_ptr_rotate.png


二進制
library/src/main/res/drawable-mdpi/indicator_arrow.png


二進制
library/src/main/res/drawable-xhdpi/default_ptr_flip.png


二進制
library/src/main/res/drawable-xhdpi/default_ptr_rotate.png


二進制
library/src/main/res/drawable-xhdpi/indicator_arrow.png


+ 18 - 0
library/src/main/res/drawable/indicator_bg_bottom.xml

@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle" >
+
+    <solid android:color="#40000000" />
+
+    <!--
+    I know the android:radius is useless here but it's needed to fix an old bug:
+    http://code.google.com/p/android/issues/detail?id=939
+    -->
+    <corners
+        android:bottomLeftRadius="0dp"
+        android:bottomRightRadius="0dp"
+        android:radius="1dp"
+        android:topLeftRadius="@dimen/indicator_corner_radius"
+        android:topRightRadius="@dimen/indicator_corner_radius" />
+
+</shape>

+ 18 - 0
library/src/main/res/drawable/indicator_bg_top.xml

@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle" >
+
+    <solid android:color="#40000000" />
+
+    <!--
+    I know the android:radius is useless here but it's needed to fix an old bug:
+    http://code.google.com/p/android/issues/detail?id=939
+    -->
+    <corners
+        android:bottomLeftRadius="@dimen/indicator_corner_radius"
+        android:bottomRightRadius="@dimen/indicator_corner_radius"
+        android:radius="1dp"
+        android:topLeftRadius="0dp"
+        android:topRightRadius="0dp" />
+
+</shape>

+ 29 - 0
library/src/main/res/layout/pull_to_refresh_header_horizontal.xml

@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<merge xmlns:android="http://schemas.android.com/apk/res/android" >
+
+    <FrameLayout
+        android:id="@+id/fl_inner"
+        android:layout_width="wrap_content"
+        android:layout_height="fill_parent"
+        android:paddingBottom="@dimen/header_footer_top_bottom_padding"
+        android:paddingLeft="@dimen/header_footer_left_right_padding"
+        android:paddingRight="@dimen/header_footer_left_right_padding"
+        android:paddingTop="@dimen/header_footer_top_bottom_padding" >
+
+        <ImageView
+            android:id="@+id/pull_to_refresh_image"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center" />
+
+        <ProgressBar
+            android:id="@+id/pull_to_refresh_progress"
+            style="?android:attr/progressBarStyleSmall"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:indeterminate="true"
+            android:visibility="gone" />
+    </FrameLayout>
+
+</merge>

+ 59 - 0
library/src/main/res/layout/pull_to_refresh_header_vertical.xml

@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<merge xmlns:android="http://schemas.android.com/apk/res/android" >
+
+    <FrameLayout
+        android:id="@+id/fl_inner"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:paddingBottom="@dimen/header_footer_top_bottom_padding"
+        android:paddingLeft="@dimen/header_footer_left_right_padding"
+        android:paddingRight="@dimen/header_footer_left_right_padding"
+        android:paddingTop="@dimen/header_footer_top_bottom_padding" >
+
+        <FrameLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="left|center_vertical" >
+
+            <ImageView
+                android:id="@+id/pull_to_refresh_image"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center" />
+
+            <ProgressBar
+                android:id="@+id/pull_to_refresh_progress"
+                style="?android:attr/progressBarStyleSmall"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:indeterminate="true"
+                android:visibility="gone" />
+        </FrameLayout>
+
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:gravity="center_horizontal"
+            android:orientation="vertical" >
+
+            <TextView
+                android:id="@+id/pull_to_refresh_text"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:singleLine="true"
+                android:textAppearance="?android:attr/textAppearance"
+                android:textStyle="bold" />
+
+            <TextView
+                android:id="@+id/pull_to_refresh_sub_text"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:singleLine="true"
+                android:textAppearance="?android:attr/textAppearanceSmall"
+                android:visibility="gone" />
+        </LinearLayout>
+    </FrameLayout>
+
+</merge>

+ 1 - 0
library/src/main/res/values/attrs.xml

@@ -103,6 +103,7 @@
         <attr name="ptrAdapterViewBackground" format="reference|color" />
         <attr name="ptrDrawableTop" format="reference" />
         <attr name="ptrDrawableBottom" format="reference" />
+
     </declare-styleable>
 
 </resources>

+ 8 - 0
library/src/main/res/values/ids.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+    <item type="id" name="gridview" />
+    <item type="id" name="webview" />
+    <item type="id" name="scrollview" />
+
+</resources>

+ 13 - 0
library/src/main/res/values/pull_refresh_strings.xml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+    <string name="pull_to_refresh_pull_label">Pull to refresh…</string>
+    <string name="pull_to_refresh_release_label">Release to refresh…</string>
+    <string name="pull_to_refresh_refreshing_label">Loading…</string>
+
+    <!-- Just use standard Pull Down String when pulling up. These can be set for languages which require it -->
+    <string name="pull_to_refresh_from_bottom_pull_label">@string/pull_to_refresh_pull_label</string>
+    <string name="pull_to_refresh_from_bottom_release_label">@string/pull_to_refresh_release_label</string>
+    <string name="pull_to_refresh_from_bottom_refreshing_label">@string/pull_to_refresh_refreshing_label</string>
+
+</resources>