├── settings.gradle ├── header.png ├── library ├── gradle.properties ├── AndroidManifest.xml ├── src │ └── uk │ │ └── co │ │ └── senab │ │ └── actionbarpulltorefresh │ │ └── library │ │ ├── sdk │ │ ├── CompatV16.java │ │ ├── CompatBase.java │ │ ├── Compat.java │ │ └── CompatV11.java │ │ ├── EnvironmentDelegate.java │ │ ├── viewdelegates │ │ ├── WebViewDelegate.java │ │ ├── ScrollYDelegate.java │ │ ├── ViewDelegate.java │ │ └── AbsListViewDelegate.java │ │ ├── listeners │ │ ├── OnRefreshListener.java │ │ └── HeaderViewListener.java │ │ ├── HeaderTransformer.java │ │ ├── InstanceCreationUtils.java │ │ ├── Options.java │ │ ├── ActionBarPullToRefresh.java │ │ ├── PullToRefreshLayout.java │ │ ├── DefaultHeaderTransformer.java │ │ └── PullToRefreshAttacher.java ├── project.properties ├── res │ ├── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── ids.xml │ │ ├── styles.xml │ │ ├── pull_refresh_strings.xml │ │ └── attrs.xml │ ├── values-ru │ │ └── pull_refresh_strings.xml │ ├── values-zh │ │ └── pull_refresh_strings.xml │ ├── values-ar │ │ └── pull_refresh_strings.xml │ ├── values-fi │ │ └── pull_refresh_strings.xml │ ├── values-iw │ │ └── pull_refresh_strings.xml │ ├── values-ja │ │ └── pull_refresh_strings.xml │ ├── values-ko │ │ └── pull_refresh_strings.xml │ ├── values-he │ │ └── pull_refresh_strings.xml │ ├── values-lt │ │ └── pull_refresh_strings.xml │ ├── values-cs │ │ └── pull_refresh_strings.xml │ ├── values-it │ │ └── pull_refresh_strings.xml │ ├── values-nl │ │ └── pull_refresh_strings.xml │ ├── values-pl │ │ └── pull_refresh_strings.xml │ ├── values-pt │ │ └── pull_refresh_strings.xml │ ├── values-de │ │ └── pull_refresh_strings.xml │ ├── values-fr │ │ └── pull_refresh_strings.xml │ ├── values-sv │ │ └── pull_refresh_strings.xml │ ├── values-pt-rBR │ │ └── pull_refresh_strings.xml │ ├── values-sk │ │ └── pull_refresh_strings.xml │ ├── values-ro │ │ └── pull_refresh_strings.xml │ ├── values-es │ │ └── pull_refresh_strings.xml │ └── layout │ │ └── default_header.xml └── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── samples └── stock │ ├── res │ ├── drawable-hdpi │ │ └── ic_launcher.png │ ├── drawable-ldpi │ │ └── ic_launcher.png │ ├── drawable-mdpi │ │ └── ic_launcher.png │ ├── drawable-xhdpi │ │ └── ic_launcher.png │ ├── drawable-xxhdpi │ │ └── ic_launcher.png │ ├── values │ │ ├── colors.xml │ │ ├── styles.xml │ │ └── strings.xml │ ├── anim │ │ ├── slide_in_top.xml │ │ └── slide_out_top.xml │ ├── layout │ │ ├── activity_fragment_tabs.xml │ │ ├── activity_webview.xml │ │ ├── activity_gridview.xml │ │ ├── activity_scrollview.xml │ │ ├── activity_listview_empty.xml │ │ ├── customised_header.xml │ │ └── layout_fragment.xml │ └── menu │ │ └── sample.xml │ ├── project.properties │ ├── src │ └── uk │ │ └── co │ │ └── senab │ │ └── actionbarpulltorefresh │ │ └── samples │ │ └── stock │ │ ├── Constants.java │ │ ├── BaseSampleActivity.java │ │ ├── ScrollViewActivity.java │ │ ├── WebViewActivity.java │ │ ├── MainActivity.java │ │ ├── ListViewActivity.java │ │ ├── FragmentTabsActivity.java │ │ └── GridViewActivity.java │ ├── AndroidManifest.xml │ └── build.gradle ├── local-deploy.sh ├── ghpages-deploy.sh ├── .gitignore ├── gradle.properties ├── README.md └── gradlew /settings.gradle: -------------------------------------------------------------------------------- 1 | include 'library' 2 | include 'samples/stock' 3 | -------------------------------------------------------------------------------- /header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wordpress-mobile/ActionBar-PullToRefresh/master/header.png -------------------------------------------------------------------------------- /library/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=ActionBar-PullToRefresh Library 2 | POM_ARTIFACT_ID=library 3 | POM_PACKAGING=aar 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wordpress-mobile/ActionBar-PullToRefresh/master/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /samples/stock/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wordpress-mobile/ActionBar-PullToRefresh/master/samples/stock/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /samples/stock/res/drawable-ldpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wordpress-mobile/ActionBar-PullToRefresh/master/samples/stock/res/drawable-ldpi/ic_launcher.png -------------------------------------------------------------------------------- /samples/stock/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wordpress-mobile/ActionBar-PullToRefresh/master/samples/stock/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /samples/stock/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wordpress-mobile/ActionBar-PullToRefresh/master/samples/stock/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /samples/stock/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wordpress-mobile/ActionBar-PullToRefresh/master/samples/stock/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /library/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /local-deploy.sh: -------------------------------------------------------------------------------- 1 | 2 | VERSION=0.9.7 3 | GROUP_ID=org.wordpress 4 | 5 | # Push to the local maven repo 6 | mvn install:install-file -Dfile=library/build/libs/library.aar \ 7 | -DgroupId=$GROUP_ID -DartifactId=pulltorefresh-main -Dpackaging=aar \ 8 | -Dversion=$VERSION 9 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Dec 19 13:13:21 CET 2013 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=http\://services.gradle.org/distributions/gradle-1.10-all.zip 7 | -------------------------------------------------------------------------------- /library/src/uk/co/senab/actionbarpulltorefresh/library/sdk/CompatV16.java: -------------------------------------------------------------------------------- 1 | package uk.co.senab.actionbarpulltorefresh.library.sdk; 2 | 3 | import android.view.View; 4 | 5 | class CompatV16 { 6 | 7 | static void postOnAnimation(View view, Runnable runnable) { 8 | view.postOnAnimation(runnable); 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /ghpages-deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | LOCAL_GH_PAGES=file:///Users/max/work/automattic/WordPress-Android-gh-pages/ 4 | GROUP_ID=org.wordpress 5 | VERSION=0.9.7 6 | 7 | # Main library 8 | mvn deploy:deploy-file -Dfile=library/build/libs/library.aar \ 9 | -Durl=$LOCAL_GH_PAGES -DgroupId=$GROUP_ID \ 10 | -DartifactId=pulltorefresh-main -Dversion=$VERSION 11 | -------------------------------------------------------------------------------- /library/src/uk/co/senab/actionbarpulltorefresh/library/sdk/CompatBase.java: -------------------------------------------------------------------------------- 1 | package uk.co.senab.actionbarpulltorefresh.library.sdk; 2 | 3 | import android.view.View; 4 | 5 | class CompatBase { 6 | 7 | static void setAlpha(View view, float alpha) { 8 | // NO-OP 9 | } 10 | 11 | static void postOnAnimation(View view, Runnable runnable) { 12 | view.postDelayed(runnable, 10l); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #Android generated 2 | bin 3 | gen 4 | gen* 5 | 6 | #Eclipse 7 | .project 8 | .classpath 9 | .settings 10 | 11 | #IntelliJ IDEA 12 | .idea 13 | *.iml 14 | *.ipr 15 | *.iws 16 | out 17 | 18 | #Maven 19 | target 20 | release.properties 21 | pom.xml.* 22 | 23 | #Ant 24 | build.xml 25 | local.properties 26 | proguard.cfg 27 | 28 | #Gradle 29 | .gradle 30 | build 31 | 32 | #OSX 33 | .DS_Store 34 | 35 | #Personal Files 36 | signing.properties -------------------------------------------------------------------------------- /library/project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system edit 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | # 10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): 11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt 12 | 13 | # Project target. 14 | target=android-18 15 | android.library=true 16 | -------------------------------------------------------------------------------- /samples/stock/project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system edit 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | # 10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): 11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt 12 | 13 | # Project target. 14 | target=android-18 15 | android.library.reference.1=../../library 16 | 17 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | VERSION_NAME=0.9.7 2 | VERSION_CODE=96 3 | GROUP=com.github.chrisbanes.actionbarpulltorefresh 4 | 5 | POM_DESCRIPTION=A modern implementation of the pull-to-refresh for Android 6 | POM_URL=https://github.com/chrisbanes/ActionBar-PullToRefresh 7 | POM_SCM_URL=https://github.com/chrisbanes/ActionBar-PullToRefresh 8 | POM_SCM_CONNECTION=scm:git@github.com:chrisbanes/ActionBar-PullToRefresh.git 9 | POM_SCM_DEV_CONNECTION=scm:git@github.com:chrisbanes/ActionBar-PullToRefresh.git 10 | POM_LICENCE_NAME=The Apache Software License, Version 2.0 11 | POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt 12 | POM_LICENCE_DIST=repo 13 | POM_DEVELOPER_ID=chrisbanes 14 | POM_DEVELOPER_NAME=Chris Banes 15 | 16 | ANDROID_BUILD_TARGET_SDK_VERSION=19 17 | ANDROID_BUILD_TOOLS_VERSION=19 18 | ANDROID_BUILD_SDK_VERSION=19 19 | -------------------------------------------------------------------------------- /library/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | #FF33B5E5 19 | -------------------------------------------------------------------------------- /samples/stock/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 20 | #99CC00 21 | -------------------------------------------------------------------------------- /library/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 4dp 20 | 21 | -------------------------------------------------------------------------------- /samples/stock/src/uk/co/senab/actionbarpulltorefresh/samples/stock/Constants.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 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 | 17 | package uk.co.senab.actionbarpulltorefresh.samples.stock; 18 | 19 | public class Constants { 20 | 21 | public static final int SIMULATED_REFRESH_LENGTH = 5000; 22 | 23 | } 24 | -------------------------------------------------------------------------------- /library/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /samples/stock/res/anim/slide_in_top.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | -------------------------------------------------------------------------------- /samples/stock/res/anim/slide_out_top.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | -------------------------------------------------------------------------------- /samples/stock/res/layout/activity_fragment_tabs.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | -------------------------------------------------------------------------------- /library/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 22 | 23 | -------------------------------------------------------------------------------- /library/src/uk/co/senab/actionbarpulltorefresh/library/sdk/Compat.java: -------------------------------------------------------------------------------- 1 | package uk.co.senab.actionbarpulltorefresh.library.sdk; 2 | 3 | import android.os.Build; 4 | import android.view.View; 5 | 6 | public class Compat { 7 | 8 | public static void setAlpha(View view, float alpha) { 9 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { 10 | CompatV11.setAlpha(view, alpha); 11 | } else { 12 | CompatBase.setAlpha(view, alpha); 13 | } 14 | } 15 | 16 | public static void postOnAnimation(View view, Runnable runnable) { 17 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 18 | CompatV16.postOnAnimation(view, runnable); 19 | } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { 20 | CompatV11.postOnAnimation(view, runnable); 21 | } else { 22 | CompatBase.postOnAnimation(view, runnable); 23 | } 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /library/res/values-ru/pull_refresh_strings.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | потяните 19 | отпустите 20 | обновление 21 | 22 | -------------------------------------------------------------------------------- /library/res/values-zh/pull_refresh_strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 下拉刷新… 20 | 放开以刷新… 21 | 正在载入… 22 | 23 | -------------------------------------------------------------------------------- /library/res/values-ar/pull_refresh_strings.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | اسحب للتحديث… 19 | اترك للتحديث… 20 | تحميل… 21 | 22 | -------------------------------------------------------------------------------- /library/res/values-fi/pull_refresh_strings.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | Päivitä vetämällä alas… 19 | Päivitä vapauttamalla… 20 | Päivitetään… 21 | -------------------------------------------------------------------------------- /library/res/values-iw/pull_refresh_strings.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | משוך לרענון… 19 | שחרר לרענון… 20 | טוען… 21 | 22 | -------------------------------------------------------------------------------- /library/res/values-ja/pull_refresh_strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 画面を引っ張って… 20 | 指を離して更新… 21 | 読み込み中… 22 | 23 | -------------------------------------------------------------------------------- /library/res/values-ko/pull_refresh_strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 당겨서 새로 고침… 20 | 놓아서 새로 고침… 21 | 로드 중… 22 | 23 | -------------------------------------------------------------------------------- /library/res/values/pull_refresh_strings.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | Pull to refresh… 20 | Release to refresh… 21 | Loading… 22 | 23 | 24 | -------------------------------------------------------------------------------- /library/res/values-he/pull_refresh_strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | משוך לרענון… 20 | שחרר לרענון… 21 | טוען… 22 | 23 | -------------------------------------------------------------------------------- /library/res/values-lt/pull_refresh_strings.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | Traukite, kad atnaujinti… 19 | Paleiskite, kad atnaujinti… 20 | Kraunama… 21 | 22 | -------------------------------------------------------------------------------- /library/res/values-cs/pull_refresh_strings.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | Tažením aktualizujete… 19 | Uvolněním aktualizujete… 20 | Načítání… 21 | 22 | -------------------------------------------------------------------------------- /library/res/values-it/pull_refresh_strings.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | Tira per aggiornare… 19 | Rilascia per aggionare… 20 | Caricamento… 21 | 22 | -------------------------------------------------------------------------------- /library/res/values-nl/pull_refresh_strings.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | Sleep om te vernieuwen… 19 | Loslaten om te vernieuwen… 20 | Laden… 21 | 22 | -------------------------------------------------------------------------------- /library/res/values-pl/pull_refresh_strings.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | Pociągnij, aby odświeżyć… 19 | Puść, aby odświeżyć… 20 | Wczytywanie… 21 | 22 | -------------------------------------------------------------------------------- /library/res/values-pt/pull_refresh_strings.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | Puxe para atualizar… 19 | Liberação para atualizar… 20 | A carregar… 21 | 22 | -------------------------------------------------------------------------------- /library/res/values-de/pull_refresh_strings.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | Ziehen zum Aktualisieren… 19 | Loslassen zum Aktualisieren… 20 | Laden… 21 | 22 | -------------------------------------------------------------------------------- /library/res/values-fr/pull_refresh_strings.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | Tirez pour rafraîchir… 19 | Relâcher pour rafraîchir… 20 | Chargement… 21 | 22 | -------------------------------------------------------------------------------- /library/res/values-sv/pull_refresh_strings.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | Dra nedåt om du vill uppdatera 20 | Släpp om du vill uppdatera 21 | Uppdaterar… 22 | 23 | 24 | -------------------------------------------------------------------------------- /library/res/values-pt-rBR/pull_refresh_strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | Puxe para atualizar… 20 | Libere para atualizar… 21 | Carregando… 22 | 23 | -------------------------------------------------------------------------------- /library/res/values-sk/pull_refresh_strings.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | Potiahnite pre načítanie... 19 | Uvoľnite pre načítanie... 20 | Načítavanie... 21 | 22 | -------------------------------------------------------------------------------- /library/res/values-ro/pull_refresh_strings.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | Trage pentru a reîmprospăta… 19 | Eliberează pentru a reîmprospăta… 20 | Încărcare… 21 | 22 | -------------------------------------------------------------------------------- /library/res/values-es/pull_refresh_strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | Desliza el dedo hacia abajo para actualizar. 20 | Soltar para actualizar… 21 | Cargando… 22 | 23 | -------------------------------------------------------------------------------- /library/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | mavenCentral() 4 | } 5 | 6 | dependencies { 7 | classpath 'com.android.tools.build:gradle:0.10.+' 8 | } 9 | } 10 | repositories { 11 | mavenCentral() 12 | } 13 | 14 | apply plugin: 'android-library' 15 | 16 | dependencies { 17 | compile 'com.github.castorflex.smoothprogressbar:library:0.3.3' 18 | } 19 | 20 | android { 21 | lintOptions { 22 | abortOnError false 23 | } 24 | 25 | compileSdkVersion Integer.parseInt(project.ANDROID_BUILD_SDK_VERSION) 26 | buildToolsVersion project.ANDROID_BUILD_TOOLS_VERSION 27 | 28 | defaultConfig { 29 | // This should be 14, but is 7 because extra-abc/extra-abs depend on this library 30 | minSdkVersion 7 31 | targetSdkVersion Integer.parseInt(project.ANDROID_BUILD_TARGET_SDK_VERSION) 32 | } 33 | 34 | sourceSets { 35 | main { 36 | manifest.srcFile 'AndroidManifest.xml' 37 | java.srcDirs = ['src'] 38 | res.srcDirs = ['res'] 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /samples/stock/res/menu/sample.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 20 | 21 | 24 | 25 | 28 | 29 | -------------------------------------------------------------------------------- /library/src/uk/co/senab/actionbarpulltorefresh/library/sdk/CompatV11.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 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 | 17 | package uk.co.senab.actionbarpulltorefresh.library.sdk; 18 | 19 | import android.animation.ValueAnimator; 20 | import android.view.View; 21 | 22 | class CompatV11 { 23 | 24 | static void setAlpha(View view, float alpha) { 25 | view.setAlpha(alpha); 26 | } 27 | 28 | static void postOnAnimation(View view, Runnable runnable) { 29 | view.postDelayed(runnable, ValueAnimator.getFrameDelay()); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /library/src/uk/co/senab/actionbarpulltorefresh/library/EnvironmentDelegate.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 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 | 17 | package uk.co.senab.actionbarpulltorefresh.library; 18 | 19 | import android.app.Activity; 20 | import android.content.Context; 21 | 22 | /** 23 | * This is used to provide platform and environment specific functionality for the Attacher. 24 | */ 25 | public interface EnvironmentDelegate { 26 | 27 | /** 28 | * @return Context which should be used for inflating the header layout 29 | */ 30 | public Context getContextForInflater(Activity activity); 31 | 32 | } 33 | -------------------------------------------------------------------------------- /library/src/uk/co/senab/actionbarpulltorefresh/library/viewdelegates/WebViewDelegate.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 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 | 17 | package uk.co.senab.actionbarpulltorefresh.library.viewdelegates; 18 | 19 | import android.view.View; 20 | import android.webkit.WebView; 21 | 22 | /** 23 | * FIXME 24 | */ 25 | public class WebViewDelegate implements ViewDelegate { 26 | 27 | public static final Class[] SUPPORTED_VIEW_CLASSES = { WebView.class }; 28 | 29 | @Override 30 | public boolean isReadyForPull(View view, float x, float y) { 31 | return view.getScrollY() <= 0; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /library/src/uk/co/senab/actionbarpulltorefresh/library/listeners/OnRefreshListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 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 | 17 | package uk.co.senab.actionbarpulltorefresh.library.listeners; 18 | 19 | import android.view.View; 20 | 21 | /** 22 | * Simple Listener to listen for any callbacks to Refresh. 23 | */ 24 | public interface OnRefreshListener { 25 | /** 26 | * Called when the user has initiated a refresh by pulling. 27 | * 28 | * @param view 29 | * - View which the user has started the refresh from. 30 | */ 31 | public void onRefreshStarted(View view); 32 | } 33 | -------------------------------------------------------------------------------- /library/src/uk/co/senab/actionbarpulltorefresh/library/viewdelegates/ScrollYDelegate.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 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 | 17 | package uk.co.senab.actionbarpulltorefresh.library.viewdelegates; 18 | 19 | import android.view.View; 20 | import android.widget.ScrollView; 21 | 22 | /** 23 | * FIXME 24 | */ 25 | public class ScrollYDelegate implements ViewDelegate { 26 | 27 | public static final Class[] SUPPORTED_VIEW_CLASSES = { ScrollView.class }; 28 | 29 | @Override 30 | public boolean isReadyForPull(View view, float x, float y) { 31 | return view.getScrollY() <= 0; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /samples/stock/res/layout/activity_webview.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 23 | 24 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /samples/stock/res/layout/activity_gridview.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 23 | 24 | 34 | 35 | -------------------------------------------------------------------------------- /samples/stock/res/layout/activity_scrollview.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 24 | 25 | 31 | 32 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /samples/stock/res/layout/activity_listview_empty.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 23 | 24 | 28 | 29 | 36 | 37 | -------------------------------------------------------------------------------- /library/src/uk/co/senab/actionbarpulltorefresh/library/viewdelegates/ViewDelegate.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 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 | 17 | package uk.co.senab.actionbarpulltorefresh.library.viewdelegates; 18 | 19 | import android.view.View; 20 | 21 | /** 22 | * ViewDelegates are what are used to de-couple the Attacher from the different types of 23 | * scrollable views. 24 | */ 25 | public interface ViewDelegate { 26 | 27 | /** 28 | * Allows you to provide support for View which do not have built-in 29 | * support. In this method you should cast view to it's 30 | * native class, and check if it is scrolled to the top. 31 | * 32 | * @param view 33 | * The view which has should be checked against. 34 | * @param x The X co-ordinate of the touch event 35 | * @param y The Y co-ordinate of the touch event 36 | * @return true if view is scrolled to the top. 37 | */ 38 | public boolean isReadyForPull(View view, float x, float y); 39 | 40 | } 41 | -------------------------------------------------------------------------------- /library/res/layout/default_header.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 21 | 22 | 27 | 28 | 34 | 35 | 36 | 37 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /samples/stock/res/layout/customised_header.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 24 | 25 | 33 | 34 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /samples/stock/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 23 | 24 | 28 | 29 | 33 | 34 | 37 | 38 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /samples/stock/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 23 | 24 | 38 | 39 | 43 | 44 | -------------------------------------------------------------------------------- /library/src/uk/co/senab/actionbarpulltorefresh/library/listeners/HeaderViewListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 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 | 17 | package uk.co.senab.actionbarpulltorefresh.library.listeners; 18 | 19 | import android.view.View; 20 | 21 | public interface HeaderViewListener { 22 | /** 23 | * The state when the header view is completely visible. 24 | */ 25 | public static int STATE_VISIBLE = 0; 26 | 27 | /** 28 | * The state when the header view is minimized. By default this means 29 | * that the progress bar is still visible, but the rest of the view is 30 | * hidden, showing the Action Bar behind. 31 | *

32 | * This will not be called in header minimization is disabled. 33 | */ 34 | public static int STATE_MINIMIZED = 1; 35 | 36 | /** 37 | * The state when the header view is completely hidden. 38 | */ 39 | public static int STATE_HIDDEN = 2; 40 | 41 | /** 42 | * Called when the visibility state of the Header View has changed. 43 | * 44 | * @param headerView 45 | * HeaderView who's state has changed. 46 | * @param state 47 | * The new state. One of {@link #STATE_VISIBLE}, 48 | * {@link #STATE_MINIMIZED} and {@link #STATE_HIDDEN} 49 | */ 50 | public void onStateChanged(View headerView, int state); 51 | } 52 | -------------------------------------------------------------------------------- /samples/stock/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | mavenCentral() 4 | } 5 | 6 | dependencies { 7 | classpath 'com.android.tools.build:gradle:0.9.+' 8 | } 9 | } 10 | repositories { 11 | mavenCentral() 12 | } 13 | 14 | apply plugin: 'android' 15 | 16 | dependencies { 17 | compile project(':library') 18 | } 19 | 20 | android { 21 | compileSdkVersion Integer.parseInt(project.ANDROID_BUILD_SDK_VERSION) 22 | buildToolsVersion project.ANDROID_BUILD_TOOLS_VERSION 23 | 24 | defaultConfig { 25 | minSdkVersion 14 26 | targetSdkVersion Integer.parseInt(project.ANDROID_BUILD_TARGET_SDK_VERSION) 27 | versionName project.VERSION_NAME 28 | versionCode Integer.parseInt(project.VERSION_CODE) 29 | } 30 | 31 | signingConfigs { release } 32 | 33 | buildTypes { 34 | release { 35 | signingConfig signingConfigs.release 36 | } 37 | } 38 | 39 | sourceSets { 40 | main { 41 | manifest.srcFile 'AndroidManifest.xml' 42 | java.srcDirs = ['src'] 43 | res.srcDirs = ['res'] 44 | } 45 | } 46 | } 47 | 48 | File propFile = file('signing.properties'); 49 | if (propFile.exists()) { 50 | def Properties props = new Properties() 51 | props.load(new FileInputStream(propFile)) 52 | 53 | if (props.containsKey('STORE_FILE') && props.containsKey('STORE_PASSWORD') && 54 | props.containsKey('KEY_ALIAS') && props.containsKey('KEY_PASSWORD')) { 55 | android.signingConfigs.release.storeFile = file(props['STORE_FILE']) 56 | android.signingConfigs.release.storePassword = props['STORE_PASSWORD'] 57 | android.signingConfigs.release.keyAlias = props['KEY_ALIAS'] 58 | android.signingConfigs.release.keyPassword = props['KEY_PASSWORD'] 59 | } else { 60 | android.buildTypes.release.signingConfig = null 61 | } 62 | } else { 63 | android.buildTypes.release.signingConfig = null 64 | } -------------------------------------------------------------------------------- /samples/stock/res/layout/layout_fragment.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 22 | 23 | 29 | 30 | 34 | 35 | 43 | 44 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /samples/stock/src/uk/co/senab/actionbarpulltorefresh/samples/stock/BaseSampleActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 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 | 17 | package uk.co.senab.actionbarpulltorefresh.samples.stock; 18 | 19 | import android.app.Activity; 20 | import android.app.Fragment; 21 | import android.os.Bundle; 22 | import android.view.Menu; 23 | import android.view.MenuItem; 24 | import android.widget.Toast; 25 | 26 | abstract class BaseSampleActivity extends Activity { 27 | 28 | @Override 29 | protected void onCreate(Bundle savedInstanceState) { 30 | super.onCreate(savedInstanceState); 31 | 32 | // Add the Sample Fragment if there is one 33 | Fragment sampleFragment = getSampleFragment(); 34 | if (sampleFragment != null) { 35 | getFragmentManager().beginTransaction() 36 | .replace(android.R.id.content, sampleFragment).commit(); 37 | } 38 | } 39 | 40 | @Override 41 | public boolean onCreateOptionsMenu(Menu menu) { 42 | getMenuInflater().inflate(R.menu.sample, menu); 43 | return super.onCreateOptionsMenu(menu); 44 | } 45 | 46 | @Override 47 | public boolean onOptionsItemSelected(MenuItem item) { 48 | switch (item.getItemId()) { 49 | case R.id.action_first: 50 | Toast.makeText(this, "First Action Item", Toast.LENGTH_SHORT).show(); 51 | return true; 52 | case R.id.action_second: 53 | Toast.makeText(this, "Second Action Item", Toast.LENGTH_SHORT).show(); 54 | return true; 55 | } 56 | return super.onOptionsItemSelected(item); 57 | } 58 | 59 | protected Fragment getSampleFragment() { 60 | return null; 61 | } 62 | 63 | } -------------------------------------------------------------------------------- /library/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /samples/stock/src/uk/co/senab/actionbarpulltorefresh/samples/stock/ScrollViewActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 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 | 17 | package uk.co.senab.actionbarpulltorefresh.samples.stock; 18 | 19 | import android.os.AsyncTask; 20 | import android.os.Bundle; 21 | import android.view.View; 22 | 23 | import uk.co.senab.actionbarpulltorefresh.library.ActionBarPullToRefresh; 24 | import uk.co.senab.actionbarpulltorefresh.library.listeners.OnRefreshListener; 25 | import uk.co.senab.actionbarpulltorefresh.library.PullToRefreshLayout; 26 | 27 | /** 28 | * This sample shows how to use ActionBar-PullToRefresh with a 29 | * {@link android.widget.ScrollView ScrollView}. 30 | */ 31 | public class ScrollViewActivity extends BaseSampleActivity 32 | implements OnRefreshListener { 33 | 34 | private PullToRefreshLayout mPullToRefreshLayout; 35 | 36 | @Override 37 | public void onCreate(Bundle savedInstanceState) { 38 | super.onCreate(savedInstanceState); 39 | setContentView(R.layout.activity_scrollview); 40 | 41 | // Now find the PullToRefreshLayout and set it up 42 | mPullToRefreshLayout = (PullToRefreshLayout) findViewById(R.id.ptr_layout); 43 | ActionBarPullToRefresh.from(this) 44 | .allChildrenArePullable() 45 | .listener(this) 46 | .setup(mPullToRefreshLayout); 47 | } 48 | 49 | @Override 50 | public void onRefreshStarted(View view) { 51 | /** 52 | * Simulate Refresh with 4 seconds sleep 53 | */ 54 | new AsyncTask() { 55 | 56 | @Override 57 | protected Void doInBackground(Void... params) { 58 | try { 59 | Thread.sleep(Constants.SIMULATED_REFRESH_LENGTH); 60 | } catch (InterruptedException e) { 61 | e.printStackTrace(); 62 | } 63 | return null; 64 | } 65 | 66 | @Override 67 | protected void onPostExecute(Void result) { 68 | super.onPostExecute(result); 69 | 70 | // Notify PullToRefreshLayout that the refresh has finished 71 | mPullToRefreshLayout.setRefreshComplete(); 72 | } 73 | }.execute(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /samples/stock/src/uk/co/senab/actionbarpulltorefresh/samples/stock/WebViewActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 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 | 17 | package uk.co.senab.actionbarpulltorefresh.samples.stock; 18 | 19 | import android.os.Bundle; 20 | import android.view.View; 21 | import android.webkit.WebView; 22 | import android.webkit.WebViewClient; 23 | 24 | import uk.co.senab.actionbarpulltorefresh.library.ActionBarPullToRefresh; 25 | import uk.co.senab.actionbarpulltorefresh.library.listeners.OnRefreshListener; 26 | import uk.co.senab.actionbarpulltorefresh.library.PullToRefreshLayout; 27 | 28 | /** 29 | * This sample shows how to use ActionBar-PullToRefresh with a 30 | * {@link android.webkit.WebView WebView}, and manually creating (and attaching) a 31 | * {@link PullToRefreshLayout} to the view. 32 | */ 33 | public class WebViewActivity extends BaseSampleActivity implements OnRefreshListener { 34 | 35 | private PullToRefreshLayout mPullToRefreshLayout; 36 | 37 | private WebView mWebView; 38 | 39 | @Override 40 | public void onCreate(Bundle savedInstanceState) { 41 | super.onCreate(savedInstanceState); 42 | setContentView(R.layout.activity_webview); 43 | 44 | // Find WebView and get it ready to display pages 45 | mWebView = (WebView) findViewById(R.id.webview); 46 | mWebView.getSettings().setJavaScriptEnabled(true); 47 | mWebView.setWebViewClient(new SampleWebViewClient()); 48 | 49 | // Now find the PullToRefreshLayout and set it up 50 | mPullToRefreshLayout = (PullToRefreshLayout) findViewById(R.id.ptr_layout); 51 | ActionBarPullToRefresh.from(this) 52 | .allChildrenArePullable() 53 | .listener(this) 54 | .setup(mPullToRefreshLayout); 55 | 56 | // Finally make the WebView load something... 57 | mWebView.loadUrl("http://www.google.com"); 58 | } 59 | 60 | @Override 61 | public void onRefreshStarted(View view) { 62 | // Here we just reload the webview 63 | mWebView.reload(); 64 | } 65 | 66 | private class SampleWebViewClient extends WebViewClient { 67 | 68 | @Override 69 | public boolean shouldOverrideUrlLoading(WebView view, String url) { 70 | // Return false so the WebView loads the url 71 | return false; 72 | } 73 | 74 | @Override 75 | public void onPageFinished(WebView view, String url) { 76 | super.onPageFinished(view, url); 77 | 78 | // If the PullToRefreshAttacher is refreshing, make it as complete 79 | if (mPullToRefreshLayout.isRefreshing()) { 80 | mPullToRefreshLayout.setRefreshComplete(); 81 | } 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ActionBar-PullToRefresh 2 | 3 | ![ActionBar-PullToRefresh](https://github.com/chrisbanes/ActionBar-PullToRefresh/raw/master/header.png) 4 | 5 | ActionBar-PullToRefresh provides an easy way to add a modern version of the pull-to-refresh interaction to your application. 6 | 7 | Please note that this is __not__ an update to [Android-PullToRefresh](https://github.com/chrisbanes/Android-PullToRefresh), this has been created from new. You should think of this as Android-PullToRefresh's younger, leaner cousin. 8 | 9 | ### This is a Preview 10 | Please note that this is currently in a preview state. This basically means that the API is not fixed and you should expect changes between releases. 11 | 12 | --- 13 | 14 | ## Sample Apps 15 | 16 | There are two sample applications, the stock sample which uses the standard library and is therefore has a `minSdkVersion` of 14. There is also a sample which uses the ActionBarSherlock extra so has a `minSdkVersion` of 7. 17 | 18 | ### Stock Sample 19 | [![Get it on Google Play](http://www.android.com/images/brand/get_it_on_play_logo_small.png)](http://play.google.com/store/apps/details?id=uk.co.senab.actionbarpulltorefresh.samples.stock) 20 | 21 | ### ActionBarSherlock Sample 22 | [![Get it on Google Play](http://www.android.com/images/brand/get_it_on_play_logo_small.png)](http://play.google.com/store/apps/details?id=uk.co.senab.actionbarpulltorefresh.samples.actionbarsherlock) 23 | 24 | ## Video 25 | 26 | [![Sample Video](http://img.youtube.com/vi/YOYtPF-4RPg/0.jpg)](https://www.youtube.com/watch?v=YOYtPF-4RPg) 27 | 28 | --- 29 | 30 | ## Supported Views 31 | 32 | ActionBar-PullToRefresh has in-built support for: 33 | 34 | * AbsListView derivatives (ListView & GridView). 35 | * ScrollView 36 | * WebView 37 | 38 | If the View you want to use is not listed above, you can easily add support in your own code by providing a `ViewDelegate`. See the `ViewDelegate` section below for more info. 39 | 40 | --- 41 | 42 | ## Usage and Integration 43 | See the Quick Start guides for more information on how to achieve a simple integration: 44 | 45 | * [Quick Start](https://github.com/chrisbanes/ActionBar-PullToRefresh/wiki/QuickStart-Stock) for API v14 and above. 46 | * [Quick Start: ActionBarCompat](https://github.com/chrisbanes/ActionBar-PullToRefresh/wiki/QuickStart-ABC) when using ActionBarCompat (appcompat). 47 | * [Quick Start: ActionBarSherlock](https://github.com/chrisbanes/ActionBar-PullToRefresh/wiki/QuickStart-ABS) when using ActionBarSherlock. 48 | 49 | Then we are some advanced integration information: 50 | 51 | * [ListFragment](https://github.com/chrisbanes/ActionBar-PullToRefresh/wiki/ListFragment) when integrating the library with a ListFragment. 52 | 53 | 54 | ## Customisation 55 | See the [Customisation](https://github.com/chrisbanes/ActionBar-PullToRefresh/wiki/Customisation) page for more information. 56 | 57 | ## License 58 | 59 | Copyright 2013 Chris Banes 60 | 61 | Licensed under the Apache License, Version 2.0 (the "License"); 62 | you may not use this file except in compliance with the License. 63 | You may obtain a copy of the License at 64 | 65 | http://www.apache.org/licenses/LICENSE-2.0 66 | 67 | Unless required by applicable law or agreed to in writing, software 68 | distributed under the License is distributed on an "AS IS" BASIS, 69 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 70 | See the License for the specific language governing permissions and 71 | limitations under the License. 72 | 73 | 74 | ![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/chrisbanes/actionbar-pulltorefresh/trend.png) 75 | -------------------------------------------------------------------------------- /library/src/uk/co/senab/actionbarpulltorefresh/library/viewdelegates/AbsListViewDelegate.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 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 | 17 | package uk.co.senab.actionbarpulltorefresh.library.viewdelegates; 18 | 19 | import android.annotation.TargetApi; 20 | import android.os.Build; 21 | import android.view.View; 22 | import android.widget.AbsListView; 23 | 24 | /** 25 | * FIXME 26 | */ 27 | public class AbsListViewDelegate implements ViewDelegate { 28 | 29 | public static final Class[] SUPPORTED_VIEW_CLASSES = { AbsListView.class }; 30 | 31 | @Override 32 | public boolean isReadyForPull(View view, final float x, final float y) { 33 | boolean ready = false; 34 | 35 | // First we check whether we're scrolled to the top 36 | AbsListView absListView = (AbsListView) view; 37 | if (absListView.getCount() == 0) { 38 | ready = true; 39 | } else if (absListView.getFirstVisiblePosition() == 0) { 40 | final View firstVisibleChild = absListView.getChildAt(0); 41 | ready = firstVisibleChild != null && firstVisibleChild.getTop() >= 0; 42 | } 43 | 44 | // Then we have to check whether the fas scroller is enabled, and check we're not starting 45 | // the gesture from the scroller 46 | if (ready && absListView.isFastScrollEnabled() && isFastScrollAlwaysVisible(absListView)) { 47 | switch (getVerticalScrollbarPosition(absListView)) { 48 | case View.SCROLLBAR_POSITION_RIGHT: 49 | ready = x < absListView.getRight() - absListView.getVerticalScrollbarWidth(); 50 | break; 51 | case View.SCROLLBAR_POSITION_LEFT: 52 | ready = x > absListView.getVerticalScrollbarWidth(); 53 | break; 54 | } 55 | } 56 | 57 | return ready; 58 | } 59 | 60 | int getVerticalScrollbarPosition(AbsListView absListView) { 61 | return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB ? 62 | CompatV11.getVerticalScrollbarPosition(absListView) : 63 | Compat.getVerticalScrollbarPosition(absListView); 64 | } 65 | 66 | boolean isFastScrollAlwaysVisible(AbsListView absListView) { 67 | return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB ? 68 | CompatV11.isFastScrollAlwaysVisible(absListView) : 69 | Compat.isFastScrollAlwaysVisible(absListView); 70 | } 71 | 72 | static class Compat { 73 | static int getVerticalScrollbarPosition(AbsListView absListView) { 74 | return View.SCROLLBAR_POSITION_RIGHT; 75 | } 76 | static boolean isFastScrollAlwaysVisible(AbsListView absListView) { 77 | return false; 78 | } 79 | } 80 | 81 | @TargetApi(Build.VERSION_CODES.HONEYCOMB) 82 | static class CompatV11 { 83 | static int getVerticalScrollbarPosition(AbsListView absListView) { 84 | return absListView.getVerticalScrollbarPosition(); 85 | } 86 | static boolean isFastScrollAlwaysVisible(AbsListView absListView) { 87 | return absListView.isFastScrollAlwaysVisible(); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /library/src/uk/co/senab/actionbarpulltorefresh/library/HeaderTransformer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 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 | 17 | package uk.co.senab.actionbarpulltorefresh.library; 18 | 19 | import android.app.Activity; 20 | import android.content.res.Configuration; 21 | import android.view.View; 22 | 23 | /** 24 | * HeaderTransformers are what controls and update the Header View to reflect the current state 25 | * of the pull-to-refresh interaction. They are responsible for showing and hiding the header 26 | * view, as well as update the state. 27 | */ 28 | public abstract class HeaderTransformer { 29 | 30 | /** 31 | * Called whether the header view has been inflated from the resources 32 | * defined in {@link Options#headerLayout}. 33 | * 34 | * @param activity The {@link android.app.Activity} that the header view is attached to. 35 | * @param headerView The inflated header view. 36 | */ 37 | public void onViewCreated(Activity activity, View headerView) {} 38 | 39 | /** 40 | * Called when the header should be reset. You should update any child 41 | * views to reflect this. 42 | *

43 | * You should not change the visibility of the header 44 | * view. 45 | */ 46 | public void onReset() {} 47 | 48 | /** 49 | * Called the user has pulled on the scrollable view. 50 | * 51 | * @param percentagePulled value between 0.0f and 1.0f depending on how far the 52 | * user has pulled. 53 | */ 54 | public void onPulled(float percentagePulled) {} 55 | 56 | /** 57 | * Called when a refresh has begun. Theoretically this call is similar 58 | * to that provided from {@link uk.co.senab.actionbarpulltorefresh.library.listeners.OnRefreshListener} but is more suitable 59 | * for header view updates. 60 | */ 61 | public void onRefreshStarted() {} 62 | 63 | /** 64 | * Called when a refresh can be initiated when the user ends the touch 65 | * event. This is only called when {@link Options#refreshOnUp} is set to 66 | * true. 67 | */ 68 | public void onReleaseToRefresh() {} 69 | 70 | /** 71 | * Called when a subview hit the top scrolled position 72 | */ 73 | public void onTopScrollChanged(boolean scrolledOnTop) {} 74 | 75 | /** 76 | * Called when the current refresh has taken longer than the time 77 | * specified in {@link Options#refreshMinimizeDelay}. 78 | */ 79 | public void onRefreshMinimized() {} 80 | 81 | /** 82 | * Called when the Header View should be made visible, usually with an animation. 83 | * 84 | * @return true if the visibility has changed. 85 | */ 86 | public abstract boolean showHeaderView(); 87 | 88 | /** 89 | * Called when the Header View should be made invisible, usually with an animation. 90 | * 91 | * @return true if the visibility has changed. 92 | */ 93 | public abstract boolean hideHeaderView(); 94 | 95 | /** 96 | * Called when the Activity's configuration has changed. 97 | * 98 | * @param activity The {@link android.app.Activity} that the header view is attached to. 99 | * @param newConfig New configuration. 100 | * 101 | * @see android.app.Activity#onConfigurationChanged(android.content.res.Configuration) 102 | */ 103 | public void onConfigurationChanged(Activity activity, Configuration newConfig) {} 104 | } 105 | -------------------------------------------------------------------------------- /library/src/uk/co/senab/actionbarpulltorefresh/library/InstanceCreationUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 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 | 17 | package uk.co.senab.actionbarpulltorefresh.library; 18 | 19 | import android.content.Context; 20 | import android.util.Log; 21 | import android.view.View; 22 | 23 | import java.lang.reflect.Constructor; 24 | import java.util.HashMap; 25 | import java.util.Map; 26 | import java.util.Set; 27 | 28 | import uk.co.senab.actionbarpulltorefresh.library.viewdelegates.AbsListViewDelegate; 29 | import uk.co.senab.actionbarpulltorefresh.library.viewdelegates.ScrollYDelegate; 30 | import uk.co.senab.actionbarpulltorefresh.library.viewdelegates.ViewDelegate; 31 | import uk.co.senab.actionbarpulltorefresh.library.viewdelegates.WebViewDelegate; 32 | 33 | class InstanceCreationUtils { 34 | 35 | private static final String LOG_TAG = "InstanceCreationUtils"; 36 | 37 | private static final Class[] VIEW_DELEGATE_CONSTRUCTOR_SIGNATURE = new Class[]{}; 38 | private static final Class[] TRANSFORMER_CONSTRUCTOR_SIGNATURE = new Class[]{}; 39 | 40 | private static final HashMap BUILT_IN_DELEGATES; 41 | static { 42 | BUILT_IN_DELEGATES = new HashMap(); 43 | addBuiltinDelegates(AbsListViewDelegate.SUPPORTED_VIEW_CLASSES, AbsListViewDelegate.class); 44 | addBuiltinDelegates(ScrollYDelegate.SUPPORTED_VIEW_CLASSES, ScrollYDelegate.class); 45 | addBuiltinDelegates(WebViewDelegate.SUPPORTED_VIEW_CLASSES, WebViewDelegate.class); 46 | } 47 | 48 | private static void addBuiltinDelegates(Class[] supportedViews, Class delegateClass) { 49 | for (int i = 0, z = supportedViews.length; i< z ; i++) { 50 | BUILT_IN_DELEGATES.put(supportedViews[i], delegateClass); 51 | } 52 | } 53 | 54 | static ViewDelegate getBuiltInViewDelegate(final View view) { 55 | final Set> entries = BUILT_IN_DELEGATES.entrySet(); 56 | for (final Map.Entry entry : entries) { 57 | if (entry.getKey().isInstance(view)) { 58 | return InstanceCreationUtils.newInstance(view.getContext(), 59 | entry.getValue(), VIEW_DELEGATE_CONSTRUCTOR_SIGNATURE); 60 | } 61 | } 62 | return null; 63 | } 64 | 65 | static T instantiateViewDelegate(Context context, String className) { 66 | try { 67 | Class clazz = context.getClassLoader().loadClass(className); 68 | return newInstance(context, clazz, VIEW_DELEGATE_CONSTRUCTOR_SIGNATURE); 69 | } catch (Exception e) { 70 | Log.w(LOG_TAG, "Cannot instantiate class: " + className, e); 71 | } 72 | return null; 73 | } 74 | 75 | static T instantiateTransformer(Context context, String className) { 76 | try { 77 | Class clazz = context.getClassLoader().loadClass(className); 78 | return newInstance(context, clazz, TRANSFORMER_CONSTRUCTOR_SIGNATURE); 79 | } catch (Exception e) { 80 | Log.w(LOG_TAG, "Cannot instantiate class: " + className, e); 81 | } 82 | return null; 83 | } 84 | 85 | private static T newInstance(Context context, Class clazz, Class[] constructorSig, 86 | Object... arguments) { 87 | try { 88 | Constructor constructor = clazz.getConstructor(constructorSig); 89 | return (T) constructor.newInstance(arguments); 90 | } catch (Exception e) { 91 | Log.w(LOG_TAG, "Cannot instantiate class: " + clazz.getName(), e); 92 | } 93 | return null; 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /samples/stock/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | ActionBarPullToRefresh 20 | ScrollView 21 | ListView 22 | GridView 23 | WebView 24 | Fragments + Tabs 25 | 26 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec 27 | sollicitudin mauris varius lacus porttitor eget blandit massa facilisis. Nulla pellentesque 28 | odio sed purus fermentum vitae viverra orci faucibus. Sed ullamcorper condimentum vulputate. 29 | Curabitur sit amet convallis velit. Vestibulum posuere eleifend risus ac adipiscing. Nam 30 | pulvinar nulla a velit faucibus imperdiet. Praesent eget nisi ac justo blandit sagittis. 31 | Maecenas at leo nisi, nec varius nisl.\n\nIn hac habitasse platea dictumst. Morbi neque 32 | tortor, vestibulum sed viverra a, luctus vel lorem. Nunc turpis eros, varius eget commodo 33 | et, euismod at eros. Sed tincidunt mi purus, vel posuere dui. Vestibulum ante lectus, porta 34 | sed mattis bibendum, scelerisque cursus sapien. Cras ultrices imperdiet fermentum. Aenean 35 | nisi nulla, euismod non blandit ac, dictum quis libero. Morbi consectetur tempor mollis. 36 | Suspendisse eget nunc arcu, vel ullamcorper augue. Integer malesuada, diam nec faucibus 37 | mollis, nisl velit euismod enim, ac mattis justo neque sit amet mauris. Vivamus pretium 38 | imperdiet pharetra.\n\nInteger sagittis augue sit amet lectus pulvinar sit amet commodo tortor 39 | mattis. Maecenas quis tellus eget ante eleifend sollicitudin non et nibh. Maecenas luctus 40 | euismod tristique. Fusce in odio nec diam blandit facilisis. Sed nec arcu eros. Vivamus quis 41 | tortor a metus tempus aliquam eget volutpat magna. Pellentesque id ultrices dolor. Sed 42 | blandit aliquet quam. Phasellus dapibus euismod vulputate. Aenean blandit, elit vitae 43 | vestibulum tincidunt, metus dui accumsan nulla, sit amet vehicula mauris lacus in est. Etiam 44 | dignissim pellentesque nulla vel malesuada. Cras vel lorem justo.\n\nSed condimentum nisl sit 45 | amet libero vestibulum hendrerit. Duis auctor tempus placerat. Proin velit ante, ornare nec 46 | dictum nec, hendrerit eu arcu. Etiam ut diam ornare quam venenatis pulvinar vitae vel leo. 47 | Vivamus consectetur, ante id interdum rhoncus, magna eros pulvinar lacus, a gravida nibh 48 | arcu vitae eros. Nulla scelerisque laoreet feugiat. Mauris sit amet gravida felis.\n\nNulla ac 49 | dolor sapien, vestibulum venenatis justo. Cras placerat velit vitae nibh pellentesque 50 | ultricies. Suspendisse adipiscing enim eu justo iaculis eu pretium urna fermentum. Duis 51 | porttitor nunc non nunc mattis vestibulum. Etiam elit tellus, feugiat in bibendum eget, 52 | adipiscing nec metus. Ut ut sem lacus, quis faucibus diam. Curabitur a nulla fermentum 53 | tortor dignissim posuere. Fusce faucibus ante ut sem imperdiet imperdiet eget vitae lorem. 54 | Etiam fringilla ornare ipsum, in sagittis quam ornare vitae. Nullam venenatis orci sit amet 55 | sapien adipiscing gravida. Proin turpis lectus, hendrerit vitae vehicula ut, auctor ac 56 | lectus. Pellentesque sollicitudin blandit ligula quis commodo. Mauris vulputate lectus in 57 | velit luctus aliquam. Quisque eget tincidunt elit. Quisque et augue quam, sed scelerisque 58 | eros. 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /samples/stock/src/uk/co/senab/actionbarpulltorefresh/samples/stock/MainActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 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 | 17 | package uk.co.senab.actionbarpulltorefresh.samples.stock; 18 | 19 | import android.app.ListActivity; 20 | import android.content.ComponentName; 21 | import android.content.Context; 22 | import android.content.Intent; 23 | import android.content.pm.ActivityInfo; 24 | import android.content.pm.PackageInfo; 25 | import android.content.pm.PackageManager; 26 | import android.content.pm.PackageManager.NameNotFoundException; 27 | import android.os.Bundle; 28 | import android.text.TextUtils; 29 | import android.view.LayoutInflater; 30 | import android.view.View; 31 | import android.view.ViewGroup; 32 | import android.widget.BaseAdapter; 33 | import android.widget.ListAdapter; 34 | import android.widget.ListView; 35 | import android.widget.TextView; 36 | 37 | import java.util.ArrayList; 38 | 39 | public class MainActivity extends ListActivity { 40 | 41 | @Override 42 | protected void onCreate(Bundle savedInstanceState) { 43 | super.onCreate(savedInstanceState); 44 | setListAdapter(getSampleAdapter()); 45 | } 46 | 47 | @Override 48 | protected void onListItemClick(ListView l, View v, int position, long id) { 49 | ActivityInfo info = (ActivityInfo) l.getItemAtPosition(position); 50 | Intent intent = new Intent(); 51 | intent.setComponent(new ComponentName(this, info.name)); 52 | startActivity(intent); 53 | } 54 | 55 | private ListAdapter getSampleAdapter() { 56 | ArrayList items = new ArrayList(); 57 | final String thisClazzName = getClass().getName(); 58 | 59 | try { 60 | PackageInfo pInfo = getPackageManager().getPackageInfo(getPackageName(), 61 | PackageManager.GET_ACTIVITIES); 62 | ActivityInfo[] aInfos = pInfo.activities; 63 | 64 | for (ActivityInfo aInfo : aInfos) { 65 | if (!thisClazzName.equals(aInfo.name)) { 66 | items.add(aInfo); 67 | } 68 | } 69 | } catch (NameNotFoundException e) { 70 | e.printStackTrace(); 71 | } 72 | 73 | return new SampleAdapter(this, items); 74 | } 75 | 76 | private static class SampleAdapter extends BaseAdapter { 77 | 78 | private final ArrayList mItems; 79 | 80 | private final LayoutInflater mInflater; 81 | 82 | public SampleAdapter(Context context, ArrayList activities) { 83 | mItems = activities; 84 | mInflater = LayoutInflater.from(context); 85 | } 86 | 87 | @Override 88 | public int getCount() { 89 | return mItems.size(); 90 | } 91 | 92 | @Override 93 | public ActivityInfo getItem(int position) { 94 | return mItems.get(position); 95 | } 96 | 97 | @Override 98 | public long getItemId(int position) { 99 | return position; 100 | } 101 | 102 | @Override 103 | public View getView(int position, View convertView, ViewGroup parent) { 104 | TextView tv = (TextView) convertView; 105 | if (tv == null) { 106 | tv = (TextView) mInflater.inflate(android.R.layout.simple_list_item_1, parent, 107 | false); 108 | } 109 | ActivityInfo item = getItem(position); 110 | if (!TextUtils.isEmpty(item.nonLocalizedLabel)) { 111 | tv.setText(item.nonLocalizedLabel); 112 | } else { 113 | tv.setText(item.labelRes); 114 | } 115 | return tv; 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /samples/stock/src/uk/co/senab/actionbarpulltorefresh/samples/stock/ListViewActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 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 | 17 | package uk.co.senab.actionbarpulltorefresh.samples.stock; 18 | 19 | import android.app.Fragment; 20 | import android.app.ListFragment; 21 | import android.os.AsyncTask; 22 | import android.os.Bundle; 23 | import android.view.View; 24 | import android.view.ViewGroup; 25 | import android.widget.ArrayAdapter; 26 | 27 | import uk.co.senab.actionbarpulltorefresh.library.ActionBarPullToRefresh; 28 | import uk.co.senab.actionbarpulltorefresh.library.listeners.OnRefreshListener; 29 | import uk.co.senab.actionbarpulltorefresh.library.PullToRefreshLayout; 30 | 31 | /** 32 | * This sample shows how to use ActionBar-PullToRefresh with a 33 | * {@link android.widget.ListView ListView}, and manually creating (and attaching) a 34 | * {@link PullToRefreshLayout} to the view. 35 | */ 36 | public class ListViewActivity extends BaseSampleActivity { 37 | 38 | @Override 39 | protected Fragment getSampleFragment() { 40 | return new SampleListFragment(); 41 | } 42 | 43 | /** 44 | * Fragment Class 45 | */ 46 | public static class SampleListFragment extends ListFragment implements 47 | OnRefreshListener { 48 | 49 | private static String[] ITEMS = {"Abbaye de Belloc", "Abbaye du Mont des Cats", "Abertam", 50 | "Abondance", "Ackawi", "Acorn", "Adelost", "Affidelice au Chablis", "Afuega'l Pitu", 51 | "Airag", "Airedale", "Aisy Cendre", "Allgauer Emmentaler", "Abbaye de Belloc", 52 | "Abbaye du Mont des Cats", "Abertam", "Abondance", "Ackawi", "Acorn", "Adelost", 53 | "Affidelice au Chablis", "Afuega'l Pitu", "Airag", "Airedale", "Aisy Cendre", 54 | "Allgauer Emmentaler"}; 55 | 56 | private PullToRefreshLayout mPullToRefreshLayout; 57 | 58 | @Override 59 | public void onViewCreated(View view, Bundle savedInstanceState) { 60 | super.onViewCreated(view,savedInstanceState); 61 | ViewGroup viewGroup = (ViewGroup) view; 62 | 63 | // As we're using a ListFragment we create a PullToRefreshLayout manually 64 | mPullToRefreshLayout = new PullToRefreshLayout(viewGroup.getContext()); 65 | 66 | // We can now setup the PullToRefreshLayout 67 | ActionBarPullToRefresh.from(getActivity()) 68 | // We need to insert the PullToRefreshLayout into the Fragment's ViewGroup 69 | .insertLayoutInto(viewGroup) 70 | // Here we mark just the ListView and it's Empty View as pullable 71 | .theseChildrenArePullable(android.R.id.list, android.R.id.empty) 72 | .listener(this) 73 | .setup(mPullToRefreshLayout); 74 | } 75 | 76 | @Override 77 | public void onActivityCreated(Bundle savedInstanceState) { 78 | super.onActivityCreated(savedInstanceState); 79 | 80 | // Set the List Adapter to display the sample items 81 | setListAdapter(new ArrayAdapter(getActivity(), 82 | android.R.layout.simple_list_item_1, ITEMS)); 83 | setListShownNoAnimation(true); 84 | } 85 | 86 | @Override 87 | public void onRefreshStarted(View view) { 88 | // Hide the list 89 | setListShown(false); 90 | 91 | /** 92 | * Simulate Refresh with 4 seconds sleep 93 | */ 94 | new AsyncTask() { 95 | 96 | @Override 97 | protected Void doInBackground(Void... params) { 98 | try { 99 | Thread.sleep(Constants.SIMULATED_REFRESH_LENGTH); 100 | } catch (InterruptedException e) { 101 | e.printStackTrace(); 102 | } 103 | return null; 104 | } 105 | 106 | @Override 107 | protected void onPostExecute(Void result) { 108 | super.onPostExecute(result); 109 | 110 | // Notify PullToRefreshLayout that the refresh has finished 111 | mPullToRefreshLayout.setRefreshComplete(); 112 | 113 | if (getView() != null) { 114 | // Show the list again 115 | setListShown(true); 116 | } 117 | } 118 | }.execute(); 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /samples/stock/src/uk/co/senab/actionbarpulltorefresh/samples/stock/FragmentTabsActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 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 | 17 | package uk.co.senab.actionbarpulltorefresh.samples.stock; 18 | 19 | import android.app.ActionBar; 20 | import android.app.Fragment; 21 | import android.app.FragmentTransaction; 22 | import android.os.AsyncTask; 23 | import android.os.Bundle; 24 | import android.view.LayoutInflater; 25 | import android.view.View; 26 | import android.view.ViewGroup; 27 | import android.widget.TextView; 28 | 29 | import uk.co.senab.actionbarpulltorefresh.library.ActionBarPullToRefresh; 30 | import uk.co.senab.actionbarpulltorefresh.library.listeners.OnRefreshListener; 31 | import uk.co.senab.actionbarpulltorefresh.library.PullToRefreshLayout; 32 | 33 | /** 34 | * A sample which show you how to use {@link PullToRefreshLayout} with Fragments. 35 | */ 36 | public class FragmentTabsActivity extends BaseSampleActivity implements ActionBar.TabListener { 37 | private static String EXTRA_TITLE = "extra_title"; 38 | 39 | @Override 40 | protected void onCreate(Bundle savedInstanceState) { 41 | super.onCreate(savedInstanceState); 42 | 43 | setContentView(R.layout.activity_fragment_tabs); 44 | 45 | // Add 3 tabs which will switch fragments 46 | ActionBar ab = getActionBar(); 47 | ab.addTab(ab.newTab().setText("Tab 1").setTabListener(this)); 48 | ab.addTab(ab.newTab().setText("Tab 2").setTabListener(this)); 49 | ab.addTab(ab.newTab().setText("Tab 3").setTabListener(this)); 50 | ab.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); 51 | } 52 | 53 | // From TabListener 54 | @Override 55 | public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) { 56 | // Create Fragment 57 | SampleFragment fragment = new SampleFragment(); 58 | 59 | // Set title for display purposes 60 | Bundle b = new Bundle(); 61 | b.putString(EXTRA_TITLE, tab.getText().toString()); 62 | fragment.setArguments(b); 63 | 64 | ft.replace(R.id.ptr_fragment, fragment); 65 | } 66 | 67 | // From TabListener 68 | @Override 69 | public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) { 70 | } 71 | 72 | // From TabListener 73 | @Override 74 | public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) { 75 | } 76 | 77 | /** 78 | * Fragment Class 79 | */ 80 | public static class SampleFragment extends Fragment implements 81 | OnRefreshListener { 82 | private PullToRefreshLayout mPullToRefreshLayout; 83 | 84 | @Override 85 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 86 | Bundle savedInstanceState) { 87 | // Inflate the layout 88 | View view = inflater.inflate(R.layout.layout_fragment, container, false); 89 | 90 | // Now give the find the PullToRefreshLayout and set it up 91 | mPullToRefreshLayout = (PullToRefreshLayout) view.findViewById(R.id.ptr_layout); 92 | ActionBarPullToRefresh.from(getActivity()) 93 | .allChildrenArePullable() 94 | .listener(this) 95 | .setup(mPullToRefreshLayout); 96 | 97 | // Set title in Fragment for display purposes. 98 | TextView title = (TextView) view.findViewById(R.id.tv_title); 99 | Bundle b = getArguments(); 100 | if (b != null) { 101 | title.setText(b.getString(EXTRA_TITLE)); 102 | } 103 | 104 | return view; 105 | } 106 | 107 | @Override 108 | public void onRefreshStarted(View view) { 109 | /** 110 | * Simulate Refresh with 4 seconds sleep 111 | */ 112 | new AsyncTask() { 113 | 114 | @Override 115 | protected Void doInBackground(Void... params) { 116 | try { 117 | Thread.sleep(Constants.SIMULATED_REFRESH_LENGTH); 118 | } catch (InterruptedException e) { 119 | e.printStackTrace(); 120 | } 121 | return null; 122 | } 123 | 124 | @Override 125 | protected void onPostExecute(Void result) { 126 | super.onPostExecute(result); 127 | 128 | // Notify PullToRefreshLayout that the refresh has finished 129 | mPullToRefreshLayout.setRefreshComplete(); 130 | } 131 | }.execute(); 132 | } 133 | } 134 | } 135 | 136 | -------------------------------------------------------------------------------- /library/src/uk/co/senab/actionbarpulltorefresh/library/Options.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 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 | 17 | package uk.co.senab.actionbarpulltorefresh.library; 18 | 19 | /** 20 | * Allows you to specify a number of configuration options when setting up a {@link PullToRefreshLayout}. 21 | */ 22 | public final class Options { 23 | 24 | /* Default configuration values */ 25 | private static final int DEFAULT_HEADER_LAYOUT = R.layout.default_header; 26 | private static final float DEFAULT_REFRESH_SCROLL_DISTANCE = 0.5f; 27 | private static final boolean DEFAULT_REFRESH_ON_UP = false; 28 | private static final int DEFAULT_REFRESH_MINIMIZED_DELAY = 1 * 1000; 29 | private static final boolean DEFAULT_REFRESH_MINIMIZE = true; 30 | 31 | public static Builder create() { 32 | return new Builder(); 33 | } 34 | 35 | Options() {} 36 | 37 | EnvironmentDelegate environmentDelegate = null; 38 | int headerLayout = DEFAULT_HEADER_LAYOUT; 39 | HeaderTransformer headerTransformer = null; 40 | float refreshScrollDistance = DEFAULT_REFRESH_SCROLL_DISTANCE; 41 | boolean refreshOnUp = DEFAULT_REFRESH_ON_UP; 42 | int refreshMinimizeDelay = DEFAULT_REFRESH_MINIMIZED_DELAY; 43 | 44 | /** 45 | * Enable or disable the header 'minimization', which by default means that the majority of 46 | * the header is hidden, leaving only the progress bar still showing. 47 | *

48 | * If set to true, the header will be minimized after the delay set in 49 | * {@link #refreshMinimizeDelay}. If set to false then the whole header will be displayed 50 | * until the refresh is finished. 51 | */ 52 | boolean refreshMinimize = DEFAULT_REFRESH_MINIMIZE; 53 | 54 | public static class Builder { 55 | final Options mOptions = new Options(); 56 | 57 | /** 58 | * EnvironmentDelegate instance which will be used. If null, we will 59 | * create an instance of the default class. 60 | */ 61 | public Builder environmentDelegate(EnvironmentDelegate environmentDelegate) { 62 | mOptions.environmentDelegate = environmentDelegate; 63 | return this; 64 | } 65 | 66 | /** 67 | * The layout resource ID which should be inflated to be displayed above 68 | * the Action Bar 69 | */ 70 | public Builder headerLayout(int headerLayoutId) { 71 | mOptions.headerLayout = headerLayoutId; 72 | return this; 73 | } 74 | 75 | /** 76 | * The header transformer to be used to transfer the header view. If 77 | * null, an instance of {@link DefaultHeaderTransformer} will be used. 78 | */ 79 | public Builder headerTransformer(HeaderTransformer headerTransformer) { 80 | mOptions.headerTransformer = headerTransformer; 81 | return this; 82 | } 83 | 84 | /** 85 | * The percentage of the refreshable view that needs to be scrolled 86 | * before a refresh is initiated. 87 | */ 88 | public Builder scrollDistance(float refreshScrollDistance) { 89 | mOptions.refreshScrollDistance = refreshScrollDistance; 90 | return this; 91 | } 92 | 93 | /** 94 | * Whether a refresh should only be initiated when the user has finished 95 | * the touch event. 96 | */ 97 | public Builder refreshOnUp(boolean enabled) { 98 | mOptions.refreshOnUp = enabled; 99 | return this; 100 | } 101 | 102 | /** 103 | * Disable the header 'minimization', which by default means that the majority of 104 | * the header is hidden, leaving only the progress bar still showing. 105 | */ 106 | public Builder noMinimize() { 107 | mOptions.refreshMinimize = false; 108 | return this; 109 | } 110 | 111 | /** 112 | * Enable header 'minimization', which by default means that the majority of 113 | * the header is hidden, leaving only the progress bar still showing. 114 | */ 115 | public Builder minimize() { 116 | return minimize(DEFAULT_REFRESH_MINIMIZED_DELAY); 117 | } 118 | 119 | /** 120 | * Enable header 'minimization' and set the delay. 121 | */ 122 | public Builder minimize(int delay) { 123 | mOptions.refreshMinimizeDelay = delay; 124 | mOptions.refreshMinimize = true; 125 | return this; 126 | } 127 | 128 | /** 129 | * @return the built {@link Options} instance. 130 | */ 131 | public Options build() { 132 | return mOptions; 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /library/src/uk/co/senab/actionbarpulltorefresh/library/ActionBarPullToRefresh.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 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 | 17 | package uk.co.senab.actionbarpulltorefresh.library; 18 | 19 | 20 | import android.app.Activity; 21 | import android.view.View; 22 | import android.view.ViewGroup; 23 | 24 | import java.util.HashMap; 25 | import java.util.Map; 26 | import java.util.Set; 27 | 28 | import uk.co.senab.actionbarpulltorefresh.library.listeners.OnRefreshListener; 29 | import uk.co.senab.actionbarpulltorefresh.library.viewdelegates.ViewDelegate; 30 | 31 | public class ActionBarPullToRefresh { 32 | 33 | public static SetupWizard from(Activity activity) { 34 | return new SetupWizard(activity); 35 | } 36 | 37 | public static final class SetupWizard { 38 | private final Activity mActivity; 39 | private Options mOptions; 40 | private int[] refreshableViewIds; 41 | private View[] refreshableViews; 42 | private OnRefreshListener mOnRefreshListener; 43 | private ViewGroup mViewGroupToInsertInto; 44 | private HashMap mViewDelegates; 45 | 46 | private SetupWizard(Activity activity) { 47 | mActivity = activity; 48 | } 49 | 50 | public SetupWizard options(Options options) { 51 | mOptions = options; 52 | return this; 53 | } 54 | 55 | public SetupWizard allChildrenArePullable() { 56 | refreshableViewIds = null; 57 | refreshableViews = null; 58 | return this; 59 | } 60 | 61 | public SetupWizard theseChildrenArePullable(int... viewIds) { 62 | refreshableViewIds = viewIds; 63 | refreshableViews = null; 64 | return this; 65 | } 66 | 67 | public SetupWizard theseChildrenArePullable(View... views) { 68 | refreshableViews = views; 69 | refreshableViewIds = null; 70 | return this; 71 | } 72 | 73 | public SetupWizard useViewDelegate(Class viewClass, ViewDelegate delegate) { 74 | if (mViewDelegates == null) { 75 | mViewDelegates = new HashMap(); 76 | } 77 | mViewDelegates.put(viewClass, delegate); 78 | return this; 79 | } 80 | 81 | public SetupWizard listener(OnRefreshListener listener) { 82 | mOnRefreshListener = listener; 83 | return this; 84 | } 85 | 86 | public SetupWizard insertLayoutInto(ViewGroup viewGroup) { 87 | mViewGroupToInsertInto = viewGroup; 88 | return this; 89 | } 90 | 91 | public void setup(PullToRefreshLayout pullToRefreshLayout) { 92 | PullToRefreshAttacher attacher = pullToRefreshLayout.createPullToRefreshAttacher( 93 | mActivity, mOptions); 94 | attacher.setOnRefreshListener(mOnRefreshListener); 95 | 96 | if (mViewGroupToInsertInto != null) { 97 | insertLayoutIntoViewGroup(mViewGroupToInsertInto, pullToRefreshLayout); 98 | } 99 | 100 | pullToRefreshLayout.setPullToRefreshAttacher(attacher); 101 | 102 | // First add the pullable child views 103 | if (refreshableViewIds != null) { 104 | pullToRefreshLayout.addChildrenAsPullable(refreshableViewIds); 105 | } else if (refreshableViews != null) { 106 | pullToRefreshLayout.addChildrenAsPullable(refreshableViews); 107 | } else { 108 | pullToRefreshLayout.addAllChildrenAsPullable(); 109 | } 110 | 111 | // Now set any custom view delegates 112 | if (mViewDelegates != null) { 113 | final Set> entries = mViewDelegates.entrySet(); 114 | for (final Map.Entry entry : entries) { 115 | attacher.useViewDelegate(entry.getKey(), entry.getValue()); 116 | } 117 | } 118 | } 119 | 120 | private static void insertLayoutIntoViewGroup(ViewGroup viewGroup, 121 | PullToRefreshLayout pullToRefreshLayout) { 122 | // Move all children to PullToRefreshLayout. This code looks a bit silly but the child 123 | // indices change every time we remove a View (so we can't just iterate through) 124 | View child = viewGroup.getChildAt(0); 125 | while (child != null) { 126 | viewGroup.removeViewAt(0); 127 | pullToRefreshLayout.addView(child); 128 | child = viewGroup.getChildAt(0); 129 | } 130 | 131 | viewGroup.addView(pullToRefreshLayout, ViewGroup.LayoutParams.MATCH_PARENT, 132 | ViewGroup.LayoutParams.MATCH_PARENT); 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /samples/stock/src/uk/co/senab/actionbarpulltorefresh/samples/stock/GridViewActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 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 | 17 | package uk.co.senab.actionbarpulltorefresh.samples.stock; 18 | 19 | import android.app.Activity; 20 | import android.os.AsyncTask; 21 | import android.os.Bundle; 22 | import android.view.View; 23 | import android.widget.AbsListView; 24 | import android.widget.ArrayAdapter; 25 | import android.widget.GridView; 26 | import android.widget.ListAdapter; 27 | import android.widget.TextView; 28 | 29 | import uk.co.senab.actionbarpulltorefresh.library.ActionBarPullToRefresh; 30 | import uk.co.senab.actionbarpulltorefresh.library.HeaderTransformer; 31 | import uk.co.senab.actionbarpulltorefresh.library.Options; 32 | import uk.co.senab.actionbarpulltorefresh.library.listeners.OnRefreshListener; 33 | import uk.co.senab.actionbarpulltorefresh.library.PullToRefreshLayout; 34 | import uk.co.senab.actionbarpulltorefresh.library.viewdelegates.AbsListViewDelegate; 35 | 36 | /** 37 | * This sample shows how to use ActionBar-PullToRefresh with a {@link android.widget.GridView 38 | * GridView}, and manually creating (and attaching) a {@link PullToRefreshLayout} to the view. 39 | */ 40 | public class GridViewActivity extends BaseSampleActivity 41 | implements OnRefreshListener { 42 | 43 | private static String[] ITEMS = {"Abbaye de Belloc", "Abbaye du Mont des Cats", "Abertam", 44 | "Abondance", "Ackawi", "Acorn", "Adelost", "Affidelice au Chablis", "Afuega'l Pitu", 45 | "Airag", "Airedale", "Aisy Cendre", "Allgauer Emmentaler", "Abbaye de Belloc", 46 | "Abbaye du Mont des Cats", "Abertam", "Abondance", "Ackawi", "Acorn", "Adelost", 47 | "Affidelice au Chablis", "Afuega'l Pitu", "Airag", "Airedale", "Aisy Cendre", 48 | "Allgauer Emmentaler"}; 49 | 50 | private PullToRefreshLayout mPullToRefreshLayout; 51 | 52 | @Override 53 | public void onCreate(Bundle savedInstanceState) { 54 | super.onCreate(savedInstanceState); 55 | setContentView(R.layout.activity_gridview); 56 | 57 | GridView gridView = (GridView) findViewById(R.id.ptr_gridview); 58 | ListAdapter adapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, 59 | ITEMS); 60 | gridView.setAdapter(adapter); 61 | 62 | // Now find the PullToRefreshLayout and set it up 63 | mPullToRefreshLayout = (PullToRefreshLayout) findViewById(R.id.ptr_layout); 64 | ActionBarPullToRefresh.from(this) 65 | .options(Options.create() 66 | // Here we make the refresh scroll distance to 75% of the GridView height 67 | .scrollDistance(.75f) 68 | // Here we define a custom header layout which will be inflated and used 69 | .headerLayout(R.layout.customised_header) 70 | // Here we define a custom header transformer which will alter the header 71 | // based on the current pull-to-refresh state 72 | .headerTransformer(new CustomisedHeaderTransformer()) 73 | .build()) 74 | .allChildrenArePullable() 75 | .listener(this) 76 | // Here we'll set a custom ViewDelegate 77 | .useViewDelegate(GridView.class, new AbsListViewDelegate()) 78 | .setup(mPullToRefreshLayout); 79 | } 80 | 81 | @Override 82 | public void onRefreshStarted(View view) { 83 | /** 84 | * Simulate Refresh with 4 seconds sleep 85 | */ 86 | new AsyncTask() { 87 | 88 | @Override 89 | protected Void doInBackground(Void... params) { 90 | try { 91 | Thread.sleep(Constants.SIMULATED_REFRESH_LENGTH); 92 | } catch (InterruptedException e) { 93 | e.printStackTrace(); 94 | } 95 | return null; 96 | } 97 | 98 | @Override 99 | protected void onPostExecute(Void result) { 100 | super.onPostExecute(result); 101 | 102 | // Notify PullToRefreshLayout that the refresh has finished 103 | mPullToRefreshLayout.setRefreshComplete(); 104 | } 105 | }.execute(); 106 | } 107 | 108 | /** 109 | * Here's a customised header transformer which displays the scroll progress as text. 110 | */ 111 | static class CustomisedHeaderTransformer extends HeaderTransformer { 112 | 113 | private View mHeaderView; 114 | private TextView mMainTextView; 115 | private TextView mProgressTextView; 116 | 117 | @Override 118 | public void onViewCreated(Activity activity, View headerView) { 119 | mHeaderView = headerView; 120 | mMainTextView = (TextView) headerView.findViewById(R.id.ptr_text); 121 | mProgressTextView = (TextView) headerView.findViewById(R.id.ptr_text_secondary); 122 | } 123 | 124 | @Override 125 | public void onReset() { 126 | mMainTextView.setVisibility(View.VISIBLE); 127 | mMainTextView.setText(R.string.pull_to_refresh_pull_label); 128 | 129 | mProgressTextView.setVisibility(View.GONE); 130 | mProgressTextView.setText(""); 131 | } 132 | 133 | @Override 134 | public void onPulled(float percentagePulled) { 135 | mProgressTextView.setVisibility(View.VISIBLE); 136 | mProgressTextView.setText(Math.round(100f * percentagePulled) + "%"); 137 | } 138 | 139 | @Override 140 | public void onRefreshStarted() { 141 | mMainTextView.setText(R.string.pull_to_refresh_refreshing_label); 142 | mProgressTextView.setVisibility(View.GONE); 143 | } 144 | 145 | @Override 146 | public void onReleaseToRefresh() { 147 | mMainTextView.setText(R.string.pull_to_refresh_release_label); 148 | } 149 | 150 | @Override 151 | public void onRefreshMinimized() { 152 | // In this header transformer, we will ignore this call 153 | } 154 | 155 | @Override 156 | public boolean showHeaderView() { 157 | final boolean changeVis = mHeaderView.getVisibility() != View.VISIBLE; 158 | if (changeVis) { 159 | mHeaderView.setVisibility(View.VISIBLE); 160 | } 161 | return changeVis; 162 | } 163 | 164 | @Override 165 | public boolean hideHeaderView() { 166 | final boolean changeVis = mHeaderView.getVisibility() == View.VISIBLE; 167 | if (changeVis) { 168 | mHeaderView.setVisibility(View.GONE); 169 | } 170 | return changeVis; 171 | } 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /library/src/uk/co/senab/actionbarpulltorefresh/library/PullToRefreshLayout.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 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 | 17 | package uk.co.senab.actionbarpulltorefresh.library; 18 | 19 | import android.app.Activity; 20 | import android.content.Context; 21 | import android.content.res.Configuration; 22 | import android.content.res.TypedArray; 23 | import android.text.TextUtils; 24 | import android.util.AttributeSet; 25 | import android.util.Log; 26 | import android.view.MotionEvent; 27 | import android.view.View; 28 | import android.widget.FrameLayout; 29 | 30 | import uk.co.senab.actionbarpulltorefresh.library.listeners.HeaderViewListener; 31 | import uk.co.senab.actionbarpulltorefresh.library.viewdelegates.ViewDelegate; 32 | 33 | /** 34 | * The main component of the library. You wrap the views you wish to be 'pullable' within this layout. 35 | * This layout is setup by using the {@link ActionBarPullToRefresh} setup-wizard return by 36 | * @link ActionBarPullToRefresh#from(android.app.Activity)}. 37 | */ 38 | public class PullToRefreshLayout extends FrameLayout { 39 | 40 | private static final boolean DEBUG = false; 41 | private static final String LOG_TAG = "PullToRefreshLayout"; 42 | 43 | private PullToRefreshAttacher mPullToRefreshAttacher; 44 | 45 | public PullToRefreshLayout(Context context) { 46 | this(context, null); 47 | } 48 | 49 | public PullToRefreshLayout(Context context, AttributeSet attrs) { 50 | this(context, attrs, 0); 51 | } 52 | 53 | public PullToRefreshLayout(Context context, AttributeSet attrs, int defStyle) { 54 | super(context, attrs, defStyle); 55 | } 56 | 57 | /** 58 | * Manually set this Attacher's refreshing state. The header will be 59 | * displayed or hidden as requested. 60 | * 61 | * @param refreshing 62 | * - Whether the attacher should be in a refreshing state, 63 | */ 64 | public final void setRefreshing(boolean refreshing) { 65 | ensureAttacher(); 66 | mPullToRefreshAttacher.setRefreshing(refreshing); 67 | } 68 | 69 | /** 70 | * @return true if this Attacher is currently in a refreshing state. 71 | */ 72 | public final boolean isRefreshing() { 73 | ensureAttacher(); 74 | return mPullToRefreshAttacher.isRefreshing(); 75 | } 76 | 77 | /** 78 | * Call this when your refresh is complete and this view should reset itself 79 | * (header view will be hidden). 80 | * 81 | * This is the equivalent of calling setRefreshing(false). 82 | */ 83 | public final void setRefreshComplete() { 84 | ensureAttacher(); 85 | mPullToRefreshAttacher.setRefreshComplete(); 86 | } 87 | 88 | /** 89 | * Set a {@link uk.co.senab.actionbarpulltorefresh.library.listeners.HeaderViewListener} which is called when the visibility 90 | * state of the Header View has changed. 91 | * 92 | * @param listener 93 | */ 94 | public final void setHeaderViewListener(HeaderViewListener listener) { 95 | ensureAttacher(); 96 | mPullToRefreshAttacher.setHeaderViewListener(listener); 97 | } 98 | 99 | /** 100 | * @return The Header View which is displayed when the user is pulling, or 101 | * we are refreshing. 102 | */ 103 | public final View getHeaderView() { 104 | ensureAttacher(); 105 | return mPullToRefreshAttacher.getHeaderView(); 106 | } 107 | 108 | /** 109 | * @return The Attacher 110 | */ 111 | public final PullToRefreshAttacher getAttacher() { 112 | ensureAttacher(); 113 | return mPullToRefreshAttacher; 114 | } 115 | 116 | /** 117 | * @return The HeaderTransformer currently used by this Attacher. 118 | */ 119 | public HeaderTransformer getHeaderTransformer() { 120 | ensureAttacher(); 121 | return mPullToRefreshAttacher.getHeaderTransformer(); 122 | } 123 | 124 | @Override 125 | public final boolean onInterceptTouchEvent(MotionEvent event) { 126 | if (DEBUG) { 127 | Log.d(LOG_TAG, "onInterceptTouchEvent. " + event.toString()); 128 | } 129 | if (isEnabled() && mPullToRefreshAttacher != null && getChildCount() > 0) { 130 | return mPullToRefreshAttacher.onInterceptTouchEvent(event); 131 | } 132 | return false; 133 | } 134 | 135 | @Override 136 | public final boolean onTouchEvent(MotionEvent event) { 137 | if (DEBUG) { 138 | Log.d(LOG_TAG, "onTouchEvent. " + event.toString()); 139 | } 140 | if (isEnabled() && mPullToRefreshAttacher != null) { 141 | return mPullToRefreshAttacher.onTouchEvent(event); 142 | } 143 | return super.onTouchEvent(event); 144 | } 145 | 146 | @Override 147 | public FrameLayout.LayoutParams generateLayoutParams(AttributeSet attrs) { 148 | return new PullToRefreshLayout.LayoutParams(getContext(), attrs); 149 | } 150 | 151 | @Override 152 | protected void onDetachedFromWindow() { 153 | // Destroy the PullToRefreshAttacher 154 | if (mPullToRefreshAttacher != null) { 155 | mPullToRefreshAttacher.destroy(); 156 | } 157 | super.onDetachedFromWindow(); 158 | } 159 | 160 | @Override 161 | protected void onConfigurationChanged(Configuration newConfig) { 162 | if (mPullToRefreshAttacher != null) { 163 | mPullToRefreshAttacher.onConfigurationChanged(newConfig); 164 | } 165 | super.onConfigurationChanged(newConfig); 166 | } 167 | 168 | void setPullToRefreshAttacher(PullToRefreshAttacher attacher) { 169 | if (mPullToRefreshAttacher != null) { 170 | mPullToRefreshAttacher.destroy(); 171 | } 172 | mPullToRefreshAttacher = attacher; 173 | } 174 | 175 | void addAllChildrenAsPullable() { 176 | ensureAttacher(); 177 | for (int i = 0, z = getChildCount(); i < z; i++) { 178 | addRefreshableView(getChildAt(i)); 179 | } 180 | } 181 | 182 | void addChildrenAsPullable(int[] viewIds) { 183 | for (int i = 0, z = viewIds.length; i < z; i++) { 184 | View view = findViewById(viewIds[i]); 185 | if (view != null) { 186 | addRefreshableView(findViewById(viewIds[i])); 187 | } 188 | } 189 | } 190 | 191 | void addChildrenAsPullable(View[] views) { 192 | for (int i = 0, z = views.length; i < z; i++) { 193 | if (views[i] != null) { 194 | addRefreshableView(views[i]); 195 | } 196 | } 197 | } 198 | 199 | void addRefreshableView(View view) { 200 | if (mPullToRefreshAttacher != null) { 201 | mPullToRefreshAttacher.addRefreshableView(view, getViewDelegateFromLayoutParams(view)); 202 | } 203 | } 204 | 205 | ViewDelegate getViewDelegateFromLayoutParams(View view) { 206 | if (view != null && view.getLayoutParams() instanceof LayoutParams) { 207 | LayoutParams lp = (LayoutParams) view.getLayoutParams(); 208 | String clazzName = lp.getViewDelegateClassName(); 209 | 210 | if (!TextUtils.isEmpty(clazzName)) { 211 | // Lets convert any relative class names (i.e. .XYZViewDelegate) 212 | final int firstDot = clazzName.indexOf('.'); 213 | if (firstDot == -1) { 214 | clazzName = getContext().getPackageName() + "." + clazzName; 215 | } else if (firstDot == 0) { 216 | clazzName = getContext().getPackageName() + clazzName; 217 | } 218 | return InstanceCreationUtils.instantiateViewDelegate(getContext(), clazzName); 219 | } 220 | } 221 | return null; 222 | } 223 | 224 | protected PullToRefreshAttacher createPullToRefreshAttacher(Activity activity, 225 | Options options) { 226 | return new PullToRefreshAttacher(activity, options != null ? options : new Options()); 227 | } 228 | 229 | private void ensureAttacher() { 230 | if (mPullToRefreshAttacher == null) { 231 | throw new IllegalStateException("You need to setup the PullToRefreshLayout before using it"); 232 | } 233 | } 234 | 235 | static class LayoutParams extends FrameLayout.LayoutParams { 236 | private final String mViewDelegateClassName; 237 | 238 | LayoutParams(Context c, AttributeSet attrs) { 239 | super(c, attrs); 240 | 241 | TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.PullToRefreshView); 242 | mViewDelegateClassName = a 243 | .getString(R.styleable.PullToRefreshView_ptrViewDelegateClass); 244 | a.recycle(); 245 | } 246 | 247 | String getViewDelegateClassName() { 248 | return mViewDelegateClassName; 249 | } 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /library/src/uk/co/senab/actionbarpulltorefresh/library/DefaultHeaderTransformer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 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 | 17 | package uk.co.senab.actionbarpulltorefresh.library; 18 | 19 | import android.animation.Animator; 20 | import android.animation.AnimatorListenerAdapter; 21 | import android.animation.AnimatorSet; 22 | import android.animation.ObjectAnimator; 23 | import android.app.Activity; 24 | import android.content.Context; 25 | import android.content.res.Configuration; 26 | import android.content.res.TypedArray; 27 | import android.graphics.PixelFormat; 28 | import android.graphics.drawable.ClipDrawable; 29 | import android.graphics.drawable.Drawable; 30 | import android.graphics.drawable.ShapeDrawable; 31 | import android.graphics.drawable.shapes.RectShape; 32 | import android.os.Build; 33 | import android.util.TypedValue; 34 | import android.view.Gravity; 35 | import android.view.View; 36 | import android.view.ViewGroup; 37 | import android.view.animation.AccelerateInterpolator; 38 | import android.view.animation.Interpolator; 39 | import android.widget.RelativeLayout; 40 | import android.widget.TextView; 41 | 42 | import fr.castorflex.android.smoothprogressbar.SmoothProgressBar; 43 | import fr.castorflex.android.smoothprogressbar.SmoothProgressDrawable; 44 | import uk.co.senab.actionbarpulltorefresh.library.sdk.Compat; 45 | 46 | /** 47 | * Default Header Transformer. 48 | */ 49 | public class DefaultHeaderTransformer extends HeaderTransformer { 50 | 51 | public static final int PROGRESS_BAR_STYLE_INSIDE = 0; 52 | public static final int PROGRESS_BAR_STYLE_OUTSIDE = 1; 53 | 54 | private View mHeaderView; 55 | private ViewGroup mContentLayout; 56 | private TextView mHeaderTextView; 57 | private SmoothProgressBar mHeaderProgressBar; 58 | 59 | private CharSequence mPullRefreshLabel, mRefreshingLabel, mReleaseLabel; 60 | 61 | private int mProgressDrawableColor; 62 | 63 | private long mAnimationDuration; 64 | private int mProgressBarStyle; 65 | private int mProgressBarHeight = RelativeLayout.LayoutParams.WRAP_CONTENT; 66 | 67 | private final Interpolator mInterpolator = new AccelerateInterpolator(); 68 | 69 | protected DefaultHeaderTransformer() { 70 | final int min = getMinimumApiLevel(); 71 | if (Build.VERSION.SDK_INT < min) { 72 | throw new IllegalStateException("This HeaderTransformer is designed to run on SDK " 73 | + min 74 | + "+. If using ActionBarSherlock or ActionBarCompat you should use the appropriate provided extra."); 75 | } 76 | } 77 | 78 | @Override 79 | public void onViewCreated(Activity activity, View headerView) { 80 | mHeaderView = headerView; 81 | 82 | // Get ProgressBar and TextView 83 | mHeaderProgressBar = (SmoothProgressBar) headerView.findViewById(R.id.ptr_progress); 84 | mHeaderTextView = (TextView) headerView.findViewById(R.id.ptr_text); 85 | mContentLayout = (ViewGroup) headerView.findViewById(R.id.ptr_content); 86 | 87 | // Default Labels to display 88 | mPullRefreshLabel = activity.getString(R.string.pull_to_refresh_pull_label); 89 | mRefreshingLabel = activity.getString(R.string.pull_to_refresh_refreshing_label); 90 | mReleaseLabel = activity.getString(R.string.pull_to_refresh_release_label); 91 | 92 | mAnimationDuration = activity.getResources() 93 | .getInteger(android.R.integer.config_shortAnimTime); 94 | 95 | mProgressDrawableColor = activity.getResources() 96 | .getColor(R.color.default_progress_bar_color); 97 | 98 | // Setup the View styles 99 | setupViewsFromStyles(activity, headerView); 100 | 101 | applyProgressBarStyle(); 102 | 103 | // Apply any custom ProgressBar colors and corner radius 104 | applyProgressBarSettings(); 105 | 106 | // FIXME: I do not like this call here 107 | onReset(); 108 | } 109 | 110 | @Override 111 | public void onConfigurationChanged(Activity activity, Configuration newConfig) { 112 | setupViewsFromStyles(activity, getHeaderView()); 113 | } 114 | 115 | @Override 116 | public void onReset() { 117 | // Reset Progress Bar 118 | if (mHeaderProgressBar != null) { 119 | mHeaderProgressBar.setVisibility(View.VISIBLE); 120 | mHeaderProgressBar.setProgress(0); 121 | mHeaderProgressBar.setIndeterminate(false); 122 | } 123 | 124 | // Reset Text View 125 | if (mHeaderTextView != null) { 126 | mHeaderTextView.setVisibility(View.VISIBLE); 127 | mHeaderTextView.setText(mPullRefreshLabel); 128 | } 129 | 130 | // Reset the Content Layout 131 | if (mContentLayout != null) { 132 | mContentLayout.setVisibility(View.VISIBLE); 133 | Compat.setAlpha(mContentLayout, 1f); 134 | } 135 | } 136 | 137 | @Override 138 | public void onPulled(float percentagePulled) { 139 | if (mHeaderProgressBar != null) { 140 | mHeaderProgressBar.setVisibility(View.VISIBLE); 141 | final float progress = mInterpolator.getInterpolation(percentagePulled); 142 | mHeaderProgressBar.setProgress(Math.round(mHeaderProgressBar.getMax() * progress)); 143 | } 144 | } 145 | 146 | @Override 147 | public void onRefreshStarted() { 148 | if (mHeaderTextView != null) { 149 | mHeaderTextView.setText(mRefreshingLabel); 150 | } 151 | if (mHeaderProgressBar != null) { 152 | mHeaderProgressBar.setVisibility(View.VISIBLE); 153 | mHeaderProgressBar.setIndeterminate(true); 154 | } 155 | } 156 | 157 | @Override 158 | public void onReleaseToRefresh() { 159 | if (mHeaderTextView != null) { 160 | mHeaderTextView.setText(mReleaseLabel); 161 | } 162 | if (mHeaderProgressBar != null) { 163 | mHeaderProgressBar.setProgress(mHeaderProgressBar.getMax()); 164 | } 165 | } 166 | 167 | @Override 168 | public void onRefreshMinimized() { 169 | // Here we fade out most of the header, leaving just the progress bar 170 | if (mContentLayout != null) { 171 | ObjectAnimator.ofFloat(mContentLayout, "alpha", 1f, 0f).start(); 172 | } 173 | } 174 | 175 | public View getHeaderView() { 176 | return mHeaderView; 177 | } 178 | 179 | @Override 180 | public boolean showHeaderView() { 181 | final boolean changeVis = mHeaderView.getVisibility() != View.VISIBLE; 182 | 183 | if (changeVis) { 184 | mHeaderView.setVisibility(View.VISIBLE); 185 | AnimatorSet animSet = new AnimatorSet(); 186 | ObjectAnimator transAnim = ObjectAnimator.ofFloat(mContentLayout, "translationY", 187 | -mContentLayout.getHeight(), 0f); 188 | ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(mHeaderView, "alpha", 0f, 1f); 189 | animSet.playTogether(transAnim, alphaAnim); 190 | animSet.setDuration(mAnimationDuration); 191 | animSet.start(); 192 | } 193 | 194 | return changeVis; 195 | } 196 | 197 | @Override 198 | public boolean hideHeaderView() { 199 | final boolean changeVis = mHeaderView.getVisibility() != View.GONE; 200 | 201 | if (changeVis) { 202 | Animator animator; 203 | if (mContentLayout.getAlpha() >= 0.5f) { 204 | // If the content layout is showing, translate and fade out 205 | animator = new AnimatorSet(); 206 | ObjectAnimator transAnim = ObjectAnimator.ofFloat(mContentLayout, "translationY", 207 | 0f, -mContentLayout.getHeight()); 208 | ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(mHeaderView, "alpha", 1f, 0f); 209 | ((AnimatorSet) animator).playTogether(transAnim, alphaAnim); 210 | } else { 211 | // If the content layout isn't showing (minimized), just fade out 212 | animator = ObjectAnimator.ofFloat(mHeaderView, "alpha", 1f, 0f); 213 | } 214 | animator.setDuration(mAnimationDuration); 215 | animator.addListener(new HideAnimationCallback()); 216 | animator.start(); 217 | } 218 | 219 | return changeVis; 220 | } 221 | 222 | /** 223 | * Set color to apply to the progress bar. 224 | *

225 | * The best way to apply a color is to load the color from resources: {@code 226 | * setProgressBarColor(getResources().getColor(R.color.your_color_name))}. 227 | * 228 | * @param color The color to use. 229 | */ 230 | public void setProgressBarColor(int color) { 231 | if (color != mProgressDrawableColor) { 232 | mProgressDrawableColor = color; 233 | applyProgressBarSettings(); 234 | } 235 | } 236 | 237 | /** 238 | * Set the progress bar style. {@code style} must be one of {@link #PROGRESS_BAR_STYLE_OUTSIDE} 239 | * or {@link #PROGRESS_BAR_STYLE_INSIDE}. 240 | */ 241 | public void setProgressBarStyle(int style) { 242 | if (mProgressBarStyle != style) { 243 | mProgressBarStyle = style; 244 | applyProgressBarStyle(); 245 | } 246 | } 247 | 248 | /** 249 | * Set the progress bar height. 250 | */ 251 | public void setProgressBarHeight(int height) { 252 | if (mProgressBarHeight != height) { 253 | mProgressBarHeight = height; 254 | applyProgressBarStyle(); 255 | } 256 | } 257 | 258 | /** 259 | * Set Text to show to prompt the user is pull (or keep pulling). 260 | * 261 | * @param pullText - Text to display. 262 | */ 263 | public void setPullText(CharSequence pullText) { 264 | mPullRefreshLabel = pullText; 265 | if (mHeaderTextView != null) { 266 | mHeaderTextView.setText(mPullRefreshLabel); 267 | } 268 | } 269 | 270 | /** 271 | * Set Text to show to tell the user that a refresh is currently in progress. 272 | * 273 | * @param refreshingText - Text to display. 274 | */ 275 | public void setRefreshingText(CharSequence refreshingText) { 276 | mRefreshingLabel = refreshingText; 277 | } 278 | 279 | /** 280 | * Set Text to show to tell the user has scrolled enough to refresh. 281 | * 282 | * @param releaseText - Text to display. 283 | */ 284 | public void setReleaseText(CharSequence releaseText) { 285 | mReleaseLabel = releaseText; 286 | } 287 | 288 | private void setupViewsFromStyles(Activity activity, View headerView) { 289 | final TypedArray styleAttrs = obtainStyledAttrsFromThemeAttr(activity, 290 | R.attr.ptrHeaderStyle, R.styleable.PullToRefreshHeader); 291 | 292 | // Retrieve the Action Bar size from the app theme or the Action Bar's style 293 | if (mContentLayout != null) { 294 | final int height = styleAttrs.getDimensionPixelSize( 295 | R.styleable.PullToRefreshHeader_ptrHeaderHeight, getActionBarSize(activity)); 296 | mContentLayout.getLayoutParams().height = height; 297 | mContentLayout.requestLayout(); 298 | } 299 | 300 | // Retrieve the Action Bar background from the app theme or the Action Bar's style (see #93) 301 | Drawable bg = styleAttrs.hasValue(R.styleable.PullToRefreshHeader_ptrHeaderBackground) 302 | ? styleAttrs.getDrawable(R.styleable.PullToRefreshHeader_ptrHeaderBackground) 303 | : getActionBarBackground(activity); 304 | if (bg != null) { 305 | mHeaderTextView.setBackgroundDrawable(bg); 306 | 307 | // If we have an opaque background we can remove the background from the content layout 308 | if (mContentLayout != null && bg.getOpacity() == PixelFormat.OPAQUE) { 309 | mContentLayout.setBackgroundResource(0); 310 | } 311 | } 312 | 313 | // Retrieve the Progress Bar Color the style 314 | if (styleAttrs.hasValue(R.styleable.PullToRefreshHeader_ptrProgressBarColor)) { 315 | mProgressDrawableColor = styleAttrs.getColor( 316 | R.styleable.PullToRefreshHeader_ptrProgressBarColor, mProgressDrawableColor); 317 | } 318 | 319 | mProgressBarStyle = styleAttrs.getInt( 320 | R.styleable.PullToRefreshHeader_ptrProgressBarStyle, PROGRESS_BAR_STYLE_OUTSIDE); 321 | 322 | if (styleAttrs.hasValue(R.styleable.PullToRefreshHeader_ptrProgressBarHeight)) { 323 | mProgressBarHeight = styleAttrs.getDimensionPixelSize( 324 | R.styleable.PullToRefreshHeader_ptrProgressBarHeight, mProgressBarHeight); 325 | } 326 | 327 | // Retrieve the text strings from the style (if they're set) 328 | if (styleAttrs.hasValue(R.styleable.PullToRefreshHeader_ptrPullText)) { 329 | mPullRefreshLabel = styleAttrs.getString(R.styleable.PullToRefreshHeader_ptrPullText); 330 | } 331 | if (styleAttrs.hasValue(R.styleable.PullToRefreshHeader_ptrRefreshingText)) { 332 | mRefreshingLabel = styleAttrs 333 | .getString(R.styleable.PullToRefreshHeader_ptrRefreshingText); 334 | } 335 | if (styleAttrs.hasValue(R.styleable.PullToRefreshHeader_ptrReleaseText)) { 336 | mReleaseLabel = styleAttrs.getString(R.styleable.PullToRefreshHeader_ptrReleaseText); 337 | } 338 | 339 | styleAttrs.recycle(); 340 | } 341 | 342 | private void applyProgressBarStyle() { 343 | RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams( 344 | RelativeLayout.LayoutParams.MATCH_PARENT, mProgressBarHeight); 345 | 346 | switch (mProgressBarStyle) { 347 | case PROGRESS_BAR_STYLE_INSIDE: 348 | lp.addRule(RelativeLayout.ALIGN_BOTTOM, R.id.ptr_content); 349 | break; 350 | case PROGRESS_BAR_STYLE_OUTSIDE: 351 | lp.addRule(RelativeLayout.BELOW, R.id.ptr_content); 352 | break; 353 | } 354 | 355 | mHeaderProgressBar.setLayoutParams(lp); 356 | } 357 | 358 | private void applyProgressBarSettings() { 359 | if (mHeaderProgressBar != null) { 360 | final int strokeWidth = mHeaderProgressBar.getResources() 361 | .getDimensionPixelSize(R.dimen.ptr_progress_bar_stroke_width); 362 | 363 | mHeaderProgressBar.setIndeterminateDrawable( 364 | new SmoothProgressDrawable.Builder(mHeaderProgressBar.getContext()) 365 | .color(mProgressDrawableColor) 366 | .strokeWidth(mProgressBarHeight) 367 | .build()); 368 | 369 | ShapeDrawable shape = new ShapeDrawable(); 370 | shape.setShape(new RectShape()); 371 | shape.getPaint().setColor(mProgressDrawableColor); 372 | ClipDrawable clipDrawable = new ClipDrawable(shape, Gravity.CENTER, ClipDrawable.HORIZONTAL); 373 | 374 | mHeaderProgressBar.setProgressDrawable(clipDrawable); 375 | } 376 | } 377 | 378 | protected Drawable getActionBarBackground(Context context) { 379 | int[] android_styleable_ActionBar = {android.R.attr.background}; 380 | 381 | // Now get the action bar style values... 382 | TypedArray abStyle = obtainStyledAttrsFromThemeAttr(context, android.R.attr.actionBarStyle, 383 | android_styleable_ActionBar); 384 | try { 385 | // background is the first attr in the array above so it's index is 0. 386 | return abStyle.getDrawable(0); 387 | } finally { 388 | abStyle.recycle(); 389 | } 390 | } 391 | 392 | protected int getActionBarSize(Context context) { 393 | int[] attrs = {android.R.attr.actionBarSize}; 394 | TypedArray values = context.getTheme().obtainStyledAttributes(attrs); 395 | try { 396 | return values.getDimensionPixelSize(0, 0); 397 | } finally { 398 | values.recycle(); 399 | } 400 | } 401 | 402 | protected int getActionBarTitleStyle(Context context) { 403 | int[] android_styleable_ActionBar = {android.R.attr.titleTextStyle}; 404 | 405 | // Now get the action bar style values... 406 | TypedArray abStyle = obtainStyledAttrsFromThemeAttr(context, android.R.attr.actionBarStyle, 407 | android_styleable_ActionBar); 408 | try { 409 | // titleTextStyle is the first attr in the array above so it's index is 0. 410 | return abStyle.getResourceId(0, 0); 411 | } finally { 412 | abStyle.recycle(); 413 | } 414 | } 415 | 416 | protected int getMinimumApiLevel() { 417 | return Build.VERSION_CODES.ICE_CREAM_SANDWICH; 418 | } 419 | 420 | class HideAnimationCallback extends AnimatorListenerAdapter { 421 | @Override 422 | public void onAnimationEnd(Animator animation) { 423 | View headerView = getHeaderView(); 424 | if (headerView != null) { 425 | headerView.setVisibility(View.GONE); 426 | } 427 | onReset(); 428 | } 429 | } 430 | 431 | protected static TypedArray obtainStyledAttrsFromThemeAttr(Context context, int themeAttr, 432 | int[] styleAttrs) { 433 | // Need to get resource id of style pointed to from the theme attr 434 | TypedValue outValue = new TypedValue(); 435 | context.getTheme().resolveAttribute(themeAttr, outValue, true); 436 | final int styleResId = outValue.resourceId; 437 | 438 | // Now return the values (from styleAttrs) from the style 439 | return context.obtainStyledAttributes(styleResId, styleAttrs); 440 | } 441 | } 442 | -------------------------------------------------------------------------------- /library/src/uk/co/senab/actionbarpulltorefresh/library/PullToRefreshAttacher.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 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 | 17 | package uk.co.senab.actionbarpulltorefresh.library; 18 | 19 | import android.annotation.TargetApi; 20 | import android.app.ActionBar; 21 | import android.app.Activity; 22 | import android.content.Context; 23 | import android.content.res.Configuration; 24 | import android.graphics.PixelFormat; 25 | import android.graphics.Rect; 26 | import android.os.Build; 27 | import android.util.Log; 28 | import android.view.Gravity; 29 | import android.view.LayoutInflater; 30 | import android.view.MotionEvent; 31 | import android.view.View; 32 | import android.view.ViewConfiguration; 33 | import android.view.ViewGroup; 34 | import android.view.WindowManager; 35 | import android.widget.AbsListView; 36 | 37 | import java.util.WeakHashMap; 38 | 39 | import uk.co.senab.actionbarpulltorefresh.library.listeners.HeaderViewListener; 40 | import uk.co.senab.actionbarpulltorefresh.library.listeners.OnRefreshListener; 41 | import uk.co.senab.actionbarpulltorefresh.library.viewdelegates.ViewDelegate; 42 | 43 | @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) 44 | public class PullToRefreshAttacher { 45 | 46 | private static final boolean DEBUG = false; 47 | private static final String LOG_TAG = "PullToRefreshAttacher"; 48 | 49 | /* Member Variables */ 50 | 51 | private EnvironmentDelegate mEnvironmentDelegate; 52 | private HeaderTransformer mHeaderTransformer; 53 | 54 | private OnRefreshListener mOnRefreshListener; 55 | 56 | private Activity mActivity; 57 | private View mHeaderView; 58 | private HeaderViewListener mHeaderViewListener; 59 | 60 | private final int mTouchSlop; 61 | private final float mRefreshScrollDistance; 62 | 63 | private float mInitialMotionY, mLastMotionY, mPullBeginY; 64 | private float mInitialMotionX; 65 | private boolean mIsBeingDragged, mIsRefreshing, mHandlingTouchEventFromDown; 66 | private View mViewBeingDragged; 67 | 68 | private final WeakHashMap mRefreshableViews; 69 | 70 | private final boolean mRefreshOnUp; 71 | private final int mRefreshMinimizeDelay; 72 | private final boolean mRefreshMinimize; 73 | private boolean mIsDestroyed = false; 74 | 75 | private final int[] mViewLocationResult = new int[2]; 76 | private final Rect mRect = new Rect(); 77 | private boolean mSubviewScrolledOnTop; 78 | 79 | private final AddHeaderViewRunnable mAddHeaderViewRunnable; 80 | 81 | private boolean mRefreshDisabled; 82 | 83 | protected PullToRefreshAttacher(Activity activity, Options options) { 84 | if (activity == null) { 85 | throw new IllegalArgumentException("activity cannot be null"); 86 | } 87 | if (options == null) { 88 | Log.i(LOG_TAG, "Given null options so using default options."); 89 | options = new Options(); 90 | } 91 | 92 | mActivity = activity; 93 | mRefreshableViews = new WeakHashMap(); 94 | 95 | // Copy necessary values from options 96 | mRefreshScrollDistance = options.refreshScrollDistance; 97 | mRefreshOnUp = options.refreshOnUp; 98 | mRefreshMinimizeDelay = options.refreshMinimizeDelay; 99 | mRefreshMinimize = options.refreshMinimize; 100 | 101 | // EnvironmentDelegate 102 | mEnvironmentDelegate = options.environmentDelegate != null 103 | ? options.environmentDelegate 104 | : createDefaultEnvironmentDelegate(); 105 | 106 | // Header Transformer 107 | mHeaderTransformer = options.headerTransformer != null 108 | ? options.headerTransformer 109 | : createDefaultHeaderTransformer(); 110 | 111 | // Get touch slop for use later 112 | mTouchSlop = ViewConfiguration.get(activity).getScaledTouchSlop(); 113 | 114 | // Get Window Decor View 115 | final ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView(); 116 | 117 | // Create Header view and then add to Decor View 118 | mHeaderView = LayoutInflater.from( 119 | mEnvironmentDelegate.getContextForInflater(activity)).inflate( 120 | options.headerLayout, decorView, false); 121 | if (mHeaderView == null) { 122 | throw new IllegalArgumentException("Must supply valid layout id for header."); 123 | } 124 | // Make Header View invisible so it still gets a layout pass 125 | mHeaderView.setVisibility(View.INVISIBLE); 126 | 127 | // Notify transformer 128 | mHeaderTransformer.onViewCreated(activity, mHeaderView); 129 | 130 | // Now HeaderView to Activity 131 | mAddHeaderViewRunnable = new AddHeaderViewRunnable(); 132 | mAddHeaderViewRunnable.start(); 133 | } 134 | 135 | /** 136 | * Add a view which will be used to initiate refresh requests. 137 | * 138 | * @param view View which will be used to initiate refresh requests. 139 | */ 140 | void addRefreshableView(View view, ViewDelegate viewDelegate) { 141 | if (isDestroyed()) return; 142 | 143 | // Check to see if view is null 144 | if (view == null) { 145 | Log.i(LOG_TAG, "Refreshable View is null."); 146 | return; 147 | } 148 | 149 | // ViewDelegate 150 | if (viewDelegate == null) { 151 | viewDelegate = InstanceCreationUtils.getBuiltInViewDelegate(view); 152 | } 153 | 154 | // View to detect refreshes for 155 | mRefreshableViews.put(view, viewDelegate); 156 | setOnScrollListener(view); 157 | } 158 | 159 | void useViewDelegate(Class viewClass, ViewDelegate delegate) { 160 | for (View view : mRefreshableViews.keySet()) { 161 | if (viewClass.isInstance(view)) { 162 | mRefreshableViews.put(view, delegate); 163 | setOnScrollListener(view); 164 | } 165 | } 166 | } 167 | 168 | /** 169 | * Set on scroll listener to detect scroll events 170 | */ 171 | void setOnScrollListener(View view) { 172 | if (view instanceof AbsListView) { 173 | ((AbsListView) view).setOnScrollListener(new AbsListView.OnScrollListener() { 174 | @Override 175 | public void onScrollStateChanged(AbsListView v, int scrollState) { 176 | } 177 | 178 | @Override 179 | public void onScroll(AbsListView v, int firstVisibleItem, int visibleItemCount, int totalItemCount) { 180 | View childView = v.getChildAt(0); 181 | int offset = 0; 182 | if (childView != null) { 183 | offset = childView.getTop(); 184 | } 185 | boolean oldSubviewScrolledOnTop = mSubviewScrolledOnTop; 186 | mSubviewScrolledOnTop = firstVisibleItem == 0 && Math.abs(offset) < 10; 187 | if (mSubviewScrolledOnTop != oldSubviewScrolledOnTop) { 188 | mHeaderTransformer.onTopScrollChanged(mSubviewScrolledOnTop); 189 | } 190 | } 191 | }); 192 | } 193 | } 194 | 195 | public void setRefreshDisabled(boolean disabled) { 196 | mRefreshDisabled = disabled; 197 | } 198 | 199 | /** 200 | * Clear all views which were previously used to initiate refresh requests. 201 | */ 202 | void clearRefreshableViews() { 203 | mRefreshableViews.clear(); 204 | } 205 | 206 | /** 207 | * This method should be called by your Activity's or Fragment's 208 | * onConfigurationChanged method. 209 | * 210 | * @param newConfig The new configuration 211 | */ 212 | public void onConfigurationChanged(Configuration newConfig) { 213 | mHeaderTransformer.onConfigurationChanged(mActivity, newConfig); 214 | } 215 | 216 | /** 217 | * Manually set this Attacher's refreshing state. The header will be 218 | * displayed or hidden as requested. 219 | * 220 | * @param refreshing 221 | * - Whether the attacher should be in a refreshing state, 222 | */ 223 | final void setRefreshing(boolean refreshing) { 224 | setRefreshingInt(null, refreshing, false); 225 | } 226 | 227 | /** 228 | * @return true if this Attacher is currently in a refreshing state. 229 | */ 230 | final boolean isRefreshing() { 231 | return mIsRefreshing; 232 | } 233 | 234 | /** 235 | * Call this when your refresh is complete and this view should reset itself 236 | * (header view will be hidden). 237 | * 238 | * This is the equivalent of calling setRefreshing(false). 239 | */ 240 | final void setRefreshComplete() { 241 | setRefreshingInt(null, false, false); 242 | } 243 | 244 | /** 245 | * Set the Listener to be called when a refresh is initiated. 246 | */ 247 | void setOnRefreshListener(OnRefreshListener listener) { 248 | mOnRefreshListener = listener; 249 | } 250 | 251 | void destroy() { 252 | if (mIsDestroyed) return; // We've already been destroyed 253 | 254 | // Remove the Header View from the Activity 255 | removeHeaderViewFromActivity(mHeaderView); 256 | 257 | // Lets clear out all of our internal state 258 | clearRefreshableViews(); 259 | 260 | mActivity = null; 261 | mHeaderView = null; 262 | mHeaderViewListener = null; 263 | mEnvironmentDelegate = null; 264 | mHeaderTransformer = null; 265 | 266 | mIsDestroyed = true; 267 | } 268 | 269 | /** 270 | * Set a {@link HeaderViewListener} which is called when the visibility 271 | * state of the Header View has changed. 272 | */ 273 | final void setHeaderViewListener(HeaderViewListener listener) { 274 | mHeaderViewListener = listener; 275 | } 276 | 277 | /** 278 | * @return The Header View which is displayed when the user is pulling, or 279 | * we are refreshing. 280 | */ 281 | final View getHeaderView() { 282 | return mHeaderView; 283 | } 284 | 285 | /** 286 | * @return The HeaderTransformer currently used by this Attacher. 287 | */ 288 | HeaderTransformer getHeaderTransformer() { 289 | return mHeaderTransformer; 290 | } 291 | 292 | final boolean onInterceptTouchEvent(MotionEvent event) { 293 | if (DEBUG) { 294 | Log.d(LOG_TAG, "onInterceptTouchEvent: " + event.toString()); 295 | } 296 | 297 | // If we're not enabled or currently refreshing don't handle any touch 298 | // events 299 | if (isRefreshing()) { 300 | return false; 301 | } 302 | 303 | final float x = event.getX(), y = event.getY(); 304 | 305 | switch (event.getAction()) { 306 | case MotionEvent.ACTION_MOVE: { 307 | // We're not currently being dragged so check to see if the user has 308 | // scrolled enough 309 | if (!mIsBeingDragged && mInitialMotionY > 0f) { 310 | final float yDiff = y - mInitialMotionY; 311 | final float xDiff = x - mInitialMotionX; 312 | 313 | if (yDiff > xDiff && yDiff > mTouchSlop) { 314 | mIsBeingDragged = true; 315 | onPullStarted(y); 316 | } else if (yDiff < -mTouchSlop) { 317 | resetTouch(); 318 | } 319 | } 320 | break; 321 | } 322 | 323 | case MotionEvent.ACTION_DOWN: { 324 | // If we're already refreshing, ignore 325 | if (canRefresh(true)) { 326 | for (View view : mRefreshableViews.keySet()) { 327 | if (isViewBeingDragged(view, event)) { 328 | mInitialMotionX = x; 329 | mInitialMotionY = y; 330 | mViewBeingDragged = view; 331 | } 332 | } 333 | } 334 | break; 335 | } 336 | 337 | case MotionEvent.ACTION_CANCEL: 338 | case MotionEvent.ACTION_UP: { 339 | resetTouch(); 340 | break; 341 | } 342 | } 343 | 344 | if (DEBUG) Log.d(LOG_TAG, "onInterceptTouchEvent. Returning " + mIsBeingDragged); 345 | 346 | return mIsBeingDragged; 347 | } 348 | 349 | final boolean isViewBeingDragged(View view, MotionEvent event) { 350 | if (view.isShown() && mRefreshableViews.containsKey(view)) { 351 | // First we need to set the rect to the view's screen co-ordinates 352 | view.getLocationOnScreen(mViewLocationResult); 353 | final int viewLeft = mViewLocationResult[0], viewTop = mViewLocationResult[1]; 354 | mRect.set(viewLeft, viewTop, viewLeft + view.getWidth(), viewTop + view.getHeight()); 355 | 356 | if (DEBUG) Log.d(LOG_TAG, "isViewBeingDragged. View Rect: " + mRect.toString()); 357 | 358 | final int rawX = (int) event.getRawX(), rawY = (int) event.getRawY(); 359 | if (mRect.contains(rawX, rawY)) { 360 | // The Touch Event is within the View's display Rect 361 | ViewDelegate delegate = mRefreshableViews.get(view); 362 | if (delegate != null) { 363 | // Now call the delegate, converting the X/Y into the View's co-ordinate system 364 | return delegate.isReadyForPull(view, rawX - mRect.left, rawY - mRect.top); 365 | } 366 | } 367 | } 368 | return false; 369 | } 370 | 371 | final boolean onTouchEvent(MotionEvent event) { 372 | if (DEBUG) { 373 | Log.d(LOG_TAG, "onTouchEvent: " + event.toString()); 374 | } 375 | 376 | // Record whether our handling is started from ACTION_DOWN 377 | if (event.getAction() == MotionEvent.ACTION_DOWN) { 378 | mHandlingTouchEventFromDown = true; 379 | } 380 | 381 | // If we're being called from ACTION_DOWN then we must call through to 382 | // onInterceptTouchEvent until it sets mIsBeingDragged 383 | if (mHandlingTouchEventFromDown && !mIsBeingDragged) { 384 | onInterceptTouchEvent(event); 385 | return true; 386 | } 387 | 388 | if (mViewBeingDragged == null) { 389 | return false; 390 | } 391 | 392 | switch (event.getAction()) { 393 | case MotionEvent.ACTION_MOVE: { 394 | // If we're already refreshing ignore it 395 | if (isRefreshing()) { 396 | return false; 397 | } 398 | final float y = event.getY(); 399 | 400 | if (mIsBeingDragged && y != mLastMotionY) { 401 | final float yDx = y - mLastMotionY; 402 | /** 403 | * Check to see if the user is scrolling the right direction 404 | * (down). We allow a small scroll up which is the check against 405 | * negative touch slop. 406 | */ 407 | if (yDx >= -mTouchSlop) { 408 | onPull(mViewBeingDragged, y); 409 | // Only record the y motion if the user has scrolled down. 410 | if (yDx > 0f) { 411 | mLastMotionY = y; 412 | } 413 | } else { 414 | onPullEnded(); 415 | resetTouch(); 416 | } 417 | } 418 | break; 419 | } 420 | 421 | case MotionEvent.ACTION_CANCEL: 422 | case MotionEvent.ACTION_UP: { 423 | checkScrollForRefresh(mViewBeingDragged); 424 | if (mIsBeingDragged) { 425 | onPullEnded(); 426 | } 427 | resetTouch(); 428 | break; 429 | } 430 | } 431 | 432 | return true; 433 | } 434 | 435 | void minimizeHeader() { 436 | if (isDestroyed()) return; 437 | 438 | mHeaderTransformer.onRefreshMinimized(); 439 | 440 | if (mHeaderViewListener != null) { 441 | mHeaderViewListener.onStateChanged(mHeaderView, HeaderViewListener.STATE_MINIMIZED); 442 | } 443 | } 444 | 445 | void resetTouch() { 446 | mIsBeingDragged = false; 447 | mHandlingTouchEventFromDown = false; 448 | mInitialMotionY = mLastMotionY = mPullBeginY = -1f; 449 | } 450 | 451 | void onPullStarted(float y) { 452 | if (DEBUG) { 453 | Log.d(LOG_TAG, "onPullStarted"); 454 | } 455 | showHeaderView(); 456 | mPullBeginY = y; 457 | } 458 | 459 | void onPull(View view, float y) { 460 | if (DEBUG) { 461 | Log.d(LOG_TAG, "onPull"); 462 | } 463 | 464 | if (mRefreshDisabled) { 465 | return ; 466 | } 467 | 468 | final float pxScrollForRefresh = getScrollNeededForRefresh(view); 469 | final float scrollLength = y - mPullBeginY; 470 | 471 | if (scrollLength < pxScrollForRefresh) { 472 | mHeaderTransformer.onPulled(scrollLength / pxScrollForRefresh); 473 | } else { 474 | if (mRefreshOnUp) { 475 | mHeaderTransformer.onReleaseToRefresh(); 476 | } else { 477 | setRefreshingInt(view, true, true); 478 | } 479 | } 480 | } 481 | 482 | void onPullEnded() { 483 | if (DEBUG) { 484 | Log.d(LOG_TAG, "onPullEnded"); 485 | } 486 | if (!mIsRefreshing) { 487 | reset(true); 488 | } 489 | } 490 | 491 | void showHeaderView() { 492 | updateHeaderViewPosition(mHeaderView); 493 | if (mHeaderTransformer.showHeaderView()) { 494 | if (mHeaderViewListener != null) { 495 | mHeaderViewListener.onStateChanged(mHeaderView, 496 | HeaderViewListener.STATE_VISIBLE); 497 | } 498 | } 499 | } 500 | 501 | void hideHeaderView() { 502 | if (mHeaderTransformer.hideHeaderView()) { 503 | if (mHeaderViewListener != null) { 504 | mHeaderViewListener.onStateChanged(mHeaderView, 505 | HeaderViewListener.STATE_HIDDEN); 506 | } 507 | } 508 | } 509 | 510 | protected final Activity getAttachedActivity() { 511 | return mActivity; 512 | } 513 | 514 | protected EnvironmentDelegate createDefaultEnvironmentDelegate() { 515 | return new EnvironmentDelegate() { 516 | @Override 517 | public Context getContextForInflater(Activity activity) { 518 | Context context = null; 519 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { 520 | ActionBar ab = activity.getActionBar(); 521 | if (ab != null) { 522 | context = ab.getThemedContext(); 523 | } 524 | } 525 | if (context == null) { 526 | context = activity; 527 | } 528 | return context; 529 | } 530 | }; 531 | } 532 | 533 | protected HeaderTransformer createDefaultHeaderTransformer() { 534 | return new DefaultHeaderTransformer(); 535 | } 536 | 537 | private boolean checkScrollForRefresh(View view) { 538 | if (mRefreshDisabled) { 539 | return false; 540 | } 541 | 542 | if (mIsBeingDragged && mRefreshOnUp && view != null) { 543 | if (mLastMotionY - mPullBeginY >= getScrollNeededForRefresh(view)) { 544 | setRefreshingInt(view, true, true); 545 | return true; 546 | } 547 | } 548 | return false; 549 | } 550 | 551 | private void setRefreshingInt(View view, boolean refreshing, boolean fromTouch) { 552 | if (isDestroyed()) return; 553 | 554 | if (DEBUG) Log.d(LOG_TAG, "setRefreshingInt: " + refreshing); 555 | // Check to see if we need to do anything 556 | if (mIsRefreshing == refreshing) { 557 | return; 558 | } 559 | 560 | resetTouch(); 561 | 562 | if (refreshing && canRefresh(fromTouch)) { 563 | startRefresh(view, fromTouch); 564 | } else { 565 | reset(fromTouch); 566 | } 567 | } 568 | 569 | /** 570 | * @param fromTouch Whether this is being invoked from a touch event 571 | * @return true if we're currently in a state where a refresh can be 572 | * started. 573 | */ 574 | private boolean canRefresh(boolean fromTouch) { 575 | return !mIsRefreshing && (!fromTouch || mOnRefreshListener != null); 576 | } 577 | 578 | private float getScrollNeededForRefresh(View view) { 579 | return view.getHeight() * mRefreshScrollDistance; 580 | } 581 | 582 | private void reset(boolean fromTouch) { 583 | // Update isRefreshing state 584 | mIsRefreshing = false; 585 | 586 | // Remove any minimize callbacks 587 | if (mRefreshMinimize) { 588 | getHeaderView().removeCallbacks(mRefreshMinimizeRunnable); 589 | } 590 | 591 | // Hide Header View 592 | hideHeaderView(); 593 | } 594 | 595 | private void startRefresh(View view, boolean fromTouch) { 596 | // Update isRefreshing state 597 | mIsRefreshing = true; 598 | 599 | // Call OnRefreshListener if this call has originated from a touch event 600 | if (fromTouch) { 601 | if (mOnRefreshListener != null) { 602 | mOnRefreshListener.onRefreshStarted(view); 603 | } 604 | } 605 | 606 | // Call Transformer 607 | mHeaderTransformer.onRefreshStarted(); 608 | 609 | // Show Header View 610 | showHeaderView(); 611 | 612 | // Post a runnable to minimize the refresh header 613 | if (mRefreshMinimize) { 614 | if (mRefreshMinimizeDelay > 0) { 615 | getHeaderView().postDelayed(mRefreshMinimizeRunnable, mRefreshMinimizeDelay); 616 | } else { 617 | getHeaderView().post(mRefreshMinimizeRunnable); 618 | } 619 | } 620 | } 621 | 622 | private boolean isDestroyed() { 623 | if (mIsDestroyed) { 624 | Log.i(LOG_TAG, "PullToRefreshAttacher is destroyed."); 625 | } 626 | return mIsDestroyed; 627 | } 628 | 629 | protected void addHeaderViewToActivity(View headerView) { 630 | // Get the Display Rect of the Decor View 631 | mActivity.getWindow().getDecorView().getWindowVisibleDisplayFrame(mRect); 632 | 633 | // Honour the requested layout params 634 | int width = WindowManager.LayoutParams.MATCH_PARENT; 635 | int height = WindowManager.LayoutParams.WRAP_CONTENT; 636 | ViewGroup.LayoutParams requestedLp = headerView.getLayoutParams(); 637 | if (requestedLp != null) { 638 | width = requestedLp.width; 639 | height = requestedLp.height; 640 | } 641 | 642 | // Create LayoutParams for adding the View as a panel 643 | WindowManager.LayoutParams wlp = new WindowManager.LayoutParams(width, height, 644 | WindowManager.LayoutParams.TYPE_APPLICATION_PANEL, 645 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, 646 | PixelFormat.TRANSLUCENT); 647 | wlp.x = 0; 648 | wlp.y = mRect.top; 649 | wlp.gravity = Gravity.TOP; 650 | 651 | // Workaround for Issue #182 652 | headerView.setTag(wlp); 653 | mActivity.getWindowManager().addView(headerView, wlp); 654 | } 655 | 656 | protected void updateHeaderViewPosition(View headerView) { 657 | // Refresh the Display Rect of the Decor View 658 | mActivity.getWindow().getDecorView().getWindowVisibleDisplayFrame(mRect); 659 | 660 | WindowManager.LayoutParams wlp = null; 661 | if (headerView.getLayoutParams() instanceof WindowManager.LayoutParams) { 662 | wlp = (WindowManager.LayoutParams) headerView.getLayoutParams(); 663 | } else if (headerView.getTag() instanceof WindowManager.LayoutParams) { 664 | wlp = (WindowManager.LayoutParams) headerView.getTag(); 665 | } 666 | 667 | if (wlp != null && wlp.y != mRect.top) { 668 | wlp.y = mRect.top; 669 | mActivity.getWindowManager().updateViewLayout(headerView, wlp); 670 | } 671 | } 672 | 673 | protected void removeHeaderViewFromActivity(View headerView) { 674 | mAddHeaderViewRunnable.finish(); 675 | 676 | if (headerView.getWindowToken() != null) { 677 | mActivity.getWindowManager().removeViewImmediate(headerView); 678 | } 679 | } 680 | 681 | private final Runnable mRefreshMinimizeRunnable = new Runnable() { 682 | @Override 683 | public void run() { 684 | minimizeHeader(); 685 | } 686 | }; 687 | 688 | private class AddHeaderViewRunnable implements Runnable { 689 | @Override 690 | public void run() { 691 | if (isDestroyed()) return; 692 | 693 | if (getDecorView().getWindowToken() != null) { 694 | // The Decor View has a Window Token, so we can add the HeaderView! 695 | addHeaderViewToActivity(mHeaderView); 696 | } else { 697 | // The Decor View doesn't have a Window Token yet, post ourselves again... 698 | start(); 699 | } 700 | } 701 | 702 | public void start() { 703 | getDecorView().post(this); 704 | } 705 | 706 | public void finish() { 707 | getDecorView().removeCallbacks(this); 708 | } 709 | 710 | private View getDecorView() { 711 | return getAttachedActivity().getWindow().getDecorView(); 712 | } 713 | } 714 | } 715 | --------------------------------------------------------------------------------