PullToRefreshBase.java 46 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717
  1. /*******************************************************************************
  2. * Copyright 2011, 2012 Chris Banes.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. *******************************************************************************/
  16. package com.handmark.pulltorefresh.library;
  17. import android.annotation.TargetApi;
  18. import android.content.Context;
  19. import android.content.res.TypedArray;
  20. import android.graphics.drawable.Drawable;
  21. import android.os.Build.VERSION;
  22. import android.os.Build.VERSION_CODES;
  23. import android.os.Bundle;
  24. import android.os.Handler;
  25. import android.os.Parcelable;
  26. import android.util.AttributeSet;
  27. import android.util.Log;
  28. import android.view.Gravity;
  29. import android.view.MotionEvent;
  30. import android.view.View;
  31. import android.view.ViewConfiguration;
  32. import android.view.ViewGroup;
  33. import android.view.animation.DecelerateInterpolator;
  34. import android.view.animation.Interpolator;
  35. import android.widget.FrameLayout;
  36. import android.widget.LinearLayout;
  37. import com.handmark.pulltorefresh.library.internal.FlipLoadingLayout;
  38. import com.handmark.pulltorefresh.library.internal.LoadingLayout;
  39. import com.handmark.pulltorefresh.library.internal.RotateLoadingLayout;
  40. import com.handmark.pulltorefresh.library.internal.Utils;
  41. import com.handmark.pulltorefresh.library.internal.ViewCompat;
  42. public abstract class PullToRefreshBase<T extends View> extends LinearLayout implements IPullToRefresh<T> {
  43. // ===========================================================
  44. // Constants
  45. // ===========================================================
  46. static final boolean DEBUG = false;
  47. static final boolean USE_HW_LAYERS = false;
  48. static final String LOG_TAG = "PullToRefresh";
  49. static final float FRICTION = 4.0f;
  50. public static final int SMOOTH_SCROLL_DURATION_MS = 500;
  51. public static final int SMOOTH_SCROLL_LONG_DURATION_MS = 1525;
  52. static final int DEMO_SCROLL_INTERVAL = 225;
  53. static final String STATE_STATE = "ptr_state";
  54. static final String STATE_MODE = "ptr_mode";
  55. static final String STATE_CURRENT_MODE = "ptr_current_mode";
  56. static final String STATE_SCROLLING_REFRESHING_ENABLED = "ptr_disable_scrolling";
  57. static final String STATE_SHOW_REFRESHING_VIEW = "ptr_show_refreshing_view";
  58. static final String STATE_SUPER = "ptr_super";
  59. // ===========================================================
  60. // Fields
  61. // ===========================================================
  62. private int mTouchSlop;
  63. private float mLastMotionX, mLastMotionY;
  64. private float mInitialMotionX, mInitialMotionY;
  65. private boolean mIsBeingDragged = false;
  66. private State mState = State.RESET;
  67. private Mode mMode = Mode.getDefault();
  68. private Mode mCurrentMode;
  69. T mRefreshableView;
  70. private FrameLayout mRefreshableViewWrapper;
  71. private boolean mShowViewWhileRefreshing = true;
  72. private boolean mScrollingWhileRefreshingEnabled = false;
  73. private boolean mFilterTouchEvents = true;
  74. private boolean mOverScrollEnabled = true;
  75. private boolean mLayoutVisibilityChangesEnabled = true;
  76. private Interpolator mScrollAnimationInterpolator;
  77. private AnimationStyle mLoadingAnimationStyle = AnimationStyle.getDefault();
  78. private LoadingLayout mHeaderLayout;
  79. private LoadingLayout mFooterLayout;
  80. private OnRefreshListener<T> mOnRefreshListener;
  81. private OnRefreshListener2<T> mOnRefreshListener2;
  82. private OnPullEventListener<T> mOnPullEventListener;
  83. private SmoothScrollRunnable mCurrentSmoothScrollRunnable;
  84. // ===========================================================
  85. // Constructors
  86. // ===========================================================
  87. private Handler mHandler=new Handler();
  88. public PullToRefreshBase(Context context) {
  89. super(context);
  90. init(context, null);
  91. }
  92. public PullToRefreshBase(Context context, AttributeSet attrs) {
  93. super(context, attrs);
  94. init(context, attrs);
  95. }
  96. public PullToRefreshBase(Context context, Mode mode) {
  97. super(context);
  98. mMode = mode;
  99. init(context, null);
  100. }
  101. public PullToRefreshBase(Context context, Mode mode, AnimationStyle animStyle) {
  102. super(context);
  103. mMode = mode;
  104. mLoadingAnimationStyle = animStyle;
  105. init(context, null);
  106. }
  107. @Override
  108. public void addView(View child, int index, ViewGroup.LayoutParams params) {
  109. if (DEBUG) {
  110. Log.d(LOG_TAG, "addView: " + child.getClass().getSimpleName());
  111. }
  112. final T refreshableView = getRefreshableView();
  113. if (refreshableView instanceof ViewGroup) {
  114. ((ViewGroup) refreshableView).addView(child, index, params);
  115. } else {
  116. throw new UnsupportedOperationException("Refreshable View is not a ViewGroup so can't addView");
  117. }
  118. }
  119. @Override
  120. public final boolean demo() {
  121. if (mMode.showHeaderLoadingLayout() && isReadyForPullStart()) {
  122. smoothScrollToAndBack(-getHeaderSize() * 2);
  123. return true;
  124. } else if (mMode.showFooterLoadingLayout() && isReadyForPullEnd()) {
  125. smoothScrollToAndBack(getFooterSize() * 2);
  126. return true;
  127. }
  128. return false;
  129. }
  130. @Override
  131. public final Mode getCurrentMode() {
  132. return mCurrentMode;
  133. }
  134. @Override
  135. public final boolean getFilterTouchEvents() {
  136. return mFilterTouchEvents;
  137. }
  138. @Override
  139. public final ILoadingLayout getLoadingLayoutProxy() {
  140. return getLoadingLayoutProxy(true, true);
  141. }
  142. @Override
  143. public final ILoadingLayout getLoadingLayoutProxy(boolean includeStart, boolean includeEnd) {
  144. return createLoadingLayoutProxy(includeStart, includeEnd);
  145. }
  146. @Override
  147. public final Mode getMode() {
  148. return mMode;
  149. }
  150. @Override
  151. public final T getRefreshableView() {
  152. return mRefreshableView;
  153. }
  154. @Override
  155. public final boolean getShowViewWhileRefreshing() {
  156. return mShowViewWhileRefreshing;
  157. }
  158. @Override
  159. public final State getState() {
  160. return mState;
  161. }
  162. /**
  163. * @deprecated See {@link #isScrollingWhileRefreshingEnabled()}.
  164. */
  165. public final boolean isDisableScrollingWhileRefreshing() {
  166. return !isScrollingWhileRefreshingEnabled();
  167. }
  168. @Override
  169. public final boolean isPullToRefreshEnabled() {
  170. return mMode.permitsPullToRefresh();
  171. }
  172. @Override
  173. public final boolean isPullToRefreshOverScrollEnabled() {
  174. return VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD && mOverScrollEnabled
  175. && OverscrollHelper.isAndroidOverScrollEnabled(mRefreshableView);
  176. }
  177. @Override
  178. public final boolean isRefreshing() {
  179. return mState == State.REFRESHING || mState == State.MANUAL_REFRESHING;
  180. }
  181. @Override
  182. public final boolean isScrollingWhileRefreshingEnabled() {
  183. return mScrollingWhileRefreshingEnabled;
  184. }
  185. @Override
  186. public final boolean onInterceptTouchEvent(MotionEvent event) {
  187. if (!isPullToRefreshEnabled()) {
  188. return false;
  189. }
  190. final int action = event.getAction();
  191. if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
  192. mIsBeingDragged = false;
  193. return false;
  194. }
  195. if (action != MotionEvent.ACTION_DOWN && mIsBeingDragged) {
  196. return true;
  197. }
  198. switch (action) {
  199. case MotionEvent.ACTION_MOVE: {
  200. // If we're refreshing, and the flag is set. Eat all MOVE events
  201. if (!mScrollingWhileRefreshingEnabled && isRefreshing()) {
  202. return true;
  203. }
  204. if (isReadyForPull()) {
  205. final float y = event.getY(), x = event.getX();
  206. final float diff, oppositeDiff, absDiff;
  207. // We need to use the correct values, based on scroll
  208. // direction
  209. switch (getPullToRefreshScrollDirection()) {
  210. case HORIZONTAL:
  211. diff = x - mLastMotionX;
  212. oppositeDiff = y - mLastMotionY;
  213. break;
  214. case VERTICAL:
  215. default:
  216. diff = y - mLastMotionY;
  217. oppositeDiff = x - mLastMotionX;
  218. break;
  219. }
  220. absDiff = Math.abs(diff);
  221. if (absDiff > mTouchSlop && (!mFilterTouchEvents || absDiff > Math.abs(oppositeDiff))) {
  222. if (mMode.showHeaderLoadingLayout() && diff >= 1f && isReadyForPullStart()) {
  223. mLastMotionY = y;
  224. mLastMotionX = x;
  225. mIsBeingDragged = true;
  226. if (mMode == Mode.BOTH) {
  227. mCurrentMode = Mode.PULL_FROM_START;
  228. }
  229. } else if (mMode.showFooterLoadingLayout() && diff <= -1f && isReadyForPullEnd()) {
  230. mLastMotionY = y;
  231. mLastMotionX = x;
  232. mIsBeingDragged = true;
  233. if (mMode == Mode.BOTH) {
  234. mCurrentMode = Mode.PULL_FROM_END;
  235. }
  236. }
  237. }
  238. }
  239. break;
  240. }
  241. case MotionEvent.ACTION_DOWN: {
  242. if (isReadyForPull()) {
  243. mLastMotionY = mInitialMotionY = event.getY();
  244. mLastMotionX = mInitialMotionX = event.getX();
  245. mIsBeingDragged = false;
  246. }
  247. break;
  248. }
  249. }
  250. return mIsBeingDragged;
  251. }
  252. @Override
  253. public final void onRefreshComplete() {
  254. if (isRefreshing()) {
  255. setState(State.RESET);
  256. }
  257. }
  258. public final void onRefreshComplete(int delay) {
  259. mHandler.postDelayed(new Runnable() {
  260. @Override
  261. public void run() {
  262. if (isRefreshing()) {
  263. setState(State.RESET);
  264. }
  265. }
  266. }, delay);
  267. }
  268. public final void onPullUpRefreshComplete() {
  269. if (isRefreshing()) {
  270. mCurrentMode=Mode.PULL_FROM_END;
  271. setState(State.RESET);
  272. }
  273. }
  274. public final void onPullDownRefreshComplete() {
  275. if (isRefreshing()) {
  276. mCurrentMode=Mode.PULL_FROM_START;
  277. setState(State.RESET);
  278. }
  279. }
  280. @Override
  281. public final boolean onTouchEvent(MotionEvent event) {
  282. if (!isPullToRefreshEnabled()) {
  283. return false;
  284. }
  285. // If we're refreshing, and the flag is set. Eat the event
  286. if (!mScrollingWhileRefreshingEnabled && isRefreshing()) {
  287. return true;
  288. }
  289. if (event.getAction() == MotionEvent.ACTION_DOWN && event.getEdgeFlags() != 0) {
  290. return false;
  291. }
  292. switch (event.getAction()) {
  293. case MotionEvent.ACTION_MOVE: {
  294. if (mIsBeingDragged) {
  295. mLastMotionY = event.getY();
  296. mLastMotionX = event.getX();
  297. pullEvent();
  298. return true;
  299. }
  300. break;
  301. }
  302. case MotionEvent.ACTION_DOWN: {
  303. if (isReadyForPull()) {
  304. mLastMotionY = mInitialMotionY = event.getY();
  305. mLastMotionX = mInitialMotionX = event.getX();
  306. return true;
  307. }
  308. break;
  309. }
  310. case MotionEvent.ACTION_CANCEL:
  311. case MotionEvent.ACTION_UP: {
  312. if (mIsBeingDragged) {
  313. mIsBeingDragged = false;
  314. if (mState == State.RELEASE_TO_REFRESH
  315. && (null != mOnRefreshListener || null != mOnRefreshListener2)) {
  316. setState(State.REFRESHING, true);
  317. return true;
  318. }
  319. // If we're already refreshing, just scroll back to the top
  320. if (isRefreshing()) {
  321. smoothScrollTo(0);
  322. return true;
  323. }
  324. // If we haven't returned by here, then we're not in a state
  325. // to pull, so just reset
  326. setState(State.RESET);
  327. return true;
  328. }
  329. break;
  330. }
  331. }
  332. return false;
  333. }
  334. public final void setScrollingWhileRefreshingEnabled(boolean allowScrollingWhileRefreshing) {
  335. mScrollingWhileRefreshingEnabled = allowScrollingWhileRefreshing;
  336. }
  337. /**
  338. * @deprecated See {@link #setScrollingWhileRefreshingEnabled(boolean)}
  339. */
  340. public void setDisableScrollingWhileRefreshing(boolean disableScrollingWhileRefreshing) {
  341. setScrollingWhileRefreshingEnabled(!disableScrollingWhileRefreshing);
  342. }
  343. @Override
  344. public final void setFilterTouchEvents(boolean filterEvents) {
  345. mFilterTouchEvents = filterEvents;
  346. }
  347. /**
  348. * @deprecated You should now call this method on the result of
  349. * {@link #getLoadingLayoutProxy()}.
  350. */
  351. public void setLastUpdatedLabel(CharSequence label) {
  352. getLoadingLayoutProxy().setLastUpdatedLabel(label);
  353. }
  354. /**
  355. * @deprecated You should now call this method on the result of
  356. * {@link #getLoadingLayoutProxy()}.
  357. */
  358. public void setLoadingDrawable(Drawable drawable) {
  359. getLoadingLayoutProxy().setLoadingDrawable(drawable);
  360. }
  361. /**
  362. * @deprecated You should now call this method on the result of
  363. * {@link #getLoadingLayoutProxy(boolean, boolean)}.
  364. */
  365. public void setLoadingDrawable(Drawable drawable, Mode mode) {
  366. getLoadingLayoutProxy(mode.showHeaderLoadingLayout(), mode.showFooterLoadingLayout()).setLoadingDrawable(
  367. drawable);
  368. }
  369. @Override
  370. public void setLongClickable(boolean longClickable) {
  371. getRefreshableView().setLongClickable(longClickable);
  372. }
  373. @Override
  374. public final void setMode(Mode mode) {
  375. if (mode != mMode) {
  376. if (DEBUG) {
  377. Log.d(LOG_TAG, "Setting mode to: " + mode);
  378. }
  379. mMode = mode;
  380. updateUIForMode();
  381. }
  382. }
  383. public void setOnPullEventListener(OnPullEventListener<T> listener) {
  384. mOnPullEventListener = listener;
  385. }
  386. @Override
  387. public final void setOnRefreshListener(OnRefreshListener<T> listener) {
  388. mOnRefreshListener = listener;
  389. mOnRefreshListener2 = null;
  390. }
  391. @Override
  392. public final void setOnRefreshListener(OnRefreshListener2<T> listener) {
  393. mOnRefreshListener2 = listener;
  394. mOnRefreshListener = null;
  395. }
  396. /**
  397. * @deprecated You should now call this method on the result of
  398. * {@link #getLoadingLayoutProxy()}.
  399. */
  400. public void setPullLabel(CharSequence pullLabel) {
  401. getLoadingLayoutProxy().setPullLabel(pullLabel);
  402. }
  403. /**
  404. * @deprecated You should now call this method on the result of
  405. * {@link #getLoadingLayoutProxy(boolean, boolean)}.
  406. */
  407. public void setPullLabel(CharSequence pullLabel, Mode mode) {
  408. getLoadingLayoutProxy(mode.showHeaderLoadingLayout(), mode.showFooterLoadingLayout()).setPullLabel(pullLabel);
  409. }
  410. /**
  411. * @param enable Whether Pull-To-Refresh should be used
  412. * @deprecated This simple calls setMode with an appropriate mode based on
  413. * the passed value.
  414. */
  415. public final void setPullToRefreshEnabled(boolean enable) {
  416. setMode(enable ? Mode.getDefault() : Mode.DISABLED);
  417. }
  418. @Override
  419. public final void setPullToRefreshOverScrollEnabled(boolean enabled) {
  420. mOverScrollEnabled = enabled;
  421. }
  422. @Override
  423. public final void setPullDownRefreshing() {
  424. if(isRefreshing()){
  425. return;
  426. }
  427. mCurrentMode=Mode.PULL_FROM_START;
  428. setRefreshing(true);
  429. }
  430. public final void setPullDownRefreshing(int delay) {
  431. mHandler.postDelayed(new Runnable() {
  432. @Override
  433. public void run() {
  434. setPullDownRefreshing();
  435. }
  436. }, delay);
  437. }
  438. @Override
  439. public final void setPullUpRefreshing() {
  440. if(isRefreshing()){
  441. return;
  442. }
  443. mCurrentMode=Mode.PULL_FROM_END;
  444. setRefreshing(true);
  445. }
  446. public final void setPullUpRefreshing(int delay) {
  447. mHandler.postDelayed(new Runnable() {
  448. @Override
  449. public void run() {
  450. setPullUpRefreshing();
  451. }
  452. }, delay);
  453. }
  454. @Override
  455. public final void setRefreshing(boolean doScroll) {
  456. if (!isRefreshing()) {
  457. setState(State.MANUAL_REFRESHING, doScroll);
  458. }
  459. }
  460. /**
  461. * @deprecated You should now call this method on the result of
  462. * {@link #getLoadingLayoutProxy()}.
  463. */
  464. public void setRefreshingLabel(CharSequence refreshingLabel) {
  465. getLoadingLayoutProxy().setRefreshingLabel(refreshingLabel);
  466. }
  467. /**
  468. * @deprecated You should now call this method on the result of
  469. * {@link #getLoadingLayoutProxy(boolean, boolean)}.
  470. */
  471. public void setRefreshingLabel(CharSequence refreshingLabel, Mode mode) {
  472. getLoadingLayoutProxy(mode.showHeaderLoadingLayout(), mode.showFooterLoadingLayout()).setRefreshingLabel(
  473. refreshingLabel);
  474. }
  475. /**
  476. * @deprecated You should now call this method on the result of
  477. * {@link #getLoadingLayoutProxy()}.
  478. */
  479. public void setReleaseLabel(CharSequence releaseLabel) {
  480. setReleaseLabel(releaseLabel, Mode.BOTH);
  481. }
  482. /**
  483. * @deprecated You should now call this method on the result of
  484. * {@link #getLoadingLayoutProxy(boolean, boolean)}.
  485. */
  486. public void setReleaseLabel(CharSequence releaseLabel, Mode mode) {
  487. getLoadingLayoutProxy(mode.showHeaderLoadingLayout(), mode.showFooterLoadingLayout()).setReleaseLabel(
  488. releaseLabel);
  489. }
  490. public void setScrollAnimationInterpolator(Interpolator interpolator) {
  491. mScrollAnimationInterpolator = interpolator;
  492. }
  493. @Override
  494. public final void setShowViewWhileRefreshing(boolean showView) {
  495. mShowViewWhileRefreshing = showView;
  496. }
  497. /**
  498. * @return Either {@link Orientation#VERTICAL} or
  499. * {@link Orientation#HORIZONTAL} depending on the scroll direction.
  500. */
  501. public abstract Orientation getPullToRefreshScrollDirection();
  502. final void setState(State state, final boolean... params) {
  503. mState = state;
  504. if (DEBUG) {
  505. Log.d(LOG_TAG, "State: " + mState.name());
  506. }
  507. switch (mState) {
  508. case RESET:
  509. onReset();
  510. break;
  511. case PULL_TO_REFRESH:
  512. onPullToRefresh();
  513. break;
  514. case RELEASE_TO_REFRESH:
  515. onReleaseToRefresh();
  516. break;
  517. case REFRESHING:
  518. case MANUAL_REFRESHING:
  519. onRefreshing(params[0]);
  520. break;
  521. case OVERSCROLLING:
  522. // NO-OP
  523. break;
  524. }
  525. // Call OnPullEventListener
  526. if (null != mOnPullEventListener) {
  527. mOnPullEventListener.onPullEvent(this, mState, mCurrentMode);
  528. }
  529. }
  530. /**
  531. * Used internally for adding view. Need because we override addView to
  532. * pass-through to the Refreshable View
  533. */
  534. protected final void addViewInternal(View child, int index, ViewGroup.LayoutParams params) {
  535. super.addView(child, index, params);
  536. }
  537. /**
  538. * Used internally for adding view. Need because we override addView to
  539. * pass-through to the Refreshable View
  540. */
  541. protected final void addViewInternal(View child, ViewGroup.LayoutParams params) {
  542. super.addView(child, -1, params);
  543. }
  544. protected LoadingLayout createLoadingLayout(Context context, Mode mode, TypedArray attrs) {
  545. LoadingLayout layout = mLoadingAnimationStyle.createLoadingLayout(context, mode,
  546. getPullToRefreshScrollDirection(), attrs);
  547. layout.setVisibility(View.INVISIBLE);
  548. return layout;
  549. }
  550. /**
  551. * Used internally for {@link #getLoadingLayoutProxy(boolean, boolean)}.
  552. * Allows derivative classes to include any extra LoadingLayouts.
  553. */
  554. protected LoadingLayoutProxy createLoadingLayoutProxy(final boolean includeStart, final boolean includeEnd) {
  555. LoadingLayoutProxy proxy = new LoadingLayoutProxy();
  556. if (includeStart && mMode.showHeaderLoadingLayout()) {
  557. proxy.addLayout(mHeaderLayout);
  558. }
  559. if (includeEnd && mMode.showFooterLoadingLayout()) {
  560. proxy.addLayout(mFooterLayout);
  561. }
  562. return proxy;
  563. }
  564. /**
  565. * This is implemented by derived classes to return the created View. If you
  566. * need to use a custom View (such as a custom ListView), override this
  567. * method and return an instance of your custom class.
  568. * <p/>
  569. * Be sure to set the ID of the view in this method, especially if you're
  570. * using a ListActivity or ListFragment.
  571. *
  572. * @param context Context to create view with
  573. * @param attrs AttributeSet from wrapped class. Means that anything you
  574. * include in the XML layout declaration will be routed to the
  575. * created View
  576. * @return New instance of the Refreshable View
  577. */
  578. protected abstract T createRefreshableView(Context context, AttributeSet attrs);
  579. protected final void disableLoadingLayoutVisibilityChanges() {
  580. mLayoutVisibilityChangesEnabled = false;
  581. }
  582. protected final LoadingLayout getFooterLayout() {
  583. return mFooterLayout;
  584. }
  585. protected final int getFooterSize() {
  586. return mFooterLayout.getContentSize();
  587. }
  588. protected final LoadingLayout getHeaderLayout() {
  589. return mHeaderLayout;
  590. }
  591. protected final int getHeaderSize() {
  592. return mHeaderLayout.getContentSize();
  593. }
  594. protected int getPullToRefreshScrollDuration() {
  595. return SMOOTH_SCROLL_DURATION_MS;
  596. }
  597. protected int getPullToRefreshScrollDurationLonger() {
  598. return SMOOTH_SCROLL_LONG_DURATION_MS;
  599. }
  600. protected FrameLayout getRefreshableViewWrapper() {
  601. return mRefreshableViewWrapper;
  602. }
  603. /**
  604. * Allows Derivative classes to handle the XML Attrs without creating a
  605. * TypedArray themsevles
  606. *
  607. * @param a - TypedArray of PullToRefresh Attributes
  608. */
  609. protected void handleStyledAttributes(TypedArray a) {
  610. }
  611. /**
  612. * Implemented by derived class to return whether the View is in a state
  613. * where the user can Pull to Refresh by scrolling from the end.
  614. *
  615. * @return true if the View is currently in the correct state (for example,
  616. * bottom of a ListView)
  617. */
  618. protected abstract boolean isReadyForPullEnd();
  619. /**
  620. * Implemented by derived class to return whether the View is in a state
  621. * where the user can Pull to Refresh by scrolling from the start.
  622. *
  623. * @return true if the View is currently the correct state (for example, top
  624. * of a ListView)
  625. */
  626. protected abstract boolean isReadyForPullStart();
  627. /**
  628. * Called by {@link #onRestoreInstanceState(Parcelable)} so that derivative
  629. * classes can handle their saved instance state.
  630. *
  631. * @param savedInstanceState - Bundle which contains saved instance state.
  632. */
  633. protected void onPtrRestoreInstanceState(Bundle savedInstanceState) {
  634. }
  635. /**
  636. * Called by {@link #onSaveInstanceState()} so that derivative classes can
  637. * save their instance state.
  638. *
  639. * @param saveState - Bundle to be updated with saved state.
  640. */
  641. protected void onPtrSaveInstanceState(Bundle saveState) {
  642. }
  643. /**
  644. * Called when the UI has been to be updated to be in the
  645. * {@link State#PULL_TO_REFRESH} state.
  646. */
  647. protected void onPullToRefresh() {
  648. switch (mCurrentMode) {
  649. case PULL_FROM_END:
  650. mFooterLayout.pullToRefresh();
  651. break;
  652. case PULL_FROM_START:
  653. mHeaderLayout.pullToRefresh();
  654. break;
  655. default:
  656. // NO-OP
  657. break;
  658. }
  659. }
  660. /**
  661. * Called when the UI has been to be updated to be in the
  662. * {@link State#REFRESHING} or {@link State#MANUAL_REFRESHING} state.
  663. *
  664. * @param doScroll - Whether the UI should scroll for this event.
  665. */
  666. protected void onRefreshing(final boolean doScroll) {
  667. if (mMode.showHeaderLoadingLayout()) {
  668. mHeaderLayout.refreshing();
  669. }
  670. if (mMode.showFooterLoadingLayout()) {
  671. mFooterLayout.refreshing();
  672. }
  673. if (doScroll) {
  674. if (mShowViewWhileRefreshing) {
  675. // Call Refresh Listener when the Scroll has finished
  676. OnSmoothScrollFinishedListener listener = new OnSmoothScrollFinishedListener() {
  677. @Override
  678. public void onSmoothScrollFinished() {
  679. callRefreshListener();
  680. }
  681. };
  682. switch (mCurrentMode) {
  683. case MANUAL_REFRESH_ONLY:
  684. case PULL_FROM_END:
  685. smoothScrollTo(getFooterSize(), listener);
  686. break;
  687. default:
  688. case PULL_FROM_START:
  689. smoothScrollTo(-getHeaderSize(), listener);
  690. break;
  691. }
  692. } else {
  693. smoothScrollTo(0);
  694. }
  695. } else {
  696. // We're not scrolling, so just call Refresh Listener now
  697. callRefreshListener();
  698. }
  699. }
  700. /**
  701. * Called when the UI has been to be updated to be in the
  702. * {@link State#RELEASE_TO_REFRESH} state.
  703. */
  704. protected void onReleaseToRefresh() {
  705. switch (mCurrentMode) {
  706. case PULL_FROM_END:
  707. mFooterLayout.releaseToRefresh();
  708. break;
  709. case PULL_FROM_START:
  710. mHeaderLayout.releaseToRefresh();
  711. break;
  712. default:
  713. // NO-OP
  714. break;
  715. }
  716. }
  717. /**
  718. * Called when the UI has been to be updated to be in the
  719. * {@link State#RESET} state.
  720. */
  721. protected void onReset() {
  722. mIsBeingDragged = false;
  723. mLayoutVisibilityChangesEnabled = true;
  724. // Always reset both layouts, just in case...
  725. mHeaderLayout.reset();
  726. mFooterLayout.reset();
  727. smoothScrollTo(0);
  728. mCurrentMode = (mMode != Mode.BOTH) ? mMode : Mode.PULL_FROM_START;
  729. }
  730. @Override
  731. protected final void onRestoreInstanceState(Parcelable state) {
  732. if (state instanceof Bundle) {
  733. Bundle bundle = (Bundle) state;
  734. setMode(Mode.mapIntToValue(bundle.getInt(STATE_MODE, 0)));
  735. mCurrentMode = Mode.mapIntToValue(bundle.getInt(STATE_CURRENT_MODE, 0));
  736. mScrollingWhileRefreshingEnabled = bundle.getBoolean(STATE_SCROLLING_REFRESHING_ENABLED, false);
  737. mShowViewWhileRefreshing = bundle.getBoolean(STATE_SHOW_REFRESHING_VIEW, true);
  738. // Let super Restore Itself
  739. super.onRestoreInstanceState(bundle.getParcelable(STATE_SUPER));
  740. State viewState = State.mapIntToValue(bundle.getInt(STATE_STATE, 0));
  741. if (viewState == State.REFRESHING || viewState == State.MANUAL_REFRESHING) {
  742. setState(viewState, true);
  743. }
  744. // Now let derivative classes restore their state
  745. onPtrRestoreInstanceState(bundle);
  746. return;
  747. }
  748. super.onRestoreInstanceState(state);
  749. }
  750. @Override
  751. protected final Parcelable onSaveInstanceState() {
  752. Bundle bundle = new Bundle();
  753. // Let derivative classes get a chance to save state first, that way we
  754. // can make sure they don't overrite any of our values
  755. onPtrSaveInstanceState(bundle);
  756. bundle.putInt(STATE_STATE, mState.getIntValue());
  757. bundle.putInt(STATE_MODE, mMode.getIntValue());
  758. bundle.putInt(STATE_CURRENT_MODE, mCurrentMode.getIntValue());
  759. bundle.putBoolean(STATE_SCROLLING_REFRESHING_ENABLED, mScrollingWhileRefreshingEnabled);
  760. bundle.putBoolean(STATE_SHOW_REFRESHING_VIEW, mShowViewWhileRefreshing);
  761. bundle.putParcelable(STATE_SUPER, super.onSaveInstanceState());
  762. return bundle;
  763. }
  764. @Override
  765. protected final void onSizeChanged(int w, int h, int oldw, int oldh) {
  766. if (DEBUG) {
  767. Log.d(LOG_TAG, String.format("onSizeChanged. W: %d, H: %d", w, h));
  768. }
  769. super.onSizeChanged(w, h, oldw, oldh);
  770. // We need to update the header/footer when our size changes
  771. refreshLoadingViewsSize();
  772. // Update the Refreshable View layout
  773. refreshRefreshableViewSize(w, h);
  774. /**
  775. * As we're currently in a Layout Pass, we need to schedule another one
  776. * to layout any changes we've made here
  777. */
  778. post(new Runnable() {
  779. @Override
  780. public void run() {
  781. requestLayout();
  782. }
  783. });
  784. }
  785. /**
  786. * Re-measure the Loading Views height, and adjust internal padding as
  787. * necessary
  788. */
  789. protected final void refreshLoadingViewsSize() {
  790. final int maximumPullScroll = (int) (getMaximumPullScroll() * 1.2f);
  791. int pLeft = getPaddingLeft();
  792. int pTop = getPaddingTop();
  793. int pRight = getPaddingRight();
  794. int pBottom = getPaddingBottom();
  795. switch (getPullToRefreshScrollDirection()) {
  796. case HORIZONTAL:
  797. if (mMode.showHeaderLoadingLayout()) {
  798. mHeaderLayout.setWidth(maximumPullScroll);
  799. pLeft = -maximumPullScroll;
  800. } else {
  801. pLeft = 0;
  802. }
  803. if (mMode.showFooterLoadingLayout()) {
  804. mFooterLayout.setWidth(maximumPullScroll);
  805. pRight = -maximumPullScroll;
  806. } else {
  807. pRight = 0;
  808. }
  809. break;
  810. case VERTICAL:
  811. if (mMode.showHeaderLoadingLayout()) {
  812. mHeaderLayout.setHeight(maximumPullScroll);
  813. pTop = -maximumPullScroll;
  814. } else {
  815. pTop = 0;
  816. }
  817. if (mMode.showFooterLoadingLayout()) {
  818. mFooterLayout.setHeight(maximumPullScroll);
  819. pBottom = -maximumPullScroll;
  820. } else {
  821. pBottom = 0;
  822. }
  823. break;
  824. }
  825. if (DEBUG) {
  826. Log.d(LOG_TAG, String.format("Setting Padding. L: %d, T: %d, R: %d, B: %d", pLeft, pTop, pRight, pBottom));
  827. }
  828. setPadding(pLeft, pTop, pRight, pBottom);
  829. }
  830. protected final void refreshRefreshableViewSize(int width, int height) {
  831. // We need to set the Height of the Refreshable View to the same as
  832. // this layout
  833. LayoutParams lp = (LayoutParams) mRefreshableViewWrapper.getLayoutParams();
  834. switch (getPullToRefreshScrollDirection()) {
  835. case HORIZONTAL:
  836. if (lp.width != width) {
  837. lp.width = width;
  838. mRefreshableViewWrapper.requestLayout();
  839. }
  840. break;
  841. case VERTICAL:
  842. if (lp.height != height) {
  843. lp.height = height;
  844. mRefreshableViewWrapper.requestLayout();
  845. }
  846. break;
  847. }
  848. }
  849. /**
  850. * Helper method which just calls scrollTo() in the correct scrolling
  851. * direction.
  852. *
  853. * @param value - New Scroll value
  854. */
  855. @TargetApi(VERSION_CODES.HONEYCOMB)
  856. protected final void setHeaderScroll(int value) {
  857. if (DEBUG) {
  858. Log.d(LOG_TAG, "setHeaderScroll: " + value);
  859. }
  860. // Clamp value to with pull scroll range
  861. final int maximumPullScroll = getMaximumPullScroll();
  862. value = Math.min(maximumPullScroll, Math.max(-maximumPullScroll, value));
  863. if (mLayoutVisibilityChangesEnabled) {
  864. if (value < 0) {
  865. mHeaderLayout.setVisibility(View.VISIBLE);
  866. } else if (value > 0) {
  867. mFooterLayout.setVisibility(View.VISIBLE);
  868. } else {
  869. mHeaderLayout.setVisibility(View.INVISIBLE);
  870. mFooterLayout.setVisibility(View.INVISIBLE);
  871. }
  872. }
  873. if (USE_HW_LAYERS) {
  874. /**
  875. * Use a Hardware Layer on the Refreshable View if we've scrolled at
  876. * all. We don't use them on the Header/Footer Views as they change
  877. * often, which would negate any HW layer performance boost.
  878. */
  879. ViewCompat.setLayerType(mRefreshableViewWrapper, value != 0 ? View.LAYER_TYPE_HARDWARE
  880. : View.LAYER_TYPE_NONE);
  881. }
  882. switch (getPullToRefreshScrollDirection()) {
  883. case VERTICAL:
  884. scrollTo(0, value);
  885. break;
  886. case HORIZONTAL:
  887. scrollTo(value, 0);
  888. break;
  889. }
  890. }
  891. /**
  892. * Smooth Scroll to position using the default duration of
  893. * {@value #SMOOTH_SCROLL_DURATION_MS} ms.
  894. *
  895. * @param scrollValue - Position to scroll to
  896. */
  897. protected final void smoothScrollTo(int scrollValue) {
  898. smoothScrollTo(scrollValue, getPullToRefreshScrollDuration());
  899. }
  900. /**
  901. * Smooth Scroll to position using the default duration of
  902. * {@value #SMOOTH_SCROLL_DURATION_MS} ms.
  903. *
  904. * @param scrollValue - Position to scroll to
  905. * @param listener - Listener for scroll
  906. */
  907. protected final void smoothScrollTo(int scrollValue, OnSmoothScrollFinishedListener listener) {
  908. smoothScrollTo(scrollValue, getPullToRefreshScrollDuration(), 0, listener);
  909. }
  910. /**
  911. * Smooth Scroll to position using the longer default duration of
  912. * {@value #SMOOTH_SCROLL_LONG_DURATION_MS} ms.
  913. *
  914. * @param scrollValue - Position to scroll to
  915. */
  916. protected final void smoothScrollToLonger(int scrollValue) {
  917. smoothScrollTo(scrollValue, getPullToRefreshScrollDurationLonger());
  918. }
  919. /**
  920. * Updates the View State when the mode has been set. This does not do any
  921. * checking that the mode is different to current state so always updates.
  922. */
  923. protected void updateUIForMode() {
  924. // We need to use the correct LayoutParam values, based on scroll
  925. // direction
  926. final LayoutParams lp = getLoadingLayoutLayoutParams();
  927. // Remove Header, and then add Header Loading View again if needed
  928. if (this == mHeaderLayout.getParent()) {
  929. removeView(mHeaderLayout);
  930. }
  931. if (mMode.showHeaderLoadingLayout()) {
  932. addViewInternal(mHeaderLayout, 0, lp);
  933. }
  934. // Remove Footer, and then add Footer Loading View again if needed
  935. if (this == mFooterLayout.getParent()) {
  936. removeView(mFooterLayout);
  937. }
  938. if (mMode.showFooterLoadingLayout()) {
  939. addViewInternal(mFooterLayout, lp);
  940. }
  941. // Hide Loading Views
  942. refreshLoadingViewsSize();
  943. // If we're not using Mode.BOTH, set mCurrentMode to mMode, otherwise
  944. // set it to pull down
  945. mCurrentMode = (mMode != Mode.BOTH) ? mMode : Mode.PULL_FROM_START;
  946. }
  947. private void addRefreshableView(Context context, T refreshableView) {
  948. mRefreshableViewWrapper = new FrameLayout(context);
  949. mRefreshableViewWrapper.addView(refreshableView, ViewGroup.LayoutParams.MATCH_PARENT,
  950. ViewGroup.LayoutParams.MATCH_PARENT);
  951. addViewInternal(mRefreshableViewWrapper, new LayoutParams(LayoutParams.MATCH_PARENT,
  952. LayoutParams.MATCH_PARENT));
  953. }
  954. private void callRefreshListener() {
  955. if (null != mOnRefreshListener) {
  956. mOnRefreshListener.onRefresh(this);
  957. } else if (null != mOnRefreshListener2) {
  958. if (mCurrentMode == Mode.PULL_FROM_START) {
  959. mOnRefreshListener2.onPullDownToRefresh(this);
  960. } else if (mCurrentMode == Mode.PULL_FROM_END) {
  961. mOnRefreshListener2.onPullUpToRefresh(this);
  962. }
  963. }
  964. }
  965. @SuppressWarnings("deprecation")
  966. private void init(Context context, AttributeSet attrs) {
  967. switch (getPullToRefreshScrollDirection()) {
  968. case HORIZONTAL:
  969. setOrientation(LinearLayout.HORIZONTAL);
  970. break;
  971. case VERTICAL:
  972. default:
  973. setOrientation(LinearLayout.VERTICAL);
  974. break;
  975. }
  976. setGravity(Gravity.CENTER);
  977. ViewConfiguration config = ViewConfiguration.get(context);
  978. mTouchSlop = config.getScaledTouchSlop();
  979. // Styleables from XML
  980. TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PullToRefresh);
  981. if (a.hasValue(R.styleable.PullToRefresh_ptrMode)) {
  982. mMode = Mode.mapIntToValue(a.getInteger(R.styleable.PullToRefresh_ptrMode, 0));
  983. }
  984. if (a.hasValue(R.styleable.PullToRefresh_ptrAnimationStyle)) {
  985. mLoadingAnimationStyle = AnimationStyle.mapIntToValue(a.getInteger(
  986. R.styleable.PullToRefresh_ptrAnimationStyle, 0));
  987. }
  988. // Refreshable View
  989. // By passing the attrs, we can add ListView/GridView params via XML
  990. mRefreshableView = createRefreshableView(context, attrs);
  991. addRefreshableView(context, mRefreshableView);
  992. // We need to create now layouts now
  993. mHeaderLayout = createLoadingLayout(context, Mode.PULL_FROM_START, a);
  994. mFooterLayout = createLoadingLayout(context, Mode.PULL_FROM_END, a);
  995. /**
  996. * Styleables from XML
  997. */
  998. if (a.hasValue(R.styleable.PullToRefresh_ptrRefreshableViewBackground)) {
  999. Drawable background = a.getDrawable(R.styleable.PullToRefresh_ptrRefreshableViewBackground);
  1000. if (null != background) {
  1001. mRefreshableView.setBackgroundDrawable(background);
  1002. }
  1003. } else if (a.hasValue(R.styleable.PullToRefresh_ptrAdapterViewBackground)) {
  1004. Utils.warnDeprecation("ptrAdapterViewBackground", "ptrRefreshableViewBackground");
  1005. Drawable background = a.getDrawable(R.styleable.PullToRefresh_ptrAdapterViewBackground);
  1006. if (null != background) {
  1007. mRefreshableView.setBackgroundDrawable(background);
  1008. }
  1009. }
  1010. if (a.hasValue(R.styleable.PullToRefresh_ptrOverScroll)) {
  1011. mOverScrollEnabled = a.getBoolean(R.styleable.PullToRefresh_ptrOverScroll, true);
  1012. }
  1013. if (a.hasValue(R.styleable.PullToRefresh_ptrScrollingWhileRefreshingEnabled)) {
  1014. mScrollingWhileRefreshingEnabled = a.getBoolean(
  1015. R.styleable.PullToRefresh_ptrScrollingWhileRefreshingEnabled, false);
  1016. }
  1017. // Let the derivative classes have a go at handling attributes, then
  1018. // recycle them...
  1019. handleStyledAttributes(a);
  1020. a.recycle();
  1021. // Finally update the UI for the modes
  1022. updateUIForMode();
  1023. }
  1024. private boolean isReadyForPull() {
  1025. switch (mMode) {
  1026. case PULL_FROM_START:
  1027. return isReadyForPullStart();
  1028. case PULL_FROM_END:
  1029. return isReadyForPullEnd();
  1030. case BOTH:
  1031. return isReadyForPullEnd() || isReadyForPullStart();
  1032. default:
  1033. return false;
  1034. }
  1035. }
  1036. /**
  1037. * Actions a Pull Event
  1038. *
  1039. * @return true if the Event has been handled, false if there has been no
  1040. * change
  1041. */
  1042. private void pullEvent() {
  1043. final int newScrollValue;
  1044. final int itemDimension;
  1045. final float initialMotionValue, lastMotionValue;
  1046. switch (getPullToRefreshScrollDirection()) {
  1047. case HORIZONTAL:
  1048. initialMotionValue = mInitialMotionX;
  1049. lastMotionValue = mLastMotionX;
  1050. break;
  1051. case VERTICAL:
  1052. default:
  1053. initialMotionValue = mInitialMotionY;
  1054. lastMotionValue = mLastMotionY;
  1055. break;
  1056. }
  1057. switch (mCurrentMode) {
  1058. case PULL_FROM_END:
  1059. newScrollValue = Math.round(Math.max(initialMotionValue - lastMotionValue, 0) / FRICTION);
  1060. itemDimension = getFooterSize();
  1061. break;
  1062. case PULL_FROM_START:
  1063. default:
  1064. newScrollValue = Math.round(Math.min(initialMotionValue - lastMotionValue, 0) / FRICTION);
  1065. itemDimension = getHeaderSize();
  1066. break;
  1067. }
  1068. setHeaderScroll(newScrollValue);
  1069. if (newScrollValue != 0 && !isRefreshing()) {
  1070. float scale = Math.abs(newScrollValue) / (float) itemDimension;
  1071. switch (mCurrentMode) {
  1072. case PULL_FROM_END:
  1073. mFooterLayout.onPull(scale);
  1074. break;
  1075. case PULL_FROM_START:
  1076. default:
  1077. mHeaderLayout.onPull(scale);
  1078. break;
  1079. }
  1080. if (mState != State.PULL_TO_REFRESH && itemDimension >= Math.abs(newScrollValue)) {
  1081. setState(State.PULL_TO_REFRESH);
  1082. } else if (mState == State.PULL_TO_REFRESH && itemDimension < Math.abs(newScrollValue)) {
  1083. setState(State.RELEASE_TO_REFRESH);
  1084. }
  1085. }
  1086. }
  1087. @TargetApi(VERSION_CODES.FROYO)
  1088. private LayoutParams getLoadingLayoutLayoutParams() {
  1089. switch (getPullToRefreshScrollDirection()) {
  1090. case HORIZONTAL:
  1091. return new LayoutParams(LayoutParams.WRAP_CONTENT,
  1092. LayoutParams.MATCH_PARENT);
  1093. case VERTICAL:
  1094. default:
  1095. return new LayoutParams(LayoutParams.MATCH_PARENT,
  1096. LayoutParams.WRAP_CONTENT);
  1097. }
  1098. }
  1099. private int getMaximumPullScroll() {
  1100. switch (getPullToRefreshScrollDirection()) {
  1101. case HORIZONTAL:
  1102. return Math.round(getWidth() / FRICTION);
  1103. case VERTICAL:
  1104. default:
  1105. return Math.round(getHeight() / FRICTION);
  1106. }
  1107. }
  1108. /**
  1109. * Smooth Scroll to position using the specific duration
  1110. *
  1111. * @param scrollValue - Position to scroll to
  1112. * @param duration - Duration of animation in milliseconds
  1113. */
  1114. private final void smoothScrollTo(int scrollValue, long duration) {
  1115. smoothScrollTo(scrollValue, duration, 0, null);
  1116. }
  1117. private final void smoothScrollTo(int newScrollValue, long duration, long delayMillis,
  1118. OnSmoothScrollFinishedListener listener) {
  1119. if (null != mCurrentSmoothScrollRunnable) {
  1120. mCurrentSmoothScrollRunnable.stop();
  1121. }
  1122. final int oldScrollValue;
  1123. switch (getPullToRefreshScrollDirection()) {
  1124. case HORIZONTAL:
  1125. oldScrollValue = getScrollX();
  1126. break;
  1127. case VERTICAL:
  1128. default:
  1129. oldScrollValue = getScrollY();
  1130. break;
  1131. }
  1132. if (oldScrollValue != newScrollValue) {
  1133. if (null == mScrollAnimationInterpolator) {
  1134. // Default interpolator is a Decelerate Interpolator
  1135. mScrollAnimationInterpolator = new DecelerateInterpolator();
  1136. }
  1137. mCurrentSmoothScrollRunnable = new SmoothScrollRunnable(oldScrollValue, newScrollValue, duration, listener);
  1138. if (delayMillis > 0) {
  1139. postDelayed(mCurrentSmoothScrollRunnable, delayMillis);
  1140. } else {
  1141. post(mCurrentSmoothScrollRunnable);
  1142. }
  1143. }
  1144. }
  1145. private final void smoothScrollToAndBack(int y) {
  1146. smoothScrollTo(y, SMOOTH_SCROLL_DURATION_MS, 0, new OnSmoothScrollFinishedListener() {
  1147. @Override
  1148. public void onSmoothScrollFinished() {
  1149. smoothScrollTo(0, SMOOTH_SCROLL_DURATION_MS, DEMO_SCROLL_INTERVAL, null);
  1150. }
  1151. });
  1152. }
  1153. public static enum AnimationStyle {
  1154. /**
  1155. * This is the default for Android-PullToRefresh. Allows you to use any
  1156. * drawable, which is automatically rotated and used as a Progress Bar.
  1157. */
  1158. ROTATE,
  1159. /**
  1160. * This is the old default, and what is commonly used on iOS. Uses an
  1161. * arrow image which flips depending on where the user has scrolled.
  1162. */
  1163. FLIP;
  1164. static AnimationStyle getDefault() {
  1165. return ROTATE;
  1166. }
  1167. /**
  1168. * Maps an int to a specific mode. This is needed when saving state, or
  1169. * inflating the view from XML where the mode is given through a attr
  1170. * int.
  1171. *
  1172. * @param modeInt - int to map a Mode to
  1173. * @return Mode that modeInt maps to, or ROTATE by default.
  1174. */
  1175. static AnimationStyle mapIntToValue(int modeInt) {
  1176. switch (modeInt) {
  1177. case 0x0:
  1178. default:
  1179. return ROTATE;
  1180. case 0x1:
  1181. return FLIP;
  1182. }
  1183. }
  1184. LoadingLayout createLoadingLayout(Context context, Mode mode, Orientation scrollDirection, TypedArray attrs) {
  1185. switch (this) {
  1186. case ROTATE:
  1187. default:
  1188. return new RotateLoadingLayout(context, mode, scrollDirection, attrs);
  1189. case FLIP:
  1190. return new FlipLoadingLayout(context, mode, scrollDirection, attrs);
  1191. }
  1192. }
  1193. }
  1194. public static enum Mode {
  1195. /**
  1196. * Disable all Pull-to-Refresh gesture and Refreshing handling
  1197. */
  1198. DISABLED(0x0),
  1199. /**
  1200. * Only allow the user to Pull from the start of the Refreshable View to
  1201. * refresh. The start is either the Top or Left, depending on the
  1202. * scrolling direction.
  1203. */
  1204. PULL_FROM_START(0x1),
  1205. /**
  1206. * Only allow the user to Pull from the end of the Refreshable View to
  1207. * refresh. The start is either the Bottom or Right, depending on the
  1208. * scrolling direction.
  1209. */
  1210. PULL_FROM_END(0x2),
  1211. /**
  1212. * Allow the user to both Pull from the start, from the end to refresh.
  1213. */
  1214. BOTH(0x3),
  1215. /**
  1216. * Disables Pull-to-Refresh gesture handling, but allows manually
  1217. * setting the Refresh state via
  1218. */
  1219. MANUAL_REFRESH_ONLY(0x4);
  1220. /**
  1221. * @deprecated Use {@link #PULL_FROM_START} from now on.
  1222. */
  1223. public static Mode PULL_DOWN_TO_REFRESH = Mode.PULL_FROM_START;
  1224. /**
  1225. * @deprecated Use {@link #PULL_FROM_END} from now on.
  1226. */
  1227. public static Mode PULL_UP_TO_REFRESH = Mode.PULL_FROM_END;
  1228. /**
  1229. * Maps an int to a specific mode. This is needed when saving state, or
  1230. * inflating the view from XML where the mode is given through a attr
  1231. * int.
  1232. *
  1233. * @param modeInt - int to map a Mode to
  1234. * @return Mode that modeInt maps to, or PULL_FROM_START by default.
  1235. */
  1236. static Mode mapIntToValue(final int modeInt) {
  1237. for (Mode value : Mode.values()) {
  1238. if (modeInt == value.getIntValue()) {
  1239. return value;
  1240. }
  1241. }
  1242. // If not, return default
  1243. return getDefault();
  1244. }
  1245. static Mode getDefault() {
  1246. return PULL_FROM_START;
  1247. }
  1248. private int mIntValue;
  1249. // The modeInt values need to match those from attrs.xml
  1250. Mode(int modeInt) {
  1251. mIntValue = modeInt;
  1252. }
  1253. /**
  1254. * @return true if the mode permits Pull-to-Refresh
  1255. */
  1256. boolean permitsPullToRefresh() {
  1257. return !(this == DISABLED || this == MANUAL_REFRESH_ONLY);
  1258. }
  1259. /**
  1260. * @return true if this mode wants the Loading Layout Header to be shown
  1261. */
  1262. public boolean showHeaderLoadingLayout() {
  1263. return this == PULL_FROM_START || this == BOTH;
  1264. }
  1265. /**
  1266. * @return true if this mode wants the Loading Layout Footer to be shown
  1267. */
  1268. public boolean showFooterLoadingLayout() {
  1269. return this == PULL_FROM_END || this == BOTH || this == MANUAL_REFRESH_ONLY;
  1270. }
  1271. int getIntValue() {
  1272. return mIntValue;
  1273. }
  1274. }
  1275. // ===========================================================
  1276. // Inner, Anonymous Classes, and Enumerations
  1277. // ===========================================================
  1278. /**
  1279. * Simple Listener that allows you to be notified when the user has scrolled
  1280. * to the end of the AdapterView. See (
  1281. * {@link PullToRefreshAdapterViewBase#setOnLastItemVisibleListener}.
  1282. *
  1283. * @author Chris Banes
  1284. */
  1285. public static interface OnLastItemVisibleListener {
  1286. /**
  1287. * Called when the user has scrolled to the end of the list
  1288. */
  1289. public void onLastItemVisible();
  1290. }
  1291. /**
  1292. * Listener that allows you to be notified when the user has started or
  1293. * finished a touch event. Useful when you want to append extra UI events
  1294. * (such as sounds). See (
  1295. * {@link PullToRefreshAdapterViewBase#setOnPullEventListener}.
  1296. *
  1297. * @author Chris Banes
  1298. */
  1299. public static interface OnPullEventListener<V extends View> {
  1300. /**
  1301. * Called when the internal state has been changed, usually by the user
  1302. * pulling.
  1303. *
  1304. * @param refreshView - View which has had it's state change.
  1305. * @param state - The new state of View.
  1306. * @param direction - One of {@link Mode#PULL_FROM_START} or
  1307. * {@link Mode#PULL_FROM_END} depending on which direction
  1308. * the user is pulling. Only useful when <var>state</var> is
  1309. * {@link State#PULL_TO_REFRESH} or
  1310. * {@link State#RELEASE_TO_REFRESH}.
  1311. */
  1312. public void onPullEvent(final PullToRefreshBase<V> refreshView, State state, Mode direction);
  1313. }
  1314. /**
  1315. * Simple Listener to listen for any callbacks to Refresh.
  1316. *
  1317. * @author Chris Banes
  1318. */
  1319. public static interface OnRefreshListener<V extends View> {
  1320. /**
  1321. * onRefresh will be called for both a Pull from start, and Pull from
  1322. * end
  1323. */
  1324. public void onRefresh(final PullToRefreshBase<V> refreshView);
  1325. }
  1326. /**
  1327. * An advanced version of the Listener to listen for callbacks to Refresh.
  1328. * This listener is different as it allows you to differentiate between Pull
  1329. * Ups, and Pull Downs.
  1330. *
  1331. * @author Chris Banes
  1332. */
  1333. public static interface OnRefreshListener2<V extends View> {
  1334. // These methods need renaming to START/END rather than DOWN/UP
  1335. /**
  1336. * onPullDownToRefresh will be called only when the user has Pulled from
  1337. * the start, and released.
  1338. */
  1339. public void onPullDownToRefresh(final PullToRefreshBase<V> refreshView);
  1340. /**
  1341. * onPullUpToRefresh will be called only when the user has Pulled from
  1342. * the end, and released.
  1343. */
  1344. public void onPullUpToRefresh(final PullToRefreshBase<V> refreshView);
  1345. }
  1346. public static enum Orientation {
  1347. VERTICAL, HORIZONTAL;
  1348. }
  1349. public static enum State {
  1350. /**
  1351. * When the UI is in a state which means that user is not interacting
  1352. * with the Pull-to-Refresh function.
  1353. */
  1354. RESET(0x0),
  1355. /**
  1356. * When the UI is being pulled by the user, but has not been pulled far
  1357. * enough so that it refreshes when released.
  1358. */
  1359. PULL_TO_REFRESH(0x1),
  1360. /**
  1361. * When the UI is being pulled by the user, and <strong>has</strong>
  1362. * been pulled far enough so that it will refresh when released.
  1363. */
  1364. RELEASE_TO_REFRESH(0x2),
  1365. /**
  1366. * When the UI is currently refreshing, caused by a pull gesture.
  1367. */
  1368. REFRESHING(0x8),
  1369. /**
  1370. * When the UI is currently refreshing, caused by a call to
  1371. */
  1372. MANUAL_REFRESHING(0x9),
  1373. /**
  1374. * When the UI is currently overscrolling, caused by a fling on the
  1375. * Refreshable View.
  1376. */
  1377. OVERSCROLLING(0x10);
  1378. /**
  1379. * Maps an int to a specific state. This is needed when saving state.
  1380. *
  1381. * @param stateInt - int to map a State to
  1382. * @return State that stateInt maps to
  1383. */
  1384. static State mapIntToValue(final int stateInt) {
  1385. for (State value : State.values()) {
  1386. if (stateInt == value.getIntValue()) {
  1387. return value;
  1388. }
  1389. }
  1390. // If not, return default
  1391. return RESET;
  1392. }
  1393. private int mIntValue;
  1394. State(int intValue) {
  1395. mIntValue = intValue;
  1396. }
  1397. int getIntValue() {
  1398. return mIntValue;
  1399. }
  1400. }
  1401. final class SmoothScrollRunnable implements Runnable {
  1402. private final Interpolator mInterpolator;
  1403. private final int mScrollToY;
  1404. private final int mScrollFromY;
  1405. private final long mDuration;
  1406. private OnSmoothScrollFinishedListener mListener;
  1407. private boolean mContinueRunning = true;
  1408. private long mStartTime = -1;
  1409. private int mCurrentY = -1;
  1410. public SmoothScrollRunnable(int fromY, int toY, long duration, OnSmoothScrollFinishedListener listener) {
  1411. mScrollFromY = fromY;
  1412. mScrollToY = toY;
  1413. mInterpolator = mScrollAnimationInterpolator;
  1414. mDuration = duration;
  1415. mListener = listener;
  1416. }
  1417. @Override
  1418. public void run() {
  1419. /**
  1420. * Only set mStartTime if this is the first time we're starting,
  1421. * else actually calculate the Y delta
  1422. */
  1423. if (mStartTime == -1) {
  1424. mStartTime = System.currentTimeMillis();
  1425. } else {
  1426. /**
  1427. * We do do all calculations in long to reduce software float
  1428. * calculations. We use 1000 as it gives us good accuracy and
  1429. * small rounding errors
  1430. */
  1431. long normalizedTime = (1000 * (System.currentTimeMillis() - mStartTime)) / mDuration;
  1432. normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0);
  1433. final int deltaY = Math.round((mScrollFromY - mScrollToY)
  1434. * mInterpolator.getInterpolation(normalizedTime / 1000f));
  1435. mCurrentY = mScrollFromY - deltaY;
  1436. setHeaderScroll(mCurrentY);
  1437. }
  1438. // If we're not at the target Y, keep going...
  1439. if (mContinueRunning && mScrollToY != mCurrentY) {
  1440. ViewCompat.postOnAnimation(PullToRefreshBase.this, this);
  1441. } else {
  1442. if (null != mListener) {
  1443. mListener.onSmoothScrollFinished();
  1444. }
  1445. }
  1446. }
  1447. public void stop() {
  1448. mContinueRunning = false;
  1449. removeCallbacks(this);
  1450. }
  1451. }
  1452. static interface OnSmoothScrollFinishedListener {
  1453. void onSmoothScrollFinished();
  1454. }
  1455. }