├── .gitignore ├── .idea ├── .name ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── gradle.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── AutoSwipeRefresh.iml ├── README.md ├── app ├── .gitignore ├── app.iml ├── build.gradle ├── proguard-rules.pro ├── source │ └── sample.gif └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── brucetoo │ │ └── autoswiperefesh │ │ └── ApplicationTest.java │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── brucetoo │ │ └── autoswiperefesh │ │ ├── CircleImageView.java │ │ ├── DotsTextView.java │ │ ├── MainActivity.java │ │ ├── MaterialProgressDrawable.java │ │ └── SwipeRefreshLayout.java │ └── res │ ├── layout │ ├── activity_main.xml │ ├── footer_view.xml │ └── list_item.xml │ ├── menu │ └── menu_main.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── values-w820dp │ └── dimens.xml │ └── values │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /local.properties 3 | /.idea/workspace.xml 4 | /.idea/libraries 5 | .DS_Store 6 | /build 7 | /captures 8 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | AutoSwipeRefesh -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | 47 | 48 | 49 | 50 | 1.7 51 | 52 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /AutoSwipeRefresh.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # AutoSwipeRefresh 3 | > **AutoSwipeRefresh** Just a demo for us how to implement auto refresh when first reach a page using [SwipeRefreshLayout](https://developer.android.com/reference/android/support/v4/widget/SwipeRefreshLayout.html), 4 | but need to change some source code.And also a wonderful foot view from [WaitingDots](https://github.com/tajchert/WaitingDots),absolutely,i modify it... 5 | 6 | ![](app/source/sample.gif) 7 | 8 | 9 | # What changed IMPORTANT 10 | 11 | [SwipeRefreshLayout.java](https://github.com/futuresimple/android-support-v4/blob/master/src/java/android/support/v4/widget/SwipeRefreshLayout.java) in this file. 12 | 13 | ```java 14 | public void setRefreshing(boolean refreshing) { 15 | if (refreshing && mRefreshing != refreshing) { 16 | // scale and show 17 | mRefreshing = refreshing; 18 | int endTarget = 0; 19 | if (!mUsingCustomStart) { 20 | endTarget = (int) (mSpinnerFinalOffset + mOriginalOffsetTop); 21 | } else { 22 | endTarget = (int) mSpinnerFinalOffset; 23 | } 24 | setTargetOffsetTopAndBottom(endTarget - mCurrentTargetOffsetTop, 25 | true /* requires update */); 26 | /** 27 | when i call setRefreshing(true) outside,params mNotify can never be true,only when u swipe down by gesture see callBack method onTouchEvent -- MotionEvent.ACTION_CANCEL 28 | and variable mRefreshListener -- onAnimationEnd 29 | NOTE: version of v4 must >= 21 30 | */ 31 | mNotify = true; 32 | startScaleUpAnimation(mRefreshListener); 33 | } else { 34 | setRefreshing(refreshing, false /* notify */); 35 | } 36 | } 37 | 38 | ``` 39 | 40 | ##USAGE 41 | ```java 42 | put following code in onCreate or onResume callback method to set auto refreshing like: 43 | 44 | handler.postDelayed(new Runnable() { 45 | @Override 46 | public void run() { 47 | refreshLayout.setRefreshing(true); 48 | } 49 | }, 500); 50 | ``` 51 | 52 | ##DEMO USED 53 | * ``` base-adapter-helper ``` [link](https://github.com/JoanZapata/base-adapter-helper) 54 | 55 | # License 56 | 57 | ``` 58 | Copyright 2015 Bruce too 59 | 60 | Licensed under the Apache License, Version 2.0 (the "License"); 61 | you may not use this file except in compliance with the License. 62 | You may obtain a copy of the License at 63 | 64 | http://www.apache.org/licenses/LICENSE-2.0 65 | 66 | Unless required by applicable law or agreed to in writing, software 67 | distributed under the License is distributed on an "AS IS" BASIS, 68 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 69 | See the License for the specific language governing permissions and 70 | limitations under the License. 71 | ``` 72 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /local.properties 3 | /.idea/workspace.xml 4 | /.idea/libraries 5 | .DS_Store 6 | /build 7 | -------------------------------------------------------------------------------- /app/app.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 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 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 22 5 | buildToolsVersion "22.0.1" 6 | 7 | defaultConfig { 8 | applicationId "com.brucetoo.autoswiperefesh" 9 | minSdkVersion 14 10 | targetSdkVersion 22 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(dir: 'libs', include: ['*.jar']) 24 | compile 'com.android.support:appcompat-v7:22.2.1' 25 | compile 'com.joanzapata.android:base-adapter-helper:1.1.11' 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/brucetoo/Library/Android/sdk/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/source/sample.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brucetoo/AutoSwipeRefesh/33a29866a35b422e9380222618ff26b100b92e28/app/source/sample.gif -------------------------------------------------------------------------------- /app/src/androidTest/java/com/brucetoo/autoswiperefesh/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.brucetoo.autoswiperefesh; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/brucetoo/autoswiperefesh/CircleImageView.java: -------------------------------------------------------------------------------- 1 | package com.brucetoo.autoswiperefesh; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.Paint; 7 | import android.graphics.RadialGradient; 8 | import android.graphics.Shader; 9 | import android.graphics.drawable.ShapeDrawable; 10 | import android.graphics.drawable.shapes.OvalShape; 11 | import android.support.v4.view.ViewCompat; 12 | import android.view.animation.Animation; 13 | import android.widget.ImageView; 14 | 15 | /** 16 | * Created by Bruce Too 17 | * On 8/7/15. 18 | * At 14:01 19 | */ 20 | public class CircleImageView extends ImageView { 21 | 22 | private static final int KEY_SHADOW_COLOR = 0x1E000000; 23 | private static final int FILL_SHADOW_COLOR = 0x3D000000; 24 | // PX 25 | private static final float X_OFFSET = 0f; 26 | private static final float Y_OFFSET = 1.75f; 27 | private static final float SHADOW_RADIUS = 3.5f; 28 | private static final int SHADOW_ELEVATION = 4; 29 | 30 | private Animation.AnimationListener mListener; 31 | private int mShadowRadius; 32 | 33 | public CircleImageView(Context context, int color, final float radius) { 34 | super(context); 35 | final float density = getContext().getResources().getDisplayMetrics().density; 36 | final int diameter = (int) (radius * density * 2); 37 | final int shadowYOffset = (int) (density * Y_OFFSET); 38 | final int shadowXOffset = (int) (density * X_OFFSET); 39 | 40 | mShadowRadius = (int) (density * SHADOW_RADIUS); 41 | 42 | ShapeDrawable circle; 43 | if (elevationSupported()) { 44 | circle = new ShapeDrawable(new OvalShape()); 45 | ViewCompat.setElevation(this, SHADOW_ELEVATION * density); 46 | } else { 47 | OvalShape oval = new OvalShadow(mShadowRadius, diameter); 48 | circle = new ShapeDrawable(oval); 49 | ViewCompat.setLayerType(this, ViewCompat.LAYER_TYPE_SOFTWARE, circle.getPaint()); 50 | circle.getPaint().setShadowLayer(mShadowRadius, shadowXOffset, shadowYOffset, 51 | KEY_SHADOW_COLOR); 52 | final int padding = mShadowRadius; 53 | // set padding so the inner image sits correctly within the shadow. 54 | setPadding(padding, padding, padding, padding); 55 | } 56 | circle.getPaint().setColor(color); 57 | setBackgroundDrawable(circle); 58 | } 59 | 60 | private boolean elevationSupported() { 61 | return android.os.Build.VERSION.SDK_INT >= 21; 62 | } 63 | 64 | @Override 65 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 66 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 67 | if (!elevationSupported()) { 68 | setMeasuredDimension(getMeasuredWidth() + mShadowRadius*2, getMeasuredHeight() 69 | + mShadowRadius*2); 70 | } 71 | } 72 | 73 | public void setAnimationListener(Animation.AnimationListener listener) { 74 | mListener = listener; 75 | } 76 | 77 | @Override 78 | public void onAnimationStart() { 79 | super.onAnimationStart(); 80 | if (mListener != null) { 81 | mListener.onAnimationStart(getAnimation()); 82 | } 83 | } 84 | 85 | @Override 86 | public void onAnimationEnd() { 87 | super.onAnimationEnd(); 88 | if (mListener != null) { 89 | mListener.onAnimationEnd(getAnimation()); 90 | } 91 | } 92 | 93 | /** 94 | * Update the background color of the circle image view. 95 | * 96 | * @param colorRes Id of a color resource. 97 | */ 98 | public void setBackgroundColorRes(int colorRes) { 99 | setBackgroundColor(getContext().getResources().getColor(colorRes)); 100 | } 101 | 102 | @Override 103 | public void setBackgroundColor(int color) { 104 | if (getBackground() instanceof ShapeDrawable) { 105 | ((ShapeDrawable) getBackground()).getPaint().setColor(color); 106 | } 107 | } 108 | 109 | private class OvalShadow extends OvalShape { 110 | private RadialGradient mRadialGradient; 111 | private Paint mShadowPaint; 112 | private int mCircleDiameter; 113 | 114 | public OvalShadow(int shadowRadius, int circleDiameter) { 115 | super(); 116 | mShadowPaint = new Paint(); 117 | mShadowRadius = shadowRadius; 118 | mCircleDiameter = circleDiameter; 119 | mRadialGradient = new RadialGradient(mCircleDiameter / 2, mCircleDiameter / 2, 120 | mShadowRadius, new int[] { 121 | FILL_SHADOW_COLOR, Color.TRANSPARENT 122 | }, null, Shader.TileMode.CLAMP); 123 | mShadowPaint.setShader(mRadialGradient); 124 | } 125 | 126 | @Override 127 | public void draw(Canvas canvas, Paint paint) { 128 | final int viewWidth = CircleImageView.this.getWidth(); 129 | final int viewHeight = CircleImageView.this.getHeight(); 130 | canvas.drawCircle(viewWidth / 2, viewHeight / 2, (mCircleDiameter / 2 + mShadowRadius), 131 | mShadowPaint); 132 | canvas.drawCircle(viewWidth / 2, viewHeight / 2, (mCircleDiameter / 2), paint); 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /app/src/main/java/com/brucetoo/autoswiperefesh/DotsTextView.java: -------------------------------------------------------------------------------- 1 | package com.brucetoo.autoswiperefesh; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorSet; 5 | import android.animation.ObjectAnimator; 6 | import android.animation.TypeEvaluator; 7 | import android.animation.ValueAnimator; 8 | import android.animation.ValueAnimator.AnimatorUpdateListener; 9 | import android.content.Context; 10 | import android.graphics.Canvas; 11 | import android.graphics.Paint; 12 | import android.text.SpannableString; 13 | import android.text.Spanned; 14 | import android.text.style.ReplacementSpan; 15 | import android.util.AttributeSet; 16 | import android.widget.TextView; 17 | 18 | public class DotsTextView extends TextView { 19 | private static final String TAG = DotsTextView.class.getSimpleName(); 20 | 21 | private JumpingSpan dotOne; 22 | private JumpingSpan dotTwo; 23 | private JumpingSpan dotThree; 24 | 25 | private int jumpHeight; 26 | private int period; 27 | 28 | private AnimatorSet mAnimatorSet = new AnimatorSet(); 29 | 30 | public DotsTextView(Context context) { 31 | super(context); 32 | init(context, null); 33 | } 34 | 35 | public DotsTextView(Context context, AttributeSet attrs) { 36 | super(context, attrs); 37 | init(context, attrs); 38 | } 39 | 40 | public DotsTextView(Context context, AttributeSet attrs, int defStyleAttr) { 41 | super(context, attrs, defStyleAttr); 42 | init(context, attrs); 43 | } 44 | 45 | private void init(Context context, AttributeSet attrs) { 46 | 47 | period = 1000; 48 | jumpHeight = (int) (getTextSize() / 4); 49 | 50 | dotOne = new JumpingSpan(); 51 | dotTwo = new JumpingSpan(); 52 | dotThree = new JumpingSpan(); 53 | 54 | SpannableString spannable = new SpannableString("..."); 55 | //SPAN_EXCLUSIVE_EXCLUSIVE 前后不包括,经常用这个flag 56 | spannable.setSpan(dotOne, 0, 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 57 | spannable.setSpan(dotTwo, 1, 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 58 | spannable.setSpan(dotThree, 2, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 59 | setText(spannable, BufferType.SPANNABLE); 60 | 61 | ObjectAnimator dotOneJumpAnimator = createDotJumpAnimator(dotOne, 0); 62 | //监听绘制每一帧图片时候 63 | dotOneJumpAnimator.addUpdateListener(new AnimatorUpdateListener() { 64 | 65 | @Override 66 | public void onAnimationUpdate(ValueAnimator valueAnimator) { 67 | //此句话可不要,直接重绘就OK 68 | Number number = (Number) valueAnimator.getAnimatedValue(); 69 | invalidate(); 70 | } 71 | }); 72 | //每一个dot执行动画的延迟时间分别时 0,1/6,1/3 73 | mAnimatorSet.playTogether(dotOneJumpAnimator, createDotJumpAnimator(dotTwo, 74 | period / 6), createDotJumpAnimator(dotThree, period * 2 / 6)); 75 | // 76 | start(); 77 | } 78 | 79 | public void start() { 80 | for (Animator animator : mAnimatorSet.getChildAnimations()) { 81 | if (animator instanceof ObjectAnimator) { 82 | ((ObjectAnimator) animator).setRepeatCount(ValueAnimator.INFINITE); 83 | } 84 | } 85 | mAnimatorSet.start(); 86 | } 87 | 88 | private ObjectAnimator createDotJumpAnimator(JumpingSpan jumpingSpan, long delay) { 89 | ObjectAnimator jumpAnimator = ObjectAnimator.ofFloat(jumpingSpan, "translationY", 0, -jumpHeight); 90 | /** 91 | * TimeInterpolator 插值器:计算动画运动时一个跟当前运动到得时间t 和 duration的一个比例因子 92 | * TypeEvaluator 利用 timeInterpolator计算出的因子算出当前运动的位置 93 | * 从TimeInterpolator的getInterpolation中获取到根据当前时间t/duration 的一个比例因子 fraction 94 | * 例如: LinearInterpolator 匀速运动时只是返回未做任何处理 95 | public float getInterpolation(float input) { 96 | return input; 97 | } 98 | 而 AccelerateDecelerateInterpolator 会利用余弦函数计算比例因子,才实现了先加速和减速的效果 99 | public float getInterpolation(float input) { 100 | return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f; 101 | } 102 | 所有的******Interpolator都是实现了TimeInterpolator类中得getInterpolation方法就能设置对应的因子值 103 | TypeEvaluator会接收返回的fraction 104 | 正弦函数轨迹执行每个 dot 动画 105 | 返回的值 会 在addUpdateListener的回调函数中执行 106 | */ 107 | jumpAnimator.setEvaluator(new TypeEvaluator() { 108 | @Override 109 | public Number evaluate(float fraction, Number from, Number to) { 110 | // Log.e(TAG,"from:"+from.floatValue()); 111 | // Log.e(TAG,"to:"+to.floatValue()); 112 | return Math.max(0, Math.sin(fraction * Math.PI * 2)) * (to.floatValue() - from.floatValue()); 113 | } 114 | }); 115 | jumpAnimator.setDuration(period); 116 | jumpAnimator.setStartDelay(delay); 117 | jumpAnimator.setRepeatCount(ValueAnimator.INFINITE); 118 | jumpAnimator.setRepeatMode(ValueAnimator.RESTART); 119 | 120 | return jumpAnimator; 121 | } 122 | 123 | public void stop() { 124 | setAllAnimationsRepeatCount(0); 125 | } 126 | 127 | private void setAllAnimationsRepeatCount(int repeatCount) { 128 | for (Animator animator : mAnimatorSet.getChildAnimations()) { 129 | if (animator instanceof ObjectAnimator) { 130 | ((ObjectAnimator) animator).setRepeatCount(repeatCount); 131 | } 132 | } 133 | } 134 | 135 | public class JumpingSpan extends ReplacementSpan { 136 | 137 | private float translationX = 0; 138 | private float translationY = 0; 139 | 140 | @Override 141 | public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fontMetricsInt) { 142 | return (int) paint.measureText(text, start, end); 143 | } 144 | 145 | @Override 146 | public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) { 147 | canvas.drawText(text, start, end, x + translationX, y + translationY, paint); 148 | } 149 | 150 | public void setTranslationX(float translationX) { 151 | this.translationX = translationX; 152 | } 153 | 154 | public void setTranslationY(float translationY) { 155 | this.translationY = translationY; 156 | } 157 | } 158 | 159 | } 160 | -------------------------------------------------------------------------------- /app/src/main/java/com/brucetoo/autoswiperefesh/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.brucetoo.autoswiperefesh; 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.util.Log; 8 | import android.view.View; 9 | import android.widget.AbsListView; 10 | import android.widget.ListView; 11 | 12 | import com.joanzapata.android.BaseAdapterHelper; 13 | import com.joanzapata.android.QuickAdapter; 14 | 15 | import java.util.Arrays; 16 | 17 | public class MainActivity extends AppCompatActivity implements SwipeRefreshLayout.OnRefreshListener { 18 | 19 | private SwipeRefreshLayout refreshLayout; 20 | private ListView listView; 21 | private QuickAdapter adapter; 22 | private String[] strs = new String[]{"content 1","content 2","content 3","content 4","content 2","content 2","content 2","content 2","content 2","content 2"}; 23 | private View progress; 24 | private boolean visible; 25 | private String TAG = MainActivity.class.getSimpleName(); 26 | private Handler handler = new Handler(new Handler.Callback() { 27 | @Override 28 | public boolean handleMessage(Message msg) { 29 | if(msg.what == 1){ 30 | //刷新数据之前delete footView 31 | if(listView.getFooterViewsCount() != 0) { 32 | listView.removeFooterView(progress); 33 | Log.e(TAG, "removeFooterView"); 34 | } 35 | adapter.addAll(Arrays.asList(strs)); 36 | //刷新数据之后add footView 37 | listView.addFooterView(progress); 38 | Log.e(TAG, "addFooterView"); 39 | } 40 | return false; 41 | } 42 | }); 43 | @Override 44 | protected void onCreate(Bundle savedInstanceState) { 45 | super.onCreate(savedInstanceState); 46 | setContentView(R.layout.activity_main); 47 | 48 | refreshLayout = (SwipeRefreshLayout) this.findViewById(R.id.refresh); 49 | listView = (ListView) this.findViewById(R.id.listview); 50 | 51 | progress = getLayoutInflater().inflate(R.layout.footer_view,null); 52 | adapter = new QuickAdapter(this,R.layout.list_item,Arrays.asList(strs)) { 53 | @Override 54 | protected void convert(BaseAdapterHelper helper, String item) { 55 | helper.setText(R.id.text,item); 56 | } 57 | }; 58 | 59 | //必须先addFooterView 才能再加载完后 delete 60 | listView.addFooterView(progress); 61 | listView.setAdapter(adapter); 62 | refreshLayout.setOnRefreshListener(this); 63 | 64 | refreshLayout.setColorSchemeResources(android.R.color.holo_blue_bright, 65 | android.R.color.holo_green_light); 66 | 67 | listView.setOnScrollListener(new AbsListView.OnScrollListener() { 68 | @Override 69 | public void onScrollStateChanged(AbsListView view, int scrollState) { 70 | // if (scrollState == SCROLL_STATE_IDLE) { 71 | // if (view.getLastVisiblePosition() == adapter.getCount() - 1) { 72 | // listView.addFooterView(progress); 73 | // //imitate getting data 74 | // handler.postDelayed(new Runnable() { 75 | // @Override 76 | // public void run() { 77 | // handler.sendEmptyMessage(1); 78 | // } 79 | // }, 2000); 80 | // } 81 | // } 82 | if(scrollState == SCROLL_STATE_IDLE && visible){ 83 | Log.e(TAG,"onScrollStateChanged:visible-"+visible); 84 | handler.postDelayed(new Runnable() { 85 | @Override 86 | public void run() { 87 | handler.sendEmptyMessage(1); 88 | } 89 | }, 2000); 90 | } 91 | } 92 | 93 | @Override 94 | public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { 95 | //第二种方式 判断是否滚动到最底部 96 | visible = (totalItemCount > 0) && (firstVisibleItem + visibleItemCount >= totalItemCount - 1); 97 | if(visible){ 98 | Log.e(TAG,"onScroll:visible-true"); 99 | } 100 | 101 | //可见的最后一个item的位置 102 | // int lastItem = firstVisibleItem + visibleItemCount; 103 | // if(lastItem == totalItemCount) { //最后一个位置等于当前item的总数 104 | // //最有一个item 顶部 和 listview底部相等时(滑动到最底部),执行刷新逻辑 先删除footview 在数据刷新完后在add 105 | //// View lastItemView = (View) listView.getChildAt(listView.getChildCount() - 1); 106 | //// if ((listView.getBottom()) == lastItemView.getBottom()) { 107 | // if (progress != null && listView.getFooterViewsCount() != 0) { 108 | // handler.postDelayed(new Runnable() { 109 | // @Override 110 | // public void run() { 111 | // handler.sendEmptyMessage(1); 112 | // } 113 | // }, 2000); 114 | // 115 | // } 116 | //// } 117 | // } 118 | } 119 | }); 120 | 121 | } 122 | 123 | @Override 124 | protected void onResume() { 125 | super.onResume(); 126 | //set auto refresh when first reach page 127 | // handler.postDelayed(new Runnable() { 128 | // @Override 129 | // public void run() { 130 | // refreshLayout.setRefreshing(true); 131 | // } 132 | // }, 500); 133 | } 134 | 135 | @Override 136 | public void onRefresh() { 137 | handler.postDelayed(new Runnable() { 138 | @Override 139 | public void run() { 140 | adapter.replaceAll(Arrays.asList(strs)); 141 | refreshLayout.setRefreshing(false); 142 | } 143 | }, 2000); 144 | } 145 | 146 | } 147 | -------------------------------------------------------------------------------- /app/src/main/java/com/brucetoo/autoswiperefesh/MaterialProgressDrawable.java: -------------------------------------------------------------------------------- 1 | package com.brucetoo.autoswiperefesh; 2 | 3 | import android.content.Context; 4 | import android.content.res.Resources; 5 | import android.graphics.Canvas; 6 | import android.graphics.Color; 7 | import android.graphics.ColorFilter; 8 | import android.graphics.Paint; 9 | import android.graphics.Path; 10 | import android.graphics.PixelFormat; 11 | import android.graphics.Rect; 12 | import android.graphics.RectF; 13 | import android.graphics.drawable.Animatable; 14 | import android.graphics.drawable.Drawable; 15 | import android.support.annotation.IntDef; 16 | import android.support.annotation.NonNull; 17 | import android.support.v4.view.animation.FastOutSlowInInterpolator; 18 | import android.util.DisplayMetrics; 19 | import android.view.View; 20 | import android.view.animation.Animation; 21 | import android.view.animation.Interpolator; 22 | import android.view.animation.LinearInterpolator; 23 | import android.view.animation.Transformation; 24 | 25 | import java.lang.annotation.Retention; 26 | import java.lang.annotation.RetentionPolicy; 27 | import java.util.ArrayList; 28 | 29 | /** 30 | * Created by Bruce Too 31 | * On 8/7/15. 32 | * At 14:02 33 | */ 34 | public class MaterialProgressDrawable extends Drawable implements Animatable { 35 | private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator(); 36 | private static final Interpolator MATERIAL_INTERPOLATOR = new FastOutSlowInInterpolator(); 37 | 38 | private static final float FULL_ROTATION = 1080.0f; 39 | @Retention(RetentionPolicy.CLASS) 40 | @IntDef({LARGE, DEFAULT}) 41 | public @interface ProgressDrawableSize {} 42 | // Maps to ProgressBar.Large style 43 | static final int LARGE = 0; 44 | // Maps to ProgressBar default style 45 | static final int DEFAULT = 1; 46 | 47 | // Maps to ProgressBar default style 48 | private static final int CIRCLE_DIAMETER = 40; 49 | private static final float CENTER_RADIUS = 8.75f; //should add up to 10 when + stroke_width 50 | private static final float STROKE_WIDTH = 2.5f; 51 | 52 | // Maps to ProgressBar.Large style 53 | private static final int CIRCLE_DIAMETER_LARGE = 56; 54 | private static final float CENTER_RADIUS_LARGE = 12.5f; 55 | private static final float STROKE_WIDTH_LARGE = 3f; 56 | 57 | private final int[] COLORS = new int[] { 58 | Color.BLACK 59 | }; 60 | 61 | /** 62 | * The value in the linear interpolator for animating the drawable at which 63 | * the color transition should start 64 | */ 65 | private static final float COLOR_START_DELAY_OFFSET = 0.75f; 66 | private static final float END_TRIM_START_DELAY_OFFSET = 0.5f; 67 | private static final float START_TRIM_DURATION_OFFSET = 0.5f; 68 | 69 | /** The duration of a single progress spin in milliseconds. */ 70 | private static final int ANIMATION_DURATION = 1332; 71 | 72 | /** The number of points in the progress "star". */ 73 | private static final float NUM_POINTS = 5f; 74 | /** The list of animators operating on this drawable. */ 75 | private final ArrayList mAnimators = new ArrayList(); 76 | 77 | /** The indicator ring, used to manage animation state. */ 78 | private final Ring mRing; 79 | 80 | /** Canvas rotation in degrees. */ 81 | private float mRotation; 82 | 83 | /** Layout info for the arrowhead in dp */ 84 | private static final int ARROW_WIDTH = 10; 85 | private static final int ARROW_HEIGHT = 5; 86 | private static final float ARROW_OFFSET_ANGLE = 5; 87 | 88 | /** Layout info for the arrowhead for the large spinner in dp */ 89 | private static final int ARROW_WIDTH_LARGE = 12; 90 | private static final int ARROW_HEIGHT_LARGE = 6; 91 | private static final float MAX_PROGRESS_ARC = .8f; 92 | 93 | private Resources mResources; 94 | private View mParent; 95 | private Animation mAnimation; 96 | private float mRotationCount; 97 | private double mWidth; 98 | private double mHeight; 99 | boolean mFinishing; 100 | 101 | public MaterialProgressDrawable(Context context, View parent) { 102 | mParent = parent; 103 | mResources = context.getResources(); 104 | 105 | mRing = new Ring(mCallback); 106 | mRing.setColors(COLORS); 107 | 108 | updateSizes(DEFAULT); 109 | setupAnimators(); 110 | } 111 | 112 | private void setSizeParameters(double progressCircleWidth, double progressCircleHeight, 113 | double centerRadius, double strokeWidth, float arrowWidth, float arrowHeight) { 114 | final Ring ring = mRing; 115 | final DisplayMetrics metrics = mResources.getDisplayMetrics(); 116 | final float screenDensity = metrics.density; 117 | 118 | mWidth = progressCircleWidth * screenDensity; 119 | mHeight = progressCircleHeight * screenDensity; 120 | ring.setStrokeWidth((float) strokeWidth * screenDensity); 121 | ring.setCenterRadius(centerRadius * screenDensity); 122 | ring.setColorIndex(0); 123 | ring.setArrowDimensions(arrowWidth * screenDensity, arrowHeight * screenDensity); 124 | ring.setInsets((int) mWidth, (int) mHeight); 125 | } 126 | 127 | /** 128 | * Set the overall size for the progress spinner. This updates the radius 129 | * and stroke width of the ring. 130 | * 131 | * @param size One of {@link MaterialProgressDrawable.LARGE} or 132 | * {@link MaterialProgressDrawable.DEFAULT} 133 | */ 134 | public void updateSizes(@ProgressDrawableSize int size) { 135 | if (size == LARGE) { 136 | setSizeParameters(CIRCLE_DIAMETER_LARGE, CIRCLE_DIAMETER_LARGE, CENTER_RADIUS_LARGE, 137 | STROKE_WIDTH_LARGE, ARROW_WIDTH_LARGE, ARROW_HEIGHT_LARGE); 138 | } else { 139 | setSizeParameters(CIRCLE_DIAMETER, CIRCLE_DIAMETER, CENTER_RADIUS, STROKE_WIDTH, 140 | ARROW_WIDTH, ARROW_HEIGHT); 141 | } 142 | } 143 | 144 | /** 145 | * @param show Set to true to display the arrowhead on the progress spinner. 146 | */ 147 | public void showArrow(boolean show) { 148 | mRing.setShowArrow(show); 149 | } 150 | 151 | /** 152 | * @param scale Set the scale of the arrowhead for the spinner. 153 | */ 154 | public void setArrowScale(float scale) { 155 | mRing.setArrowScale(scale); 156 | } 157 | 158 | /** 159 | * Set the start and end trim for the progress spinner arc. 160 | * 161 | * @param startAngle start angle 162 | * @param endAngle end angle 163 | */ 164 | public void setStartEndTrim(float startAngle, float endAngle) { 165 | mRing.setStartTrim(startAngle); 166 | mRing.setEndTrim(endAngle); 167 | } 168 | 169 | /** 170 | * Set the amount of rotation to apply to the progress spinner. 171 | * 172 | * @param rotation Rotation is from [0..1] 173 | */ 174 | public void setProgressRotation(float rotation) { 175 | mRing.setRotation(rotation); 176 | } 177 | 178 | /** 179 | * Update the background color of the circle image view. 180 | */ 181 | public void setBackgroundColor(int color) { 182 | mRing.setBackgroundColor(color); 183 | } 184 | 185 | /** 186 | * Set the colors used in the progress animation from color resources. 187 | * The first color will also be the color of the bar that grows in response 188 | * to a user swipe gesture. 189 | * 190 | * @param colors 191 | */ 192 | public void setColorSchemeColors(int... colors) { 193 | mRing.setColors(colors); 194 | mRing.setColorIndex(0); 195 | } 196 | 197 | @Override 198 | public int getIntrinsicHeight() { 199 | return (int) mHeight; 200 | } 201 | 202 | @Override 203 | public int getIntrinsicWidth() { 204 | return (int) mWidth; 205 | } 206 | 207 | @Override 208 | public void draw(Canvas c) { 209 | final Rect bounds = getBounds(); 210 | final int saveCount = c.save(); 211 | c.rotate(mRotation, bounds.exactCenterX(), bounds.exactCenterY()); 212 | mRing.draw(c, bounds); 213 | c.restoreToCount(saveCount); 214 | } 215 | 216 | @Override 217 | public void setAlpha(int alpha) { 218 | mRing.setAlpha(alpha); 219 | } 220 | 221 | public int getAlpha() { 222 | return mRing.getAlpha(); 223 | } 224 | 225 | @Override 226 | public void setColorFilter(ColorFilter colorFilter) { 227 | mRing.setColorFilter(colorFilter); 228 | } 229 | 230 | @SuppressWarnings("unused") 231 | void setRotation(float rotation) { 232 | mRotation = rotation; 233 | invalidateSelf(); 234 | } 235 | 236 | @SuppressWarnings("unused") 237 | private float getRotation() { 238 | return mRotation; 239 | } 240 | 241 | @Override 242 | public int getOpacity() { 243 | return PixelFormat.TRANSLUCENT; 244 | } 245 | 246 | @Override 247 | public boolean isRunning() { 248 | final ArrayList animators = mAnimators; 249 | final int N = animators.size(); 250 | for (int i = 0; i < N; i++) { 251 | final Animation animator = animators.get(i); 252 | if (animator.hasStarted() && !animator.hasEnded()) { 253 | return true; 254 | } 255 | } 256 | return false; 257 | } 258 | 259 | @Override 260 | public void start() { 261 | mAnimation.reset(); 262 | mRing.storeOriginals(); 263 | // Already showing some part of the ring 264 | if (mRing.getEndTrim() != mRing.getStartTrim()) { 265 | mFinishing = true; 266 | mAnimation.setDuration(ANIMATION_DURATION/2); 267 | mParent.startAnimation(mAnimation); 268 | } else { 269 | mRing.setColorIndex(0); 270 | mRing.resetOriginals(); 271 | mAnimation.setDuration(ANIMATION_DURATION); 272 | mParent.startAnimation(mAnimation); 273 | } 274 | } 275 | 276 | @Override 277 | public void stop() { 278 | mParent.clearAnimation(); 279 | setRotation(0); 280 | mRing.setShowArrow(false); 281 | mRing.setColorIndex(0); 282 | mRing.resetOriginals(); 283 | } 284 | 285 | private float getMinProgressArc(Ring ring) { 286 | return (float) Math.toRadians( 287 | ring.getStrokeWidth() / (2 * Math.PI * ring.getCenterRadius())); 288 | } 289 | 290 | // Adapted from ArgbEvaluator.java 291 | private int evaluateColorChange(float fraction, int startValue, int endValue) { 292 | int startInt = (Integer) startValue; 293 | int startA = (startInt >> 24) & 0xff; 294 | int startR = (startInt >> 16) & 0xff; 295 | int startG = (startInt >> 8) & 0xff; 296 | int startB = startInt & 0xff; 297 | 298 | int endInt = (Integer) endValue; 299 | int endA = (endInt >> 24) & 0xff; 300 | int endR = (endInt >> 16) & 0xff; 301 | int endG = (endInt >> 8) & 0xff; 302 | int endB = endInt & 0xff; 303 | 304 | return (int)((startA + (int)(fraction * (endA - startA))) << 24) | 305 | (int)((startR + (int)(fraction * (endR - startR))) << 16) | 306 | (int)((startG + (int)(fraction * (endG - startG))) << 8) | 307 | (int)((startB + (int)(fraction * (endB - startB)))); 308 | } 309 | 310 | /** 311 | * Update the ring color if this is within the last 25% of the animation. 312 | * The new ring color will be a translation from the starting ring color to 313 | * the next color. 314 | */ 315 | private void updateRingColor(float interpolatedTime, Ring ring) { 316 | if (interpolatedTime > COLOR_START_DELAY_OFFSET) { 317 | // scale the interpolatedTime so that the full 318 | // transformation from 0 - 1 takes place in the 319 | // remaining time 320 | ring.setColor(evaluateColorChange((interpolatedTime - COLOR_START_DELAY_OFFSET) 321 | / (1.0f - COLOR_START_DELAY_OFFSET), ring.getStartingColor(), 322 | ring.getNextColor())); 323 | } 324 | } 325 | 326 | private void applyFinishTranslation(float interpolatedTime, Ring ring) { 327 | // shrink back down and complete a full rotation before 328 | // starting other circles 329 | // Rotation goes between [0..1]. 330 | updateRingColor(interpolatedTime, ring); 331 | float targetRotation = (float) (Math.floor(ring.getStartingRotation() / MAX_PROGRESS_ARC) 332 | + 1f); 333 | final float minProgressArc = getMinProgressArc(ring); 334 | final float startTrim = ring.getStartingStartTrim() 335 | + (ring.getStartingEndTrim() - minProgressArc - ring.getStartingStartTrim()) 336 | * interpolatedTime; 337 | ring.setStartTrim(startTrim); 338 | ring.setEndTrim(ring.getStartingEndTrim()); 339 | final float rotation = ring.getStartingRotation() 340 | + ((targetRotation - ring.getStartingRotation()) * interpolatedTime); 341 | ring.setRotation(rotation); 342 | } 343 | 344 | private void setupAnimators() { 345 | final Ring ring = mRing; 346 | final Animation animation = new Animation() { 347 | @Override 348 | public void applyTransformation(float interpolatedTime, Transformation t) { 349 | if (mFinishing) { 350 | applyFinishTranslation(interpolatedTime, ring); 351 | } else { 352 | // The minProgressArc is calculated from 0 to create an 353 | // angle that matches the stroke width. 354 | final float minProgressArc = getMinProgressArc(ring); 355 | final float startingEndTrim = ring.getStartingEndTrim(); 356 | final float startingTrim = ring.getStartingStartTrim(); 357 | final float startingRotation = ring.getStartingRotation(); 358 | 359 | updateRingColor(interpolatedTime, ring); 360 | 361 | // Moving the start trim only occurs in the first 50% of a 362 | // single ring animation 363 | if (interpolatedTime <= START_TRIM_DURATION_OFFSET) { 364 | // scale the interpolatedTime so that the full 365 | // transformation from 0 - 1 takes place in the 366 | // remaining time 367 | final float scaledTime = (interpolatedTime) 368 | / (1.0f - START_TRIM_DURATION_OFFSET); 369 | final float startTrim = startingTrim 370 | + ((MAX_PROGRESS_ARC - minProgressArc) * MATERIAL_INTERPOLATOR 371 | .getInterpolation(scaledTime)); 372 | ring.setStartTrim(startTrim); 373 | } 374 | 375 | // Moving the end trim starts after 50% of a single ring 376 | // animation completes 377 | if (interpolatedTime > END_TRIM_START_DELAY_OFFSET) { 378 | // scale the interpolatedTime so that the full 379 | // transformation from 0 - 1 takes place in the 380 | // remaining time 381 | final float minArc = MAX_PROGRESS_ARC - minProgressArc; 382 | float scaledTime = (interpolatedTime - START_TRIM_DURATION_OFFSET) 383 | / (1.0f - START_TRIM_DURATION_OFFSET); 384 | final float endTrim = startingEndTrim 385 | + (minArc * MATERIAL_INTERPOLATOR.getInterpolation(scaledTime)); 386 | ring.setEndTrim(endTrim); 387 | } 388 | 389 | final float rotation = startingRotation + (0.25f * interpolatedTime); 390 | ring.setRotation(rotation); 391 | 392 | float groupRotation = ((FULL_ROTATION / NUM_POINTS) * interpolatedTime) 393 | + (FULL_ROTATION * (mRotationCount / NUM_POINTS)); 394 | setRotation(groupRotation); 395 | } 396 | } 397 | }; 398 | animation.setRepeatCount(Animation.INFINITE); 399 | animation.setRepeatMode(Animation.RESTART); 400 | animation.setInterpolator(LINEAR_INTERPOLATOR); 401 | animation.setAnimationListener(new Animation.AnimationListener() { 402 | 403 | @Override 404 | public void onAnimationStart(Animation animation) { 405 | mRotationCount = 0; 406 | } 407 | 408 | @Override 409 | public void onAnimationEnd(Animation animation) { 410 | // do nothing 411 | } 412 | 413 | @Override 414 | public void onAnimationRepeat(Animation animation) { 415 | ring.storeOriginals(); 416 | ring.goToNextColor(); 417 | ring.setStartTrim(ring.getEndTrim()); 418 | if (mFinishing) { 419 | // finished closing the last ring from the swipe gesture; go 420 | // into progress mode 421 | mFinishing = false; 422 | animation.setDuration(ANIMATION_DURATION); 423 | ring.setShowArrow(false); 424 | } else { 425 | mRotationCount = (mRotationCount + 1) % (NUM_POINTS); 426 | } 427 | } 428 | }); 429 | mAnimation = animation; 430 | } 431 | 432 | private final Callback mCallback = new Callback() { 433 | @Override 434 | public void invalidateDrawable(Drawable d) { 435 | invalidateSelf(); 436 | } 437 | 438 | @Override 439 | public void scheduleDrawable(Drawable d, Runnable what, long when) { 440 | scheduleSelf(what, when); 441 | } 442 | 443 | @Override 444 | public void unscheduleDrawable(Drawable d, Runnable what) { 445 | unscheduleSelf(what); 446 | } 447 | }; 448 | 449 | private static class Ring { 450 | private final RectF mTempBounds = new RectF(); 451 | private final Paint mPaint = new Paint(); 452 | private final Paint mArrowPaint = new Paint(); 453 | 454 | private final Callback mCallback; 455 | 456 | private float mStartTrim = 0.0f; 457 | private float mEndTrim = 0.0f; 458 | private float mRotation = 0.0f; 459 | private float mStrokeWidth = 5.0f; 460 | private float mStrokeInset = 2.5f; 461 | 462 | private int[] mColors; 463 | // mColorIndex represents the offset into the available mColors that the 464 | // progress circle should currently display. As the progress circle is 465 | // animating, the mColorIndex moves by one to the next available color. 466 | private int mColorIndex; 467 | private float mStartingStartTrim; 468 | private float mStartingEndTrim; 469 | private float mStartingRotation; 470 | private boolean mShowArrow; 471 | private Path mArrow; 472 | private float mArrowScale; 473 | private double mRingCenterRadius; 474 | private int mArrowWidth; 475 | private int mArrowHeight; 476 | private int mAlpha; 477 | private final Paint mCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG); 478 | private int mBackgroundColor; 479 | private int mCurrentColor; 480 | 481 | public Ring(Callback callback) { 482 | mCallback = callback; 483 | 484 | mPaint.setStrokeCap(Paint.Cap.SQUARE); 485 | mPaint.setAntiAlias(true); 486 | mPaint.setStyle(Paint.Style.STROKE); 487 | 488 | mArrowPaint.setStyle(Paint.Style.FILL); 489 | mArrowPaint.setAntiAlias(true); 490 | } 491 | 492 | public void setBackgroundColor(int color) { 493 | mBackgroundColor = color; 494 | } 495 | 496 | /** 497 | * Set the dimensions of the arrowhead. 498 | * 499 | * @param width Width of the hypotenuse of the arrow head 500 | * @param height Height of the arrow point 501 | */ 502 | public void setArrowDimensions(float width, float height) { 503 | mArrowWidth = (int) width; 504 | mArrowHeight = (int) height; 505 | } 506 | 507 | /** 508 | * Draw the progress spinner 509 | */ 510 | public void draw(Canvas c, Rect bounds) { 511 | final RectF arcBounds = mTempBounds; 512 | arcBounds.set(bounds); 513 | arcBounds.inset(mStrokeInset, mStrokeInset); 514 | 515 | final float startAngle = (mStartTrim + mRotation) * 360; 516 | final float endAngle = (mEndTrim + mRotation) * 360; 517 | float sweepAngle = endAngle - startAngle; 518 | 519 | mPaint.setColor(mCurrentColor); 520 | c.drawArc(arcBounds, startAngle, sweepAngle, false, mPaint); 521 | 522 | drawTriangle(c, startAngle, sweepAngle, bounds); 523 | 524 | if (mAlpha < 255) { 525 | mCirclePaint.setColor(mBackgroundColor); 526 | mCirclePaint.setAlpha(255 - mAlpha); 527 | c.drawCircle(bounds.exactCenterX(), bounds.exactCenterY(), bounds.width() / 2, 528 | mCirclePaint); 529 | } 530 | } 531 | 532 | private void drawTriangle(Canvas c, float startAngle, float sweepAngle, Rect bounds) { 533 | if (mShowArrow) { 534 | if (mArrow == null) { 535 | mArrow = new Path(); 536 | mArrow.setFillType(Path.FillType.EVEN_ODD); 537 | } else { 538 | mArrow.reset(); 539 | } 540 | 541 | // Adjust the position of the triangle so that it is inset as 542 | // much as the arc, but also centered on the arc. 543 | float inset = (int) mStrokeInset / 2 * mArrowScale; 544 | float x = (float) (mRingCenterRadius * Math.cos(0) + bounds.exactCenterX()); 545 | float y = (float) (mRingCenterRadius * Math.sin(0) + bounds.exactCenterY()); 546 | 547 | // Update the path each time. This works around an issue in SKIA 548 | // where concatenating a rotation matrix to a scale matrix 549 | // ignored a starting negative rotation. This appears to have 550 | // been fixed as of API 21. 551 | mArrow.moveTo(0, 0); 552 | mArrow.lineTo(mArrowWidth * mArrowScale, 0); 553 | mArrow.lineTo((mArrowWidth * mArrowScale / 2), (mArrowHeight 554 | * mArrowScale)); 555 | mArrow.offset(x - inset, y); 556 | mArrow.close(); 557 | // draw a triangle 558 | mArrowPaint.setColor(mCurrentColor); 559 | c.rotate(startAngle + sweepAngle - ARROW_OFFSET_ANGLE, bounds.exactCenterX(), 560 | bounds.exactCenterY()); 561 | c.drawPath(mArrow, mArrowPaint); 562 | } 563 | } 564 | 565 | /** 566 | * Set the colors the progress spinner alternates between. 567 | * 568 | * @param colors Array of integers describing the colors. Must be non-null. 569 | */ 570 | public void setColors(@NonNull int[] colors) { 571 | mColors = colors; 572 | // if colors are reset, make sure to reset the color index as well 573 | setColorIndex(0); 574 | } 575 | 576 | /** 577 | * Set the absolute color of the progress spinner. This is should only 578 | * be used when animating between current and next color when the 579 | * spinner is rotating. 580 | * 581 | * @param color int describing the color. 582 | */ 583 | public void setColor(int color) { 584 | mCurrentColor = color; 585 | } 586 | 587 | /** 588 | * @param index Index into the color array of the color to display in 589 | * the progress spinner. 590 | */ 591 | public void setColorIndex(int index) { 592 | mColorIndex = index; 593 | mCurrentColor = mColors[mColorIndex]; 594 | } 595 | 596 | /** 597 | * @return int describing the next color the progress spinner should use when drawing. 598 | */ 599 | public int getNextColor() { 600 | return mColors[getNextColorIndex()]; 601 | } 602 | 603 | private int getNextColorIndex() { 604 | return (mColorIndex + 1) % (mColors.length); 605 | } 606 | 607 | /** 608 | * Proceed to the next available ring color. This will automatically 609 | * wrap back to the beginning of colors. 610 | */ 611 | public void goToNextColor() { 612 | setColorIndex(getNextColorIndex()); 613 | } 614 | 615 | public void setColorFilter(ColorFilter filter) { 616 | mPaint.setColorFilter(filter); 617 | invalidateSelf(); 618 | } 619 | 620 | /** 621 | * @param alpha Set the alpha of the progress spinner and associated arrowhead. 622 | */ 623 | public void setAlpha(int alpha) { 624 | mAlpha = alpha; 625 | } 626 | 627 | /** 628 | * @return Current alpha of the progress spinner and arrowhead. 629 | */ 630 | public int getAlpha() { 631 | return mAlpha; 632 | } 633 | 634 | /** 635 | * @param strokeWidth Set the stroke width of the progress spinner in pixels. 636 | */ 637 | public void setStrokeWidth(float strokeWidth) { 638 | mStrokeWidth = strokeWidth; 639 | mPaint.setStrokeWidth(strokeWidth); 640 | invalidateSelf(); 641 | } 642 | 643 | @SuppressWarnings("unused") 644 | public float getStrokeWidth() { 645 | return mStrokeWidth; 646 | } 647 | 648 | @SuppressWarnings("unused") 649 | public void setStartTrim(float startTrim) { 650 | mStartTrim = startTrim; 651 | invalidateSelf(); 652 | } 653 | 654 | @SuppressWarnings("unused") 655 | public float getStartTrim() { 656 | return mStartTrim; 657 | } 658 | 659 | public float getStartingStartTrim() { 660 | return mStartingStartTrim; 661 | } 662 | 663 | public float getStartingEndTrim() { 664 | return mStartingEndTrim; 665 | } 666 | 667 | public int getStartingColor() { 668 | return mColors[mColorIndex]; 669 | } 670 | 671 | @SuppressWarnings("unused") 672 | public void setEndTrim(float endTrim) { 673 | mEndTrim = endTrim; 674 | invalidateSelf(); 675 | } 676 | 677 | @SuppressWarnings("unused") 678 | public float getEndTrim() { 679 | return mEndTrim; 680 | } 681 | 682 | @SuppressWarnings("unused") 683 | public void setRotation(float rotation) { 684 | mRotation = rotation; 685 | invalidateSelf(); 686 | } 687 | 688 | @SuppressWarnings("unused") 689 | public float getRotation() { 690 | return mRotation; 691 | } 692 | 693 | public void setInsets(int width, int height) { 694 | final float minEdge = (float) Math.min(width, height); 695 | float insets; 696 | if (mRingCenterRadius <= 0 || minEdge < 0) { 697 | insets = (float) Math.ceil(mStrokeWidth / 2.0f); 698 | } else { 699 | insets = (float) (minEdge / 2.0f - mRingCenterRadius); 700 | } 701 | mStrokeInset = insets; 702 | } 703 | 704 | @SuppressWarnings("unused") 705 | public float getInsets() { 706 | return mStrokeInset; 707 | } 708 | 709 | /** 710 | * @param centerRadius Inner radius in px of the circle the progress 711 | * spinner arc traces. 712 | */ 713 | public void setCenterRadius(double centerRadius) { 714 | mRingCenterRadius = centerRadius; 715 | } 716 | 717 | public double getCenterRadius() { 718 | return mRingCenterRadius; 719 | } 720 | 721 | /** 722 | * @param show Set to true to show the arrow head on the progress spinner. 723 | */ 724 | public void setShowArrow(boolean show) { 725 | if (mShowArrow != show) { 726 | mShowArrow = show; 727 | invalidateSelf(); 728 | } 729 | } 730 | 731 | /** 732 | * @param scale Set the scale of the arrowhead for the spinner. 733 | */ 734 | public void setArrowScale(float scale) { 735 | if (scale != mArrowScale) { 736 | mArrowScale = scale; 737 | invalidateSelf(); 738 | } 739 | } 740 | 741 | /** 742 | * @return The amount the progress spinner is currently rotated, between [0..1]. 743 | */ 744 | public float getStartingRotation() { 745 | return mStartingRotation; 746 | } 747 | 748 | /** 749 | * If the start / end trim are offset to begin with, store them so that 750 | * animation starts from that offset. 751 | */ 752 | public void storeOriginals() { 753 | mStartingStartTrim = mStartTrim; 754 | mStartingEndTrim = mEndTrim; 755 | mStartingRotation = mRotation; 756 | } 757 | 758 | /** 759 | * Reset the progress spinner to default rotation, start and end angles. 760 | */ 761 | public void resetOriginals() { 762 | mStartingStartTrim = 0; 763 | mStartingEndTrim = 0; 764 | mStartingRotation = 0; 765 | setStartTrim(0); 766 | setEndTrim(0); 767 | setRotation(0); 768 | } 769 | 770 | private void invalidateSelf() { 771 | mCallback.invalidateDrawable(null); 772 | } 773 | } 774 | } 775 | -------------------------------------------------------------------------------- /app/src/main/java/com/brucetoo/autoswiperefesh/SwipeRefreshLayout.java: -------------------------------------------------------------------------------- 1 | package com.brucetoo.autoswiperefesh; 2 | 3 | import android.content.Context; 4 | import android.content.res.Resources; 5 | import android.content.res.TypedArray; 6 | import android.support.v4.view.MotionEventCompat; 7 | import android.support.v4.view.ViewCompat; 8 | import android.util.AttributeSet; 9 | import android.util.DisplayMetrics; 10 | import android.util.Log; 11 | import android.view.MotionEvent; 12 | import android.view.View; 13 | import android.view.ViewConfiguration; 14 | import android.view.ViewGroup; 15 | import android.view.animation.Animation; 16 | import android.view.animation.DecelerateInterpolator; 17 | import android.view.animation.Transformation; 18 | import android.widget.AbsListView; 19 | 20 | /** 21 | * Created by Bruce Too 22 | * On 8/7/15. 23 | * At 11:42 24 | * All source code from support v4 SwipeRefreshLayout 25 | * just modify {@param mNotify} 26 | * when first reach one page use SwipeRefreshLayout,i may just want it to be refresh automatically 27 | * but former code need user pull to refresh 28 | * see code method {@link #setRefreshing(boolean)} 29 | * and set mNotify = true; 30 | * Then u see {@param mRefreshListener} 31 | * if (mNotify) { //make callback available here ,then u can auto refresh now 32 | if (mListener != null) { 33 | mListener.onRefresh(); 34 | } 35 | } 36 | */ 37 | public class SwipeRefreshLayout extends ViewGroup { 38 | // Maps to ProgressBar.Large style 39 | public static final int LARGE = MaterialProgressDrawable.LARGE; 40 | // Maps to ProgressBar default style 41 | public static final int DEFAULT = MaterialProgressDrawable.DEFAULT; 42 | 43 | private static final String LOG_TAG = SwipeRefreshLayout.class.getSimpleName(); 44 | 45 | private static final int MAX_ALPHA = 255; 46 | private static final int STARTING_PROGRESS_ALPHA = (int) (.3f * MAX_ALPHA); 47 | 48 | private static final int CIRCLE_DIAMETER = 40; 49 | private static final int CIRCLE_DIAMETER_LARGE = 56; 50 | 51 | private static final float DECELERATE_INTERPOLATION_FACTOR = 2f; 52 | private static final int INVALID_POINTER = -1; 53 | private static final float DRAG_RATE = .5f; 54 | 55 | // Max amount of circle that can be filled by progress during swipe gesture, 56 | // where 1.0 is a full circle 57 | private static final float MAX_PROGRESS_ANGLE = .8f; 58 | 59 | private static final int SCALE_DOWN_DURATION = 150; 60 | 61 | private static final int ALPHA_ANIMATION_DURATION = 300; 62 | 63 | private static final int ANIMATE_TO_TRIGGER_DURATION = 200; 64 | 65 | private static final int ANIMATE_TO_START_DURATION = 200; 66 | 67 | // Default background for the progress spinner 68 | private static final int CIRCLE_BG_LIGHT = 0xFFFAFAFA; 69 | // Default offset in dips from the top of the view to where the progress spinner should stop 70 | private static final int DEFAULT_CIRCLE_TARGET = 64; 71 | 72 | private View mTarget; // the target of the gesture 73 | private OnRefreshListener mListener; 74 | private boolean mRefreshing = false; 75 | private int mTouchSlop; 76 | private float mTotalDragDistance = -1; 77 | private int mMediumAnimationDuration; 78 | private int mCurrentTargetOffsetTop; 79 | // Whether or not the starting offset has been determined. 80 | private boolean mOriginalOffsetCalculated = false; 81 | 82 | private float mInitialMotionY; 83 | private float mInitialDownY; 84 | private boolean mIsBeingDragged; 85 | private int mActivePointerId = INVALID_POINTER; 86 | // Whether this item is scaled up rather than clipped 87 | private boolean mScale; 88 | 89 | // Target is returning to its start offset because it was cancelled or a 90 | // refresh was triggered. 91 | private boolean mReturningToStart; 92 | private final DecelerateInterpolator mDecelerateInterpolator; 93 | private static final int[] LAYOUT_ATTRS = new int[] { 94 | android.R.attr.enabled 95 | }; 96 | 97 | private CircleImageView mCircleView; 98 | private int mCircleViewIndex = -1; 99 | 100 | protected int mFrom; 101 | 102 | private float mStartingScale; 103 | 104 | protected int mOriginalOffsetTop; 105 | 106 | private MaterialProgressDrawable mProgress; 107 | 108 | private Animation mScaleAnimation; 109 | 110 | private Animation mScaleDownAnimation; 111 | 112 | private Animation mAlphaStartAnimation; 113 | 114 | private Animation mAlphaMaxAnimation; 115 | 116 | private Animation mScaleDownToStartAnimation; 117 | 118 | private float mSpinnerFinalOffset; 119 | 120 | private boolean mNotify; 121 | 122 | private int mCircleWidth; 123 | 124 | private int mCircleHeight; 125 | 126 | // Whether the client has set a custom starting position; 127 | private boolean mUsingCustomStart; 128 | 129 | private Animation.AnimationListener mRefreshListener = new Animation.AnimationListener() { 130 | @Override 131 | public void onAnimationStart(Animation animation) { 132 | } 133 | 134 | @Override 135 | public void onAnimationRepeat(Animation animation) { 136 | } 137 | 138 | @Override 139 | public void onAnimationEnd(Animation animation) { 140 | if (mRefreshing) { 141 | // Make sure the progress view is fully visible 142 | mProgress.setAlpha(MAX_ALPHA); 143 | mProgress.start(); 144 | if (mNotify) { 145 | if (mListener != null) { 146 | mListener.onRefresh(); 147 | } 148 | } 149 | } else { 150 | mProgress.stop(); 151 | mCircleView.setVisibility(View.GONE); 152 | setColorViewAlpha(MAX_ALPHA); 153 | // Return the circle to its start position 154 | if (mScale) { 155 | setAnimationProgress(0 /* animation complete and view is hidden */); 156 | } else { 157 | setTargetOffsetTopAndBottom(mOriginalOffsetTop - mCurrentTargetOffsetTop, 158 | true /* requires update */); 159 | } 160 | } 161 | mCurrentTargetOffsetTop = mCircleView.getTop(); 162 | } 163 | }; 164 | 165 | private void setColorViewAlpha(int targetAlpha) { 166 | mCircleView.getBackground().setAlpha(targetAlpha); 167 | mProgress.setAlpha(targetAlpha); 168 | } 169 | 170 | /** 171 | * The refresh indicator starting and resting position is always positioned 172 | * near the top of the refreshing content. This position is a consistent 173 | * location, but can be adjusted in either direction based on whether or not 174 | * there is a toolbar or actionbar present. 175 | * 176 | * @param scale Set to true if there is no view at a higher z-order than 177 | * where the progress spinner is set to appear. 178 | * @param start The offset in pixels from the top of this view at which the 179 | * progress spinner should appear. 180 | * @param end The offset in pixels from the top of this view at which the 181 | * progress spinner should come to rest after a successful swipe 182 | * gesture. 183 | */ 184 | public void setProgressViewOffset(boolean scale, int start, int end) { 185 | mScale = scale; 186 | mCircleView.setVisibility(View.GONE); 187 | mOriginalOffsetTop = mCurrentTargetOffsetTop = start; 188 | mSpinnerFinalOffset = end; 189 | mUsingCustomStart = true; 190 | mCircleView.invalidate(); 191 | } 192 | 193 | /** 194 | * The refresh indicator resting position is always positioned near the top 195 | * of the refreshing content. This position is a consistent location, but 196 | * can be adjusted in either direction based on whether or not there is a 197 | * toolbar or actionbar present. 198 | * 199 | * @param scale Set to true if there is no view at a higher z-order than 200 | * where the progress spinner is set to appear. 201 | * @param end The offset in pixels from the top of this view at which the 202 | * progress spinner should come to rest after a successful swipe 203 | * gesture. 204 | */ 205 | public void setProgressViewEndTarget(boolean scale, int end) { 206 | mSpinnerFinalOffset = end; 207 | mScale = scale; 208 | mCircleView.invalidate(); 209 | } 210 | 211 | /** 212 | * One of DEFAULT, or LARGE. 213 | */ 214 | public void setSize(int size) { 215 | if (size != MaterialProgressDrawable.LARGE && size != MaterialProgressDrawable.DEFAULT) { 216 | return; 217 | } 218 | final DisplayMetrics metrics = getResources().getDisplayMetrics(); 219 | if (size == MaterialProgressDrawable.LARGE) { 220 | mCircleHeight = mCircleWidth = (int) (CIRCLE_DIAMETER_LARGE * metrics.density); 221 | } else { 222 | mCircleHeight = mCircleWidth = (int) (CIRCLE_DIAMETER * metrics.density); 223 | } 224 | // force the bounds of the progress circle inside the circle view to 225 | // update by setting it to null before updating its size and then 226 | // re-setting it 227 | mCircleView.setImageDrawable(null); 228 | mProgress.updateSizes(size); 229 | mCircleView.setImageDrawable(mProgress); 230 | } 231 | 232 | /** 233 | * Simple constructor to use when creating a SwipeRefreshLayout from code. 234 | * 235 | * @param context 236 | */ 237 | public SwipeRefreshLayout(Context context) { 238 | this(context, null); 239 | } 240 | 241 | /** 242 | * Constructor that is called when inflating SwipeRefreshLayout from XML. 243 | * 244 | * @param context 245 | * @param attrs 246 | */ 247 | public SwipeRefreshLayout(Context context, AttributeSet attrs) { 248 | super(context, attrs); 249 | 250 | mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); 251 | 252 | mMediumAnimationDuration = getResources().getInteger( 253 | android.R.integer.config_mediumAnimTime); 254 | 255 | setWillNotDraw(false); 256 | mDecelerateInterpolator = new DecelerateInterpolator(DECELERATE_INTERPOLATION_FACTOR); 257 | 258 | final TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS); 259 | setEnabled(a.getBoolean(0, true)); 260 | a.recycle(); 261 | 262 | final DisplayMetrics metrics = getResources().getDisplayMetrics(); 263 | mCircleWidth = (int) (CIRCLE_DIAMETER * metrics.density); 264 | mCircleHeight = (int) (CIRCLE_DIAMETER * metrics.density); 265 | 266 | createProgressView(); 267 | ViewCompat.setChildrenDrawingOrderEnabled(this, true); 268 | // the absolute offset has to take into account that the circle starts at an offset 269 | mSpinnerFinalOffset = DEFAULT_CIRCLE_TARGET * metrics.density; 270 | mTotalDragDistance = mSpinnerFinalOffset; 271 | } 272 | 273 | protected int getChildDrawingOrder(int childCount, int i) { 274 | if (mCircleViewIndex < 0) { 275 | return i; 276 | } else if (i == childCount - 1) { 277 | // Draw the selected child last 278 | return mCircleViewIndex; 279 | } else if (i >= mCircleViewIndex) { 280 | // Move the children after the selected child earlier one 281 | return i + 1; 282 | } else { 283 | // Keep the children before the selected child the same 284 | return i; 285 | } 286 | } 287 | 288 | private void createProgressView() { 289 | mCircleView = new CircleImageView(getContext(), CIRCLE_BG_LIGHT, CIRCLE_DIAMETER/2); 290 | mProgress = new MaterialProgressDrawable(getContext(), this); 291 | mProgress.setBackgroundColor(CIRCLE_BG_LIGHT); 292 | mCircleView.setImageDrawable(mProgress); 293 | mCircleView.setVisibility(View.GONE); 294 | addView(mCircleView); 295 | } 296 | 297 | /** 298 | * Set the listener to be notified when a refresh is triggered via the swipe 299 | * gesture. 300 | */ 301 | public void setOnRefreshListener(OnRefreshListener listener) { 302 | mListener = listener; 303 | } 304 | 305 | /** 306 | * Pre API 11, alpha is used to make the progress circle appear instead of scale. 307 | */ 308 | private boolean isAlphaUsedForScale() { 309 | return android.os.Build.VERSION.SDK_INT < 11; 310 | } 311 | 312 | /** 313 | * Notify the widget that refresh state has changed. Do not call this when 314 | * refresh is triggered by a swipe gesture. 315 | * 316 | * @param refreshing Whether or not the view should show refresh progress. 317 | */ 318 | public void setRefreshing(boolean refreshing) { 319 | if (refreshing && mRefreshing != refreshing) { 320 | // scale and show 321 | mRefreshing = refreshing; 322 | int endTarget = 0; 323 | if (!mUsingCustomStart) { 324 | endTarget = (int) (mSpinnerFinalOffset + mOriginalOffsetTop); 325 | } else { 326 | endTarget = (int) mSpinnerFinalOffset; 327 | } 328 | setTargetOffsetTopAndBottom(endTarget - mCurrentTargetOffsetTop, 329 | true /* requires update */); 330 | mNotify = true; 331 | startScaleUpAnimation(mRefreshListener); 332 | } else { 333 | setRefreshing(refreshing, false /* notify */); 334 | } 335 | } 336 | 337 | private void startScaleUpAnimation(Animation.AnimationListener listener) { 338 | mCircleView.setVisibility(View.VISIBLE); 339 | if (android.os.Build.VERSION.SDK_INT >= 11) { 340 | // Pre API 11, alpha is used in place of scale up to show the 341 | // progress circle appearing. 342 | // Don't adjust the alpha during appearance otherwise. 343 | mProgress.setAlpha(MAX_ALPHA); 344 | } 345 | mScaleAnimation = new Animation() { 346 | @Override 347 | public void applyTransformation(float interpolatedTime, Transformation t) { 348 | setAnimationProgress(interpolatedTime); 349 | } 350 | }; 351 | mScaleAnimation.setDuration(mMediumAnimationDuration); 352 | if (listener != null) { 353 | mCircleView.setAnimationListener(listener); 354 | } 355 | mCircleView.clearAnimation(); 356 | mCircleView.startAnimation(mScaleAnimation); 357 | } 358 | 359 | /** 360 | * Pre API 11, this does an alpha animation. 361 | * @param progress 362 | */ 363 | private void setAnimationProgress(float progress) { 364 | if (isAlphaUsedForScale()) { 365 | setColorViewAlpha((int) (progress * MAX_ALPHA)); 366 | } else { 367 | ViewCompat.setScaleX(mCircleView, progress); 368 | ViewCompat.setScaleY(mCircleView, progress); 369 | } 370 | } 371 | 372 | private void setRefreshing(boolean refreshing, final boolean notify) { 373 | if (mRefreshing != refreshing) { 374 | mNotify = notify; 375 | ensureTarget(); 376 | mRefreshing = refreshing; 377 | if (mRefreshing) { 378 | animateOffsetToCorrectPosition(mCurrentTargetOffsetTop, mRefreshListener); 379 | } else { 380 | startScaleDownAnimation(mRefreshListener); 381 | } 382 | } 383 | } 384 | 385 | private void startScaleDownAnimation(Animation.AnimationListener listener) { 386 | mScaleDownAnimation = new Animation() { 387 | @Override 388 | public void applyTransformation(float interpolatedTime, Transformation t) { 389 | setAnimationProgress(1 - interpolatedTime); 390 | } 391 | }; 392 | mScaleDownAnimation.setDuration(SCALE_DOWN_DURATION); 393 | mCircleView.setAnimationListener(listener); 394 | mCircleView.clearAnimation(); 395 | mCircleView.startAnimation(mScaleDownAnimation); 396 | } 397 | 398 | private void startProgressAlphaStartAnimation() { 399 | mAlphaStartAnimation = startAlphaAnimation(mProgress.getAlpha(), STARTING_PROGRESS_ALPHA); 400 | } 401 | 402 | private void startProgressAlphaMaxAnimation() { 403 | mAlphaMaxAnimation = startAlphaAnimation(mProgress.getAlpha(), MAX_ALPHA); 404 | } 405 | 406 | private Animation startAlphaAnimation(final int startingAlpha, final int endingAlpha) { 407 | // Pre API 11, alpha is used in place of scale. Don't also use it to 408 | // show the trigger point. 409 | if (mScale && isAlphaUsedForScale()) { 410 | return null; 411 | } 412 | Animation alpha = new Animation() { 413 | @Override 414 | public void applyTransformation(float interpolatedTime, Transformation t) { 415 | mProgress 416 | .setAlpha((int) (startingAlpha+ ((endingAlpha - startingAlpha) 417 | * interpolatedTime))); 418 | } 419 | }; 420 | alpha.setDuration(ALPHA_ANIMATION_DURATION); 421 | // Clear out the previous animation listeners. 422 | mCircleView.setAnimationListener(null); 423 | mCircleView.clearAnimation(); 424 | mCircleView.startAnimation(alpha); 425 | return alpha; 426 | } 427 | 428 | /** 429 | * @deprecated Use {@link #setProgressBackgroundColorSchemeResource(int)} 430 | */ 431 | @Deprecated 432 | public void setProgressBackgroundColor(int colorRes) { 433 | setProgressBackgroundColorSchemeResource(colorRes); 434 | } 435 | 436 | /** 437 | * Set the background color of the progress spinner disc. 438 | * 439 | * @param colorRes Resource id of the color. 440 | */ 441 | public void setProgressBackgroundColorSchemeResource(int colorRes) { 442 | setProgressBackgroundColorSchemeColor(getResources().getColor(colorRes)); 443 | } 444 | 445 | /** 446 | * Set the background color of the progress spinner disc. 447 | * 448 | * @param color 449 | */ 450 | public void setProgressBackgroundColorSchemeColor(int color) { 451 | mCircleView.setBackgroundColor(color); 452 | mProgress.setBackgroundColor(color); 453 | } 454 | 455 | /** 456 | * @deprecated Use {@link #setColorSchemeResources(int...)} 457 | */ 458 | @Deprecated 459 | public void setColorScheme(int... colors) { 460 | setColorSchemeResources(colors); 461 | } 462 | 463 | /** 464 | * Set the color resources used in the progress animation from color resources. 465 | * The first color will also be the color of the bar that grows in response 466 | * to a user swipe gesture. 467 | * 468 | * @param colorResIds 469 | */ 470 | public void setColorSchemeResources(int... colorResIds) { 471 | final Resources res = getResources(); 472 | int[] colorRes = new int[colorResIds.length]; 473 | for (int i = 0; i < colorResIds.length; i++) { 474 | colorRes[i] = res.getColor(colorResIds[i]); 475 | } 476 | setColorSchemeColors(colorRes); 477 | } 478 | 479 | /** 480 | * Set the colors used in the progress animation. The first 481 | * color will also be the color of the bar that grows in response to a user 482 | * swipe gesture. 483 | * 484 | * @param colors 485 | */ 486 | public void setColorSchemeColors(int... colors) { 487 | ensureTarget(); 488 | mProgress.setColorSchemeColors(colors); 489 | } 490 | 491 | /** 492 | * @return Whether the SwipeRefreshWidget is actively showing refresh 493 | * progress. 494 | */ 495 | public boolean isRefreshing() { 496 | return mRefreshing; 497 | } 498 | 499 | private void ensureTarget() { 500 | // Don't bother getting the parent height if the parent hasn't been laid 501 | // out yet. 502 | if (mTarget == null) { 503 | for (int i = 0; i < getChildCount(); i++) { 504 | View child = getChildAt(i); 505 | if (!child.equals(mCircleView)) { 506 | mTarget = child; 507 | break; 508 | } 509 | } 510 | } 511 | } 512 | 513 | /** 514 | * Set the distance to trigger a sync in dips 515 | * 516 | * @param distance 517 | */ 518 | public void setDistanceToTriggerSync(int distance) { 519 | mTotalDragDistance = distance; 520 | } 521 | 522 | @Override 523 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 524 | final int width = getMeasuredWidth(); 525 | final int height = getMeasuredHeight(); 526 | if (getChildCount() == 0) { 527 | return; 528 | } 529 | if (mTarget == null) { 530 | ensureTarget(); 531 | } 532 | if (mTarget == null) { 533 | return; 534 | } 535 | final View child = mTarget; 536 | final int childLeft = getPaddingLeft(); 537 | final int childTop = getPaddingTop(); 538 | final int childWidth = width - getPaddingLeft() - getPaddingRight(); 539 | final int childHeight = height - getPaddingTop() - getPaddingBottom(); 540 | child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight); 541 | int circleWidth = mCircleView.getMeasuredWidth(); 542 | int circleHeight = mCircleView.getMeasuredHeight(); 543 | mCircleView.layout((width / 2 - circleWidth / 2), mCurrentTargetOffsetTop, 544 | (width / 2 + circleWidth / 2), mCurrentTargetOffsetTop + circleHeight); 545 | } 546 | 547 | @Override 548 | public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 549 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 550 | if (mTarget == null) { 551 | ensureTarget(); 552 | } 553 | if (mTarget == null) { 554 | return; 555 | } 556 | mTarget.measure(MeasureSpec.makeMeasureSpec( 557 | getMeasuredWidth() - getPaddingLeft() - getPaddingRight(), 558 | MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec( 559 | getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), MeasureSpec.EXACTLY)); 560 | mCircleView.measure(MeasureSpec.makeMeasureSpec(mCircleWidth, MeasureSpec.EXACTLY), 561 | MeasureSpec.makeMeasureSpec(mCircleHeight, MeasureSpec.EXACTLY)); 562 | if (!mUsingCustomStart && !mOriginalOffsetCalculated) { 563 | mOriginalOffsetCalculated = true; 564 | mCurrentTargetOffsetTop = mOriginalOffsetTop = -mCircleView.getMeasuredHeight(); 565 | } 566 | mCircleViewIndex = -1; 567 | // Get the index of the circleview. 568 | for (int index = 0; index < getChildCount(); index++) { 569 | if (getChildAt(index) == mCircleView) { 570 | mCircleViewIndex = index; 571 | break; 572 | } 573 | } 574 | } 575 | 576 | /** 577 | * Get the diameter of the progress circle that is displayed as part of the 578 | * swipe to refresh layout. This is not valid until a measure pass has 579 | * completed. 580 | * 581 | * @return Diameter in pixels of the progress circle view. 582 | */ 583 | public int getProgressCircleDiameter() { 584 | return mCircleView != null ?mCircleView.getMeasuredHeight() : 0; 585 | } 586 | 587 | /** 588 | * @return Whether it is possible for the child view of this layout to 589 | * scroll up. Override this if the child view is a custom view. 590 | */ 591 | public boolean canChildScrollUp() { 592 | if (android.os.Build.VERSION.SDK_INT < 14) { 593 | if (mTarget instanceof AbsListView) { 594 | final AbsListView absListView = (AbsListView) mTarget; 595 | return absListView.getChildCount() > 0 596 | && (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0) 597 | .getTop() < absListView.getPaddingTop()); 598 | } else { 599 | return ViewCompat.canScrollVertically(mTarget, -1) || mTarget.getScrollY() > 0; 600 | } 601 | } else { 602 | return ViewCompat.canScrollVertically(mTarget, -1); 603 | } 604 | } 605 | 606 | @Override 607 | public boolean onInterceptTouchEvent(MotionEvent ev) { 608 | ensureTarget(); 609 | 610 | final int action = MotionEventCompat.getActionMasked(ev); 611 | 612 | if (mReturningToStart && action == MotionEvent.ACTION_DOWN) { 613 | mReturningToStart = false; 614 | } 615 | 616 | if (!isEnabled() || mReturningToStart || canChildScrollUp() || mRefreshing) { 617 | // Fail fast if we're not in a state where a swipe is possible 618 | return false; 619 | } 620 | 621 | switch (action) { 622 | case MotionEvent.ACTION_DOWN: 623 | setTargetOffsetTopAndBottom(mOriginalOffsetTop - mCircleView.getTop(), true); 624 | mActivePointerId = MotionEventCompat.getPointerId(ev, 0); 625 | mIsBeingDragged = false; 626 | final float initialDownY = getMotionEventY(ev, mActivePointerId); 627 | if (initialDownY == -1) { 628 | return false; 629 | } 630 | mInitialDownY = initialDownY; 631 | break; 632 | 633 | case MotionEvent.ACTION_MOVE: 634 | if (mActivePointerId == INVALID_POINTER) { 635 | Log.e(LOG_TAG, "Got ACTION_MOVE event but don't have an active pointer id."); 636 | return false; 637 | } 638 | 639 | final float y = getMotionEventY(ev, mActivePointerId); 640 | if (y == -1) { 641 | return false; 642 | } 643 | final float yDiff = y - mInitialDownY; 644 | if (yDiff > mTouchSlop && !mIsBeingDragged) { 645 | mInitialMotionY = mInitialDownY + mTouchSlop; 646 | mIsBeingDragged = true; 647 | mProgress.setAlpha(STARTING_PROGRESS_ALPHA); 648 | } 649 | break; 650 | 651 | case MotionEventCompat.ACTION_POINTER_UP: 652 | onSecondaryPointerUp(ev); 653 | break; 654 | 655 | case MotionEvent.ACTION_UP: 656 | case MotionEvent.ACTION_CANCEL: 657 | mIsBeingDragged = false; 658 | mActivePointerId = INVALID_POINTER; 659 | break; 660 | } 661 | 662 | return mIsBeingDragged; 663 | } 664 | 665 | private float getMotionEventY(MotionEvent ev, int activePointerId) { 666 | final int index = MotionEventCompat.findPointerIndex(ev, activePointerId); 667 | if (index < 0) { 668 | return -1; 669 | } 670 | return MotionEventCompat.getY(ev, index); 671 | } 672 | 673 | @Override 674 | public void requestDisallowInterceptTouchEvent(boolean b) { 675 | // Nope. 676 | } 677 | 678 | private boolean isAnimationRunning(Animation animation) { 679 | return animation != null && animation.hasStarted() && !animation.hasEnded(); 680 | } 681 | 682 | @Override 683 | public boolean onTouchEvent(MotionEvent ev) { 684 | final int action = MotionEventCompat.getActionMasked(ev); 685 | 686 | if (mReturningToStart && action == MotionEvent.ACTION_DOWN) { 687 | mReturningToStart = false; 688 | } 689 | 690 | if (!isEnabled() || mReturningToStart || canChildScrollUp()) { 691 | // Fail fast if we're not in a state where a swipe is possible 692 | return false; 693 | } 694 | 695 | switch (action) { 696 | case MotionEvent.ACTION_DOWN: 697 | mActivePointerId = MotionEventCompat.getPointerId(ev, 0); 698 | mIsBeingDragged = false; 699 | break; 700 | 701 | case MotionEvent.ACTION_MOVE: { 702 | final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId); 703 | if (pointerIndex < 0) { 704 | Log.e(LOG_TAG, "Got ACTION_MOVE event but have an invalid active pointer id."); 705 | return false; 706 | } 707 | 708 | final float y = MotionEventCompat.getY(ev, pointerIndex); 709 | final float overscrollTop = (y - mInitialMotionY) * DRAG_RATE; 710 | if (mIsBeingDragged) { 711 | mProgress.showArrow(true); 712 | float originalDragPercent = overscrollTop / mTotalDragDistance; 713 | if (originalDragPercent < 0) { 714 | return false; 715 | } 716 | float dragPercent = Math.min(1f, Math.abs(originalDragPercent)); 717 | float adjustedPercent = (float) Math.max(dragPercent - .4, 0) * 5 / 3; 718 | float extraOS = Math.abs(overscrollTop) - mTotalDragDistance; 719 | float slingshotDist = mUsingCustomStart ? mSpinnerFinalOffset 720 | - mOriginalOffsetTop : mSpinnerFinalOffset; 721 | float tensionSlingshotPercent = Math.max(0, 722 | Math.min(extraOS, slingshotDist * 2) / slingshotDist); 723 | float tensionPercent = (float) ((tensionSlingshotPercent / 4) - Math.pow( 724 | (tensionSlingshotPercent / 4), 2)) * 2f; 725 | float extraMove = (slingshotDist) * tensionPercent * 2; 726 | 727 | int targetY = mOriginalOffsetTop 728 | + (int) ((slingshotDist * dragPercent) + extraMove); 729 | // where 1.0f is a full circle 730 | if (mCircleView.getVisibility() != View.VISIBLE) { 731 | mCircleView.setVisibility(View.VISIBLE); 732 | } 733 | if (!mScale) { 734 | ViewCompat.setScaleX(mCircleView, 1f); 735 | ViewCompat.setScaleY(mCircleView, 1f); 736 | } 737 | if (overscrollTop < mTotalDragDistance) { 738 | if (mScale) { 739 | setAnimationProgress(overscrollTop / mTotalDragDistance); 740 | } 741 | if (mProgress.getAlpha() > STARTING_PROGRESS_ALPHA 742 | && !isAnimationRunning(mAlphaStartAnimation)) { 743 | // Animate the alpha 744 | startProgressAlphaStartAnimation(); 745 | } 746 | float strokeStart = adjustedPercent * .8f; 747 | mProgress.setStartEndTrim(0f, Math.min(MAX_PROGRESS_ANGLE, strokeStart)); 748 | mProgress.setArrowScale(Math.min(1f, adjustedPercent)); 749 | } else { 750 | if (mProgress.getAlpha() < MAX_ALPHA 751 | && !isAnimationRunning(mAlphaMaxAnimation)) { 752 | // Animate the alpha 753 | startProgressAlphaMaxAnimation(); 754 | } 755 | } 756 | float rotation = (-0.25f + .4f * adjustedPercent + tensionPercent * 2) * .5f; 757 | mProgress.setProgressRotation(rotation); 758 | setTargetOffsetTopAndBottom(targetY - mCurrentTargetOffsetTop, 759 | true /* requires update */); 760 | } 761 | break; 762 | } 763 | case MotionEventCompat.ACTION_POINTER_DOWN: { 764 | final int index = MotionEventCompat.getActionIndex(ev); 765 | mActivePointerId = MotionEventCompat.getPointerId(ev, index); 766 | break; 767 | } 768 | 769 | case MotionEventCompat.ACTION_POINTER_UP: 770 | onSecondaryPointerUp(ev); 771 | break; 772 | 773 | case MotionEvent.ACTION_UP: 774 | case MotionEvent.ACTION_CANCEL: { 775 | if (mActivePointerId == INVALID_POINTER) { 776 | if (action == MotionEvent.ACTION_UP) { 777 | Log.e(LOG_TAG, "Got ACTION_UP event but don't have an active pointer id."); 778 | } 779 | return false; 780 | } 781 | final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId); 782 | final float y = MotionEventCompat.getY(ev, pointerIndex); 783 | final float overscrollTop = (y - mInitialMotionY) * DRAG_RATE; 784 | mIsBeingDragged = false; 785 | if (overscrollTop > mTotalDragDistance) { 786 | setRefreshing(true, true /* notify */); 787 | } else { 788 | // cancel refresh 789 | mRefreshing = false; 790 | mProgress.setStartEndTrim(0f, 0f); 791 | Animation.AnimationListener listener = null; 792 | if (!mScale) { 793 | listener = new Animation.AnimationListener() { 794 | 795 | @Override 796 | public void onAnimationStart(Animation animation) { 797 | } 798 | 799 | @Override 800 | public void onAnimationEnd(Animation animation) { 801 | if (!mScale) { 802 | startScaleDownAnimation(null); 803 | } 804 | } 805 | 806 | @Override 807 | public void onAnimationRepeat(Animation animation) { 808 | } 809 | 810 | }; 811 | } 812 | animateOffsetToStartPosition(mCurrentTargetOffsetTop, listener); 813 | mProgress.showArrow(false); 814 | } 815 | mActivePointerId = INVALID_POINTER; 816 | return false; 817 | } 818 | } 819 | 820 | return true; 821 | } 822 | 823 | private void animateOffsetToCorrectPosition(int from, Animation.AnimationListener listener) { 824 | mFrom = from; 825 | mAnimateToCorrectPosition.reset(); 826 | mAnimateToCorrectPosition.setDuration(ANIMATE_TO_TRIGGER_DURATION); 827 | mAnimateToCorrectPosition.setInterpolator(mDecelerateInterpolator); 828 | if (listener != null) { 829 | mCircleView.setAnimationListener(listener); 830 | } 831 | mCircleView.clearAnimation(); 832 | mCircleView.startAnimation(mAnimateToCorrectPosition); 833 | } 834 | 835 | private void animateOffsetToStartPosition(int from, Animation.AnimationListener listener) { 836 | if (mScale) { 837 | // Scale the item back down 838 | startScaleDownReturnToStartAnimation(from, listener); 839 | } else { 840 | mFrom = from; 841 | mAnimateToStartPosition.reset(); 842 | mAnimateToStartPosition.setDuration(ANIMATE_TO_START_DURATION); 843 | mAnimateToStartPosition.setInterpolator(mDecelerateInterpolator); 844 | if (listener != null) { 845 | mCircleView.setAnimationListener(listener); 846 | } 847 | mCircleView.clearAnimation(); 848 | mCircleView.startAnimation(mAnimateToStartPosition); 849 | } 850 | } 851 | 852 | private final Animation mAnimateToCorrectPosition = new Animation() { 853 | @Override 854 | public void applyTransformation(float interpolatedTime, Transformation t) { 855 | int targetTop = 0; 856 | int endTarget = 0; 857 | if (!mUsingCustomStart) { 858 | endTarget = (int) (mSpinnerFinalOffset - Math.abs(mOriginalOffsetTop)); 859 | } else { 860 | endTarget = (int) mSpinnerFinalOffset; 861 | } 862 | targetTop = (mFrom + (int) ((endTarget - mFrom) * interpolatedTime)); 863 | int offset = targetTop - mCircleView.getTop(); 864 | setTargetOffsetTopAndBottom(offset, false /* requires update */); 865 | mProgress.setArrowScale(1 - interpolatedTime); 866 | } 867 | }; 868 | 869 | private void moveToStart(float interpolatedTime) { 870 | int targetTop = 0; 871 | targetTop = (mFrom + (int) ((mOriginalOffsetTop - mFrom) * interpolatedTime)); 872 | int offset = targetTop - mCircleView.getTop(); 873 | setTargetOffsetTopAndBottom(offset, false /* requires update */); 874 | } 875 | 876 | private final Animation mAnimateToStartPosition = new Animation() { 877 | @Override 878 | public void applyTransformation(float interpolatedTime, Transformation t) { 879 | moveToStart(interpolatedTime); 880 | } 881 | }; 882 | 883 | private void startScaleDownReturnToStartAnimation(int from, 884 | Animation.AnimationListener listener) { 885 | mFrom = from; 886 | if (isAlphaUsedForScale()) { 887 | mStartingScale = mProgress.getAlpha(); 888 | } else { 889 | mStartingScale = ViewCompat.getScaleX(mCircleView); 890 | } 891 | mScaleDownToStartAnimation = new Animation() { 892 | @Override 893 | public void applyTransformation(float interpolatedTime, Transformation t) { 894 | float targetScale = (mStartingScale + (-mStartingScale * interpolatedTime)); 895 | setAnimationProgress(targetScale); 896 | moveToStart(interpolatedTime); 897 | } 898 | }; 899 | mScaleDownToStartAnimation.setDuration(SCALE_DOWN_DURATION); 900 | if (listener != null) { 901 | mCircleView.setAnimationListener(listener); 902 | } 903 | mCircleView.clearAnimation(); 904 | mCircleView.startAnimation(mScaleDownToStartAnimation); 905 | } 906 | 907 | private void setTargetOffsetTopAndBottom(int offset, boolean requiresUpdate) { 908 | mCircleView.bringToFront(); 909 | mCircleView.offsetTopAndBottom(offset); 910 | mCurrentTargetOffsetTop = mCircleView.getTop(); 911 | if (requiresUpdate && android.os.Build.VERSION.SDK_INT < 11) { 912 | invalidate(); 913 | } 914 | } 915 | 916 | private void onSecondaryPointerUp(MotionEvent ev) { 917 | final int pointerIndex = MotionEventCompat.getActionIndex(ev); 918 | final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex); 919 | if (pointerId == mActivePointerId) { 920 | // This was our active pointer going up. Choose a new 921 | // active pointer and adjust accordingly. 922 | final int newPointerIndex = pointerIndex == 0 ? 1 : 0; 923 | mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex); 924 | } 925 | } 926 | 927 | /** 928 | * Classes that wish to be notified when the swipe gesture correctly 929 | * triggers a refresh should implement this interface. 930 | */ 931 | public interface OnRefreshListener { 932 | public void onRefresh(); 933 | } 934 | } 935 | 936 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 11 | 12 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/layout/footer_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 14 | 15 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/layout/list_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_main.xml: -------------------------------------------------------------------------------- 1 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brucetoo/AutoSwipeRefesh/33a29866a35b422e9380222618ff26b100b92e28/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brucetoo/AutoSwipeRefesh/33a29866a35b422e9380222618ff26b100b92e28/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brucetoo/AutoSwipeRefesh/33a29866a35b422e9380222618ff26b100b92e28/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brucetoo/AutoSwipeRefesh/33a29866a35b422e9380222618ff26b100b92e28/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 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 | AutoSwipeRefesh 3 | 4 | Hello world! 5 | Settings 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /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.2.3' 9 | 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | jcenter() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brucetoo/AutoSwipeRefesh/33a29866a35b422e9380222618ff26b100b92e28/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Aug 07 15:47:41 CST 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 | # 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | --------------------------------------------------------------------------------