├── .classpath ├── .gitignore ├── .project ├── .settings ├── com.aptana.editor.common.prefs ├── org.eclipse.jdt.apt.core.prefs └── org.eclipse.jdt.core.prefs ├── README.md ├── android ├── CHANGELOG.txt ├── LICENSE ├── LICENSE.txt ├── assets │ └── README ├── build.properties ├── build.xml ├── dist │ └── swiperefreshlayout.jar ├── documentation │ └── index.md ├── example │ └── app.js ├── hooks │ ├── README │ ├── add.py │ ├── install.py │ ├── remove.py │ └── uninstall.py ├── java-sources.txt ├── libs │ ├── armeabi-v7a │ │ └── libcom.rkam.swiperefreshlayout.so │ └── x86 │ │ └── libcom.rkam.swiperefreshlayout.so ├── manifest ├── platform │ ├── README │ └── android │ │ └── res │ │ ├── layout │ │ └── swipe_refresh.xml │ │ └── values │ │ └── colors.xml ├── src │ └── com │ │ └── rkam │ │ └── swiperefreshlayout │ │ ├── CircleImageView.java │ │ ├── FastOutSlowInInterpolator.java │ │ ├── LookupTableInterpolator.java │ │ ├── MaterialProgressDrawable.java │ │ ├── MySwipeRefreshLayout.java │ │ ├── ScrollingView.java │ │ ├── SwipeProgressBar.java │ │ ├── SwipeRefresh.java │ │ ├── SwipeRefreshLayout.java │ │ ├── SwipeRefreshProxy.java │ │ └── SwiperefreshlayoutModule.java └── timodule.xml └── dist ├── com.rkam.swiperefreshlayout-android-0.1.zip ├── com.rkam.swiperefreshlayout-android-0.2.zip ├── com.rkam.swiperefreshlayout-android-0.3.zip ├── com.rkam.swiperefreshlayout-android-0.4.1.zip ├── com.rkam.swiperefreshlayout-android-0.5.4.zip ├── com.rkam.swiperefreshlayout-android-0.5.zip ├── com.rkam.swiperefreshlayout-android-0.6.zip ├── com.rkam.swiperefreshlayout-android-0.7.zip └── com.rkam.swiperefreshlayout-android-0.8.zip /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | tmp 2 | bin 3 | build 4 | .apt_generated 5 | 6 | .DS_Store 7 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | swiperefreshlayout 4 | 5 | 6 | 7 | 8 | 9 | com.appcelerator.titanium.core.builder 10 | 11 | 12 | 13 | 14 | com.aptana.ide.core.unifiedBuilder 15 | 16 | 17 | 18 | 19 | org.eclipse.jdt.core.javabuilder 20 | 21 | 22 | 23 | 24 | 25 | org.eclipse.jdt.core.javanature 26 | com.appcelerator.titanium.mobile.module.nature 27 | com.aptana.projects.webnature 28 | 29 | 30 | -------------------------------------------------------------------------------- /.settings/com.aptana.editor.common.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | selectUserAgents=com.appcelerator.titanium.mobile.module.nature\:android 3 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.apt.core.prefs: -------------------------------------------------------------------------------- 1 | #Thu Sep 02 15:18:34 CDT 2010 2 | eclipse.preferences.version=1 3 | org.eclipse.jdt.apt.aptEnabled=true 4 | org.eclipse.jdt.apt.genSrcDir=.apt_generated 5 | org.eclipse.jdt.apt.reconcileEnabled=true 6 | 7 | org.eclipse.jdt.apt.processorOptions/kroll.jsonFile=swiperefreshlayout.json 8 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled 3 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 4 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve 5 | org.eclipse.jdt.core.compiler.compliance=1.6 6 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate 7 | org.eclipse.jdt.core.compiler.debug.localVariable=generate 8 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate 9 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error 10 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error 11 | org.eclipse.jdt.core.compiler.processAnnotations=enabled 12 | org.eclipse.jdt.core.compiler.source=1.6 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Ti.SwipeRefreshLayout Titanium Module 2 | =========================================== 3 | 4 | ![tiswipe](https://cloud.githubusercontent.com/assets/878389/4853094/b9a89378-6082-11e4-8a85-28159b1a54cd.gif) 5 | 6 | Building the module 7 | ----------- 8 | You'll need to modify "build.xml" and ".classpath" according to paths on your machine. 9 | 10 | 11 | To initialize 12 | ----------- 13 | The initialization of the module requires a handle to the scrollable view. If the scrollable view is in xml, you can create a new controller for the scrollable view and then pass it in as the view. 14 | 15 | var myListView = Ti.UI.createListView({ 16 | height: Ti.UI.FILL, 17 | width: Ti.UI.FILL 18 | }) 19 | 20 | var swipeRefreshModule = require('com.rkam.swiperefreshlayout'); 21 | var swipeRefresh = swipeRefreshModule.createSwipeRefresh({ 22 | view: myListView, 23 | height: Ti.UI.FILL, 24 | width: Ti.UI.FILL 25 | }); 26 | 27 | $.content.add(swipeRefresh); 28 | 29 | Setting color scheme 30 | ----------- 31 | If you want to modify the 4 colours used in the progress bars at the top, modify the colors.xml file in (module folder)/platform/android/res/values which looks like this. 32 | 33 | 34 | 35 | 36 | #1996BE 37 | #48B6D9 38 | #80D3ED 39 | #C2F0FF 40 | 41 | 42 | Adding the event listener for when refresh happens 43 | ----------- 44 | A refreshing event is fired when the swipe gesture is completed. 45 | 46 | swipeRefresh.addEventListener('refreshing', function() { 47 | 48 | // Put your refresh code here 49 | 50 | }); 51 | 52 | Set refreshing 53 | ----------- 54 | Used to stop the progress bar animation when refreshing is done. 55 | 56 | swipeRefresh.setRefreshing(false); 57 | 58 | Check if refreshing 59 | ----------- 60 | The state of the SwipeRefreshLayout, whether the animation is showing or not. 61 | 62 | var isRefreshing = swipeRefresh.isRefreshing(); 63 | 64 | Alloy 65 | ----------- 66 | Alloy usage example: 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /android/CHANGELOG.txt: -------------------------------------------------------------------------------- 1 | Place your change log text here. This file will be incorporated with your app at package time. -------------------------------------------------------------------------------- /android/LICENSE: -------------------------------------------------------------------------------- 1 | TODO: place your license here and we'll include it in the module distribution 2 | -------------------------------------------------------------------------------- /android/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Place your license text here. This file will be incorporated with your app at package time. -------------------------------------------------------------------------------- /android/assets/README: -------------------------------------------------------------------------------- 1 | Place your assets like PNG files in this directory and they will be packaged with your module. 2 | 3 | If you create a file named com.rkam.swiperefreshlayout.js in this directory, it will be 4 | compiled and used as your module. This allows you to run pure Javascript 5 | modules that are pre-compiled. 6 | 7 | -------------------------------------------------------------------------------- /android/build.properties: -------------------------------------------------------------------------------- 1 | titanium.platform=/Users/InGrowth/Library/Application Support/Titanium/mobilesdk/osx/6.0.0.GA/android 2 | android.platform=/Users/InGrowth/Library/android-sdk-macosx/platforms/android-23 3 | google.apis=/Users/InGrowth/Library/android-sdk-macosx/add-ons/addon-google_apis-google-23 4 | android.ndk=/Users/InGrowth/Library/android-sdk-macosx/ndk-bundle -------------------------------------------------------------------------------- /android/build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Ant build script for Titanium Android module swiperefreshlayout 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /android/dist/swiperefreshlayout.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iskugor/Ti.SwipeRefreshLayout/5b0f33ecb7b6f55315b12ed4f99f5c6c3457f5c3/android/dist/swiperefreshlayout.jar -------------------------------------------------------------------------------- /android/documentation/index.md: -------------------------------------------------------------------------------- 1 | # swiperefreshlayout Module 2 | 3 | ## Description 4 | 5 | TODO: Enter your module description here 6 | 7 | ## Accessing the swiperefreshlayout Module 8 | 9 | To access this module from JavaScript, you would do the following: 10 | 11 | var swiperefreshlayout = require("com.raymondkam.swiperefreshlayout"); 12 | 13 | The swiperefreshlayout variable is a reference to the Module object. 14 | 15 | ## Reference 16 | 17 | TODO: If your module has an API, you should document 18 | the reference here. 19 | 20 | ### ___PROJECTNAMEASIDENTIFIER__.function 21 | 22 | TODO: This is an example of a module function. 23 | 24 | ### ___PROJECTNAMEASIDENTIFIER__.property 25 | 26 | TODO: This is an example of a module property. 27 | 28 | ## Usage 29 | 30 | TODO: Enter your usage example here 31 | 32 | ## Author 33 | 34 | TODO: Enter your author name, email and other contact 35 | details you want to share here. 36 | 37 | ## License 38 | 39 | TODO: Enter your license/legal information here. 40 | -------------------------------------------------------------------------------- /android/example/app.js: -------------------------------------------------------------------------------- 1 | // This is a test harness for your module 2 | // You should do something interesting in this harness 3 | // to test out the module and to provide instructions 4 | // to users on how to use it by example. 5 | 6 | 7 | // open a single window 8 | var win = Ti.UI.createWindow({ 9 | backgroundColor:'white' 10 | }); 11 | var label = Ti.UI.createLabel(); 12 | win.add(label); 13 | win.open(); 14 | 15 | // TODO: write your module tests here 16 | var swiperefreshlayout = require('com.rkam.swiperefreshlayout'); 17 | Ti.API.info("module is => " + swiperefreshlayout); 18 | 19 | label.text = swiperefreshlayout.example(); 20 | 21 | Ti.API.info("module exampleProp is => " + swiperefreshlayout.exampleProp); 22 | swiperefreshlayout.exampleProp = "This is a test value"; 23 | 24 | if (Ti.Platform.name == "android") { 25 | var proxy = swiperefreshlayout.createExample({ 26 | message: "Creating an example Proxy", 27 | backgroundColor: "red", 28 | width: 100, 29 | height: 100, 30 | top: 100, 31 | left: 150 32 | }); 33 | 34 | proxy.printMessage("Hello world!"); 35 | proxy.message = "Hi world!. It's me again."; 36 | proxy.printMessage("Hello world!"); 37 | win.add(proxy); 38 | } 39 | 40 | -------------------------------------------------------------------------------- /android/hooks/README: -------------------------------------------------------------------------------- 1 | These files are not yet supported as of 1.4.0 but will be in a near future release. 2 | -------------------------------------------------------------------------------- /android/hooks/add.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # This is the module project add hook that will be 4 | # called when your module is added to a project 5 | # 6 | import os, sys 7 | 8 | def dequote(s): 9 | if s[0:1] == '"': 10 | return s[1:-1] 11 | return s 12 | 13 | def main(args,argc): 14 | # You will get the following command line arguments 15 | # in the following order: 16 | # 17 | # project_dir = the full path to the project root directory 18 | # project_type = the type of project (desktop, mobile, ipad) 19 | # project_name = the name of the project 20 | # 21 | project_dir = dequote(os.path.expanduser(args[1])) 22 | project_type = dequote(args[2]) 23 | project_name = dequote(args[3]) 24 | 25 | # TODO: write your add hook here (optional) 26 | 27 | 28 | # exit 29 | sys.exit(0) 30 | 31 | 32 | 33 | if __name__ == '__main__': 34 | main(sys.argv,len(sys.argv)) 35 | 36 | -------------------------------------------------------------------------------- /android/hooks/install.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # This is the module install hook that will be 4 | # called when your module is first installed 5 | # 6 | import os, sys 7 | 8 | def main(args,argc): 9 | 10 | # TODO: write your install hook here (optional) 11 | 12 | # exit 13 | sys.exit(0) 14 | 15 | 16 | 17 | if __name__ == '__main__': 18 | main(sys.argv,len(sys.argv)) 19 | 20 | -------------------------------------------------------------------------------- /android/hooks/remove.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # This is the module project remove hook that will be 4 | # called when your module is remove from a project 5 | # 6 | import os, sys 7 | 8 | def dequote(s): 9 | if s[0:1] == '"': 10 | return s[1:-1] 11 | return s 12 | 13 | def main(args,argc): 14 | # You will get the following command line arguments 15 | # in the following order: 16 | # 17 | # project_dir = the full path to the project root directory 18 | # project_type = the type of project (desktop, mobile, ipad) 19 | # project_name = the name of the project 20 | # 21 | project_dir = dequote(os.path.expanduser(args[1])) 22 | project_type = dequote(args[2]) 23 | project_name = dequote(args[3]) 24 | 25 | # TODO: write your remove hook here (optional) 26 | 27 | # exit 28 | sys.exit(0) 29 | 30 | 31 | 32 | if __name__ == '__main__': 33 | main(sys.argv,len(sys.argv)) 34 | 35 | -------------------------------------------------------------------------------- /android/hooks/uninstall.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # This is the module uninstall hook that will be 4 | # called when your module is uninstalled 5 | # 6 | import os, sys 7 | 8 | def main(args,argc): 9 | 10 | # TODO: write your uninstall hook here (optional) 11 | 12 | # exit 13 | sys.exit(0) 14 | 15 | 16 | if __name__ == '__main__': 17 | main(sys.argv,len(sys.argv)) 18 | 19 | -------------------------------------------------------------------------------- /android/java-sources.txt: -------------------------------------------------------------------------------- 1 | "/Users/InGrowth/Documents/Projects/Ti-Android-SwipeRefreshLayout/android/build/generated/java/com/rkam/swiperefreshlayout/SwiperefreshlayoutBootstrap.java" 2 | "/Users/InGrowth/Documents/Projects/Ti-Android-SwipeRefreshLayout/android/src/com/rkam/swiperefreshlayout/CircleImageView.java" 3 | "/Users/InGrowth/Documents/Projects/Ti-Android-SwipeRefreshLayout/android/src/com/rkam/swiperefreshlayout/FastOutSlowInInterpolator.java" 4 | "/Users/InGrowth/Documents/Projects/Ti-Android-SwipeRefreshLayout/android/src/com/rkam/swiperefreshlayout/LookupTableInterpolator.java" 5 | "/Users/InGrowth/Documents/Projects/Ti-Android-SwipeRefreshLayout/android/src/com/rkam/swiperefreshlayout/MaterialProgressDrawable.java" 6 | "/Users/InGrowth/Documents/Projects/Ti-Android-SwipeRefreshLayout/android/src/com/rkam/swiperefreshlayout/MySwipeRefreshLayout.java" 7 | "/Users/InGrowth/Documents/Projects/Ti-Android-SwipeRefreshLayout/android/src/com/rkam/swiperefreshlayout/ScrollingView.java" 8 | "/Users/InGrowth/Documents/Projects/Ti-Android-SwipeRefreshLayout/android/src/com/rkam/swiperefreshlayout/SwipeProgressBar.java" 9 | "/Users/InGrowth/Documents/Projects/Ti-Android-SwipeRefreshLayout/android/src/com/rkam/swiperefreshlayout/SwipeRefresh.java" 10 | "/Users/InGrowth/Documents/Projects/Ti-Android-SwipeRefreshLayout/android/src/com/rkam/swiperefreshlayout/SwipeRefreshLayout.java" 11 | "/Users/InGrowth/Documents/Projects/Ti-Android-SwipeRefreshLayout/android/src/com/rkam/swiperefreshlayout/SwipeRefreshProxy.java" 12 | "/Users/InGrowth/Documents/Projects/Ti-Android-SwipeRefreshLayout/android/src/com/rkam/swiperefreshlayout/SwiperefreshlayoutModule.java" -------------------------------------------------------------------------------- /android/libs/armeabi-v7a/libcom.rkam.swiperefreshlayout.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iskugor/Ti.SwipeRefreshLayout/5b0f33ecb7b6f55315b12ed4f99f5c6c3457f5c3/android/libs/armeabi-v7a/libcom.rkam.swiperefreshlayout.so -------------------------------------------------------------------------------- /android/libs/x86/libcom.rkam.swiperefreshlayout.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iskugor/Ti.SwipeRefreshLayout/5b0f33ecb7b6f55315b12ed4f99f5c6c3457f5c3/android/libs/x86/libcom.rkam.swiperefreshlayout.so -------------------------------------------------------------------------------- /android/manifest: -------------------------------------------------------------------------------- 1 | # 2 | # this is your module manifest and used by Titanium 3 | # during compilation, packaging, distribution, etc. 4 | # 5 | version: 0.8 6 | apiversion: 3 7 | description: My module 8 | author: Raymond Kam 9 | license: Specify your license 10 | copyright: Copyright (c) 2014 by Your Company 11 | 12 | 13 | # these should not be edited 14 | name: swiperefreshlayout 15 | moduleid: com.rkam.swiperefreshlayout 16 | guid: fc0f2168-ac27-4239-bcb0-c51d7683eb05 17 | platform: android 18 | minsdk: 6.0.0.GA 19 | architectures: armeabi-v7a x86 20 | -------------------------------------------------------------------------------- /android/platform/README: -------------------------------------------------------------------------------- 1 | You can place platform-specific files here in sub-folders named "android" and/or "iphone", just as you can with normal Titanium Mobile SDK projects. Any folders and files you place here will be merged with the platform-specific files in a Titanium Mobile project that uses this module. 2 | 3 | When a Titanium Mobile project that uses this module is built, the files from this platform/ folder will be treated the same as files (if any) from the Titanium Mobile project's platform/ folder. 4 | -------------------------------------------------------------------------------- /android/platform/android/res/layout/swipe_refresh.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | -------------------------------------------------------------------------------- /android/platform/android/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #1996BE 5 | #48B6D9 6 | #80D3ED 7 | #C2F0FF 8 | -------------------------------------------------------------------------------- /android/src/com/rkam/swiperefreshlayout/CircleImageView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.rkam.swiperefreshlayout; 18 | 19 | import android.content.Context; 20 | import android.graphics.Canvas; 21 | import android.graphics.Color; 22 | import android.graphics.Paint; 23 | import android.graphics.RadialGradient; 24 | import android.graphics.Shader; 25 | import android.graphics.drawable.ShapeDrawable; 26 | import android.graphics.drawable.shapes.OvalShape; 27 | import android.support.v4.view.ViewCompat; 28 | import android.view.animation.Animation; 29 | import android.widget.ImageView; 30 | 31 | /** 32 | * Private class created to work around issues with AnimationListeners being 33 | * called before the animation is actually complete and support shadows on older 34 | * platforms. 35 | * 36 | * @hide 37 | */ 38 | class CircleImageView extends ImageView { 39 | 40 | private static final int KEY_SHADOW_COLOR = 0x1E000000; 41 | private static final int FILL_SHADOW_COLOR = 0x3D000000; 42 | // PX 43 | private static final float X_OFFSET = 0f; 44 | private static final float Y_OFFSET = 1.75f; 45 | private static final float SHADOW_RADIUS = 3.5f; 46 | private static final int SHADOW_ELEVATION = 4; 47 | 48 | private Animation.AnimationListener mListener; 49 | private int mShadowRadius; 50 | 51 | public CircleImageView(Context context, int color, final float radius) { 52 | super(context); 53 | final float density = getContext().getResources().getDisplayMetrics().density; 54 | final int diameter = (int) (radius * density * 2); 55 | final int shadowYOffset = (int) (density * Y_OFFSET); 56 | final int shadowXOffset = (int) (density * X_OFFSET); 57 | 58 | mShadowRadius = (int) (density * SHADOW_RADIUS); 59 | 60 | ShapeDrawable circle; 61 | if (elevationSupported()) { 62 | circle = new ShapeDrawable(new OvalShape()); 63 | setElevation(SHADOW_ELEVATION * density); 64 | } else { 65 | OvalShape oval = new OvalShadow(mShadowRadius, diameter); 66 | circle = new ShapeDrawable(oval); 67 | ViewCompat.setLayerType(this, ViewCompat.LAYER_TYPE_SOFTWARE, circle.getPaint()); 68 | circle.getPaint().setShadowLayer(mShadowRadius, shadowXOffset, shadowYOffset, 69 | KEY_SHADOW_COLOR); 70 | final int padding = mShadowRadius; 71 | // set padding so the inner image sits correctly within the shadow. 72 | setPadding(padding, padding, padding, padding); 73 | } 74 | circle.getPaint().setColor(color); 75 | setBackgroundDrawable(circle); 76 | } 77 | 78 | private boolean elevationSupported() { 79 | return android.os.Build.VERSION.SDK_INT >= 21; 80 | } 81 | 82 | @Override 83 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 84 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 85 | if (!elevationSupported()) { 86 | setMeasuredDimension(getMeasuredWidth() + mShadowRadius*2, getMeasuredHeight() 87 | + mShadowRadius*2); 88 | } 89 | } 90 | 91 | public void setAnimationListener(Animation.AnimationListener listener) { 92 | mListener = listener; 93 | } 94 | 95 | @Override 96 | public void onAnimationStart() { 97 | super.onAnimationStart(); 98 | if (mListener != null) { 99 | mListener.onAnimationStart(getAnimation()); 100 | } 101 | } 102 | 103 | @Override 104 | public void onAnimationEnd() { 105 | super.onAnimationEnd(); 106 | if (mListener != null) { 107 | mListener.onAnimationEnd(getAnimation()); 108 | } 109 | } 110 | 111 | /** 112 | * Update the background color of the circle image view. 113 | * 114 | * @param colorRes Id of a color resource. 115 | */ 116 | public void setBackgroundColorRes(int colorRes) { 117 | setBackgroundColor(getContext().getResources().getColor(colorRes)); 118 | } 119 | 120 | @Override 121 | public void setBackgroundColor(int color) { 122 | if (getBackground() instanceof ShapeDrawable) { 123 | ((ShapeDrawable) getBackground()).getPaint().setColor(color); 124 | } 125 | } 126 | 127 | private class OvalShadow extends OvalShape { 128 | private RadialGradient mRadialGradient; 129 | private Paint mShadowPaint; 130 | private int mCircleDiameter; 131 | 132 | public OvalShadow(int shadowRadius, int circleDiameter) { 133 | super(); 134 | mShadowPaint = new Paint(); 135 | mShadowRadius = shadowRadius; 136 | mCircleDiameter = circleDiameter; 137 | mRadialGradient = new RadialGradient(mCircleDiameter / 2, mCircleDiameter / 2, 138 | mShadowRadius, new int[] { 139 | FILL_SHADOW_COLOR, Color.TRANSPARENT 140 | }, null, Shader.TileMode.CLAMP); 141 | mShadowPaint.setShader(mRadialGradient); 142 | } 143 | 144 | @Override 145 | public void draw(Canvas canvas, Paint paint) { 146 | final int viewWidth = CircleImageView.this.getWidth(); 147 | final int viewHeight = CircleImageView.this.getHeight(); 148 | canvas.drawCircle(viewWidth / 2, viewHeight / 2, (mCircleDiameter / 2 + mShadowRadius), 149 | mShadowPaint); 150 | canvas.drawCircle(viewWidth / 2, viewHeight / 2, (mCircleDiameter / 2), paint); 151 | } 152 | } 153 | } -------------------------------------------------------------------------------- /android/src/com/rkam/swiperefreshlayout/FastOutSlowInInterpolator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.rkam.swiperefreshlayout; 18 | 19 | /** 20 | * Interpolator corresponding to {@link android.R.interpolator#fast_out_slow_in}. 21 | * 22 | * Uses a lookup table for the Bezier curve from (0,0) to (1,1) with control points: 23 | * P0 (0, 0) 24 | * P1 (0.4, 0) 25 | * P2 (0.2, 1.0) 26 | * P3 (1.0, 1.0) 27 | */ 28 | public class FastOutSlowInInterpolator extends LookupTableInterpolator { 29 | 30 | /** 31 | * Lookup table values sampled with x at regular intervals between 0 and 1 for a total of 32 | * 201 points. 33 | */ 34 | private static final float[] VALUES = new float[] { 35 | 0.0000f, 0.0001f, 0.0002f, 0.0005f, 0.0009f, 0.0014f, 0.0020f, 36 | 0.0027f, 0.0036f, 0.0046f, 0.0058f, 0.0071f, 0.0085f, 0.0101f, 37 | 0.0118f, 0.0137f, 0.0158f, 0.0180f, 0.0205f, 0.0231f, 0.0259f, 38 | 0.0289f, 0.0321f, 0.0355f, 0.0391f, 0.0430f, 0.0471f, 0.0514f, 39 | 0.0560f, 0.0608f, 0.0660f, 0.0714f, 0.0771f, 0.0830f, 0.0893f, 40 | 0.0959f, 0.1029f, 0.1101f, 0.1177f, 0.1257f, 0.1339f, 0.1426f, 41 | 0.1516f, 0.1610f, 0.1707f, 0.1808f, 0.1913f, 0.2021f, 0.2133f, 42 | 0.2248f, 0.2366f, 0.2487f, 0.2611f, 0.2738f, 0.2867f, 0.2998f, 43 | 0.3131f, 0.3265f, 0.3400f, 0.3536f, 0.3673f, 0.3810f, 0.3946f, 44 | 0.4082f, 0.4217f, 0.4352f, 0.4485f, 0.4616f, 0.4746f, 0.4874f, 45 | 0.5000f, 0.5124f, 0.5246f, 0.5365f, 0.5482f, 0.5597f, 0.5710f, 46 | 0.5820f, 0.5928f, 0.6033f, 0.6136f, 0.6237f, 0.6335f, 0.6431f, 47 | 0.6525f, 0.6616f, 0.6706f, 0.6793f, 0.6878f, 0.6961f, 0.7043f, 48 | 0.7122f, 0.7199f, 0.7275f, 0.7349f, 0.7421f, 0.7491f, 0.7559f, 49 | 0.7626f, 0.7692f, 0.7756f, 0.7818f, 0.7879f, 0.7938f, 0.7996f, 50 | 0.8053f, 0.8108f, 0.8162f, 0.8215f, 0.8266f, 0.8317f, 0.8366f, 51 | 0.8414f, 0.8461f, 0.8507f, 0.8551f, 0.8595f, 0.8638f, 0.8679f, 52 | 0.8720f, 0.8760f, 0.8798f, 0.8836f, 0.8873f, 0.8909f, 0.8945f, 53 | 0.8979f, 0.9013f, 0.9046f, 0.9078f, 0.9109f, 0.9139f, 0.9169f, 54 | 0.9198f, 0.9227f, 0.9254f, 0.9281f, 0.9307f, 0.9333f, 0.9358f, 55 | 0.9382f, 0.9406f, 0.9429f, 0.9452f, 0.9474f, 0.9495f, 0.9516f, 56 | 0.9536f, 0.9556f, 0.9575f, 0.9594f, 0.9612f, 0.9629f, 0.9646f, 57 | 0.9663f, 0.9679f, 0.9695f, 0.9710f, 0.9725f, 0.9739f, 0.9753f, 58 | 0.9766f, 0.9779f, 0.9791f, 0.9803f, 0.9815f, 0.9826f, 0.9837f, 59 | 0.9848f, 0.9858f, 0.9867f, 0.9877f, 0.9885f, 0.9894f, 0.9902f, 60 | 0.9910f, 0.9917f, 0.9924f, 0.9931f, 0.9937f, 0.9944f, 0.9949f, 61 | 0.9955f, 0.9960f, 0.9964f, 0.9969f, 0.9973f, 0.9977f, 0.9980f, 62 | 0.9984f, 0.9986f, 0.9989f, 0.9991f, 0.9993f, 0.9995f, 0.9997f, 63 | 0.9998f, 0.9999f, 0.9999f, 1.0000f, 1.0000f 64 | }; 65 | 66 | public FastOutSlowInInterpolator() { 67 | super(VALUES); 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /android/src/com/rkam/swiperefreshlayout/LookupTableInterpolator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.rkam.swiperefreshlayout; 18 | 19 | import android.view.animation.Interpolator; 20 | 21 | /** 22 | * An {@link Interpolator} that uses a lookup table to compute an interpolation based on a 23 | * given input. 24 | */ 25 | abstract class LookupTableInterpolator implements Interpolator { 26 | 27 | private final float[] mValues; 28 | private final float mStepSize; 29 | 30 | public LookupTableInterpolator(float[] values) { 31 | mValues = values; 32 | mStepSize = 1f / (mValues.length - 1); 33 | } 34 | 35 | @Override 36 | public float getInterpolation(float input) { 37 | if (input >= 1.0f) { 38 | return 1.0f; 39 | } 40 | if (input <= 0f) { 41 | return 0f; 42 | } 43 | 44 | // Calculate index - We use min with length - 2 to avoid IndexOutOfBoundsException when 45 | // we lerp (linearly interpolate) in the return statement 46 | int position = Math.min((int) (input * (mValues.length - 1)), mValues.length - 2); 47 | 48 | // Calculate values to account for small offsets as the lookup table has discrete values 49 | float quantized = position * mStepSize; 50 | float diff = input - quantized; 51 | float weight = diff / mStepSize; 52 | 53 | // Linearly interpolate between the table values 54 | return mValues[position] + weight * (mValues[position + 1] - mValues[position]); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /android/src/com/rkam/swiperefreshlayout/MaterialProgressDrawable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.rkam.swiperefreshlayout; 18 | 19 | import android.view.animation.AccelerateDecelerateInterpolator; 20 | import android.view.animation.Interpolator; 21 | import android.view.animation.Animation; 22 | import android.view.animation.LinearInterpolator; 23 | import android.view.animation.Transformation; 24 | import android.content.Context; 25 | import android.content.res.Resources; 26 | import android.graphics.Canvas; 27 | import android.graphics.Color; 28 | import android.graphics.ColorFilter; 29 | import android.graphics.Paint; 30 | import android.graphics.Paint.Style; 31 | import android.graphics.Path; 32 | import android.graphics.PixelFormat; 33 | import android.graphics.Rect; 34 | import android.graphics.RectF; 35 | import android.graphics.drawable.Drawable; 36 | import android.graphics.drawable.Animatable; 37 | import android.support.annotation.IntDef; 38 | import android.support.annotation.NonNull; 39 | import android.util.DisplayMetrics; 40 | import android.view.View; 41 | 42 | import java.lang.annotation.Retention; 43 | import java.lang.annotation.RetentionPolicy; 44 | import java.util.ArrayList; 45 | 46 | /** 47 | * Fancy progress indicator for Material theme. 48 | * 49 | * @hide 50 | */ 51 | class MaterialProgressDrawable extends Drawable implements Animatable { 52 | private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator(); 53 | private static final Interpolator END_CURVE_INTERPOLATOR = new EndCurveInterpolator(); 54 | private static final Interpolator START_CURVE_INTERPOLATOR = new StartCurveInterpolator(); 55 | private static final Interpolator EASE_INTERPOLATOR = new AccelerateDecelerateInterpolator(); 56 | 57 | @Retention(RetentionPolicy.CLASS) 58 | @IntDef({LARGE, DEFAULT}) 59 | public @interface ProgressDrawableSize {} 60 | // Maps to ProgressBar.Large style 61 | static final int LARGE = 0; 62 | // Maps to ProgressBar default style 63 | static final int DEFAULT = 1; 64 | 65 | // Maps to ProgressBar default style 66 | private static final int CIRCLE_DIAMETER = 40; 67 | private static final float CENTER_RADIUS = 8.75f; //should add up to 10 when + stroke_width 68 | private static final float STROKE_WIDTH = 2.5f; 69 | 70 | // Maps to ProgressBar.Large style 71 | private static final int CIRCLE_DIAMETER_LARGE = 56; 72 | private static final float CENTER_RADIUS_LARGE = 12.5f; 73 | private static final float STROKE_WIDTH_LARGE = 3f; 74 | 75 | private final int[] COLORS = new int[] { 76 | Color.BLACK 77 | }; 78 | 79 | /** The duration of a single progress spin in milliseconds. */ 80 | private static final int ANIMATION_DURATION = 1000 * 80 / 60; 81 | 82 | /** The number of points in the progress "star". */ 83 | private static final float NUM_POINTS = 5f; 84 | /** The list of animators operating on this drawable. */ 85 | private final ArrayList mAnimators = new ArrayList(); 86 | 87 | /** The indicator ring, used to manage animation state. */ 88 | private final Ring mRing; 89 | 90 | /** Canvas rotation in degrees. */ 91 | private float mRotation; 92 | 93 | /** Layout info for the arrowhead in dp */ 94 | private static final int ARROW_WIDTH = 10; 95 | private static final int ARROW_HEIGHT = 5; 96 | private static final float ARROW_OFFSET_ANGLE = 5; 97 | 98 | /** Layout info for the arrowhead for the large spinner in dp */ 99 | private static final int ARROW_WIDTH_LARGE = 12; 100 | private static final int ARROW_HEIGHT_LARGE = 6; 101 | private static final float MAX_PROGRESS_ARC = .8f; 102 | 103 | private Resources mResources; 104 | private View mParent; 105 | private Animation mAnimation; 106 | private float mRotationCount; 107 | private double mWidth; 108 | private double mHeight; 109 | boolean mFinishing; 110 | 111 | public MaterialProgressDrawable(Context context, View parent) { 112 | mParent = parent; 113 | mResources = context.getResources(); 114 | 115 | mRing = new Ring(mCallback); 116 | mRing.setColors(COLORS); 117 | 118 | updateSizes(DEFAULT); 119 | setupAnimators(); 120 | } 121 | 122 | private void setSizeParameters(double progressCircleWidth, double progressCircleHeight, 123 | double centerRadius, double strokeWidth, float arrowWidth, float arrowHeight) { 124 | final Ring ring = mRing; 125 | final DisplayMetrics metrics = mResources.getDisplayMetrics(); 126 | final float screenDensity = metrics.density; 127 | 128 | mWidth = progressCircleWidth * screenDensity; 129 | mHeight = progressCircleHeight * screenDensity; 130 | ring.setStrokeWidth((float) strokeWidth * screenDensity); 131 | ring.setCenterRadius(centerRadius * screenDensity); 132 | ring.setColorIndex(0); 133 | ring.setArrowDimensions(arrowWidth * screenDensity, arrowHeight * screenDensity); 134 | ring.setInsets((int) mWidth, (int) mHeight); 135 | } 136 | 137 | /** 138 | * Set the overall size for the progress spinner. This updates the radius 139 | * and stroke width of the ring. 140 | * 141 | * @param size One of {@link MaterialProgressDrawable.LARGE} or 142 | * {@link MaterialProgressDrawable.DEFAULT} 143 | */ 144 | public void updateSizes(@ProgressDrawableSize int size) { 145 | if (size == LARGE) { 146 | setSizeParameters(CIRCLE_DIAMETER_LARGE, CIRCLE_DIAMETER_LARGE, CENTER_RADIUS_LARGE, 147 | STROKE_WIDTH_LARGE, ARROW_WIDTH_LARGE, ARROW_HEIGHT_LARGE); 148 | } else { 149 | setSizeParameters(CIRCLE_DIAMETER, CIRCLE_DIAMETER, CENTER_RADIUS, STROKE_WIDTH, 150 | ARROW_WIDTH, ARROW_HEIGHT); 151 | } 152 | } 153 | 154 | /** 155 | * @param show Set to true to display the arrowhead on the progress spinner. 156 | */ 157 | public void showArrow(boolean show) { 158 | mRing.setShowArrow(show); 159 | } 160 | 161 | /** 162 | * @param scale Set the scale of the arrowhead for the spinner. 163 | */ 164 | public void setArrowScale(float scale) { 165 | mRing.setArrowScale(scale); 166 | } 167 | 168 | /** 169 | * Set the start and end trim for the progress spinner arc. 170 | * 171 | * @param startAngle start angle 172 | * @param endAngle end angle 173 | */ 174 | public void setStartEndTrim(float startAngle, float endAngle) { 175 | mRing.setStartTrim(startAngle); 176 | mRing.setEndTrim(endAngle); 177 | } 178 | 179 | /** 180 | * Set the amount of rotation to apply to the progress spinner. 181 | * 182 | * @param rotation Rotation is from [0..1] 183 | */ 184 | public void setProgressRotation(float rotation) { 185 | mRing.setRotation(rotation); 186 | } 187 | 188 | /** 189 | * Update the background color of the circle image view. 190 | */ 191 | public void setBackgroundColor(int color) { 192 | mRing.setBackgroundColor(color); 193 | } 194 | 195 | /** 196 | * Set the colors used in the progress animation from color resources. 197 | * The first color will also be the color of the bar that grows in response 198 | * to a user swipe gesture. 199 | * 200 | * @param colors 201 | */ 202 | public void setColorSchemeColors(int... colors) { 203 | mRing.setColors(colors); 204 | mRing.setColorIndex(0); 205 | } 206 | 207 | @Override 208 | public int getIntrinsicHeight() { 209 | return (int) mHeight; 210 | } 211 | 212 | @Override 213 | public int getIntrinsicWidth() { 214 | return (int) mWidth; 215 | } 216 | 217 | @Override 218 | public void draw(Canvas c) { 219 | final Rect bounds = getBounds(); 220 | final int saveCount = c.save(); 221 | c.rotate(mRotation, bounds.exactCenterX(), bounds.exactCenterY()); 222 | mRing.draw(c, bounds); 223 | c.restoreToCount(saveCount); 224 | } 225 | 226 | @Override 227 | public void setAlpha(int alpha) { 228 | mRing.setAlpha(alpha); 229 | } 230 | 231 | public int getAlpha() { 232 | return mRing.getAlpha(); 233 | } 234 | 235 | @Override 236 | public void setColorFilter(ColorFilter colorFilter) { 237 | mRing.setColorFilter(colorFilter); 238 | } 239 | 240 | @SuppressWarnings("unused") 241 | void setRotation(float rotation) { 242 | mRotation = rotation; 243 | invalidateSelf(); 244 | } 245 | 246 | @SuppressWarnings("unused") 247 | private float getRotation() { 248 | return mRotation; 249 | } 250 | 251 | @Override 252 | public int getOpacity() { 253 | return PixelFormat.TRANSLUCENT; 254 | } 255 | 256 | @Override 257 | public boolean isRunning() { 258 | final ArrayList animators = mAnimators; 259 | final int N = animators.size(); 260 | for (int i = 0; i < N; i++) { 261 | final Animation animator = animators.get(i); 262 | if (animator.hasStarted() && !animator.hasEnded()) { 263 | return true; 264 | } 265 | } 266 | return false; 267 | } 268 | 269 | @Override 270 | public void start() { 271 | mAnimation.reset(); 272 | mRing.storeOriginals(); 273 | // Already showing some part of the ring 274 | if (mRing.getEndTrim() != mRing.getStartTrim()) { 275 | mFinishing = true; 276 | mAnimation.setDuration(ANIMATION_DURATION/2); 277 | mParent.startAnimation(mAnimation); 278 | } else { 279 | mRing.setColorIndex(0); 280 | mRing.resetOriginals(); 281 | mAnimation.setDuration(ANIMATION_DURATION); 282 | mParent.startAnimation(mAnimation); 283 | } 284 | } 285 | 286 | @Override 287 | public void stop() { 288 | mParent.clearAnimation(); 289 | setRotation(0); 290 | mRing.setShowArrow(false); 291 | mRing.setColorIndex(0); 292 | mRing.resetOriginals(); 293 | } 294 | 295 | private void applyFinishTranslation(float interpolatedTime, Ring ring) { 296 | // shrink back down and complete a full rotation before 297 | // starting other circles 298 | // Rotation goes between [0..1]. 299 | float targetRotation = (float) (Math.floor(ring.getStartingRotation() / MAX_PROGRESS_ARC) 300 | + 1f); 301 | final float startTrim = ring.getStartingStartTrim() 302 | + (ring.getStartingEndTrim() - ring.getStartingStartTrim()) * interpolatedTime; 303 | ring.setStartTrim(startTrim); 304 | final float rotation = ring.getStartingRotation() 305 | + ((targetRotation - ring.getStartingRotation()) * interpolatedTime); 306 | ring.setRotation(rotation); 307 | } 308 | 309 | private void setupAnimators() { 310 | final Ring ring = mRing; 311 | final Animation animation = new Animation() { 312 | @Override 313 | public void applyTransformation(float interpolatedTime, Transformation t) { 314 | if (mFinishing) { 315 | applyFinishTranslation(interpolatedTime, ring); 316 | } else { 317 | // The minProgressArc is calculated from 0 to create an 318 | // angle that 319 | // matches the stroke width. 320 | final float minProgressArc = (float) Math.toRadians( 321 | ring.getStrokeWidth() / (2 * Math.PI * ring.getCenterRadius())); 322 | final float startingEndTrim = ring.getStartingEndTrim(); 323 | final float startingTrim = ring.getStartingStartTrim(); 324 | final float startingRotation = ring.getStartingRotation(); 325 | 326 | // Offset the minProgressArc to where the endTrim is 327 | // located. 328 | final float minArc = MAX_PROGRESS_ARC - minProgressArc; 329 | final float endTrim = startingEndTrim + (minArc 330 | * START_CURVE_INTERPOLATOR.getInterpolation(interpolatedTime)); 331 | ring.setEndTrim(endTrim); 332 | 333 | final float startTrim = startingTrim + (MAX_PROGRESS_ARC 334 | * END_CURVE_INTERPOLATOR.getInterpolation(interpolatedTime)); 335 | ring.setStartTrim(startTrim); 336 | 337 | final float rotation = startingRotation + (0.25f * interpolatedTime); 338 | ring.setRotation(rotation); 339 | 340 | float groupRotation = ((720.0f / NUM_POINTS) * interpolatedTime) 341 | + (720.0f * (mRotationCount / NUM_POINTS)); 342 | setRotation(groupRotation); 343 | } 344 | } 345 | }; 346 | animation.setRepeatCount(Animation.INFINITE); 347 | animation.setRepeatMode(Animation.RESTART); 348 | animation.setInterpolator(LINEAR_INTERPOLATOR); 349 | animation.setAnimationListener(new Animation.AnimationListener() { 350 | 351 | @Override 352 | public void onAnimationStart(Animation animation) { 353 | mRotationCount = 0; 354 | } 355 | 356 | @Override 357 | public void onAnimationEnd(Animation animation) { 358 | // do nothing 359 | } 360 | 361 | @Override 362 | public void onAnimationRepeat(Animation animation) { 363 | ring.storeOriginals(); 364 | ring.goToNextColor(); 365 | ring.setStartTrim(ring.getEndTrim()); 366 | if (mFinishing) { 367 | // finished closing the last ring from the swipe gesture; go 368 | // into progress mode 369 | mFinishing = false; 370 | animation.setDuration(ANIMATION_DURATION); 371 | ring.setShowArrow(false); 372 | } else { 373 | mRotationCount = (mRotationCount + 1) % (NUM_POINTS); 374 | } 375 | } 376 | }); 377 | mAnimation = animation; 378 | } 379 | 380 | private final Callback mCallback = new Callback() { 381 | @Override 382 | public void invalidateDrawable(Drawable d) { 383 | invalidateSelf(); 384 | } 385 | 386 | @Override 387 | public void scheduleDrawable(Drawable d, Runnable what, long when) { 388 | scheduleSelf(what, when); 389 | } 390 | 391 | @Override 392 | public void unscheduleDrawable(Drawable d, Runnable what) { 393 | unscheduleSelf(what); 394 | } 395 | }; 396 | 397 | private static class Ring { 398 | private final RectF mTempBounds = new RectF(); 399 | private final Paint mPaint = new Paint(); 400 | private final Paint mArrowPaint = new Paint(); 401 | 402 | private final Callback mCallback; 403 | 404 | private float mStartTrim = 0.0f; 405 | private float mEndTrim = 0.0f; 406 | private float mRotation = 0.0f; 407 | private float mStrokeWidth = 5.0f; 408 | private float mStrokeInset = 2.5f; 409 | 410 | private int[] mColors; 411 | // mColorIndex represents the offset into the available mColors that the 412 | // progress circle should currently display. As the progress circle is 413 | // animating, the mColorIndex moves by one to the next available color. 414 | private int mColorIndex; 415 | private float mStartingStartTrim; 416 | private float mStartingEndTrim; 417 | private float mStartingRotation; 418 | private boolean mShowArrow; 419 | private Path mArrow; 420 | private float mArrowScale; 421 | private double mRingCenterRadius; 422 | private int mArrowWidth; 423 | private int mArrowHeight; 424 | private int mAlpha; 425 | private final Paint mCirclePaint = new Paint(); 426 | private int mBackgroundColor; 427 | 428 | public Ring(Callback callback) { 429 | mCallback = callback; 430 | 431 | mPaint.setStrokeCap(Paint.Cap.SQUARE); 432 | mPaint.setAntiAlias(true); 433 | mPaint.setStyle(Style.STROKE); 434 | 435 | mArrowPaint.setStyle(Paint.Style.FILL); 436 | mArrowPaint.setAntiAlias(true); 437 | } 438 | 439 | public void setBackgroundColor(int color) { 440 | mBackgroundColor = color; 441 | } 442 | 443 | /** 444 | * Set the dimensions of the arrowhead. 445 | * 446 | * @param width Width of the hypotenuse of the arrow head 447 | * @param height Height of the arrow point 448 | */ 449 | public void setArrowDimensions(float width, float height) { 450 | mArrowWidth = (int) width; 451 | mArrowHeight = (int) height; 452 | } 453 | 454 | /** 455 | * Draw the progress spinner 456 | */ 457 | public void draw(Canvas c, Rect bounds) { 458 | final RectF arcBounds = mTempBounds; 459 | arcBounds.set(bounds); 460 | arcBounds.inset(mStrokeInset, mStrokeInset); 461 | 462 | final float startAngle = (mStartTrim + mRotation) * 360; 463 | final float endAngle = (mEndTrim + mRotation) * 360; 464 | float sweepAngle = endAngle - startAngle; 465 | 466 | mPaint.setColor(mColors[mColorIndex]); 467 | c.drawArc(arcBounds, startAngle, sweepAngle, false, mPaint); 468 | 469 | drawTriangle(c, startAngle, sweepAngle, bounds); 470 | 471 | if (mAlpha < 255) { 472 | mCirclePaint.setColor(mBackgroundColor); 473 | mCirclePaint.setAlpha(255 - mAlpha); 474 | c.drawCircle(bounds.exactCenterX(), bounds.exactCenterY(), bounds.width() / 2, 475 | mCirclePaint); 476 | } 477 | } 478 | 479 | private void drawTriangle(Canvas c, float startAngle, float sweepAngle, Rect bounds) { 480 | if (mShowArrow) { 481 | if (mArrow == null) { 482 | mArrow = new android.graphics.Path(); 483 | mArrow.setFillType(android.graphics.Path.FillType.EVEN_ODD); 484 | } else { 485 | mArrow.reset(); 486 | } 487 | 488 | // Adjust the position of the triangle so that it is inset as 489 | // much as the arc, but also centered on the arc. 490 | float inset = (int) mStrokeInset / 2 * mArrowScale; 491 | float x = (float) (mRingCenterRadius * Math.cos(0) + bounds.exactCenterX()); 492 | float y = (float) (mRingCenterRadius * Math.sin(0) + bounds.exactCenterY()); 493 | 494 | // Update the path each time. This works around an issue in SKIA 495 | // where concatenating a rotation matrix to a scale matrix 496 | // ignored a starting negative rotation. This appears to have 497 | // been fixed as of API 21. 498 | mArrow.moveTo(0, 0); 499 | mArrow.lineTo(mArrowWidth * mArrowScale, 0); 500 | mArrow.lineTo((mArrowWidth * mArrowScale / 2), (mArrowHeight 501 | * mArrowScale)); 502 | mArrow.offset(x - inset, y); 503 | mArrow.close(); 504 | // draw a triangle 505 | mArrowPaint.setColor(mColors[mColorIndex]); 506 | c.rotate(startAngle + sweepAngle - ARROW_OFFSET_ANGLE, bounds.exactCenterX(), 507 | bounds.exactCenterY()); 508 | c.drawPath(mArrow, mArrowPaint); 509 | } 510 | } 511 | 512 | /** 513 | * Set the colors the progress spinner alternates between. 514 | * 515 | * @param colors Array of integers describing the colors. Must be non-null. 516 | */ 517 | public void setColors(@NonNull int[] colors) { 518 | mColors = colors; 519 | // if colors are reset, make sure to reset the color index as well 520 | setColorIndex(0); 521 | } 522 | 523 | /** 524 | * @param index Index into the color array of the color to display in 525 | * the progress spinner. 526 | */ 527 | public void setColorIndex(int index) { 528 | mColorIndex = index; 529 | } 530 | 531 | /** 532 | * Proceed to the next available ring color. This will automatically 533 | * wrap back to the beginning of colors. 534 | */ 535 | public void goToNextColor() { 536 | mColorIndex = (mColorIndex + 1) % (mColors.length); 537 | } 538 | 539 | public void setColorFilter(ColorFilter filter) { 540 | mPaint.setColorFilter(filter); 541 | invalidateSelf(); 542 | } 543 | 544 | /** 545 | * @param alpha Set the alpha of the progress spinner and associated arrowhead. 546 | */ 547 | public void setAlpha(int alpha) { 548 | mAlpha = alpha; 549 | } 550 | 551 | /** 552 | * @return Current alpha of the progress spinner and arrowhead. 553 | */ 554 | public int getAlpha() { 555 | return mAlpha; 556 | } 557 | 558 | /** 559 | * @param strokeWidth Set the stroke width of the progress spinner in pixels. 560 | */ 561 | public void setStrokeWidth(float strokeWidth) { 562 | mStrokeWidth = strokeWidth; 563 | mPaint.setStrokeWidth(strokeWidth); 564 | invalidateSelf(); 565 | } 566 | 567 | @SuppressWarnings("unused") 568 | public float getStrokeWidth() { 569 | return mStrokeWidth; 570 | } 571 | 572 | @SuppressWarnings("unused") 573 | public void setStartTrim(float startTrim) { 574 | mStartTrim = startTrim; 575 | invalidateSelf(); 576 | } 577 | 578 | @SuppressWarnings("unused") 579 | public float getStartTrim() { 580 | return mStartTrim; 581 | } 582 | 583 | public float getStartingStartTrim() { 584 | return mStartingStartTrim; 585 | } 586 | 587 | public float getStartingEndTrim() { 588 | return mStartingEndTrim; 589 | } 590 | 591 | @SuppressWarnings("unused") 592 | public void setEndTrim(float endTrim) { 593 | mEndTrim = endTrim; 594 | invalidateSelf(); 595 | } 596 | 597 | @SuppressWarnings("unused") 598 | public float getEndTrim() { 599 | return mEndTrim; 600 | } 601 | 602 | @SuppressWarnings("unused") 603 | public void setRotation(float rotation) { 604 | mRotation = rotation; 605 | invalidateSelf(); 606 | } 607 | 608 | @SuppressWarnings("unused") 609 | public float getRotation() { 610 | return mRotation; 611 | } 612 | 613 | public void setInsets(int width, int height) { 614 | final float minEdge = (float) Math.min(width, height); 615 | float insets; 616 | if (mRingCenterRadius <= 0 || minEdge < 0) { 617 | insets = (float) Math.ceil(mStrokeWidth / 2.0f); 618 | } else { 619 | insets = (float) (minEdge / 2.0f - mRingCenterRadius); 620 | } 621 | mStrokeInset = insets; 622 | } 623 | 624 | @SuppressWarnings("unused") 625 | public float getInsets() { 626 | return mStrokeInset; 627 | } 628 | 629 | /** 630 | * @param centerRadius Inner radius in px of the circle the progress 631 | * spinner arc traces. 632 | */ 633 | public void setCenterRadius(double centerRadius) { 634 | mRingCenterRadius = centerRadius; 635 | } 636 | 637 | public double getCenterRadius() { 638 | return mRingCenterRadius; 639 | } 640 | 641 | /** 642 | * @param show Set to true to show the arrow head on the progress spinner. 643 | */ 644 | public void setShowArrow(boolean show) { 645 | if (mShowArrow != show) { 646 | mShowArrow = show; 647 | invalidateSelf(); 648 | } 649 | } 650 | 651 | /** 652 | * @param scale Set the scale of the arrowhead for the spinner. 653 | */ 654 | public void setArrowScale(float scale) { 655 | if (scale != mArrowScale) { 656 | mArrowScale = scale; 657 | invalidateSelf(); 658 | } 659 | } 660 | 661 | /** 662 | * @return The amount the progress spinner is currently rotated, between [0..1]. 663 | */ 664 | public float getStartingRotation() { 665 | return mStartingRotation; 666 | } 667 | 668 | /** 669 | * If the start / end trim are offset to begin with, store them so that 670 | * animation starts from that offset. 671 | */ 672 | public void storeOriginals() { 673 | mStartingStartTrim = mStartTrim; 674 | mStartingEndTrim = mEndTrim; 675 | mStartingRotation = mRotation; 676 | } 677 | 678 | /** 679 | * Reset the progress spinner to default rotation, start and end angles. 680 | */ 681 | public void resetOriginals() { 682 | mStartingStartTrim = 0; 683 | mStartingEndTrim = 0; 684 | mStartingRotation = 0; 685 | setStartTrim(0); 686 | setEndTrim(0); 687 | setRotation(0); 688 | } 689 | 690 | private void invalidateSelf() { 691 | mCallback.invalidateDrawable(null); 692 | } 693 | } 694 | 695 | /** 696 | * Squishes the interpolation curve into the second half of the animation. 697 | */ 698 | private static class EndCurveInterpolator extends AccelerateDecelerateInterpolator { 699 | @Override 700 | public float getInterpolation(float input) { 701 | return super.getInterpolation(Math.max(0, (input - 0.5f) * 2.0f)); 702 | } 703 | } 704 | 705 | /** 706 | * Squishes the interpolation curve into the first half of the animation. 707 | */ 708 | private static class StartCurveInterpolator extends AccelerateDecelerateInterpolator { 709 | @Override 710 | public float getInterpolation(float input) { 711 | return super.getInterpolation(Math.min(1, input * 2.0f)); 712 | } 713 | } 714 | } -------------------------------------------------------------------------------- /android/src/com/rkam/swiperefreshlayout/MySwipeRefreshLayout.java: -------------------------------------------------------------------------------- 1 | package com.rkam.swiperefreshlayout; 2 | 3 | import android.content.Context; 4 | import android.support.v4.view.ViewCompat; 5 | import android.util.AttributeSet; 6 | import android.view.View; 7 | import android.widget.AbsListView; 8 | import android.widget.FrameLayout; 9 | import android.widget.ScrollView; 10 | import android.widget.RelativeLayout; 11 | 12 | /** 13 | * MySwipeRefreshLayout is a modified SwipeRefreshLayout so that Titanium views 14 | * are supported. It overrides the canChildScrollUp method used by Android to 15 | * determine whether the gesture is for refresh or if the user is just scrolling up 16 | * the scrollable view. 17 | */ 18 | public class MySwipeRefreshLayout extends SwipeRefreshLayout { 19 | 20 | private View nativeView; // usually the layout wrapping the listview 21 | private View nativeChildView; // the native android listview 22 | 23 | public MySwipeRefreshLayout(Context context) { 24 | super(context); 25 | } 26 | 27 | public MySwipeRefreshLayout(Context context, AttributeSet attrs) { 28 | super(context, attrs); 29 | } 30 | 31 | public View getNativeView() { 32 | return nativeView; 33 | } 34 | 35 | public void setNativeView(View view) { 36 | this.nativeView = view; 37 | } 38 | 39 | @Override 40 | public boolean canChildScrollUp() { 41 | // ScrollViews are also an instance of FrameLayouts and we do not want to get 42 | // the ScrollView's child view as it will not work. 43 | if (nativeView instanceof FrameLayout && !(nativeView instanceof ScrollView)) { 44 | // Try to get the native Android ListView inside the FrameLayout 45 | nativeChildView = ((FrameLayout) nativeView).getChildAt(0); 46 | } else if(nativeView instanceof RelativeLayout){ 47 | //get the ListView inside the tableView 48 | nativeChildView = ((RelativeLayout) nativeView).getChildAt(1); 49 | nativeChildView = ((FrameLayout) nativeChildView).getChildAt(0); 50 | } else { 51 | nativeChildView = nativeView; 52 | } 53 | if (android.os.Build.VERSION.SDK_INT < 14) { 54 | if (nativeChildView instanceof AbsListView) { 55 | final AbsListView absListView = (AbsListView) nativeChildView; 56 | return absListView.getChildCount() > 0 57 | && (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0) 58 | .getTop() < absListView.getPaddingTop()); 59 | } else { 60 | return nativeChildView.getScrollY() > 0; 61 | } 62 | } else { 63 | return ViewCompat.canScrollVertically(nativeChildView, -1); 64 | } 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /android/src/com/rkam/swiperefreshlayout/ScrollingView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.rkam.swiperefreshlayout; 18 | 19 | /** 20 | * An interface that can be implemented by Views to provide scroll related APIs. 21 | */ 22 | public interface ScrollingView { 23 | /** 24 | *

Compute the horizontal range that the horizontal scrollbar 25 | * represents.

26 | * 27 | *

The range is expressed in arbitrary units that must be the same as the 28 | * units used by {@link #computeHorizontalScrollExtent()} and 29 | * {@link #computeHorizontalScrollOffset()}.

30 | * 31 | *

The default range is the drawing width of this view.

32 | * 33 | * @return the total horizontal range represented by the horizontal 34 | * scrollbar 35 | * 36 | * @see #computeHorizontalScrollExtent() 37 | * @see #computeHorizontalScrollOffset() 38 | * @see android.widget.ScrollBarDrawable 39 | */ 40 | int computeHorizontalScrollRange(); 41 | 42 | /** 43 | *

Compute the horizontal offset of the horizontal scrollbar's thumb 44 | * within the horizontal range. This value is used to compute the position 45 | * of the thumb within the scrollbar's track.

46 | * 47 | *

The range is expressed in arbitrary units that must be the same as the 48 | * units used by {@link #computeHorizontalScrollRange()} and 49 | * {@link #computeHorizontalScrollExtent()}.

50 | * 51 | *

The default offset is the scroll offset of this view.

52 | * 53 | * @return the horizontal offset of the scrollbar's thumb 54 | * 55 | * @see #computeHorizontalScrollRange() 56 | * @see #computeHorizontalScrollExtent() 57 | * @see android.widget.ScrollBarDrawable 58 | */ 59 | int computeHorizontalScrollOffset(); 60 | 61 | /** 62 | *

Compute the horizontal extent of the horizontal scrollbar's thumb 63 | * within the horizontal range. This value is used to compute the length 64 | * of the thumb within the scrollbar's track.

65 | * 66 | *

The range is expressed in arbitrary units that must be the same as the 67 | * units used by {@link #computeHorizontalScrollRange()} and 68 | * {@link #computeHorizontalScrollOffset()}.

69 | * 70 | *

The default extent is the drawing width of this view.

71 | * 72 | * @return the horizontal extent of the scrollbar's thumb 73 | * 74 | * @see #computeHorizontalScrollRange() 75 | * @see #computeHorizontalScrollOffset() 76 | * @see android.widget.ScrollBarDrawable 77 | */ 78 | int computeHorizontalScrollExtent(); 79 | 80 | /** 81 | *

Compute the vertical range that the vertical scrollbar represents.

82 | * 83 | *

The range is expressed in arbitrary units that must be the same as the 84 | * units used by {@link #computeVerticalScrollExtent()} and 85 | * {@link #computeVerticalScrollOffset()}.

86 | * 87 | * @return the total vertical range represented by the vertical scrollbar 88 | * 89 | *

The default range is the drawing height of this view.

90 | * 91 | * @see #computeVerticalScrollExtent() 92 | * @see #computeVerticalScrollOffset() 93 | * @see android.widget.ScrollBarDrawable 94 | */ 95 | int computeVerticalScrollRange(); 96 | 97 | /** 98 | *

Compute the vertical offset of the vertical scrollbar's thumb 99 | * within the horizontal range. This value is used to compute the position 100 | * of the thumb within the scrollbar's track.

101 | * 102 | *

The range is expressed in arbitrary units that must be the same as the 103 | * units used by {@link #computeVerticalScrollRange()} and 104 | * {@link #computeVerticalScrollExtent()}.

105 | * 106 | *

The default offset is the scroll offset of this view.

107 | * 108 | * @return the vertical offset of the scrollbar's thumb 109 | * 110 | * @see #computeVerticalScrollRange() 111 | * @see #computeVerticalScrollExtent() 112 | * @see android.widget.ScrollBarDrawable 113 | */ 114 | int computeVerticalScrollOffset(); 115 | 116 | /** 117 | *

Compute the vertical extent of the vertical scrollbar's thumb 118 | * within the vertical range. This value is used to compute the length 119 | * of the thumb within the scrollbar's track.

120 | * 121 | *

The range is expressed in arbitrary units that must be the same as the 122 | * units used by {@link #computeVerticalScrollRange()} and 123 | * {@link #computeVerticalScrollOffset()}.

124 | * 125 | *

The default extent is the drawing height of this view.

126 | * 127 | * @return the vertical extent of the scrollbar's thumb 128 | * 129 | * @see #computeVerticalScrollRange() 130 | * @see #computeVerticalScrollOffset() 131 | * @see android.widget.ScrollBarDrawable 132 | */ 133 | int computeVerticalScrollExtent(); 134 | } -------------------------------------------------------------------------------- /android/src/com/rkam/swiperefreshlayout/SwipeProgressBar.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.rkam.swiperefreshlayout; 18 | 19 | import android.graphics.Canvas; 20 | import android.graphics.Paint; 21 | import android.graphics.Rect; 22 | import android.graphics.RectF; 23 | import android.support.v4.view.ViewCompat; 24 | import android.view.View; 25 | import android.view.animation.AnimationUtils; 26 | import android.view.animation.Interpolator; 27 | 28 | 29 | /** 30 | * Custom progress bar that shows a cycle of colors as widening circles that 31 | * overdraw each other. When finished, the bar is cleared from the inside out as 32 | * the main cycle continues. Before running, this can also indicate how close 33 | * the user is to triggering something (e.g. how far they need to pull down to 34 | * trigger a refresh). 35 | */ 36 | final class SwipeProgressBar { 37 | 38 | // Default progress animation colors are grays. 39 | private final static int COLOR1 = 0xB3000000; 40 | private final static int COLOR2 = 0x80000000; 41 | private final static int COLOR3 = 0x4d000000; 42 | private final static int COLOR4 = 0x1a000000; 43 | 44 | // The duration of the animation cycle. 45 | private static final int ANIMATION_DURATION_MS = 2000; 46 | 47 | // The duration of the animation to clear the bar. 48 | private static final int FINISH_ANIMATION_DURATION_MS = 1000; 49 | 50 | // Interpolator for varying the speed of the animation. 51 | private static final Interpolator INTERPOLATOR = new FastOutSlowInInterpolator(); 52 | 53 | private final Paint mPaint = new Paint(); 54 | private final RectF mClipRect = new RectF(); 55 | private float mTriggerPercentage; 56 | private long mStartTime; 57 | private long mFinishTime; 58 | private boolean mRunning; 59 | 60 | // Colors used when rendering the animation, 61 | private int mColor1; 62 | private int mColor2; 63 | private int mColor3; 64 | private int mColor4; 65 | private View mParent; 66 | 67 | private Rect mBounds = new Rect(); 68 | 69 | public SwipeProgressBar(View parent) { 70 | mParent = parent; 71 | mColor1 = COLOR1; 72 | mColor2 = COLOR2; 73 | mColor3 = COLOR3; 74 | mColor4 = COLOR4; 75 | } 76 | 77 | /** 78 | * Set the four colors used in the progress animation. The first color will 79 | * also be the color of the bar that grows in response to a user swipe 80 | * gesture. 81 | * 82 | * @param color1 Integer representation of a color. 83 | * @param color2 Integer representation of a color. 84 | * @param color3 Integer representation of a color. 85 | * @param color4 Integer representation of a color. 86 | */ 87 | void setColorScheme(int color1, int color2, int color3, int color4) { 88 | mColor1 = color1; 89 | mColor2 = color2; 90 | mColor3 = color3; 91 | mColor4 = color4; 92 | } 93 | 94 | /** 95 | * Update the progress the user has made toward triggering the swipe 96 | * gesture. and use this value to update the percentage of the trigger that 97 | * is shown. 98 | */ 99 | void setTriggerPercentage(float triggerPercentage) { 100 | mTriggerPercentage = triggerPercentage; 101 | mStartTime = 0; 102 | ViewCompat.postInvalidateOnAnimation( 103 | mParent, mBounds.left, mBounds.top, mBounds.right, mBounds.bottom); 104 | } 105 | 106 | /** 107 | * Start showing the progress animation. 108 | */ 109 | void start() { 110 | if (!mRunning) { 111 | mTriggerPercentage = 0; 112 | mStartTime = AnimationUtils.currentAnimationTimeMillis(); 113 | mRunning = true; 114 | mParent.postInvalidate(); 115 | } 116 | } 117 | 118 | /** 119 | * Stop showing the progress animation. 120 | */ 121 | void stop() { 122 | if (mRunning) { 123 | mTriggerPercentage = 0; 124 | mFinishTime = AnimationUtils.currentAnimationTimeMillis(); 125 | mRunning = false; 126 | mParent.postInvalidate(); 127 | } 128 | } 129 | 130 | /** 131 | * @return Return whether the progress animation is currently running. 132 | */ 133 | boolean isRunning() { 134 | return mRunning || mFinishTime > 0; 135 | } 136 | 137 | void draw(Canvas canvas) { 138 | final int width = mBounds.width(); 139 | final int height = mBounds.height(); 140 | final int cx = width / 2; 141 | final int cy = height / 2; 142 | boolean drawTriggerWhileFinishing = false; 143 | int restoreCount = canvas.save(); 144 | canvas.clipRect(mBounds); 145 | 146 | if (mRunning || (mFinishTime > 0)) { 147 | long now = AnimationUtils.currentAnimationTimeMillis(); 148 | long elapsed = (now - mStartTime) % ANIMATION_DURATION_MS; 149 | long iterations = (now - mStartTime) / ANIMATION_DURATION_MS; 150 | float rawProgress = (elapsed / (ANIMATION_DURATION_MS / 100f)); 151 | 152 | // If we're not running anymore, that means we're running through 153 | // the finish animation. 154 | if (!mRunning) { 155 | // If the finish animation is done, don't draw anything, and 156 | // don't repost. 157 | if ((now - mFinishTime) >= FINISH_ANIMATION_DURATION_MS) { 158 | mFinishTime = 0; 159 | return; 160 | } 161 | 162 | // Otherwise, use a 0 opacity alpha layer to clear the animation 163 | // from the inside out. This layer will prevent the circles from 164 | // drawing within its bounds. 165 | long finishElapsed = (now - mFinishTime) % FINISH_ANIMATION_DURATION_MS; 166 | float finishProgress = (finishElapsed / (FINISH_ANIMATION_DURATION_MS / 100f)); 167 | float pct = (finishProgress / 100f); 168 | // Radius of the circle is half of the screen. 169 | float clearRadius = width / 2 * INTERPOLATOR.getInterpolation(pct); 170 | mClipRect.set(cx - clearRadius, 0, cx + clearRadius, height); 171 | canvas.saveLayerAlpha(mClipRect, 0, 0); 172 | // Only draw the trigger if there is a space in the center of 173 | // this refreshing view that needs to be filled in by the 174 | // trigger. If the progress view is just still animating, let it 175 | // continue animating. 176 | drawTriggerWhileFinishing = true; 177 | } 178 | 179 | // First fill in with the last color that would have finished drawing. 180 | if (iterations == 0) { 181 | canvas.drawColor(mColor1); 182 | } else { 183 | if (rawProgress >= 0 && rawProgress < 25) { 184 | canvas.drawColor(mColor4); 185 | } else if (rawProgress >= 25 && rawProgress < 50) { 186 | canvas.drawColor(mColor1); 187 | } else if (rawProgress >= 50 && rawProgress < 75) { 188 | canvas.drawColor(mColor2); 189 | } else { 190 | canvas.drawColor(mColor3); 191 | } 192 | } 193 | 194 | // Then draw up to 4 overlapping concentric circles of varying radii, based on how far 195 | // along we are in the cycle. 196 | // progress 0-50 draw mColor2 197 | // progress 25-75 draw mColor3 198 | // progress 50-100 draw mColor4 199 | // progress 75 (wrap to 25) draw mColor1 200 | if ((rawProgress >= 0 && rawProgress <= 25)) { 201 | float pct = (((rawProgress + 25) * 2) / 100f); 202 | drawCircle(canvas, cx, cy, mColor1, pct); 203 | } 204 | if (rawProgress >= 0 && rawProgress <= 50) { 205 | float pct = ((rawProgress * 2) / 100f); 206 | drawCircle(canvas, cx, cy, mColor2, pct); 207 | } 208 | if (rawProgress >= 25 && rawProgress <= 75) { 209 | float pct = (((rawProgress - 25) * 2) / 100f); 210 | drawCircle(canvas, cx, cy, mColor3, pct); 211 | } 212 | if (rawProgress >= 50 && rawProgress <= 100) { 213 | float pct = (((rawProgress - 50) * 2) / 100f); 214 | drawCircle(canvas, cx, cy, mColor4, pct); 215 | } 216 | if ((rawProgress >= 75 && rawProgress <= 100)) { 217 | float pct = (((rawProgress - 75) * 2) / 100f); 218 | drawCircle(canvas, cx, cy, mColor1, pct); 219 | } 220 | if (mTriggerPercentage > 0 && drawTriggerWhileFinishing) { 221 | // There is some portion of trigger to draw. Restore the canvas, 222 | // then draw the trigger. Otherwise, the trigger does not appear 223 | // until after the bar has finished animating and appears to 224 | // just jump in at a larger width than expected. 225 | canvas.restoreToCount(restoreCount); 226 | restoreCount = canvas.save(); 227 | canvas.clipRect(mBounds); 228 | drawTrigger(canvas, cx, cy); 229 | } 230 | // Keep running until we finish out the last cycle. 231 | ViewCompat.postInvalidateOnAnimation( 232 | mParent, mBounds.left, mBounds.top, mBounds.right, mBounds.bottom); 233 | } else { 234 | // Otherwise if we're in the middle of a trigger, draw that. 235 | if (mTriggerPercentage > 0 && mTriggerPercentage <= 1.0) { 236 | drawTrigger(canvas, cx, cy); 237 | } 238 | } 239 | canvas.restoreToCount(restoreCount); 240 | } 241 | 242 | private void drawTrigger(Canvas canvas, int cx, int cy) { 243 | mPaint.setColor(mColor1); 244 | canvas.drawCircle(cx, cy, cx * mTriggerPercentage, mPaint); 245 | } 246 | 247 | /** 248 | * Draws a circle centered in the view. 249 | * 250 | * @param canvas the canvas to draw on 251 | * @param cx the center x coordinate 252 | * @param cy the center y coordinate 253 | * @param color the color to draw 254 | * @param pct the percentage of the view that the circle should cover 255 | */ 256 | private void drawCircle(Canvas canvas, float cx, float cy, int color, float pct) { 257 | mPaint.setColor(color); 258 | canvas.save(); 259 | canvas.translate(cx, cy); 260 | float radiusScale = INTERPOLATOR.getInterpolation(pct); 261 | canvas.scale(radiusScale, radiusScale); 262 | canvas.drawCircle(0, 0, cx, mPaint); 263 | canvas.restore(); 264 | } 265 | 266 | /** 267 | * Set the drawing bounds of this SwipeProgressBar. 268 | */ 269 | void setBounds(int left, int top, int right, int bottom) { 270 | mBounds.left = left; 271 | mBounds.top = top; 272 | mBounds.right = right; 273 | mBounds.bottom = bottom; 274 | } 275 | } -------------------------------------------------------------------------------- /android/src/com/rkam/swiperefreshlayout/SwipeRefresh.java: -------------------------------------------------------------------------------- 1 | package com.rkam.swiperefreshlayout; 2 | 3 | import org.appcelerator.kroll.KrollDict; 4 | import org.appcelerator.titanium.TiApplication; 5 | import org.appcelerator.titanium.proxy.TiViewProxy; 6 | import org.appcelerator.titanium.util.TiRHelper; 7 | import org.appcelerator.titanium.util.TiRHelper.ResourceNotFoundException; 8 | import org.appcelerator.titanium.view.TiUIView; 9 | 10 | import android.util.Log; 11 | import android.view.LayoutInflater; 12 | 13 | import com.rkam.swiperefreshlayout.SwipeRefreshLayout.OnRefreshListener; 14 | 15 | public class SwipeRefresh extends TiUIView { 16 | 17 | private MySwipeRefreshLayout layout; 18 | private TiViewProxy view; 19 | 20 | public static final String PROPERTY_VIEW = "view"; 21 | public static final String PROPERTY_COLOR_SCHEME = "colorScheme"; 22 | private static final String TAG = "SwipeRefresh"; 23 | 24 | int color1 = 0; 25 | int color2 = 0; 26 | int color3 = 0; 27 | int color4 = 0; 28 | int layout_swipe_refresh = 0; 29 | 30 | // Constructor for SwipeRefresh 31 | public SwipeRefresh(final SwipeRefreshProxy proxy) { 32 | super(proxy); 33 | 34 | try { 35 | layout_swipe_refresh = TiRHelper.getResource("layout.swipe_refresh"); 36 | color1 = TiRHelper.getResource("color.color1"); 37 | color2 = TiRHelper.getResource("color.color2"); 38 | color3 = TiRHelper.getResource("color.color3"); 39 | color4 = TiRHelper.getResource("color.color4"); 40 | } 41 | catch (ResourceNotFoundException e) { 42 | Log.e(TAG, "Resources not found!"); 43 | } 44 | 45 | LayoutInflater inflater = LayoutInflater.from(TiApplication.getInstance()); 46 | layout = (MySwipeRefreshLayout) inflater.inflate(layout_swipe_refresh, null, false); 47 | 48 | layout.setOnRefreshListener(new OnRefreshListener() { 49 | @Override 50 | public void onRefresh() { 51 | if (proxy.hasListeners("refreshing")) { 52 | proxy.fireEvent("refreshing", null); 53 | } 54 | } 55 | }); 56 | 57 | setNativeView(layout); 58 | } 59 | 60 | @Override 61 | public void processProperties(KrollDict d) { 62 | if (d.containsKey(PROPERTY_VIEW)) { 63 | Object view = d.get(PROPERTY_VIEW); 64 | if (view != null && view instanceof TiViewProxy) { 65 | this.view = (TiViewProxy) view; 66 | this.layout.setNativeView(this.view.getOrCreateView().getNativeView()); 67 | this.layout.addView(this.view.getOrCreateView().getOuterView()); 68 | this.layout.setColorScheme(color1, color2, color3, color4); 69 | } 70 | } 71 | super.processProperties(d); 72 | } 73 | 74 | public void add(TiViewProxy view) { 75 | if (view != null && view instanceof TiViewProxy) { 76 | this.view = (TiViewProxy) view; 77 | this.layout.setNativeView(this.view.getOrCreateView().getNativeView()); 78 | this.layout.addView(this.view.getOrCreateView().getOuterView()); 79 | this.layout.setColorScheme(color1, color2, color3, color4); 80 | } 81 | } 82 | 83 | public boolean isRefreshing() { 84 | return this.layout.isRefreshing(); 85 | } 86 | 87 | public void setRefreshing(final boolean refreshing) { 88 | if (this.view != null) { 89 | this.view.getActivity().runOnUiThread(new Runnable() { 90 | @Override 91 | public void run() { 92 | layout.setRefreshing(refreshing); 93 | } 94 | }); 95 | } 96 | } 97 | 98 | public void setEnabled(final boolean refreshEnabled) { 99 | if (this.view != null) { 100 | this.view.getActivity().runOnUiThread(new Runnable() { 101 | @Override 102 | public void run() { 103 | layout.setEnabled(refreshEnabled); 104 | } 105 | }); 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /android/src/com/rkam/swiperefreshlayout/SwipeRefreshLayout.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.rkam.swiperefreshlayout; 18 | 19 | import java.lang.reflect.InvocationTargetException; 20 | import java.lang.reflect.Method; 21 | 22 | import android.content.Context; 23 | import android.content.res.Resources; 24 | import android.content.res.TypedArray; 25 | import android.support.v4.view.MotionEventCompat; 26 | import android.support.v4.view.ViewCompat; 27 | import android.util.AttributeSet; 28 | import android.util.DisplayMetrics; 29 | import android.util.Log; 30 | import android.view.MotionEvent; 31 | import android.view.View; 32 | import android.view.ViewConfiguration; 33 | import android.view.ViewGroup; 34 | import android.view.animation.Animation; 35 | import android.view.animation.Animation.AnimationListener; 36 | import android.view.animation.DecelerateInterpolator; 37 | import android.view.animation.Transformation; 38 | import android.widget.AbsListView; 39 | 40 | /** 41 | * The SwipeRefreshLayout should be used whenever the user can refresh the 42 | * contents of a view via a vertical swipe gesture. The activity that 43 | * instantiates this view should add an OnRefreshListener to be notified 44 | * whenever the swipe to refresh gesture is completed. The SwipeRefreshLayout 45 | * will notify the listener each and every time the gesture is completed again; 46 | * the listener is responsible for correctly determining when to actually 47 | * initiate a refresh of its content. If the listener determines there should 48 | * not be a refresh, it must call setRefreshing(false) to cancel any visual 49 | * indication of a refresh. If an activity wishes to show just the progress 50 | * animation, it should call setRefreshing(true). To disable the gesture and 51 | * progress animation, call setEnabled(false) on the view. 52 | *

53 | * This layout should be made the parent of the view that will be refreshed as a 54 | * result of the gesture and can only support one direct child. This view will 55 | * also be made the target of the gesture and will be forced to match both the 56 | * width and the height supplied in this layout. The SwipeRefreshLayout does not 57 | * provide accessibility events; instead, a menu item must be provided to allow 58 | * refresh of the content wherever this gesture is used. 59 | *

60 | */ 61 | public class SwipeRefreshLayout extends ViewGroup { 62 | // Maps to ProgressBar.Large style 63 | public static final int LARGE = MaterialProgressDrawable.LARGE; 64 | // Maps to ProgressBar default style 65 | public static final int DEFAULT = MaterialProgressDrawable.DEFAULT; 66 | 67 | private static final String LOG_TAG = SwipeRefreshLayout.class.getSimpleName(); 68 | 69 | private static final int MAX_ALPHA = 255; 70 | private static final int STARTING_PROGRESS_ALPHA = (int) (.3f * MAX_ALPHA); 71 | 72 | private static final int CIRCLE_DIAMETER = 40; 73 | private static final int CIRCLE_DIAMETER_LARGE = 56; 74 | 75 | private static final float DECELERATE_INTERPOLATION_FACTOR = 2f; 76 | private static final int INVALID_POINTER = -1; 77 | private static final float DRAG_RATE = .5f; 78 | 79 | // Max amount of circle that can be filled by progress during swipe gesture, 80 | // where 1.0 is a full circle 81 | private static final float MAX_PROGRESS_ANGLE = .8f; 82 | 83 | private static final int SCALE_DOWN_DURATION = 150; 84 | 85 | private static final int ALPHA_ANIMATION_DURATION = 300; 86 | 87 | private static final int ANIMATE_TO_TRIGGER_DURATION = 200; 88 | 89 | private static final int ANIMATE_TO_START_DURATION = 200; 90 | 91 | // Default background for the progress spinner 92 | private static final int CIRCLE_BG_LIGHT = 0xFFFAFAFA; 93 | // Default offset in dips from the top of the view to where the progress spinner should stop 94 | private static final int DEFAULT_CIRCLE_TARGET = 64; 95 | 96 | private View mTarget; // the target of the gesture 97 | private OnRefreshListener mListener; 98 | private boolean mRefreshing = false; 99 | private int mTouchSlop; 100 | private float mTotalDragDistance = -1; 101 | private int mMediumAnimationDuration; 102 | private int mCurrentTargetOffsetTop; 103 | // Whether or not the starting offset has been determined. 104 | private boolean mOriginalOffsetCalculated = false; 105 | 106 | private float mInitialMotionY; 107 | private float mInitialDownY; 108 | private boolean mIsBeingDragged; 109 | private int mActivePointerId = INVALID_POINTER; 110 | // Whether this item is scaled up rather than clipped 111 | private boolean mScale; 112 | 113 | // Target is returning to its start offset because it was cancelled or a 114 | // refresh was triggered. 115 | private boolean mReturningToStart; 116 | private final DecelerateInterpolator mDecelerateInterpolator; 117 | private static final int[] LAYOUT_ATTRS = new int[] { 118 | android.R.attr.enabled 119 | }; 120 | 121 | private CircleImageView mCircleView; 122 | private int mCircleViewIndex = -1; 123 | 124 | protected int mFrom; 125 | 126 | private float mStartingScale; 127 | 128 | protected int mOriginalOffsetTop; 129 | 130 | private MaterialProgressDrawable mProgress; 131 | 132 | private Animation mScaleAnimation; 133 | 134 | private Animation mScaleDownAnimation; 135 | 136 | private Animation mAlphaStartAnimation; 137 | 138 | private Animation mAlphaMaxAnimation; 139 | 140 | private Animation mScaleDownToStartAnimation; 141 | 142 | private float mSpinnerFinalOffset; 143 | 144 | private boolean mNotify; 145 | 146 | private int mCircleWidth; 147 | 148 | private int mCircleHeight; 149 | 150 | // Whether the client has set a custom starting position; 151 | private boolean mUsingCustomStart; 152 | 153 | private Animation.AnimationListener mRefreshListener = new Animation.AnimationListener() { 154 | @Override 155 | public void onAnimationStart(Animation animation) { 156 | } 157 | 158 | @Override 159 | public void onAnimationRepeat(Animation animation) { 160 | } 161 | 162 | @Override 163 | public void onAnimationEnd(Animation animation) { 164 | if (mRefreshing) { 165 | // Make sure the progress view is fully visible 166 | mProgress.setAlpha(MAX_ALPHA); 167 | mProgress.start(); 168 | if (mNotify) { 169 | if (mListener != null) { 170 | mListener.onRefresh(); 171 | } 172 | } 173 | } else { 174 | mProgress.stop(); 175 | mCircleView.setVisibility(View.GONE); 176 | setColorViewAlpha(MAX_ALPHA); 177 | // Return the circle to its start position 178 | if (mScale) { 179 | setAnimationProgress(0 /* animation complete and view is hidden */); 180 | } else { 181 | setTargetOffsetTopAndBottom(mOriginalOffsetTop - mCurrentTargetOffsetTop, 182 | true /* requires update */); 183 | } 184 | } 185 | mCurrentTargetOffsetTop = mCircleView.getTop(); 186 | } 187 | }; 188 | 189 | private void setColorViewAlpha(int targetAlpha) { 190 | mCircleView.getBackground().setAlpha(targetAlpha); 191 | mProgress.setAlpha(targetAlpha); 192 | } 193 | 194 | /** 195 | * The refresh indicator starting and resting position is always positioned 196 | * near the top of the refreshing content. This position is a consistent 197 | * location, but can be adjusted in either direction based on whether or not 198 | * there is a toolbar or actionbar present. 199 | * 200 | * @param scale Set to true if there is no view at a higher z-order than 201 | * where the progress spinner is set to appear. 202 | * @param start The offset in pixels from the top of this view at which the 203 | * progress spinner should appear. 204 | * @param end The offset in pixels from the top of this view at which the 205 | * progress spinner should come to rest after a successful swipe 206 | * gesture. 207 | */ 208 | public void setProgressViewOffset(boolean scale, int start, int end) { 209 | mScale = scale; 210 | mCircleView.setVisibility(View.GONE); 211 | mOriginalOffsetTop = mCurrentTargetOffsetTop = start; 212 | mSpinnerFinalOffset = end; 213 | mUsingCustomStart = true; 214 | mCircleView.invalidate(); 215 | } 216 | 217 | /** 218 | * The refresh indicator resting position is always positioned near the top 219 | * of the refreshing content. This position is a consistent location, but 220 | * can be adjusted in either direction based on whether or not there is a 221 | * toolbar or actionbar present. 222 | * 223 | * @param scale Set to true if there is no view at a higher z-order than 224 | * where the progress spinner is set to appear. 225 | * @param end The offset in pixels from the top of this view at which the 226 | * progress spinner should come to rest after a successful swipe 227 | * gesture. 228 | */ 229 | public void setProgressViewEndTarget(boolean scale, int end) { 230 | mSpinnerFinalOffset = end; 231 | mScale = scale; 232 | mCircleView.invalidate(); 233 | } 234 | 235 | /** 236 | * One of DEFAULT, or LARGE. 237 | */ 238 | public void setSize(int size) { 239 | if (size != MaterialProgressDrawable.LARGE && size != MaterialProgressDrawable.DEFAULT) { 240 | return; 241 | } 242 | final DisplayMetrics metrics = getResources().getDisplayMetrics(); 243 | if (size == MaterialProgressDrawable.LARGE) { 244 | mCircleHeight = mCircleWidth = (int) (CIRCLE_DIAMETER_LARGE * metrics.density); 245 | } else { 246 | mCircleHeight = mCircleWidth = (int) (CIRCLE_DIAMETER * metrics.density); 247 | } 248 | // force the bounds of the progress circle inside the circle view to 249 | // update by setting it to null before updating its size and then 250 | // re-setting it 251 | mCircleView.setImageDrawable(null); 252 | mProgress.updateSizes(size); 253 | mCircleView.setImageDrawable(mProgress); 254 | } 255 | 256 | /** 257 | * Simple constructor to use when creating a SwipeRefreshLayout from code. 258 | * 259 | * @param context 260 | */ 261 | public SwipeRefreshLayout(Context context) { 262 | this(context, null); 263 | } 264 | 265 | /** 266 | * Constructor that is called when inflating SwipeRefreshLayout from XML. 267 | * 268 | * @param context 269 | * @param attrs 270 | */ 271 | public SwipeRefreshLayout(Context context, AttributeSet attrs) { 272 | super(context, attrs); 273 | 274 | mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); 275 | 276 | mMediumAnimationDuration = getResources().getInteger( 277 | android.R.integer.config_mediumAnimTime); 278 | 279 | setWillNotDraw(false); 280 | mDecelerateInterpolator = new DecelerateInterpolator(DECELERATE_INTERPOLATION_FACTOR); 281 | 282 | final TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS); 283 | setEnabled(a.getBoolean(0, true)); 284 | a.recycle(); 285 | 286 | final DisplayMetrics metrics = getResources().getDisplayMetrics(); 287 | mCircleWidth = (int) (CIRCLE_DIAMETER * metrics.density); 288 | mCircleHeight = (int) (CIRCLE_DIAMETER * metrics.density); 289 | 290 | createProgressView(); 291 | SwipeRefreshLayout.setChildrenDrawingOrderEnabled(this, true); 292 | // the absolute offset has to take into account that the circle starts at an offset 293 | mSpinnerFinalOffset = DEFAULT_CIRCLE_TARGET * metrics.density; 294 | mTotalDragDistance = mSpinnerFinalOffset; 295 | } 296 | 297 | protected int getChildDrawingOrder(int childCount, int i) { 298 | if (mCircleViewIndex < 0) { 299 | return i; 300 | } else if (i == childCount - 1) { 301 | // Draw the selected child last 302 | return mCircleViewIndex; 303 | } else if (i >= mCircleViewIndex) { 304 | // Move the children after the selected child earlier one 305 | return i + 1; 306 | } else { 307 | // Keep the children before the selected child the same 308 | return i; 309 | } 310 | } 311 | 312 | private void createProgressView() { 313 | mCircleView = new CircleImageView(getContext(), CIRCLE_BG_LIGHT, CIRCLE_DIAMETER/2); 314 | mProgress = new MaterialProgressDrawable(getContext(), this); 315 | mProgress.setBackgroundColor(CIRCLE_BG_LIGHT); 316 | mCircleView.setImageDrawable(mProgress); 317 | mCircleView.setVisibility(View.GONE); 318 | addView(mCircleView); 319 | } 320 | 321 | /** 322 | * Set the listener to be notified when a refresh is triggered via the swipe 323 | * gesture. 324 | */ 325 | public void setOnRefreshListener(OnRefreshListener listener) { 326 | mListener = listener; 327 | } 328 | 329 | /** 330 | * Pre API 11, alpha is used to make the progress circle appear instead of scale. 331 | */ 332 | private boolean isAlphaUsedForScale() { 333 | return android.os.Build.VERSION.SDK_INT < 11; 334 | } 335 | 336 | /** 337 | * Notify the widget that refresh state has changed. Do not call this when 338 | * refresh is triggered by a swipe gesture. 339 | * 340 | * @param refreshing Whether or not the view should show refresh progress. 341 | */ 342 | public void setRefreshing(boolean refreshing) { 343 | if (refreshing && mRefreshing != refreshing) { 344 | // scale and show 345 | mRefreshing = refreshing; 346 | int endTarget = 0; 347 | if (!mUsingCustomStart) { 348 | endTarget = (int) (mSpinnerFinalOffset + mOriginalOffsetTop); 349 | } else { 350 | endTarget = (int) mSpinnerFinalOffset; 351 | } 352 | setTargetOffsetTopAndBottom(endTarget - mCurrentTargetOffsetTop, 353 | true /* requires update */); 354 | mNotify = false; 355 | startScaleUpAnimation(mRefreshListener); 356 | } else { 357 | setRefreshing(refreshing, false /* notify */); 358 | } 359 | } 360 | 361 | private void startScaleUpAnimation(AnimationListener listener) { 362 | mCircleView.setVisibility(View.VISIBLE); 363 | if (android.os.Build.VERSION.SDK_INT >= 11) { 364 | // Pre API 11, alpha is used in place of scale up to show the 365 | // progress circle appearing. 366 | // Don't adjust the alpha during appearance otherwise. 367 | mProgress.setAlpha(MAX_ALPHA); 368 | } 369 | mScaleAnimation = new Animation() { 370 | @Override 371 | public void applyTransformation(float interpolatedTime, Transformation t) { 372 | setAnimationProgress(interpolatedTime); 373 | } 374 | }; 375 | mScaleAnimation.setDuration(mMediumAnimationDuration); 376 | if (listener != null) { 377 | mCircleView.setAnimationListener(listener); 378 | } 379 | mCircleView.clearAnimation(); 380 | mCircleView.startAnimation(mScaleAnimation); 381 | } 382 | 383 | /** 384 | * Pre API 11, this does an alpha animation. 385 | * @param progress 386 | */ 387 | private void setAnimationProgress(float progress) { 388 | if (isAlphaUsedForScale()) { 389 | setColorViewAlpha((int) (progress * MAX_ALPHA)); 390 | } else { 391 | mCircleView.setScaleX(progress); 392 | mCircleView.setScaleY(progress); 393 | } 394 | } 395 | 396 | private void setRefreshing(boolean refreshing, final boolean notify) { 397 | if (mRefreshing != refreshing) { 398 | mNotify = notify; 399 | ensureTarget(); 400 | mRefreshing = refreshing; 401 | if (mRefreshing) { 402 | animateOffsetToCorrectPosition(mCurrentTargetOffsetTop, mRefreshListener); 403 | } else { 404 | startScaleDownAnimation(mRefreshListener); 405 | } 406 | } 407 | } 408 | 409 | private void startScaleDownAnimation(Animation.AnimationListener listener) { 410 | mScaleDownAnimation = new Animation() { 411 | @Override 412 | public void applyTransformation(float interpolatedTime, Transformation t) { 413 | setAnimationProgress(1 - interpolatedTime); 414 | } 415 | }; 416 | mScaleDownAnimation.setDuration(SCALE_DOWN_DURATION); 417 | mCircleView.setAnimationListener(listener); 418 | mCircleView.clearAnimation(); 419 | mCircleView.startAnimation(mScaleDownAnimation); 420 | } 421 | 422 | private void startProgressAlphaStartAnimation() { 423 | mAlphaStartAnimation = startAlphaAnimation(mProgress.getAlpha(), STARTING_PROGRESS_ALPHA); 424 | } 425 | 426 | private void startProgressAlphaMaxAnimation() { 427 | mAlphaMaxAnimation = startAlphaAnimation(mProgress.getAlpha(), MAX_ALPHA); 428 | } 429 | 430 | private Animation startAlphaAnimation(final int startingAlpha, final int endingAlpha) { 431 | // Pre API 11, alpha is used in place of scale. Don't also use it to 432 | // show the trigger point. 433 | if (mScale && isAlphaUsedForScale()) { 434 | return null; 435 | } 436 | Animation alpha = new Animation() { 437 | @Override 438 | public void applyTransformation(float interpolatedTime, Transformation t) { 439 | mProgress 440 | .setAlpha((int) (startingAlpha+ ((endingAlpha - startingAlpha) 441 | * interpolatedTime))); 442 | } 443 | }; 444 | alpha.setDuration(ALPHA_ANIMATION_DURATION); 445 | // Clear out the previous animation listeners. 446 | mCircleView.setAnimationListener(null); 447 | mCircleView.clearAnimation(); 448 | mCircleView.startAnimation(alpha); 449 | return alpha; 450 | } 451 | 452 | /** 453 | * @deprecated Use {@link #setProgressBackgroundColorSchemeResource(int)} 454 | */ 455 | @Deprecated 456 | public void setProgressBackgroundColor(int colorRes) { 457 | setProgressBackgroundColorSchemeResource(colorRes); 458 | } 459 | 460 | /** 461 | * Set the background color of the progress spinner disc. 462 | * 463 | * @param colorRes Resource id of the color. 464 | */ 465 | public void setProgressBackgroundColorSchemeResource(int colorRes) { 466 | setProgressBackgroundColorSchemeColor(getResources().getColor(colorRes)); 467 | } 468 | 469 | /** 470 | * Set the background color of the progress spinner disc. 471 | * 472 | * @param color 473 | */ 474 | public void setProgressBackgroundColorSchemeColor(int color) { 475 | mCircleView.setBackgroundColor(color); 476 | mProgress.setBackgroundColor(color); 477 | } 478 | 479 | /** 480 | * @deprecated Use {@link #setColorSchemeResources(int...)} 481 | */ 482 | @Deprecated 483 | public void setColorScheme(int... colors) { 484 | setColorSchemeResources(colors); 485 | } 486 | 487 | /** 488 | * Set the color resources used in the progress animation from color resources. 489 | * The first color will also be the color of the bar that grows in response 490 | * to a user swipe gesture. 491 | * 492 | * @param colorResIds 493 | */ 494 | public void setColorSchemeResources(int... colorResIds) { 495 | final Resources res = getResources(); 496 | int[] colorRes = new int[colorResIds.length]; 497 | for (int i = 0; i < colorResIds.length; i++) { 498 | colorRes[i] = res.getColor(colorResIds[i]); 499 | } 500 | setColorSchemeColors(colorRes); 501 | } 502 | 503 | /** 504 | * Set the colors used in the progress animation. The first 505 | * color will also be the color of the bar that grows in response to a user 506 | * swipe gesture. 507 | * 508 | * @param colors 509 | */ 510 | public void setColorSchemeColors(int... colors) { 511 | ensureTarget(); 512 | mProgress.setColorSchemeColors(colors); 513 | } 514 | 515 | /** 516 | * @return Whether the SwipeRefreshWidget is actively showing refresh 517 | * progress. 518 | */ 519 | public boolean isRefreshing() { 520 | return mRefreshing; 521 | } 522 | 523 | private void ensureTarget() { 524 | // Don't bother getting the parent height if the parent hasn't been laid 525 | // out yet. 526 | if (mTarget == null) { 527 | for (int i = 0; i < getChildCount(); i++) { 528 | View child = getChildAt(i); 529 | if (!child.equals(mCircleView)) { 530 | mTarget = child; 531 | break; 532 | } 533 | } 534 | } 535 | } 536 | 537 | /** 538 | * Set the distance to trigger a sync in dips 539 | * 540 | * @param distance 541 | */ 542 | public void setDistanceToTriggerSync(int distance) { 543 | mTotalDragDistance = distance; 544 | } 545 | 546 | @Override 547 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 548 | final int width = getMeasuredWidth(); 549 | final int height = getMeasuredHeight(); 550 | if (getChildCount() == 0) { 551 | return; 552 | } 553 | if (mTarget == null) { 554 | ensureTarget(); 555 | } 556 | if (mTarget == null) { 557 | return; 558 | } 559 | final View child = mTarget; 560 | final int childLeft = getPaddingLeft(); 561 | final int childTop = getPaddingTop(); 562 | final int childWidth = width - getPaddingLeft() - getPaddingRight(); 563 | final int childHeight = height - getPaddingTop() - getPaddingBottom(); 564 | child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight); 565 | int circleWidth = mCircleView.getMeasuredWidth(); 566 | int circleHeight = mCircleView.getMeasuredHeight(); 567 | mCircleView.layout((width / 2 - circleWidth / 2), mCurrentTargetOffsetTop, 568 | (width / 2 + circleWidth / 2), mCurrentTargetOffsetTop + circleHeight); 569 | } 570 | 571 | @Override 572 | public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 573 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 574 | if (mTarget == null) { 575 | ensureTarget(); 576 | } 577 | if (mTarget == null) { 578 | return; 579 | } 580 | mTarget.measure(MeasureSpec.makeMeasureSpec( 581 | getMeasuredWidth() - getPaddingLeft() - getPaddingRight(), 582 | MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec( 583 | getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), MeasureSpec.EXACTLY)); 584 | mCircleView.measure(MeasureSpec.makeMeasureSpec(mCircleWidth, MeasureSpec.EXACTLY), 585 | MeasureSpec.makeMeasureSpec(mCircleHeight, MeasureSpec.EXACTLY)); 586 | if (!mUsingCustomStart && !mOriginalOffsetCalculated) { 587 | mOriginalOffsetCalculated = true; 588 | mCurrentTargetOffsetTop = mOriginalOffsetTop = -mCircleView.getMeasuredHeight(); 589 | } 590 | mCircleViewIndex = -1; 591 | // Get the index of the circleview. 592 | for (int index = 0; index < getChildCount(); index++) { 593 | if (getChildAt(index) == mCircleView) { 594 | mCircleViewIndex = index; 595 | break; 596 | } 597 | } 598 | } 599 | 600 | /** 601 | * Get the diameter of the progress circle that is displayed as part of the 602 | * swipe to refresh layout. This is not valid until a measure pass has 603 | * completed. 604 | * 605 | * @return Diameter in pixels of the progress circle view. 606 | */ 607 | public int getProgressCircleDiameter() { 608 | return mCircleView != null ?mCircleView.getMeasuredHeight() : 0; 609 | } 610 | 611 | /** 612 | * @return Whether it is possible for the child view of this layout to 613 | * scroll up. Override this if the child view is a custom view. 614 | */ 615 | public boolean canChildScrollUp() { 616 | if (android.os.Build.VERSION.SDK_INT < 14) { 617 | if (mTarget instanceof AbsListView) { 618 | final AbsListView absListView = (AbsListView) mTarget; 619 | return absListView.getChildCount() > 0 620 | && (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0) 621 | .getTop() < absListView.getPaddingTop()); 622 | } else { 623 | return ViewCompat.canScrollVertically(mTarget, -1) || mTarget.getScrollY() > 0; 624 | } 625 | } else { 626 | return ViewCompat.canScrollVertically(mTarget, -1); 627 | } 628 | } 629 | 630 | @Override 631 | public boolean onInterceptTouchEvent(MotionEvent ev) { 632 | ensureTarget(); 633 | 634 | final int action = MotionEventCompat.getActionMasked(ev); 635 | 636 | if (mReturningToStart && action == MotionEvent.ACTION_DOWN) { 637 | mReturningToStart = false; 638 | } 639 | 640 | if (!isEnabled() || mReturningToStart || canChildScrollUp() || mRefreshing) { 641 | // Fail fast if we're not in a state where a swipe is possible 642 | return false; 643 | } 644 | 645 | switch (action) { 646 | case MotionEvent.ACTION_DOWN: 647 | setTargetOffsetTopAndBottom(mOriginalOffsetTop - mCircleView.getTop(), true); 648 | mActivePointerId = MotionEventCompat.getPointerId(ev, 0); 649 | mIsBeingDragged = false; 650 | final float initialDownY = getMotionEventY(ev, mActivePointerId); 651 | if (initialDownY == -1) { 652 | return false; 653 | } 654 | mInitialDownY = initialDownY; 655 | break; 656 | 657 | case MotionEvent.ACTION_MOVE: 658 | if (mActivePointerId == INVALID_POINTER) { 659 | Log.e(LOG_TAG, "Got ACTION_MOVE event but don't have an active pointer id."); 660 | return false; 661 | } 662 | 663 | final float y = getMotionEventY(ev, mActivePointerId); 664 | if (y == -1) { 665 | return false; 666 | } 667 | final float yDiff = y - mInitialDownY; 668 | if (yDiff > mTouchSlop && !mIsBeingDragged) { 669 | mInitialMotionY = mInitialDownY + mTouchSlop; 670 | mIsBeingDragged = true; 671 | mProgress.setAlpha(STARTING_PROGRESS_ALPHA); 672 | } 673 | break; 674 | 675 | case MotionEventCompat.ACTION_POINTER_UP: 676 | onSecondaryPointerUp(ev); 677 | break; 678 | 679 | case MotionEvent.ACTION_UP: 680 | case MotionEvent.ACTION_CANCEL: 681 | mIsBeingDragged = false; 682 | mActivePointerId = INVALID_POINTER; 683 | break; 684 | } 685 | 686 | return mIsBeingDragged; 687 | } 688 | 689 | private float getMotionEventY(MotionEvent ev, int activePointerId) { 690 | final int index = MotionEventCompat.findPointerIndex(ev, activePointerId); 691 | if (index < 0) { 692 | return -1; 693 | } 694 | return MotionEventCompat.getY(ev, index); 695 | } 696 | 697 | @Override 698 | public void requestDisallowInterceptTouchEvent(boolean b) { 699 | // Nope. 700 | } 701 | 702 | private boolean isAnimationRunning(Animation animation) { 703 | return animation != null && animation.hasStarted() && !animation.hasEnded(); 704 | } 705 | 706 | @Override 707 | public boolean onTouchEvent(MotionEvent ev) { 708 | final int action = MotionEventCompat.getActionMasked(ev); 709 | 710 | if (mReturningToStart && action == MotionEvent.ACTION_DOWN) { 711 | mReturningToStart = false; 712 | } 713 | 714 | if (!isEnabled() || mReturningToStart || canChildScrollUp()) { 715 | // Fail fast if we're not in a state where a swipe is possible 716 | return false; 717 | } 718 | 719 | switch (action) { 720 | case MotionEvent.ACTION_DOWN: 721 | mActivePointerId = MotionEventCompat.getPointerId(ev, 0); 722 | mIsBeingDragged = false; 723 | break; 724 | 725 | case MotionEvent.ACTION_MOVE: { 726 | final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId); 727 | if (pointerIndex < 0) { 728 | Log.e(LOG_TAG, "Got ACTION_MOVE event but have an invalid active pointer id."); 729 | return false; 730 | } 731 | 732 | final float y = MotionEventCompat.getY(ev, pointerIndex); 733 | final float overscrollTop = (y - mInitialMotionY) * DRAG_RATE; 734 | if (mIsBeingDragged) { 735 | mProgress.showArrow(true); 736 | float originalDragPercent = overscrollTop / mTotalDragDistance; 737 | if (originalDragPercent < 0) { 738 | return false; 739 | } 740 | float dragPercent = Math.min(1f, Math.abs(originalDragPercent)); 741 | float adjustedPercent = (float) Math.max(dragPercent - .4, 0) * 5 / 3; 742 | float extraOS = Math.abs(overscrollTop) - mTotalDragDistance; 743 | float slingshotDist = mUsingCustomStart ? mSpinnerFinalOffset 744 | - mOriginalOffsetTop : mSpinnerFinalOffset; 745 | float tensionSlingshotPercent = Math.max(0, 746 | Math.min(extraOS, slingshotDist * 2) / slingshotDist); 747 | float tensionPercent = (float) ((tensionSlingshotPercent / 4) - Math.pow( 748 | (tensionSlingshotPercent / 4), 2)) * 2f; 749 | float extraMove = (slingshotDist) * tensionPercent * 2; 750 | 751 | int targetY = mOriginalOffsetTop 752 | + (int) ((slingshotDist * dragPercent) + extraMove); 753 | // where 1.0f is a full circle 754 | if (mCircleView.getVisibility() != View.VISIBLE) { 755 | mCircleView.setVisibility(View.VISIBLE); 756 | } 757 | if (!mScale && !isAlphaUsedForScale()) { 758 | mCircleView.setScaleX(1f); 759 | mCircleView.setScaleY(1f); 760 | } 761 | if (overscrollTop < mTotalDragDistance) { 762 | if (mScale) { 763 | setAnimationProgress(overscrollTop / mTotalDragDistance); 764 | } 765 | if (mProgress.getAlpha() > STARTING_PROGRESS_ALPHA 766 | && !isAnimationRunning(mAlphaStartAnimation)) { 767 | // Animate the alpha 768 | startProgressAlphaStartAnimation(); 769 | } 770 | float strokeStart = adjustedPercent * .8f; 771 | mProgress.setStartEndTrim(0f, Math.min(MAX_PROGRESS_ANGLE, strokeStart)); 772 | mProgress.setArrowScale(Math.min(1f, adjustedPercent)); 773 | } else { 774 | if (mProgress.getAlpha() < MAX_ALPHA 775 | && !isAnimationRunning(mAlphaMaxAnimation)) { 776 | // Animate the alpha 777 | startProgressAlphaMaxAnimation(); 778 | } 779 | } 780 | float rotation = (-0.25f + .4f * adjustedPercent + tensionPercent * 2) * .5f; 781 | mProgress.setProgressRotation(rotation); 782 | setTargetOffsetTopAndBottom(targetY - mCurrentTargetOffsetTop, 783 | true /* requires update */); 784 | } 785 | break; 786 | } 787 | case MotionEventCompat.ACTION_POINTER_DOWN: { 788 | final int index = MotionEventCompat.getActionIndex(ev); 789 | mActivePointerId = MotionEventCompat.getPointerId(ev, index); 790 | break; 791 | } 792 | 793 | case MotionEventCompat.ACTION_POINTER_UP: 794 | onSecondaryPointerUp(ev); 795 | break; 796 | 797 | case MotionEvent.ACTION_UP: 798 | case MotionEvent.ACTION_CANCEL: { 799 | if (mActivePointerId == INVALID_POINTER) { 800 | if (action == MotionEvent.ACTION_UP) { 801 | Log.e(LOG_TAG, "Got ACTION_UP event but don't have an active pointer id."); 802 | } 803 | return false; 804 | } 805 | final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId); 806 | final float y = MotionEventCompat.getY(ev, pointerIndex); 807 | final float overscrollTop = (y - mInitialMotionY) * DRAG_RATE; 808 | mIsBeingDragged = false; 809 | if (overscrollTop > mTotalDragDistance) { 810 | setRefreshing(true, true /* notify */); 811 | } else { 812 | // cancel refresh 813 | mRefreshing = false; 814 | mProgress.setStartEndTrim(0f, 0f); 815 | Animation.AnimationListener listener = null; 816 | if (!mScale) { 817 | listener = new Animation.AnimationListener() { 818 | 819 | @Override 820 | public void onAnimationStart(Animation animation) { 821 | } 822 | 823 | @Override 824 | public void onAnimationEnd(Animation animation) { 825 | if (!mScale) { 826 | startScaleDownAnimation(null); 827 | } 828 | } 829 | 830 | @Override 831 | public void onAnimationRepeat(Animation animation) { 832 | } 833 | 834 | }; 835 | } 836 | animateOffsetToStartPosition(mCurrentTargetOffsetTop, listener); 837 | mProgress.showArrow(false); 838 | } 839 | mActivePointerId = INVALID_POINTER; 840 | return false; 841 | } 842 | } 843 | 844 | return true; 845 | } 846 | 847 | private void animateOffsetToCorrectPosition(int from, AnimationListener listener) { 848 | mFrom = from; 849 | mAnimateToCorrectPosition.reset(); 850 | mAnimateToCorrectPosition.setDuration(ANIMATE_TO_TRIGGER_DURATION); 851 | mAnimateToCorrectPosition.setInterpolator(mDecelerateInterpolator); 852 | if (listener != null) { 853 | mCircleView.setAnimationListener(listener); 854 | } 855 | mCircleView.clearAnimation(); 856 | mCircleView.startAnimation(mAnimateToCorrectPosition); 857 | } 858 | 859 | private void animateOffsetToStartPosition(int from, AnimationListener listener) { 860 | if (mScale) { 861 | // Scale the item back down 862 | startScaleDownReturnToStartAnimation(from, listener); 863 | } else { 864 | mFrom = from; 865 | mAnimateToStartPosition.reset(); 866 | mAnimateToStartPosition.setDuration(ANIMATE_TO_START_DURATION); 867 | mAnimateToStartPosition.setInterpolator(mDecelerateInterpolator); 868 | if (listener != null) { 869 | mCircleView.setAnimationListener(listener); 870 | } 871 | mCircleView.clearAnimation(); 872 | mCircleView.startAnimation(mAnimateToStartPosition); 873 | } 874 | } 875 | 876 | private final Animation mAnimateToCorrectPosition = new Animation() { 877 | @Override 878 | public void applyTransformation(float interpolatedTime, Transformation t) { 879 | int targetTop = 0; 880 | int endTarget = 0; 881 | if (!mUsingCustomStart) { 882 | endTarget = (int) (mSpinnerFinalOffset - Math.abs(mOriginalOffsetTop)); 883 | } else { 884 | endTarget = (int) mSpinnerFinalOffset; 885 | } 886 | targetTop = (mFrom + (int) ((endTarget - mFrom) * interpolatedTime)); 887 | int offset = targetTop - mCircleView.getTop(); 888 | setTargetOffsetTopAndBottom(offset, false /* requires update */); 889 | mProgress.setArrowScale(1 - interpolatedTime); 890 | } 891 | }; 892 | 893 | private void moveToStart(float interpolatedTime) { 894 | int targetTop = 0; 895 | targetTop = (mFrom + (int) ((mOriginalOffsetTop - mFrom) * interpolatedTime)); 896 | int offset = targetTop - mCircleView.getTop(); 897 | setTargetOffsetTopAndBottom(offset, false /* requires update */); 898 | } 899 | 900 | private final Animation mAnimateToStartPosition = new Animation() { 901 | @Override 902 | public void applyTransformation(float interpolatedTime, Transformation t) { 903 | moveToStart(interpolatedTime); 904 | } 905 | }; 906 | 907 | private void startScaleDownReturnToStartAnimation(int from, 908 | Animation.AnimationListener listener) { 909 | mFrom = from; 910 | if (isAlphaUsedForScale()) { 911 | mStartingScale = mProgress.getAlpha(); 912 | } else { 913 | mStartingScale = mCircleView.getScaleX(); 914 | } 915 | mScaleDownToStartAnimation = new Animation() { 916 | @Override 917 | public void applyTransformation(float interpolatedTime, Transformation t) { 918 | float targetScale = (mStartingScale + (-mStartingScale * interpolatedTime)); 919 | setAnimationProgress(targetScale); 920 | moveToStart(interpolatedTime); 921 | } 922 | }; 923 | mScaleDownToStartAnimation.setDuration(SCALE_DOWN_DURATION); 924 | if (listener != null) { 925 | mCircleView.setAnimationListener(listener); 926 | } 927 | mCircleView.clearAnimation(); 928 | mCircleView.startAnimation(mScaleDownToStartAnimation); 929 | } 930 | 931 | private void setTargetOffsetTopAndBottom(int offset, boolean requiresUpdate) { 932 | mCircleView.bringToFront(); 933 | mCircleView.offsetTopAndBottom(offset); 934 | mCurrentTargetOffsetTop = mCircleView.getTop(); 935 | if (requiresUpdate && android.os.Build.VERSION.SDK_INT < 11) { 936 | invalidate(); 937 | } 938 | } 939 | 940 | private void onSecondaryPointerUp(MotionEvent ev) { 941 | final int pointerIndex = MotionEventCompat.getActionIndex(ev); 942 | final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex); 943 | if (pointerId == mActivePointerId) { 944 | // This was our active pointer going up. Choose a new 945 | // active pointer and adjust accordingly. 946 | final int newPointerIndex = pointerIndex == 0 ? 1 : 0; 947 | mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex); 948 | } 949 | } 950 | 951 | /** 952 | * Classes that wish to be notified when the swipe gesture correctly 953 | * triggers a refresh should implement this interface. 954 | */ 955 | public interface OnRefreshListener { 956 | public void onRefresh(); 957 | } 958 | 959 | public static void setChildrenDrawingOrderEnabled(ViewGroup viewGroup, boolean enabled) { 960 | final String TAG = "ViewCompat"; 961 | Method sChildrenDrawingOrderMethod = null; 962 | 963 | 964 | try { 965 | sChildrenDrawingOrderMethod = ViewGroup.class 966 | .getDeclaredMethod("setChildrenDrawingOrderEnabled", boolean.class); 967 | } catch (NoSuchMethodException e) { 968 | Log.e(TAG, "Unable to find childrenDrawingOrderEnabled", e); 969 | } 970 | sChildrenDrawingOrderMethod.setAccessible(true); 971 | 972 | try { 973 | sChildrenDrawingOrderMethod.invoke(viewGroup, enabled); 974 | } catch (IllegalAccessException e) { 975 | Log.e(TAG, "Unable to invoke childrenDrawingOrderEnabled", e); 976 | } catch (IllegalArgumentException e) { 977 | Log.e(TAG, "Unable to invoke childrenDrawingOrderEnabled", e); 978 | } catch (InvocationTargetException e) { 979 | Log.e(TAG, "Unable to invoke childrenDrawingOrderEnabled", e); 980 | } 981 | } 982 | } -------------------------------------------------------------------------------- /android/src/com/rkam/swiperefreshlayout/SwipeRefreshProxy.java: -------------------------------------------------------------------------------- 1 | package com.rkam.swiperefreshlayout; 2 | 3 | import org.appcelerator.kroll.annotations.Kroll; 4 | import org.appcelerator.titanium.proxy.TiViewProxy; 5 | import org.appcelerator.titanium.view.TiUIView; 6 | 7 | import android.app.Activity; 8 | 9 | @Kroll.proxy(creatableInModule=SwiperefreshlayoutModule.class) 10 | public class SwipeRefreshProxy extends TiViewProxy { 11 | 12 | private SwipeRefresh swipeRefresh; 13 | 14 | public SwipeRefreshProxy() { 15 | super(); 16 | this.swipeRefresh = new SwipeRefresh(this); 17 | } 18 | 19 | @Override 20 | public TiUIView createView(Activity activity) { 21 | return this.swipeRefresh; 22 | } 23 | 24 | @Kroll.method 25 | public void setRefreshing(boolean refreshing) { 26 | if (this.swipeRefresh != null){ 27 | this.swipeRefresh.setRefreshing(refreshing); 28 | } 29 | } 30 | 31 | @Kroll.method 32 | public void add(TiViewProxy view) { 33 | this.swipeRefresh.add(view); 34 | return; 35 | } 36 | 37 | @Kroll.method @Kroll.getProperty 38 | public boolean isRefreshing() { 39 | if (this.swipeRefresh != null){ 40 | return this.swipeRefresh.isRefreshing(); 41 | } 42 | return false; 43 | } 44 | 45 | @Kroll.method 46 | public void setEnabled(boolean refreshEnabled) { 47 | if (this.swipeRefresh != null){ 48 | this.swipeRefresh.setEnabled(refreshEnabled); 49 | } 50 | return; 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /android/src/com/rkam/swiperefreshlayout/SwiperefreshlayoutModule.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was auto-generated by the Titanium Module SDK helper for Android 3 | * Appcelerator Titanium Mobile 4 | * Copyright (c) 2009-2013 by Appcelerator, Inc. All Rights Reserved. 5 | * Licensed under the terms of the Apache Public License 6 | * Please see the LICENSE included with this distribution for details. 7 | * 8 | */ 9 | package com.rkam.swiperefreshlayout; 10 | 11 | import org.appcelerator.kroll.KrollModule; 12 | import org.appcelerator.kroll.annotations.Kroll; 13 | 14 | import org.appcelerator.titanium.TiApplication; 15 | import org.appcelerator.kroll.common.Log; 16 | 17 | @Kroll.module(name="Swiperefreshlayout", id="com.rkam.swiperefreshlayout") 18 | public class SwiperefreshlayoutModule extends KrollModule 19 | { 20 | 21 | // Standard Debugging variables 22 | private static final String TAG = "SwiperefreshlayoutModule"; 23 | 24 | // You can define constants with @Kroll.constant, for example: 25 | // @Kroll.constant public static final String EXTERNAL_NAME = value; 26 | 27 | public SwiperefreshlayoutModule() 28 | { 29 | super(); 30 | } 31 | 32 | @Kroll.onAppCreate 33 | public static void onAppCreate(TiApplication app) 34 | { 35 | Log.d(TAG, "inside onAppCreate"); 36 | // put module init code that needs to run when the application is created 37 | } 38 | 39 | } 40 | 41 | -------------------------------------------------------------------------------- /android/timodule.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /dist/com.rkam.swiperefreshlayout-android-0.1.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iskugor/Ti.SwipeRefreshLayout/5b0f33ecb7b6f55315b12ed4f99f5c6c3457f5c3/dist/com.rkam.swiperefreshlayout-android-0.1.zip -------------------------------------------------------------------------------- /dist/com.rkam.swiperefreshlayout-android-0.2.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iskugor/Ti.SwipeRefreshLayout/5b0f33ecb7b6f55315b12ed4f99f5c6c3457f5c3/dist/com.rkam.swiperefreshlayout-android-0.2.zip -------------------------------------------------------------------------------- /dist/com.rkam.swiperefreshlayout-android-0.3.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iskugor/Ti.SwipeRefreshLayout/5b0f33ecb7b6f55315b12ed4f99f5c6c3457f5c3/dist/com.rkam.swiperefreshlayout-android-0.3.zip -------------------------------------------------------------------------------- /dist/com.rkam.swiperefreshlayout-android-0.4.1.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iskugor/Ti.SwipeRefreshLayout/5b0f33ecb7b6f55315b12ed4f99f5c6c3457f5c3/dist/com.rkam.swiperefreshlayout-android-0.4.1.zip -------------------------------------------------------------------------------- /dist/com.rkam.swiperefreshlayout-android-0.5.4.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iskugor/Ti.SwipeRefreshLayout/5b0f33ecb7b6f55315b12ed4f99f5c6c3457f5c3/dist/com.rkam.swiperefreshlayout-android-0.5.4.zip -------------------------------------------------------------------------------- /dist/com.rkam.swiperefreshlayout-android-0.5.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iskugor/Ti.SwipeRefreshLayout/5b0f33ecb7b6f55315b12ed4f99f5c6c3457f5c3/dist/com.rkam.swiperefreshlayout-android-0.5.zip -------------------------------------------------------------------------------- /dist/com.rkam.swiperefreshlayout-android-0.6.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iskugor/Ti.SwipeRefreshLayout/5b0f33ecb7b6f55315b12ed4f99f5c6c3457f5c3/dist/com.rkam.swiperefreshlayout-android-0.6.zip -------------------------------------------------------------------------------- /dist/com.rkam.swiperefreshlayout-android-0.7.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iskugor/Ti.SwipeRefreshLayout/5b0f33ecb7b6f55315b12ed4f99f5c6c3457f5c3/dist/com.rkam.swiperefreshlayout-android-0.7.zip -------------------------------------------------------------------------------- /dist/com.rkam.swiperefreshlayout-android-0.8.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iskugor/Ti.SwipeRefreshLayout/5b0f33ecb7b6f55315b12ed4f99f5c6c3457f5c3/dist/com.rkam.swiperefreshlayout-android-0.8.zip --------------------------------------------------------------------------------