├── PullRefreshLayout ├── AndroidManifest.xml ├── build.gradle ├── project.properties ├── res │ ├── drawable-hdpi │ │ ├── loading_inner.png │ │ ├── loading_outer.png │ │ └── pull_refresh_arrow.png │ ├── drawable │ │ ├── pull_down_refresh_loading.xml │ │ └── pull_up_loading.xml │ ├── layout │ │ ├── layout_pull_down_refreshing.xml │ │ └── layout_pull_up_loading.xml │ └── values │ │ ├── attrs.xml │ │ ├── colors.xml │ │ └── strings.xml └── src │ └── com │ └── wj │ └── refresh │ ├── LoadingFooterView.java │ ├── OnRefreshListener.java │ ├── PullRefreshLayout.java │ └── RefreshHeaderView.java ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── refresh │ │ └── demo │ │ └── MainActivity.java │ └── res │ ├── layout │ └── activity_main.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── mipmap-xxxhdpi │ └── ic_launcher.png │ ├── values-w820dp │ └── dimens.xml │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── pullDown.gif ├── pullUp.gif └── settings.gradle /PullRefreshLayout/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /PullRefreshLayout/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'com.github.dcendents.android-maven' 3 | apply plugin: 'com.jfrog.bintray' 4 | // This is the library version used when deploying the artifact 5 | version = "1.0.1" 6 | 7 | android { 8 | compileSdkVersion 23 9 | buildToolsVersion "23.0.1" 10 | resourcePrefix "pullrefreshlayout" 11 | defaultConfig { 12 | minSdkVersion 9 13 | targetSdkVersion 23 14 | versionCode 2 15 | versionName version 16 | } 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | 24 | sourceSets { 25 | main { 26 | manifest.srcFile 'AndroidManifest.xml' 27 | java.srcDirs = ['src'] 28 | resources.srcDirs = ['src'] 29 | aidl.srcDirs = ['src'] 30 | renderscript.srcDirs = ['src'] 31 | res.srcDirs = ['res'] 32 | assets.srcDirs = ['assets'] 33 | } 34 | 35 | // Move the tests to tests/java, tests/res, etc... 36 | instrumentTest.setRoot('tests') 37 | 38 | // Move the build types to build-types/ 39 | // For instance, build-types/debug/java, build-types/debug/AndroidManifest.xml, ... 40 | // This moves them out of them default location under src//... which would 41 | // conflict with src/ being used by the main source set. 42 | // Adding new build types or product flavors should be accompanied 43 | // by a similar customization. 44 | debug.setRoot('build-types/debug') 45 | release.setRoot('build-types/release') 46 | } 47 | 48 | lintOptions { 49 | abortOnError false 50 | } 51 | } 52 | 53 | dependencies { 54 | compile fileTree(include: ['*.jar'], dir: 'libs') 55 | compile 'com.android.support:appcompat-v7:23.0.1' 56 | compile 'com.nineoldandroids:library:2.4.0' 57 | } 58 | def siteUrl = 'https://github.com/weijia1991/PullRefreshLayout' // 项目的主页 59 | def gitUrl = 'https://github.com/weijia1991/PullRefreshLayout.git' // Git仓库的url 60 | group = "com.wj.refresh" // Maven Group ID for the artifact 61 | install { 62 | repositories.mavenInstaller { 63 | // This generates POM.xml with proper parameters 64 | pom { 65 | project { 66 | packaging 'aar' 67 | // Add your description here 68 | name 'This project aims to provide a reusable pull to refresh widget for Android.' //项目描述 69 | url siteUrl 70 | // Set your license 71 | licenses { 72 | license { 73 | name 'The Apache Software License, Version 2.0' 74 | url 'http://www.apache.org/licenses/LICENSE-2.0.txt' 75 | } 76 | } 77 | developers { 78 | developer { 79 | id 'weijia1991' //填写的一些基本信息 80 | name 'ze@lot' 81 | email '965115495@qq.com' 82 | } 83 | } 84 | scm { 85 | connection gitUrl 86 | developerConnection gitUrl 87 | url siteUrl 88 | } 89 | } 90 | } 91 | } 92 | } 93 | task sourcesJar(type: Jar) { 94 | from android.sourceSets.main.java.srcDirs 95 | classifier = 'sources' 96 | } 97 | task javadoc(type: Javadoc) { 98 | source = android.sourceSets.main.java.srcDirs 99 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) 100 | } 101 | task javadocJar(type: Jar, dependsOn: javadoc) { 102 | classifier = 'javadoc' 103 | from javadoc.destinationDir 104 | } 105 | artifacts { 106 | archives javadocJar 107 | archives sourcesJar 108 | } 109 | Properties properties = new Properties() 110 | properties.load(project.rootProject.file('local.properties').newDataInputStream()) 111 | bintray { 112 | user = properties.getProperty("bintray.user") 113 | key = properties.getProperty("bintray.apikey") 114 | configurations = ['archives'] 115 | pkg { 116 | repo = "maven" 117 | name = "PullRefreshLayout" //发布到JCenter上的项目名字 118 | websiteUrl = siteUrl 119 | vcsUrl = gitUrl 120 | licenses = ["Apache-2.0"] 121 | publish = true 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /PullRefreshLayout/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-19 15 | android.library=true 16 | -------------------------------------------------------------------------------- /PullRefreshLayout/res/drawable-hdpi/loading_inner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weijia1991/PullRefreshLayout/f588b4907f01b55ad7c94b4c7837f44cd6e04ade/PullRefreshLayout/res/drawable-hdpi/loading_inner.png -------------------------------------------------------------------------------- /PullRefreshLayout/res/drawable-hdpi/loading_outer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weijia1991/PullRefreshLayout/f588b4907f01b55ad7c94b4c7837f44cd6e04ade/PullRefreshLayout/res/drawable-hdpi/loading_outer.png -------------------------------------------------------------------------------- /PullRefreshLayout/res/drawable-hdpi/pull_refresh_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weijia1991/PullRefreshLayout/f588b4907f01b55ad7c94b4c7837f44cd6e04ade/PullRefreshLayout/res/drawable-hdpi/pull_refresh_arrow.png -------------------------------------------------------------------------------- /PullRefreshLayout/res/drawable/pull_down_refresh_loading.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /PullRefreshLayout/res/drawable/pull_up_loading.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /PullRefreshLayout/res/layout/layout_pull_down_refreshing.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 13 | 14 | 17 | 18 | 25 | 26 | 34 | 35 | 36 | 44 | 45 | -------------------------------------------------------------------------------- /PullRefreshLayout/res/layout/layout_pull_up_loading.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 14 | 15 | 23 | -------------------------------------------------------------------------------- /PullRefreshLayout/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /PullRefreshLayout/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #888888 5 | 6 | -------------------------------------------------------------------------------- /PullRefreshLayout/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 下拉可以刷新 3 | 松开立即刷新 4 | 正在刷新数据 5 | 上拉加载 6 | 正在加载数据 7 | 8 | -------------------------------------------------------------------------------- /PullRefreshLayout/src/com/wj/refresh/LoadingFooterView.java: -------------------------------------------------------------------------------- 1 | package com.wj.refresh; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.LayoutInflater; 6 | import android.widget.FrameLayout; 7 | import android.widget.ProgressBar; 8 | import android.widget.TextView; 9 | 10 | /** 11 | * 上拉加载视图 12 | * Created by jia.wei on 16/3/28. 13 | */ 14 | public class LoadingFooterView extends FrameLayout { 15 | 16 | private ProgressBar mLoadingPb; 17 | private TextView mLoadLabelTv; 18 | private PullUpLoadStatus mLoadStatus = PullUpLoadStatus.PULL_UP_LOAD; 19 | 20 | public LoadingFooterView(Context context) { 21 | this(context, null); 22 | } 23 | 24 | public LoadingFooterView(Context context, AttributeSet attrs) { 25 | super(context, attrs); 26 | init(context); 27 | } 28 | 29 | private void init(Context context) { 30 | LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 31 | inflater.inflate(R.layout.layout_pull_up_loading, this, true); 32 | 33 | mLoadingPb = (ProgressBar) findViewById(R.id.pb_loading); 34 | mLoadLabelTv = (TextView) findViewById(R.id.tv_load_label); 35 | } 36 | 37 | private void update() { 38 | if (mLoadStatus == PullUpLoadStatus.PULL_UP_LOAD) { 39 | mLoadingPb.setVisibility(GONE); 40 | mLoadLabelTv.setText(getResources().getString(R.string.pull_up_load_label)); 41 | } else if (mLoadStatus == PullUpLoadStatus.LOADING) { 42 | mLoadingPb.setVisibility(VISIBLE); 43 | mLoadLabelTv.setText(getResources().getString(R.string.pull_up_loading_label)); 44 | } 45 | } 46 | 47 | public void setLoadStatus(PullUpLoadStatus loadStatus) { 48 | mLoadStatus = loadStatus; 49 | update(); 50 | } 51 | 52 | public PullUpLoadStatus getLoadStatus() { 53 | return mLoadStatus; 54 | } 55 | 56 | /** 57 | * 上拉加载状态 58 | */ 59 | public enum PullUpLoadStatus { 60 | /** 上拉加载 */ 61 | PULL_UP_LOAD, 62 | /** 正在加载 */ 63 | LOADING 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /PullRefreshLayout/src/com/wj/refresh/OnRefreshListener.java: -------------------------------------------------------------------------------- 1 | package com.wj.refresh; 2 | 3 | /** 4 | * 刷新接口 5 | * Created by jia.wei on 16/3/28. 6 | */ 7 | public interface OnRefreshListener { 8 | /** 9 | * 下拉刷新回调 10 | */ 11 | void onPullDownRefresh(); 12 | /** 13 | * 上拉加载更多回调 14 | */ 15 | void onPullUpRefresh(); 16 | } 17 | -------------------------------------------------------------------------------- /PullRefreshLayout/src/com/wj/refresh/PullRefreshLayout.java: -------------------------------------------------------------------------------- 1 | package com.wj.refresh; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.os.Build; 6 | import android.support.v4.view.ViewCompat; 7 | import android.util.AttributeSet; 8 | import android.view.MotionEvent; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | import android.view.animation.AccelerateInterpolator; 12 | import android.widget.AbsListView; 13 | import android.widget.ScrollView; 14 | 15 | import com.nineoldandroids.animation.Animator; 16 | import com.nineoldandroids.animation.ValueAnimator; 17 | 18 | /** 19 | * 下拉刷新上拉加载控件 20 | * Created by jia.wei on 16/3/28. 21 | */ 22 | public class PullRefreshLayout extends ViewGroup { 23 | 24 | private RefreshHeaderView mRefreshHeaderView = null; // 下拉刷新view 25 | private LoadingFooterView mLoadingFooterView = null; // 上拉加载view 26 | private View mRefreshView = null; // 刷新view 27 | 28 | private OnRefreshListener mRefreshListener = null; 29 | private float mDownY; 30 | private float mDownX; 31 | private float mLastY; 32 | private boolean mIsLoadingMore; // 是否正在加载 33 | private boolean mIsDispatchDown; 34 | private float mPullDownDistance; // 下拉距离 35 | private float mPullUpDistance; // 上拉距离 36 | 37 | private boolean mIsHeaderCollapseAnimating; 38 | private boolean mIsFooterCollapseAnimating; 39 | 40 | private int mMode; 41 | // 关闭下拉刷新和上拉加载更多 42 | public static final int DISABLED = 0x0; 43 | // 只开启下拉刷新 44 | public static final int PULL_FROM_START = 0x1; 45 | // 只开启上拉加载更多 46 | public static final int PULL_FROM_END = 0x2; 47 | // 开启下拉刷新和上拉加载更多 48 | public static final int BOTH = 0x3; 49 | 50 | // 阻力因数,数值越小阻力越大,数值必须大于1 51 | private final float PULL_DOWN_FACTOR = 20; 52 | private final float PULL_UP_FACTOR = 5; 53 | 54 | public PullRefreshLayout(Context context) { 55 | this(context, null); 56 | } 57 | 58 | public PullRefreshLayout(Context context, AttributeSet attrs) { 59 | super(context, attrs); 60 | 61 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PullRefreshLayout); 62 | if (a.hasValue(R.styleable.PullRefreshLayout_refreshMode)) { 63 | mMode = a.getInteger(R.styleable.PullRefreshLayout_refreshMode, DISABLED); 64 | } 65 | a.recycle(); 66 | 67 | createRefreshHeaderView(); 68 | createLoadingFooterView(); 69 | } 70 | 71 | @Override 72 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 73 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 74 | 75 | if (mRefreshView == null) { 76 | ensureRefreshView(); 77 | } 78 | if (mRefreshView == null) { 79 | return; 80 | } 81 | mRefreshView.measure(MeasureSpec.makeMeasureSpec( 82 | getMeasuredWidth() - getPaddingLeft() - getPaddingRight(), 83 | MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec( 84 | getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), MeasureSpec.EXACTLY)); 85 | 86 | measureView(mRefreshHeaderView); 87 | measureView(mLoadingFooterView); 88 | } 89 | 90 | @Override 91 | protected void onLayout(boolean changed, int l, int t, int r, int b) { 92 | if (0 == getChildCount()) { 93 | return; 94 | } 95 | 96 | int childLeft = getPaddingLeft(); 97 | int childTop = getPaddingTop(); 98 | int childRight = getPaddingRight(); 99 | int childBottom = getPaddingBottom(); 100 | 101 | int width = getMeasuredWidth(); 102 | int height = getMeasuredHeight(); 103 | int refreshViewWidth = width - childLeft - childRight; 104 | int refreshViewHeight = height - childTop - childBottom; 105 | mRefreshView.layout(childLeft, childTop, childLeft + refreshViewWidth, childTop + refreshViewHeight); 106 | 107 | int headerHeight = mRefreshHeaderView.getMeasuredHeight(); 108 | int headerWidth = mRefreshHeaderView.getMeasuredWidth() - childLeft - childRight; 109 | mRefreshHeaderView.layout(childLeft, - headerHeight, childLeft + headerWidth, 0); 110 | 111 | int footerHeight = mLoadingFooterView.getMeasuredHeight(); 112 | int footerWidth = mLoadingFooterView.getMeasuredWidth() - childLeft - childRight; 113 | int footerTop = refreshViewHeight + childBottom; 114 | mLoadingFooterView.layout(childLeft, footerTop, childLeft + footerWidth, footerTop + footerHeight); 115 | } 116 | 117 | @Override 118 | public boolean onInterceptTouchEvent(MotionEvent ev) { 119 | int action = ev.getAction(); 120 | switch (action) { 121 | case MotionEvent.ACTION_DOWN: 122 | mDownX = ev.getX(); 123 | mDownY = ev.getY(); 124 | mLastY = mDownY; 125 | break; 126 | case MotionEvent.ACTION_MOVE: 127 | float dx = ev.getX() - mDownX; 128 | float dy = ev.getY() - mDownY; 129 | RefreshHeaderView.PullDownRefreshStatus refreshStatus = mRefreshHeaderView.getRefreshStatus(); 130 | 131 | if ((refreshStatus == RefreshHeaderView.PullDownRefreshStatus.REFRESHING || mIsLoadingMore)) { 132 | if (Math.abs(dy) > 5) { 133 | return true; 134 | } 135 | } else { 136 | if (Math.abs(dy) > Math.abs(dx)) { 137 | return true; 138 | } 139 | } 140 | } 141 | return false; 142 | } 143 | 144 | @Override 145 | public boolean onTouchEvent(MotionEvent event) { 146 | if (mIsHeaderCollapseAnimating || mIsFooterCollapseAnimating) { 147 | return true; 148 | } 149 | 150 | int action = event.getAction(); 151 | switch (action) { 152 | case MotionEvent.ACTION_MOVE: 153 | if (!mIsFooterCollapseAnimating && !mIsHeaderCollapseAnimating) { 154 | scrollRefreshLayout(event); 155 | } 156 | mLastY = event.getY(); 157 | break; 158 | case MotionEvent.ACTION_CANCEL: 159 | case MotionEvent.ACTION_UP: 160 | RefreshHeaderView.PullDownRefreshStatus refreshStatus = mRefreshHeaderView.getRefreshStatus(); 161 | // 根据下拉状态,判断是隐藏header,还是触发下拉刷新 162 | if (refreshStatus == RefreshHeaderView.PullDownRefreshStatus.RELEASE_REFRESH) { 163 | collapseRefreshHeaderView(RefreshHeaderView.PullDownRefreshStatus.REFRESHING); 164 | } else if (refreshStatus == RefreshHeaderView.PullDownRefreshStatus.PULL_DOWN_REFRESH && getScrollY() < 0) { 165 | collapseRefreshHeaderView(refreshStatus); 166 | } 167 | 168 | if (!mIsLoadingMore && getScrollY() > 0) { 169 | collapseLoadingFooterView(); 170 | } 171 | 172 | mPullDownDistance = 0; 173 | mPullUpDistance = 0; 174 | 175 | if (mIsDispatchDown) { 176 | mRefreshView.dispatchTouchEvent(event); 177 | mIsDispatchDown = false; 178 | } 179 | break; 180 | } 181 | return true; 182 | } 183 | 184 | /** 185 | * 根据手指在屏幕上滑动的距离滚动RefreshLayout 186 | */ 187 | private void scrollRefreshLayout(MotionEvent event) { 188 | int scrollY = getScrollY(); 189 | float dy = mLastY - event.getY(); 190 | RefreshHeaderView.PullDownRefreshStatus refreshStatus = mRefreshHeaderView.getRefreshStatus(); 191 | 192 | if (refreshStatus == RefreshHeaderView.PullDownRefreshStatus.REFRESHING) { 193 | if (scrollY == 0) { 194 | // scrollY == 0时,下拉刷新view已经不可见,此时需要判断事件由谁来处理 195 | if (dy < 0 && !canChildScrollUp()) { 196 | //方向为下拉,并且refreshView不能继续往下滚时自己处理事件,否则事件分发给refreshView处理 197 | scrollRefreshLayoutWithRefreshing((int) dy, scrollY); 198 | } else { 199 | dispatchTouchEventToRefreshView(event); 200 | } 201 | } else { 202 | scrollRefreshLayoutWithRefreshing((int) dy, scrollY); 203 | } 204 | } else if (mIsLoadingMore) { 205 | if (scrollY == 0) { 206 | // scrollY == 0时,上拉加载view已经不可见,此时需要判断事件由谁来处理 207 | if (dy > 0 && !canChildScrollDown()) { 208 | // 方向为上拉,并且refreshView不能继续往上滚时自己处理事件,否则事件分发给refreshView处理 209 | scrollRefreshLayoutWithLoading((int) dy, scrollY); 210 | } else { 211 | dispatchTouchEventToRefreshView(event); 212 | } 213 | } else { 214 | scrollRefreshLayoutWithLoading((int) dy, scrollY); 215 | } 216 | } else { 217 | if (dy < 0 && !canChildScrollUp() || getScrollY() < 0) { 218 | // 下拉刷新 219 | if (mMode == PULL_FROM_START || mMode == BOTH) { 220 | mPullDownDistance += -dy; 221 | 222 | if (mPullDownDistance > 0) { 223 | // 阻尼 224 | float damping = (float) (mPullDownDistance / (Math.log(mPullDownDistance) / Math.log(PULL_DOWN_FACTOR))); 225 | damping = Math.max(0, damping); 226 | scrollTo(0, (int) -damping); 227 | 228 | if (damping >= mRefreshHeaderView.getHeight()) { 229 | // 滚动距离超过下拉刷新视图的高,将状态变为释放 230 | mRefreshHeaderView.setRefreshStatus(RefreshHeaderView.PullDownRefreshStatus.RELEASE_REFRESH); 231 | } else { 232 | mRefreshHeaderView.setRefreshStatus(RefreshHeaderView.PullDownRefreshStatus.PULL_DOWN_REFRESH); 233 | } 234 | } else { 235 | scrollTo(0, 0); 236 | mPullDownDistance = 0; 237 | } 238 | } 239 | } else if (dy > 0 && !canChildScrollDown() || getScrollY() > 0) { 240 | if (mMode == PULL_FROM_END || mMode == BOTH) { 241 | // 上拉加载 242 | mPullUpDistance += dy; 243 | if (mPullUpDistance > 0) { 244 | // 阻尼 245 | float damping = (float) (mPullUpDistance / (Math.log(mPullUpDistance) / Math.log(PULL_UP_FACTOR))); 246 | damping = Math.max(0, damping); 247 | damping = Math.min(damping, mLoadingFooterView.getHeight()); 248 | scrollTo(0, (int) damping); 249 | 250 | if (damping == mLoadingFooterView.getHeight()) { 251 | // 触发加载,将状态变为加载 252 | mLoadingFooterView.setLoadStatus(LoadingFooterView.PullUpLoadStatus.LOADING); 253 | mIsLoadingMore = true; 254 | 255 | if (mRefreshListener != null) { 256 | mRefreshListener.onPullUpRefresh(); 257 | } 258 | } 259 | } else { 260 | scrollTo(0, 0); 261 | mPullUpDistance = 0; 262 | } 263 | } 264 | } else { 265 | if (!(!canChildScrollDown() && dy > 0)) { 266 | dispatchTouchEventToRefreshView(event); 267 | } 268 | } 269 | } 270 | } 271 | 272 | /** 273 | * 根据下拉刷新状态,折叠下拉刷新view 274 | * @param refreshStatus 下拉刷新状态 275 | */ 276 | private void collapseRefreshHeaderView(final RefreshHeaderView.PullDownRefreshStatus refreshStatus) { 277 | int scrollToY = 0; 278 | if (refreshStatus == RefreshHeaderView.PullDownRefreshStatus.REFRESHING) { 279 | scrollToY = - mRefreshHeaderView.getHeight(); 280 | } else if (refreshStatus == RefreshHeaderView.PullDownRefreshStatus.PULL_DOWN_REFRESH) { 281 | scrollToY = 0; 282 | } 283 | 284 | ValueAnimator animator = ValueAnimator.ofInt(getScrollY(), scrollToY); 285 | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 286 | @Override 287 | public void onAnimationUpdate(ValueAnimator animation) { 288 | int value = (int) animation.getAnimatedValue(); 289 | scrollTo(0, value); 290 | } 291 | }); 292 | animator.addListener(new Animator.AnimatorListener() { 293 | @Override 294 | public void onAnimationStart(Animator animation) { 295 | mIsHeaderCollapseAnimating = true; 296 | } 297 | 298 | @Override 299 | public void onAnimationEnd(Animator animation) { 300 | mIsHeaderCollapseAnimating = false; 301 | 302 | if (refreshStatus == RefreshHeaderView.PullDownRefreshStatus.REFRESHING) { 303 | if (mRefreshListener != null) { 304 | mRefreshListener.onPullDownRefresh(); 305 | } 306 | } 307 | mRefreshHeaderView.setRefreshStatus(refreshStatus); 308 | } 309 | 310 | @Override 311 | public void onAnimationCancel(Animator animation) { 312 | mIsHeaderCollapseAnimating = false; 313 | } 314 | 315 | @Override 316 | public void onAnimationRepeat(Animator animation) { 317 | 318 | } 319 | }); 320 | animator.setDuration(200); 321 | animator.setInterpolator(new AccelerateInterpolator(3)); 322 | animator.start(); 323 | } 324 | 325 | /** 326 | * 收起上拉加载view 327 | */ 328 | private void collapseLoadingFooterView() { 329 | if (getScrollY() != 0) { 330 | ValueAnimator animator = ValueAnimator.ofInt(getScrollY(), 0); 331 | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 332 | @Override 333 | public void onAnimationUpdate(ValueAnimator animation) { 334 | int value = (int) animation.getAnimatedValue(); 335 | scrollTo(0, value); 336 | } 337 | }); 338 | animator.addListener(new Animator.AnimatorListener() { 339 | @Override 340 | public void onAnimationStart(Animator animation) { 341 | mIsFooterCollapseAnimating = true; 342 | } 343 | 344 | @Override 345 | public void onAnimationEnd(Animator animation) { 346 | mIsFooterCollapseAnimating = false; 347 | mIsLoadingMore = false; 348 | mLoadingFooterView.setLoadStatus(LoadingFooterView.PullUpLoadStatus.PULL_UP_LOAD); 349 | 350 | if (mRefreshView instanceof AbsListView) { 351 | ((AbsListView) mRefreshView).smoothScrollBy(100, 200); 352 | } else if (mRefreshView instanceof ScrollView) { 353 | ((ScrollView) mRefreshView).smoothScrollBy(100, 200); 354 | } 355 | } 356 | 357 | @Override 358 | public void onAnimationCancel(Animator animation) { 359 | mIsFooterCollapseAnimating = false; 360 | } 361 | 362 | @Override 363 | public void onAnimationRepeat(Animator animation) { 364 | 365 | } 366 | }); 367 | animator.setDuration(300); 368 | animator.setInterpolator(new AccelerateInterpolator(3)); 369 | animator.start(); 370 | } else { 371 | mIsLoadingMore = false; 372 | mLoadingFooterView.setLoadStatus(LoadingFooterView.PullUpLoadStatus.PULL_UP_LOAD); 373 | } 374 | } 375 | 376 | /** 377 | * 创建下拉刷新view 378 | */ 379 | private void createRefreshHeaderView() { 380 | mRefreshHeaderView = new RefreshHeaderView(getContext()); 381 | addView(mRefreshHeaderView); 382 | } 383 | 384 | /** 385 | * 创建上拉加载view 386 | */ 387 | private void createLoadingFooterView() { 388 | mLoadingFooterView = new LoadingFooterView(getContext()); 389 | addView(mLoadingFooterView); 390 | } 391 | 392 | /** 393 | * 确定刷新view 394 | */ 395 | private void ensureRefreshView() { 396 | if (mRefreshView == null) { 397 | for (int i = 0; i < getChildCount(); i++) { 398 | View child = getChildAt(i); 399 | if (!child.equals(mRefreshHeaderView) && !child.equals(mLoadingFooterView)) { 400 | mRefreshView = child; 401 | mRefreshView.setOverScrollMode(View.OVER_SCROLL_NEVER); 402 | break; 403 | } 404 | } 405 | } 406 | } 407 | 408 | /** 409 | * 测量view 410 | * @param targetView 目标view 411 | */ 412 | private void measureView(View targetView) { 413 | if (targetView == null) { 414 | return; 415 | } 416 | 417 | LayoutParams p = targetView.getLayoutParams(); 418 | if (p == null) { 419 | p = new LayoutParams( 420 | LayoutParams.MATCH_PARENT, 421 | LayoutParams.WRAP_CONTENT); 422 | } 423 | 424 | int lpHeight = p.height; 425 | int childHeightSpec; 426 | if (lpHeight > 0) { 427 | childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); 428 | } else { 429 | childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 430 | } 431 | int childWidthSpec = MeasureSpec.makeMeasureSpec( 432 | getMeasuredWidth() - getPaddingLeft() - getPaddingRight(), 433 | MeasureSpec.EXACTLY); 434 | targetView.measure(childWidthSpec, childHeightSpec); 435 | } 436 | 437 | /** 438 | * refreshView是否已经滚到最顶部 439 | * @return true:没有滚到最顶部 440 | */ 441 | public boolean canChildScrollUp() { 442 | if (mRefreshView == null) { 443 | return false; 444 | } 445 | if (Build.VERSION.SDK_INT < 14) { 446 | if (mRefreshView instanceof AbsListView) { 447 | final AbsListView absListView = (AbsListView) mRefreshView; 448 | return absListView.getChildCount() > 0 449 | && (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0) 450 | .getTop() < absListView.getPaddingTop()); 451 | } else { 452 | return ViewCompat.canScrollVertically(mRefreshView, -1) || mRefreshView.getScrollY() > 0; 453 | } 454 | } else { 455 | return ViewCompat.canScrollVertically(mRefreshView, -1); 456 | } 457 | } 458 | 459 | /** 460 | * refreshView是否已经滚到最底部 461 | * @return true:没有滚到最底部 462 | */ 463 | public boolean canChildScrollDown() { 464 | if (mRefreshView == null) { 465 | return false; 466 | } 467 | if (Build.VERSION.SDK_INT < 14) { 468 | if (mRefreshView instanceof AbsListView) { 469 | final AbsListView absListView = (AbsListView) mRefreshView; 470 | if (absListView.getChildCount() > 0) { 471 | int lastChildBottom = absListView.getChildAt(absListView.getChildCount() - 1).getBottom(); 472 | return absListView.getLastVisiblePosition() == absListView.getAdapter().getCount() - 1 && lastChildBottom <= absListView.getMeasuredHeight(); 473 | } else { 474 | return false; 475 | } 476 | 477 | } else { 478 | return ViewCompat.canScrollVertically(mRefreshView, 1) || mRefreshView.getScrollY() > 0; 479 | } 480 | } else { 481 | return ViewCompat.canScrollVertically(mRefreshView, 1); 482 | } 483 | } 484 | 485 | private void dispatchTouchEventToRefreshView(MotionEvent event) { 486 | if (mIsDispatchDown) { 487 | mRefreshView.dispatchTouchEvent(event); 488 | } else { 489 | mIsDispatchDown = true; 490 | 491 | MotionEvent obtain = MotionEvent.obtain(event); 492 | obtain.setAction(MotionEvent.ACTION_DOWN); 493 | mRefreshView.dispatchTouchEvent(obtain); 494 | } 495 | } 496 | 497 | private void scrollRefreshLayoutWithRefreshing(int dy, int scrollY) { 498 | int targetY = scrollY + dy; 499 | if (targetY < -mRefreshHeaderView.getHeight()) { 500 | dy = -mRefreshHeaderView.getHeight() - scrollY; 501 | } else if (targetY > 0) { 502 | dy = -scrollY; 503 | } 504 | scrollBy(0, dy); 505 | } 506 | 507 | private void scrollRefreshLayoutWithLoading(int dy, int scrollY) { 508 | int targetY = scrollY + dy; 509 | if (targetY > mLoadingFooterView.getHeight()) { 510 | dy = mLoadingFooterView.getHeight() - scrollY; 511 | } else if (targetY < 0) { 512 | dy = -scrollY; 513 | } 514 | scrollBy(0, dy); 515 | } 516 | 517 | public void setOnRefreshListener(OnRefreshListener listener) { 518 | mRefreshListener = listener; 519 | } 520 | 521 | public void setMode(int mode) { 522 | mMode = mode; 523 | } 524 | 525 | public void onRefreshComplete() { 526 | if (mRefreshHeaderView != null && mRefreshHeaderView.getRefreshStatus() == RefreshHeaderView.PullDownRefreshStatus.REFRESHING 527 | && !mIsHeaderCollapseAnimating) { 528 | collapseRefreshHeaderView(RefreshHeaderView.PullDownRefreshStatus.PULL_DOWN_REFRESH); 529 | } 530 | if (mIsLoadingMore && !mIsFooterCollapseAnimating) { 531 | mPullUpDistance = 0; 532 | collapseLoadingFooterView(); 533 | } 534 | } 535 | } 536 | -------------------------------------------------------------------------------- /PullRefreshLayout/src/com/wj/refresh/RefreshHeaderView.java: -------------------------------------------------------------------------------- 1 | package com.wj.refresh; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.LayoutInflater; 6 | import android.widget.FrameLayout; 7 | import android.widget.ImageView; 8 | import android.widget.ProgressBar; 9 | import android.widget.TextView; 10 | 11 | import com.nineoldandroids.animation.ObjectAnimator; 12 | 13 | /** 14 | * 下拉刷新视图 15 | * Created by jia.wei on 16/3/28. 16 | */ 17 | public class RefreshHeaderView extends FrameLayout { 18 | 19 | private ImageView mPullArrowIv = null; 20 | private ProgressBar mRefreshingPb = null; 21 | private TextView mRefreshLabelTv = null; 22 | 23 | private PullDownRefreshStatus mRefreshStatus = PullDownRefreshStatus.PULL_DOWN_REFRESH; 24 | 25 | public RefreshHeaderView(Context context) { 26 | this(context, null); 27 | } 28 | 29 | public RefreshHeaderView(Context context, AttributeSet attrs) { 30 | super(context, attrs); 31 | init(context); 32 | } 33 | 34 | private void init(Context context) { 35 | LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 36 | inflater.inflate(R.layout.layout_pull_down_refreshing, this, true); 37 | 38 | mPullArrowIv = (ImageView) findViewById(R.id.iv_pull_arrow); 39 | mRefreshingPb = (ProgressBar) findViewById(R.id.pb_refreshing); 40 | mRefreshLabelTv = (TextView) findViewById(R.id.tv_refresh_label); 41 | } 42 | 43 | /** 44 | * 更新下拉图标 45 | * @param refreshStatus {@link PullDownRefreshStatus} 46 | */ 47 | private void updatePullIcon(PullDownRefreshStatus refreshStatus) { 48 | if (mPullArrowIv != null) { 49 | if (refreshStatus != mRefreshStatus) { 50 | if (refreshStatus == PullDownRefreshStatus.RELEASE_REFRESH) { 51 | mPullArrowIv.setVisibility(VISIBLE); 52 | mRefreshingPb.setVisibility(INVISIBLE); 53 | 54 | ObjectAnimator.ofFloat(mPullArrowIv, "rotation", 0, -180) 55 | .setDuration(150) 56 | .start(); 57 | } else if (refreshStatus == PullDownRefreshStatus.PULL_DOWN_REFRESH) { 58 | mPullArrowIv.setVisibility(VISIBLE); 59 | mRefreshingPb.setVisibility(INVISIBLE); 60 | 61 | ObjectAnimator.ofFloat(mPullArrowIv, "rotation", -180, 0) 62 | .setDuration(150) 63 | .start(); 64 | } else if (refreshStatus == PullDownRefreshStatus.REFRESHING) { 65 | mPullArrowIv.setVisibility(INVISIBLE); 66 | mRefreshingPb.setVisibility(VISIBLE); 67 | } 68 | } 69 | } 70 | } 71 | 72 | /** 73 | * 跟新下拉刷新的标签 74 | * @param refreshStatus {@link PullDownRefreshStatus} 75 | */ 76 | private void updateRefreshLabel(PullDownRefreshStatus refreshStatus) { 77 | if (mRefreshLabelTv != null) { 78 | if (refreshStatus == PullDownRefreshStatus.RELEASE_REFRESH) { 79 | mRefreshLabelTv.setText(getContext().getResources().getString(R.string.pull_down_release_label)); 80 | } else if (refreshStatus == PullDownRefreshStatus.PULL_DOWN_REFRESH) { 81 | mRefreshLabelTv.setText(getContext().getResources().getString(R.string.pull_down_refresh_label)); 82 | } else if (refreshStatus == PullDownRefreshStatus.REFRESHING) { 83 | mRefreshLabelTv.setText(getContext().getResources().getString(R.string.pull_down_refreshing_label)); 84 | } 85 | } 86 | } 87 | 88 | public void setRefreshStatus(PullDownRefreshStatus refreshStatus) { 89 | updatePullIcon(refreshStatus); 90 | updateRefreshLabel(refreshStatus); 91 | 92 | mRefreshStatus = refreshStatus; 93 | } 94 | 95 | public PullDownRefreshStatus getRefreshStatus() { 96 | return mRefreshStatus; 97 | } 98 | 99 | /** 100 | * 下拉刷新状态 101 | */ 102 | public enum PullDownRefreshStatus { 103 | /** 释放刷新 */ 104 | RELEASE_REFRESH, 105 | /** 下拉刷新 */ 106 | PULL_DOWN_REFRESH, 107 | /** 正在刷新 */ 108 | REFRESHING 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PullRefreshLayout 2 | PullRefreshLayout是一个为android组件提供下拉刷新和上拉加载功能的轻便的自定义控件

3 | ![](https://github.com/weijia1991/PullRefreshLayout/blob/master/pullDown.gif)

4 | ![](https://github.com/weijia1991/PullRefreshLayout/blob/master/pullUp.gif)

5 | # Usage 6 | gradle:

7 | `dependencies {`
8 | `   compile 'com.wj.refresh:PullRefreshLayout:1.0.1'`
9 | `}`

10 | 1.xml layout:
11 | ` 12 | `    android:layout_width="match_parent"`
13 | `    android:layout_height="match_parent"`
14 | `    srl:refreshMode="both" >`

15 | `    <...ListView or GridView or ScrollView and more...>`

16 | ``

17 | 2.Set up refresh listener: 
18 | `mRefreshLayout.setOnRefreshListener(new OnRefreshListener() {`
19 | `    @Override`
20 | `    public void onPullDownRefresh() {`
21 | `      // The drop-down refresh`
22 | `    }`
23 | `    @Override`
24 | `    public void onPullUpRefresh() {`
25 | `      // Pull on loading`
26 | `    }`
27 | `});`

28 | 3.Refresh to complete:
29 | `mRefreshLayout.onRefreshComplete();` 30 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "24.0.0 rc1" 6 | 7 | defaultConfig { 8 | applicationId "com.refresh.demo" 9 | minSdkVersion 9 10 | targetSdkVersion 23 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(include: ['*.jar'], dir: 'libs') 24 | compile 'com.android.support:appcompat-v7:23.2.1' 25 | compile project(':PullRefreshLayout') 26 | } 27 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/weijia/Tools/SDK_Studio/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/refresh/demo/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.refresh.demo; 2 | 3 | import android.os.Bundle; 4 | import android.os.Handler; 5 | import android.os.Message; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.view.Gravity; 8 | import android.view.View; 9 | import android.widget.AbsListView; 10 | import android.widget.AdapterView; 11 | import android.widget.ArrayAdapter; 12 | import android.widget.ListView; 13 | import android.widget.TextView; 14 | import android.widget.Toast; 15 | 16 | import com.wj.refresh.OnRefreshListener; 17 | import com.wj.refresh.PullRefreshLayout; 18 | 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | 22 | public class MainActivity extends AppCompatActivity { 23 | 24 | private PullRefreshLayout mRefreshLayout; 25 | private ListView mListView; 26 | private List mData; 27 | 28 | @Override 29 | protected void onCreate(Bundle savedInstanceState) { 30 | super.onCreate(savedInstanceState); 31 | setContentView(R.layout.activity_main); 32 | 33 | initBoot(); 34 | initData(); 35 | initEvent(); 36 | } 37 | 38 | private void initBoot() { 39 | mRefreshLayout = (PullRefreshLayout) findViewById(R.id.refresh_layout); 40 | mListView = (ListView) findViewById(R.id.lv); 41 | mData = new ArrayList<>(); 42 | } 43 | 44 | private void initData() { 45 | for (int i = 0; i < 20; i++) { 46 | mData.add("refresh " + i); 47 | } 48 | 49 | TextView headerTv = new TextView(this); 50 | headerTv.setText("HeaderView"); 51 | headerTv.setTextSize(20); 52 | headerTv.setGravity(Gravity.CENTER); 53 | headerTv.setLayoutParams(new AbsListView.LayoutParams(AbsListView.LayoutParams.MATCH_PARENT, 200)); 54 | mListView.addHeaderView(headerTv); 55 | 56 | TextView footerTv = new TextView(this); 57 | footerTv.setText("FooterView"); 58 | footerTv.setTextSize(20); 59 | footerTv.setGravity(Gravity.CENTER); 60 | footerTv.setLayoutParams(new AbsListView.LayoutParams(AbsListView.LayoutParams.MATCH_PARENT, 200)); 61 | mListView.addFooterView(footerTv); 62 | 63 | ArrayAdapter adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, mData); 64 | mListView.setAdapter(adapter); 65 | } 66 | 67 | private void initEvent() { 68 | mRefreshLayout.setOnRefreshListener(new OnRefreshListener() { 69 | @Override 70 | public void onPullDownRefresh() { 71 | mHandler.sendEmptyMessageDelayed(0, 5000); 72 | } 73 | 74 | @Override 75 | public void onPullUpRefresh() { 76 | mHandler.sendEmptyMessageDelayed(0, 5000); 77 | } 78 | }); 79 | mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { 80 | @Override 81 | public void onItemClick(AdapterView parent, View view, int position, long id) { 82 | Toast.makeText(MainActivity.this, "Item is clicked which position is" + position, Toast.LENGTH_SHORT).show(); 83 | } 84 | }); 85 | } 86 | 87 | Handler mHandler = new Handler() { 88 | @Override 89 | public void handleMessage(Message msg) { 90 | mRefreshLayout.onRefreshComplete(); 91 | } 92 | }; 93 | 94 | } 95 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weijia1991/PullRefreshLayout/f588b4907f01b55ad7c94b4c7837f44cd6e04ade/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weijia1991/PullRefreshLayout/f588b4907f01b55ad7c94b4c7837f44cd6e04ade/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weijia1991/PullRefreshLayout/f588b4907f01b55ad7c94b4c7837f44cd6e04ade/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weijia1991/PullRefreshLayout/f588b4907f01b55ad7c94b4c7837f44cd6e04ade/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weijia1991/PullRefreshLayout/f588b4907f01b55ad7c94b4c7837f44cd6e04ade/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | PullRefreshLayout 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:1.3.0' 9 | classpath 'com.github.dcendents:android-maven-gradle-plugin:1.3' 10 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.2' 11 | // NOTE: Do not place your application dependencies here; they belong 12 | // in the individual module build.gradle files 13 | } 14 | } 15 | 16 | allprojects { 17 | repositories { 18 | jcenter() 19 | } 20 | } 21 | 22 | task clean(type: Delete) { 23 | delete rootProject.buildDir 24 | } 25 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weijia1991/PullRefreshLayout/f588b4907f01b55ad7c94b4c7837f44cd6e04ade/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Dec 28 10:00:20 PST 2015 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-all.zip 7 | -------------------------------------------------------------------------------- /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 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /pullDown.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weijia1991/PullRefreshLayout/f588b4907f01b55ad7c94b4c7837f44cd6e04ade/pullDown.gif -------------------------------------------------------------------------------- /pullUp.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weijia1991/PullRefreshLayout/f588b4907f01b55ad7c94b4c7837f44cd6e04ade/pullUp.gif -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':PullRefreshLayout' 2 | --------------------------------------------------------------------------------