├── app ├── .gitignore ├── src │ └── main │ │ ├── res │ │ ├── values │ │ │ ├── strings.xml │ │ │ ├── colors.xml │ │ │ ├── dimens.xml │ │ │ └── styles.xml │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ ├── values-w820dp │ │ │ └── dimens.xml │ │ └── layout │ │ │ └── activity_main.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── songxian │ │ └── smilerefresh │ │ └── MainActivity.java ├── proguard-rules.pro └── build.gradle ├── refresh_view_library ├── .gitignore ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── song │ │ └── refresh_view │ │ ├── util │ │ ├── Utils.java │ │ └── Logger.java │ │ ├── CircleImageView.java │ │ ├── SmileProgressDrawable.java │ │ └── PullToRefreshView.java ├── proguard-rules.pro └── build.gradle ├── settings.gradle ├── gif └── Effect.gif ├── .idea ├── copyright │ └── profiles_settings.xml ├── vcs.xml ├── gradle.xml ├── compiler.xml ├── modules.xml └── misc.xml ├── .gitignore ├── gradle.properties ├── README.md ├── gradlew.bat └── gradlew /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /refresh_view_library/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':refresh_view_library' 2 | -------------------------------------------------------------------------------- /gif/Effect.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hsongxian/SmileRefresh/HEAD/gif/Effect.gif -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | "SmileRefresh " 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hsongxian/SmileRefresh/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hsongxian/SmileRefresh/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hsongxian/SmileRefresh/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hsongxian/SmileRefresh/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hsongxian/SmileRefresh/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /refresh_view_library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /refresh_view_library/src/main/java/com/song/refresh_view/util/Utils.java: -------------------------------------------------------------------------------- 1 | package com.song.refresh_view.util; 2 | 3 | import android.content.Context; 4 | 5 | public class Utils { 6 | 7 | public static int convertDpToPixel(Context context, int dp) { 8 | float density = context.getResources().getDisplayMetrics().density; 9 | return Math.round((float) dp * density); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in D:\android\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /refresh_view_library/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in D:\android\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /refresh_view_library/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'com.github.dcendents.android-maven' 3 | 4 | group='com.github.songixan' 5 | android { 6 | compileSdkVersion 24 7 | buildToolsVersion "24.0.2" 8 | 9 | defaultConfig { 10 | minSdkVersion 15 11 | targetSdkVersion 24 12 | versionCode 1 13 | versionName "1.0" 14 | } 15 | buildTypes { 16 | release { 17 | minifyEnabled false 18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 19 | } 20 | } 21 | } 22 | 23 | dependencies { 24 | compile fileTree(dir: 'libs', include: ['*.jar']) 25 | testCompile 'junit:junit:4.12' 26 | compile 'com.android.support:appcompat-v7:24.2.1' 27 | } 28 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 24 5 | buildToolsVersion "24.0.3" 6 | defaultConfig { 7 | applicationId "com.songxian.smilerefresh" 8 | minSdkVersion 15 9 | targetSdkVersion 24 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(include: ['*.jar'], dir: 'libs') 24 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 25 | exclude group: 'com.android.support', module: 'support-annotations' 26 | }) 27 | compile 'com.android.support:appcompat-v7:24.2.1' 28 | testCompile 'junit:junit:4.12' 29 | compile project(':refresh_view_library') 30 | } 31 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SmileRefresh 2 | 3 | 4 | ![这里写图片描述](http://img.blog.csdn.net/20161013202135750) 5 | 6 | 7 | ####描述: 8 | PullToRefreshView 是基于v4库的SwipeRefreshLayout上修改的动画的,SwipeRefreshLayout 有的方法PullToRefreshView 也有。 9 | ####引入: 10 | 1.在Properties的build.gradle 添加 11 | 12 | ```java 13 | allprojects { 14 | repositories { 15 | ...... 16 | maven { url "https://jitpack.io" } 17 | 18 | } 19 | } 20 | ``` 21 | 2.在Module的build.gradle 添加 22 | ```java 23 | compile 'com.github.songixan:SmileRefresh:1.1' 24 | 25 | ``` 26 | 27 | ####用法: 28 | 29 | 30 | 31 | 1. xml添加 32 | ```html 33 | 40 | 41 | 45 | 46 | 47 | ``` 48 | 49 | 2. java设置 50 | 51 | - 初始化 52 | 53 | ```java 54 | mRefreshView = (PullToRefreshView) findViewById(R.id.refreshView); 55 | mRefreshView.setColorSchemeColors(Color.RED,Color.BLUE); // 颜色 56 | mRefreshView.setSmileStrokeWidth(8); // 设置绘制的笑脸的宽度 57 | mRefreshView.setSmileInterpolator(new LinearInterpolator()); // 笑脸动画转动的插值器 58 | mRefreshView.setSmileAnimationDuration(2000); // 设置笑脸旋转动画的时长 59 | //设置下拉刷新监听 60 | mRefreshView.setOnRefreshListener(new PullToRefreshView.OnRefreshListener() { 61 | @Override 62 | public void onRefresh() { 63 | requestData(); //请求数据 64 | } 65 | 66 | }); 67 | ``` 68 | 69 | 70 | 71 | - 刷新完成 72 | 73 | 74 | ```java 75 | // 请求数据完成 76 | mRefreshView.setRefreshing(false); 77 | 78 | ``` 79 | 80 | 81 | 博客:http://blog.csdn.net/qq_32464809/article/details/52809722 82 | -------------------------------------------------------------------------------- /app/src/main/java/com/songxian/smilerefresh/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.songxian.smilerefresh; 2 | 3 | import android.graphics.Color; 4 | import android.support.v4.app.Fragment; 5 | import android.support.v4.app.FragmentPagerAdapter; 6 | import android.support.v4.view.PagerAdapter; 7 | import android.support.v4.view.ViewPager; 8 | import android.support.v7.app.AppCompatActivity; 9 | import android.os.Bundle; 10 | import android.view.View; 11 | import android.view.animation.LinearInterpolator; 12 | import android.widget.ArrayAdapter; 13 | import android.widget.ListView; 14 | 15 | import com.song.refresh_view.PullToRefreshView; 16 | 17 | import java.util.ArrayList; 18 | import java.util.List; 19 | 20 | import static java.security.AccessController.getContext; 21 | 22 | public class MainActivity extends AppCompatActivity { 23 | 24 | private ListView mListView; 25 | private PullToRefreshView mRefreshView; 26 | private List mData; 27 | private ArrayAdapter mAdapter; 28 | 29 | @Override 30 | protected void onCreate(Bundle savedInstanceState) { 31 | super.onCreate(savedInstanceState); 32 | setContentView(R.layout.activity_main); 33 | init(); 34 | } 35 | 36 | 37 | private void init() { 38 | 39 | mListView = (ListView)findViewById(R.id.list); 40 | mRefreshView = (PullToRefreshView) findViewById(R.id.refreshView); 41 | mRefreshView.setColorSchemeColors(Color.RED,Color.BLUE); // 颜色 42 | // mRefreshView.setSmileStrokeWidth(8); // 设置绘制的笑脸的宽度 43 | // mRefreshView.setSmileInterpolator(new LinearInterpolator()); // 笑脸动画转动的插值器 44 | // mRefreshView.setSmileAnimationDuration(2000); // 设置笑脸旋转动画的时长 45 | 46 | mRefreshView.setOnRefreshListener(new PullToRefreshView.OnRefreshListener() { 47 | @Override 48 | public void onRefresh() { 49 | requestData(); 50 | } 51 | 52 | }); 53 | 54 | mData = new ArrayList<>(); 55 | for (int i = 0; i < 10; i++) 56 | mData.add(String.valueOf(i)); 57 | mAdapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, mData); 58 | mListView.setAdapter(mAdapter); 59 | 60 | } 61 | 62 | 63 | private void requestData() { 64 | mRefreshView.postDelayed(new Runnable() { 65 | @Override 66 | public void run() { 67 | mData.add(0, "新来的朋友"); 68 | mAdapter.notifyDataSetChanged(); 69 | mRefreshView.setRefreshing(false); 70 | } 71 | },5000); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /refresh_view_library/src/main/java/com/song/refresh_view/util/Logger.java: -------------------------------------------------------------------------------- 1 | package com.song.refresh_view.util; 2 | 3 | import android.text.TextUtils; 4 | 5 | public final class Logger { 6 | 7 | private static final String TAG = "Rocket"; 8 | 9 | /** 10 | * Set true or false if you want read logs or not 11 | */ 12 | private static boolean logEnabled_d = true; 13 | private static boolean logEnabled_i = true; 14 | private static boolean logEnabled_e = true; 15 | 16 | public static void d() { 17 | if (logEnabled_d) { 18 | android.util.Log.v(TAG, getLocation()); 19 | } 20 | } 21 | 22 | public static void d(Object msg) { 23 | if (logEnabled_d) { 24 | android.util.Log.v(TAG, getLocation() + msg); 25 | } 26 | } 27 | 28 | public static void i(String msg) { 29 | if (logEnabled_i) { 30 | android.util.Log.i(TAG, getLocation() + msg); 31 | } 32 | } 33 | 34 | public static void i() { 35 | if (logEnabled_i) { 36 | android.util.Log.i(TAG, getLocation()); 37 | } 38 | } 39 | 40 | public static void e(String msg) { 41 | if (logEnabled_e) { 42 | android.util.Log.e(TAG, getLocation() + msg); 43 | } 44 | } 45 | 46 | public static void e(String msg, Throwable e) { 47 | if (logEnabled_e) { 48 | android.util.Log.e(TAG, getLocation() + msg, e); 49 | } 50 | } 51 | 52 | public static void e(Throwable e) { 53 | if (logEnabled_e) { 54 | android.util.Log.e(TAG, getLocation(), e); 55 | } 56 | } 57 | 58 | public static void e() { 59 | if (logEnabled_e) { 60 | android.util.Log.e(TAG, getLocation()); 61 | } 62 | } 63 | 64 | private static String getLocation() { 65 | final String className = Logger.class.getName(); 66 | final StackTraceElement[] traces = Thread.currentThread() 67 | .getStackTrace(); 68 | boolean found = false; 69 | 70 | for (StackTraceElement trace : traces) { 71 | try { 72 | if (found) { 73 | if (!trace.getClassName().startsWith(className)) { 74 | Class clazz = Class.forName(trace.getClassName()); 75 | return "[" + getClassName(clazz) + ":" 76 | + trace.getMethodName() + ":" 77 | + trace.getLineNumber() + "]: "; 78 | } 79 | } else if (trace.getClassName().startsWith(className)) { 80 | found = true; 81 | } 82 | } catch (ClassNotFoundException ignored) { 83 | } 84 | } 85 | 86 | return "[]: "; 87 | } 88 | 89 | private static String getClassName(Class clazz) { 90 | if (clazz != null) { 91 | if (!TextUtils.isEmpty(clazz.getSimpleName())) { 92 | return clazz.getSimpleName(); 93 | } 94 | 95 | return getClassName(clazz.getEnclosingClass()); 96 | } 97 | 98 | return ""; 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /refresh_view_library/src/main/java/com/song/refresh_view/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.song.refresh_view; 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 | int mShadowRadius; 50 | 51 | CircleImageView(Context context, int color) { 52 | super(context); 53 | final float density = getContext().getResources().getDisplayMetrics().density; 54 | final int shadowYOffset = (int) (density * Y_OFFSET); 55 | final int shadowXOffset = (int) (density * X_OFFSET); 56 | 57 | mShadowRadius = (int) (density * SHADOW_RADIUS); 58 | 59 | ShapeDrawable circle; 60 | if (elevationSupported()) { 61 | circle = new ShapeDrawable(new OvalShape()); 62 | ViewCompat.setElevation(this, SHADOW_ELEVATION * density); 63 | } else { 64 | OvalShape oval = new OvalShadow(mShadowRadius); 65 | circle = new ShapeDrawable(oval); 66 | ViewCompat.setLayerType(this, ViewCompat.LAYER_TYPE_SOFTWARE, circle.getPaint()); 67 | circle.getPaint().setShadowLayer(mShadowRadius, shadowXOffset, shadowYOffset, 68 | KEY_SHADOW_COLOR); 69 | final int padding = mShadowRadius; 70 | // set padding so the inner image sits correctly within the shadow. 71 | setPadding(padding, padding, padding, padding); 72 | } 73 | circle.getPaint().setColor(color); 74 | setBackgroundDrawable(circle); 75 | } 76 | 77 | private boolean elevationSupported() { 78 | return android.os.Build.VERSION.SDK_INT >= 21; 79 | } 80 | 81 | @Override 82 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 83 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 84 | if (!elevationSupported()) { 85 | setMeasuredDimension(getMeasuredWidth() + mShadowRadius * 2, getMeasuredHeight() 86 | + mShadowRadius * 2); 87 | } 88 | } 89 | 90 | public void setAnimationListener(Animation.AnimationListener listener) { 91 | mListener = listener; 92 | } 93 | 94 | @Override 95 | public void onAnimationStart() { 96 | super.onAnimationStart(); 97 | if (mListener != null) { 98 | mListener.onAnimationStart(getAnimation()); 99 | } 100 | } 101 | 102 | @Override 103 | public void onAnimationEnd() { 104 | super.onAnimationEnd(); 105 | if (mListener != null) { 106 | mListener.onAnimationEnd(getAnimation()); 107 | } 108 | } 109 | 110 | /** 111 | * Update the background color of the circle image view. 112 | * 113 | * @param colorRes Id of a color resource. 114 | */ 115 | public void setBackgroundColorRes(int colorRes) { 116 | setBackgroundColor(getContext().getResources().getColor(colorRes)); 117 | } 118 | 119 | @Override 120 | public void setBackgroundColor(int color) { 121 | if (getBackground() instanceof ShapeDrawable) { 122 | ((ShapeDrawable) getBackground()).getPaint().setColor(color); 123 | } 124 | } 125 | 126 | private class OvalShadow extends OvalShape { 127 | private RadialGradient mRadialGradient; 128 | private Paint mShadowPaint; 129 | 130 | OvalShadow(int shadowRadius) { 131 | super(); 132 | mShadowPaint = new Paint(); 133 | mShadowRadius = shadowRadius; 134 | updateRadialGradient((int) rect().width()); 135 | } 136 | 137 | @Override 138 | protected void onResize(float width, float height) { 139 | super.onResize(width, height); 140 | updateRadialGradient((int) width); 141 | } 142 | 143 | @Override 144 | public void draw(Canvas canvas, Paint paint) { 145 | final int viewWidth = CircleImageView.this.getWidth(); 146 | final int viewHeight = CircleImageView.this.getHeight(); 147 | canvas.drawCircle(viewWidth / 2, viewHeight / 2, viewWidth / 2, mShadowPaint); 148 | canvas.drawCircle(viewWidth / 2, viewHeight / 2, viewWidth / 2 - mShadowRadius, paint); 149 | } 150 | 151 | private void updateRadialGradient(int diameter) { 152 | mRadialGradient = new RadialGradient(diameter / 2, diameter / 2, 153 | mShadowRadius, new int[]{FILL_SHADOW_COLOR, Color.TRANSPARENT}, 154 | null, Shader.TileMode.CLAMP); 155 | mShadowPaint.setShader(mRadialGradient); 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /refresh_view_library/src/main/java/com/song/refresh_view/SmileProgressDrawable.java: -------------------------------------------------------------------------------- 1 | package com.song.refresh_view; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.ColorFilter; 7 | import android.graphics.Paint; 8 | import android.graphics.Rect; 9 | import android.graphics.RectF; 10 | import android.graphics.drawable.Animatable; 11 | import android.graphics.drawable.Drawable; 12 | import android.support.annotation.IntDef; 13 | import android.support.annotation.NonNull; 14 | import android.view.View; 15 | import android.view.animation.Animation; 16 | import android.view.animation.DecelerateInterpolator; 17 | import android.view.animation.Interpolator; 18 | import android.view.animation.LinearInterpolator; 19 | import android.view.animation.Transformation; 20 | 21 | import com.song.refresh_view.util.Logger; 22 | import com.song.refresh_view.util.Utils; 23 | 24 | import java.lang.annotation.Retention; 25 | import java.lang.annotation.RetentionPolicy; 26 | import java.util.ArrayList; 27 | 28 | 29 | /** 30 | * Created by Administrator on 2016/10/13 0013. 31 | */ 32 | 33 | public class SmileProgressDrawable extends Drawable implements Animatable { 34 | 35 | private static final Interpolator TIME_INTERPOLATOR = new DecelerateInterpolator(); 36 | 37 | private static final float FULL_ROTATION = 1080.0f; 38 | private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator(); 39 | private static final float COLOR_START_DELAY_OFFSET = 0.75f; 40 | private int mBackgroundColor; 41 | private int[] mColorSchemeColors; 42 | private Animation mAnimation; 43 | 44 | public void setBackgroundColor(int backgroundColor) { 45 | mBackgroundColor = backgroundColor; 46 | } 47 | 48 | public void setColorSchemeColors(int[] colors) { 49 | mSmile.setColors(colors); 50 | mSmile.setColorIndex(0); 51 | } 52 | 53 | @Retention(RetentionPolicy.SOURCE) 54 | @IntDef({LARGE, DEFAULT}) 55 | public @interface ProgressDrawableSize { 56 | } 57 | 58 | // Maps to ProgressBar.Large style 59 | static final int LARGE = 0; 60 | // Maps to ProgressBar default style 61 | static final int DEFAULT = 1; 62 | 63 | 64 | private static final int[] COLORS = new int[]{ 65 | Color.BLACK 66 | }; 67 | 68 | 69 | public int getAnimationDuration() { 70 | return animationDuration; 71 | } 72 | 73 | public void setAnimationDuration(int animationDuration) { 74 | this.animationDuration = animationDuration; 75 | } 76 | 77 | /** 78 | * The duration of a single progress spin in milliseconds. 79 | */ 80 | private int animationDuration = 2332; 81 | 82 | public float mRotationCount; 83 | 84 | /** 85 | * The number of points in the progress "star". 86 | */ 87 | private static final float NUM_POINTS = 5f; 88 | /** 89 | * The list of animators operating on this drawable. 90 | */ 91 | private final ArrayList mAnimators = new ArrayList(); 92 | 93 | /** 94 | * The indicator ring, used to manage animation state. 95 | */ 96 | private final SmileProgressDrawable.Smile mSmile; 97 | 98 | /** 99 | * Canvas rotation in degrees. 100 | */ 101 | private float mRotation; 102 | 103 | 104 | private View mParent; 105 | 106 | 107 | public SmileProgressDrawable(Context context, View parent) { 108 | mParent = parent; 109 | mSmile = new Smile(context,mCallback); 110 | setupAnimators(); 111 | } 112 | 113 | private void setupAnimators() { 114 | final Animation animation = new Animation() { 115 | @Override 116 | public void applyTransformation(float interpolatedTime, Transformation t) { 117 | updateRingColor(interpolatedTime, mSmile); 118 | mSmile.setAnimatedValue(interpolatedTime); 119 | } 120 | }; 121 | animation.setRepeatCount(Animation.INFINITE); 122 | animation.setRepeatMode(Animation.RESTART); 123 | animation.setInterpolator(TIME_INTERPOLATOR); 124 | // animation.setInterpolator(LINEAR_INTERPOLATOR); 125 | animation.setAnimationListener(new Animation.AnimationListener() { 126 | 127 | 128 | @Override 129 | public void onAnimationStart(Animation animation) { 130 | mRotationCount = 0; 131 | } 132 | 133 | @Override 134 | public void onAnimationEnd(Animation animation) { 135 | // do nothing 136 | } 137 | 138 | @Override 139 | public void onAnimationRepeat(Animation animation) { 140 | mSmile.goToNextColor(); 141 | 142 | mRotationCount = (mRotationCount + 1) % (NUM_POINTS); 143 | } 144 | }); 145 | 146 | mAnimation = animation; 147 | } 148 | 149 | 150 | public void setInterpolator(Interpolator interpolator){ 151 | mAnimation.setInterpolator(interpolator); 152 | } 153 | @Override 154 | public void start() { 155 | Logger.d(); 156 | 157 | mAnimation.setDuration(animationDuration); 158 | mParent.startAnimation(mAnimation); 159 | 160 | } 161 | 162 | @Override 163 | public void stop() { 164 | mParent.clearAnimation(); 165 | mSmile.setColorIndex(0); 166 | mSmile.setAnimatedValue(0); 167 | 168 | } 169 | 170 | @Override 171 | public boolean isRunning() { 172 | final ArrayList animators = mAnimators; 173 | final int N = animators.size(); 174 | for (int i = 0; i < N; i++) { 175 | final Animation animator = animators.get(i); 176 | if (animator.hasStarted() && !animator.hasEnded()) { 177 | return true; 178 | } 179 | } 180 | return false; 181 | } 182 | 183 | @Override 184 | public void draw(Canvas canvas) { 185 | final Rect bounds = getBounds(); 186 | final int saveCount = canvas.save(); 187 | canvas.rotate(mRotation, bounds.exactCenterX(), bounds.exactCenterY()); 188 | mSmile.draw(canvas, bounds); 189 | canvas.restoreToCount(saveCount); 190 | } 191 | 192 | @Override 193 | public void setAlpha(int alpha) { 194 | 195 | } 196 | 197 | @Override 198 | public void setColorFilter(ColorFilter colorFilter) { 199 | 200 | } 201 | 202 | public int getAlpha() { 203 | return mSmile.getAlpha(); 204 | } 205 | 206 | 207 | @Override 208 | public int getOpacity() { 209 | return 0; 210 | } 211 | 212 | public void setPercentage(float v) { 213 | mSmile.setAnimatedValue(v); 214 | } 215 | 216 | 217 | @SuppressWarnings("unused") 218 | private float getRotation() { 219 | return mRotation; 220 | } 221 | 222 | /** 223 | * Update the ring color if this is within the last 25% of the animation. 224 | * The new ring color will be a translation from the starting ring color to 225 | * the next color. 226 | */ 227 | void updateRingColor(float interpolatedTime, Smile smile) { 228 | if (interpolatedTime > COLOR_START_DELAY_OFFSET) { 229 | Logger.d(interpolatedTime); 230 | // scale the interpolatedTime so that the full 231 | // transformation from 0 - 1 takes place in the 232 | // remaining time 233 | int color = evaluateColorChange((interpolatedTime - COLOR_START_DELAY_OFFSET) 234 | / (1.0f - COLOR_START_DELAY_OFFSET), smile.getStartingColor(), 235 | smile.getNextColor()); 236 | smile.setColor(color); 237 | } 238 | } 239 | 240 | // Adapted from ArgbEvaluator.java 241 | private int evaluateColorChange(float fraction, int startValue, int endValue) { 242 | int startInt = (Integer) startValue; 243 | int startA = (startInt >> 24) & 0xff; 244 | int startR = (startInt >> 16) & 0xff; 245 | int startG = (startInt >> 8) & 0xff; 246 | int startB = startInt & 0xff; 247 | 248 | int endInt = (Integer) endValue; 249 | int endA = (endInt >> 24) & 0xff; 250 | int endR = (endInt >> 16) & 0xff; 251 | int endG = (endInt >> 8) & 0xff; 252 | int endB = endInt & 0xff; 253 | 254 | return (int) ((startA + (int) (fraction * (endA - startA))) << 24) 255 | | (int) ((startR + (int) (fraction * (endR - startR))) << 16) 256 | | (int) ((startG + (int) (fraction * (endG - startG))) << 8) 257 | | (int) ((startB + (int) (fraction * (endB - startB)))); 258 | } 259 | 260 | 261 | 262 | /** 263 | * @param strokeWidth Set the stroke width of the progress spinner in pixels. 264 | */ 265 | public void setStrokeWidth(float strokeWidth) { 266 | mSmile.setStrokeWidth(strokeWidth); 267 | } 268 | 269 | 270 | private static class Smile { 271 | private final RectF mTempBounds = new RectF(); 272 | private final Paint mPaint = new Paint(); 273 | private final Callback mCallback; 274 | 275 | private int[] mColors = { 276 | Color.BLACK 277 | }; 278 | 279 | private int mColorIndex; 280 | private int mCurrentColor = Color.BLACK; 281 | 282 | public static final float ANIMATED_VALUE_MAX = 855; 283 | private final Paint mCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG); 284 | private int mUsedValue; 285 | private int mc; 286 | 287 | public int getAnimatedValue() { 288 | return animatedValue; 289 | } 290 | 291 | int animatedValue; 292 | private float mStrokeWidth; 293 | private int mAlpha; 294 | 295 | public Smile(Context context,Callback callback) { 296 | this.mCallback = callback; 297 | mPaint.setStyle(Paint.Style.STROKE); // 画笔类型 298 | mPaint.setAntiAlias(true); // 抗锯齿 299 | mPaint.setStrokeCap(Paint.Cap.ROUND);//圆角笔触 300 | mStrokeWidth = Utils.convertDpToPixel(context, (int) 4); 301 | mPaint.setStrokeWidth(mStrokeWidth); 302 | 303 | } 304 | 305 | /** 306 | * Set the colors the progress spinner alternates between. 307 | * 308 | * @param colors Array of integers describing the colors. Must be non-null. 309 | */ 310 | public void setColors(@NonNull int[] colors) { 311 | mColors = colors; 312 | // if colors are reset, make sure to reset the color index as well 313 | setColorIndex(0); 314 | } 315 | 316 | /** 317 | * @return int describing the next color the progress spinner should use when drawing. 318 | */ 319 | public int getNextColor() { 320 | return mColors[getNextColorIndex()]; 321 | } 322 | 323 | private int getNextColorIndex() { 324 | return (mColorIndex + 1) % (mColors.length); 325 | } 326 | 327 | /** 328 | * Proceed to the next available ring color. This will automatically 329 | * wrap back to the beginning of colors. 330 | */ 331 | public void goToNextColor() { 332 | setColorIndex(getNextColorIndex()); 333 | } 334 | 335 | 336 | public int getAlpha() { 337 | return mAlpha; 338 | } 339 | 340 | /** 341 | * @param alpha Set the alpha of the progress spinner and associated arrowhead. 342 | */ 343 | public void setAlpha(int alpha) { 344 | mAlpha = alpha; 345 | } 346 | 347 | 348 | /** 349 | * Set the absolute color of the progress spinner. This is should only 350 | * be used when animating between current and next color when the 351 | * spinner is rotating. 352 | * 353 | * @param color int describing the color. 354 | */ 355 | public void setColor(int color) { 356 | mCurrentColor = color; 357 | } 358 | 359 | 360 | public void setColorIndex(int colorIndex) { 361 | mColorIndex = colorIndex; 362 | mCurrentColor = mColors[mColorIndex]; 363 | 364 | } 365 | 366 | 367 | /** 368 | * @param strokeWidth Set the stroke width of the progress spinner in pixels. 369 | */ 370 | public void setStrokeWidth(float strokeWidth) { 371 | mStrokeWidth = strokeWidth; 372 | mPaint.setStrokeWidth(strokeWidth); 373 | } 374 | 375 | 376 | public void draw(Canvas canvas, Rect bounds) { 377 | final RectF arcBounds = mTempBounds; 378 | arcBounds.set(bounds); 379 | canvas.translate(arcBounds.width() / 2, arcBounds.height() / 2); 380 | 381 | mPaint.setColor(mCurrentColor); 382 | smileAnimator(canvas, mPaint, arcBounds); 383 | 384 | } 385 | 386 | 387 | private void smileAnimator(Canvas canvas, Paint mPaint, RectF bounds) { 388 | float point = Math.min(bounds.width(), bounds.height()) * 0.4f / 2; 389 | float r = point * (float) Math.sqrt(2); 390 | RectF rectF = new RectF(-r, -r, r, r); 391 | canvas.save(); 392 | 393 | // rotate 394 | if (animatedValue >= 135) { 395 | canvas.rotate(animatedValue - 135); 396 | } 397 | 398 | // draw mouth 399 | float startAngle = 0, sweepAngle = 0; 400 | if (animatedValue < 135) { 401 | startAngle = animatedValue + 5; 402 | sweepAngle = 170 + animatedValue / 3; 403 | } else if (animatedValue < 270) { 404 | startAngle = 135 + 5; 405 | sweepAngle = 170 + animatedValue / 3; 406 | } else if (animatedValue < 630) { 407 | startAngle = 135 + 5; 408 | sweepAngle = 260 - (animatedValue - 270) / 5; 409 | } else if (animatedValue < 720) { 410 | startAngle = 135 - (animatedValue - 630) / 2 + 5; 411 | sweepAngle = 260 - (animatedValue - 270) / 5; 412 | } else { 413 | startAngle = 135 - (animatedValue - 630) / 2 - (animatedValue - 720) / 6 + 5; 414 | sweepAngle = 170; 415 | } 416 | canvas.drawArc(rectF, startAngle, sweepAngle, false, mPaint); 417 | 418 | // draw eye 419 | canvas.drawPoints(new float[]{ 420 | -point, -point 421 | , point, -point 422 | }, mPaint); 423 | 424 | canvas.restore(); 425 | } 426 | 427 | public void setAnimatedValue(float v) { 428 | mUsedValue = animatedValue; 429 | if (v <= 1) // 百分比 430 | this.animatedValue = (int) (ANIMATED_VALUE_MAX * v); 431 | else // 动画的值 432 | this.animatedValue = (int) v; 433 | if (mUsedValue != animatedValue) 434 | invalidateSelf(); 435 | } 436 | 437 | private void invalidateSelf() { 438 | mCallback.invalidateDrawable(null); 439 | } 440 | 441 | public int getStartingColor() { 442 | return mColors[mColorIndex]; 443 | } 444 | } 445 | 446 | private final Callback mCallback = new Callback() { 447 | @Override 448 | public void invalidateDrawable(Drawable d) { 449 | invalidateSelf(); 450 | } 451 | 452 | @Override 453 | public void scheduleDrawable(Drawable d, Runnable what, long when) { 454 | scheduleSelf(what, when); 455 | } 456 | 457 | @Override 458 | public void unscheduleDrawable(Drawable d, Runnable what) { 459 | unscheduleSelf(what); 460 | } 461 | }; 462 | 463 | 464 | } 465 | -------------------------------------------------------------------------------- /refresh_view_library/src/main/java/com/song/refresh_view/PullToRefreshView.java: -------------------------------------------------------------------------------- 1 | package com.song.refresh_view;/* 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 | 18 | import android.content.Context; 19 | import android.content.res.Resources; 20 | import android.content.res.TypedArray; 21 | import android.support.annotation.ColorInt; 22 | import android.support.annotation.ColorRes; 23 | import android.support.annotation.Nullable; 24 | import android.support.annotation.VisibleForTesting; 25 | import android.support.v4.view.MotionEventCompat; 26 | import android.support.v4.view.NestedScrollingChild; 27 | import android.support.v4.view.NestedScrollingChildHelper; 28 | import android.support.v4.view.NestedScrollingParent; 29 | import android.support.v4.view.NestedScrollingParentHelper; 30 | import android.support.v4.view.ViewCompat; 31 | import android.support.v4.widget.SwipeRefreshLayout; 32 | import android.util.AttributeSet; 33 | import android.util.DisplayMetrics; 34 | import android.util.Log; 35 | import android.view.MotionEvent; 36 | import android.view.View; 37 | import android.view.ViewConfiguration; 38 | import android.view.ViewGroup; 39 | import android.view.animation.Animation; 40 | import android.view.animation.Animation.AnimationListener; 41 | import android.view.animation.DecelerateInterpolator; 42 | import android.view.animation.Interpolator; 43 | import android.view.animation.Transformation; 44 | import android.widget.AbsListView; 45 | import android.widget.Toast; 46 | 47 | import static android.R.attr.animationDuration; 48 | 49 | /** 50 | * The SwipeRefreshLayout should be used whenever the user can refresh the 51 | * contents of a view via a vertical swipe gesture. The activity that 52 | * instantiates this view should add an OnRefreshListener to be notified 53 | * whenever the swipe to refresh gesture is completed. The SwipeRefreshLayout 54 | * will notify the listener each and every time the gesture is completed again; 55 | * the listener is responsible for correctly determining when to actually 56 | * initiate a refresh of its content. If the listener determines there should 57 | * not be a refresh, it must call setRefreshing(false) to cancel any visual 58 | * indication of a refresh. If an activity wishes to show just the progress 59 | * animation, it should call setRefreshing(true). To disable the gesture and 60 | * progress animation, call setEnabled(false) on the view. 61 | *

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

69 | */ 70 | public class PullToRefreshView extends ViewGroup implements NestedScrollingParent, 71 | NestedScrollingChild { 72 | // Maps to ProgressBar.Large style 73 | public static final int LARGE = SmileProgressDrawable.LARGE; 74 | // Maps to ProgressBar default style 75 | public static final int DEFAULT = SmileProgressDrawable.DEFAULT; 76 | 77 | @VisibleForTesting 78 | static final int CIRCLE_DIAMETER = 40; 79 | @VisibleForTesting 80 | static final int CIRCLE_DIAMETER_LARGE = 56; 81 | 82 | private static final String LOG_TAG = SwipeRefreshLayout.class.getSimpleName(); 83 | 84 | private static final int MAX_ALPHA = 255; 85 | private static final int STARTING_PROGRESS_ALPHA = (int) (.3f * MAX_ALPHA); 86 | 87 | private static final float DECELERATE_INTERPOLATION_FACTOR = 2f; 88 | private static final int INVALID_POINTER = -1; 89 | private static final float DRAG_RATE = .5f; 90 | 91 | // Max amount of circle that can be filled by progress during swipe gesture, 92 | // where 1.0 is a full circle 93 | private static final float MAX_PROGRESS_ANGLE = .8f; 94 | 95 | private static final int SCALE_DOWN_DURATION = 150; 96 | 97 | private static final int ALPHA_ANIMATION_DURATION = 300; 98 | 99 | private static final int ANIMATE_TO_TRIGGER_DURATION = 200; 100 | 101 | private static final int ANIMATE_TO_START_DURATION = 200; 102 | 103 | // Default background for the progress spinner 104 | private static final int CIRCLE_BG_LIGHT = 0xFFFAFAFA; 105 | // Default offset in dips from the top of the view to where the progress spinner should stop 106 | private static final int DEFAULT_CIRCLE_TARGET = 64; 107 | 108 | private View mTarget; // the target of the gesture 109 | OnRefreshListener mListener; 110 | boolean mRefreshing = false; 111 | private int mTouchSlop; 112 | private float mTotalDragDistance = -1; 113 | 114 | // If nested scrolling is enabled, the total amount that needed to be 115 | // consumed by this as the nested scrolling parent is used in place of the 116 | // overscroll determined by MOVE events in the onTouch handler 117 | private float mTotalUnconsumed; 118 | private final NestedScrollingParentHelper mNestedScrollingParentHelper; 119 | private final NestedScrollingChildHelper mNestedScrollingChildHelper; 120 | private final int[] mParentScrollConsumed = new int[2]; 121 | private final int[] mParentOffsetInWindow = new int[2]; 122 | private boolean mNestedScrollInProgress; 123 | 124 | private int mMediumAnimationDuration; 125 | int mCurrentTargetOffsetTop; 126 | 127 | private float mInitialMotionY; 128 | private float mInitialDownY; 129 | private boolean mIsBeingDragged; 130 | private int mActivePointerId = INVALID_POINTER; 131 | // Whether this item is scaled up rather than clipped 132 | boolean mScale; 133 | 134 | // Target is returning to its start offset because it was cancelled or a 135 | // refresh was triggered. 136 | private boolean mReturningToStart; 137 | private final DecelerateInterpolator mDecelerateInterpolator; 138 | private static final int[] LAYOUT_ATTRS = new int[]{ 139 | android.R.attr.enabled 140 | }; 141 | 142 | CircleImageView mCircleView; 143 | private int mCircleViewIndex = -1; 144 | 145 | protected int mFrom; 146 | 147 | float mStartingScale; 148 | 149 | protected int mOriginalOffsetTop; 150 | 151 | SmileProgressDrawable mProgress; 152 | 153 | private Animation mScaleAnimation; 154 | 155 | private Animation mScaleDownAnimation; 156 | 157 | private Animation mAlphaStartAnimation; 158 | 159 | private Animation mAlphaMaxAnimation; 160 | 161 | private Animation mScaleDownToStartAnimation; 162 | 163 | float mSpinnerFinalOffset; 164 | 165 | boolean mNotify; 166 | 167 | private int mCircleDiameter; 168 | 169 | // Whether the client has set a custom starting position; 170 | boolean mUsingCustomStart; 171 | 172 | private OnChildScrollUpCallback mChildScrollUpCallback; 173 | 174 | private Animation.AnimationListener mRefreshListener = new Animation.AnimationListener() { 175 | @Override 176 | public void onAnimationStart(Animation animation) { 177 | } 178 | 179 | @Override 180 | public void onAnimationRepeat(Animation animation) { 181 | } 182 | 183 | @Override 184 | public void onAnimationEnd(Animation animation) { 185 | if (mRefreshing) { 186 | // Make sure the progress view is fully visible 187 | mProgress.setAlpha(MAX_ALPHA); 188 | mProgress.start(); 189 | if (mNotify) { 190 | if (mListener != null) { 191 | mListener.onRefresh(); 192 | } 193 | } 194 | mCurrentTargetOffsetTop = mCircleView.getTop(); 195 | } else { 196 | reset(); 197 | } 198 | } 199 | }; 200 | private Toast mToast; 201 | 202 | void reset() { 203 | mCircleView.clearAnimation(); 204 | mProgress.stop(); 205 | mCircleView.setVisibility(View.GONE); 206 | setColorViewAlpha(MAX_ALPHA); 207 | // Return the circle to its start position 208 | if (mScale) { 209 | setAnimationProgress(0 /* animation complete and view is hidden */); 210 | } else { 211 | setTargetOffsetTopAndBottom(mOriginalOffsetTop - mCurrentTargetOffsetTop, 212 | true /* requires update */); 213 | } 214 | mCurrentTargetOffsetTop = mCircleView.getTop(); 215 | } 216 | 217 | @Override 218 | public void setEnabled(boolean enabled) { 219 | super.setEnabled(enabled); 220 | if (!enabled) { 221 | reset(); 222 | } 223 | } 224 | 225 | @Override 226 | protected void onDetachedFromWindow() { 227 | super.onDetachedFromWindow(); 228 | reset(); 229 | } 230 | 231 | private void setColorViewAlpha(int targetAlpha) { 232 | mCircleView.getBackground().setAlpha(targetAlpha); 233 | mProgress.setAlpha(targetAlpha); 234 | } 235 | 236 | /** 237 | * The refresh indicator starting and resting position is always positioned 238 | * near the top of the refreshing content. This position is a consistent 239 | * location, but can be adjusted in either direction based on whether or not 240 | * there is a toolbar or actionbar present. 241 | *

242 | * Note: Calling this will reset the position of the refresh indicator to 243 | * start. 244 | *

245 | * 246 | * @param scale Set to true if there is no view at a higher z-order than where the progress 247 | * spinner is set to appear. Setting it to true will cause indicator to be scaled 248 | * up rather than clipped. 249 | * @param start The offset in pixels from the top of this view at which the 250 | * progress spinner should appear. 251 | * @param end The offset in pixels from the top of this view at which the 252 | * progress spinner should come to rest after a successful swipe 253 | * gesture. 254 | */ 255 | public void setProgressViewOffset(boolean scale, int start, int end) { 256 | mScale = scale; 257 | mOriginalOffsetTop = start; 258 | mSpinnerFinalOffset = end; 259 | mUsingCustomStart = true; 260 | reset(); 261 | mRefreshing = false; 262 | } 263 | 264 | /** 265 | * The refresh indicator resting position is always positioned near the top 266 | * of the refreshing content. This position is a consistent location, but 267 | * can be adjusted in either direction based on whether or not there is a 268 | * toolbar or actionbar present. 269 | * 270 | * @param scale Set to true if there is no view at a higher z-order than where the progress 271 | * spinner is set to appear. Setting it to true will cause indicator to be scaled 272 | * up rather than clipped. 273 | * @param end The offset in pixels from the top of this view at which the 274 | * progress spinner should come to rest after a successful swipe 275 | * gesture. 276 | */ 277 | public void setProgressViewEndTarget(boolean scale, int end) { 278 | mSpinnerFinalOffset = end; 279 | mScale = scale; 280 | mCircleView.invalidate(); 281 | } 282 | 283 | /** 284 | * One of DEFAULT, or LARGE. 285 | */ 286 | public void setSize(int size) { 287 | if (size != SmileProgressDrawable.LARGE && size != SmileProgressDrawable.DEFAULT) { 288 | return; 289 | } 290 | final DisplayMetrics metrics = getResources().getDisplayMetrics(); 291 | if (size == SmileProgressDrawable.LARGE) { 292 | mCircleDiameter = (int) (CIRCLE_DIAMETER_LARGE * metrics.density); 293 | } else { 294 | mCircleDiameter = (int) (CIRCLE_DIAMETER * metrics.density); 295 | } 296 | // force the bounds of the progress circle inside the circle view to 297 | // update by setting it to null before updating its size and then 298 | // re-setting it 299 | mCircleView.setImageDrawable(null); 300 | mCircleView.setImageDrawable(mProgress); 301 | } 302 | 303 | /** 304 | * Simple constructor to use when creating a SwipeRefreshLayout from code. 305 | * 306 | * @param context 307 | */ 308 | public PullToRefreshView(Context context) { 309 | this(context, null); 310 | } 311 | 312 | /** 313 | * Constructor that is called when inflating SwipeRefreshLayout from XML. 314 | * 315 | * @param context 316 | * @param attrs 317 | */ 318 | public PullToRefreshView(Context context, AttributeSet attrs) { 319 | super(context, attrs); 320 | 321 | mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); 322 | 323 | mMediumAnimationDuration = getResources().getInteger( 324 | android.R.integer.config_mediumAnimTime); 325 | 326 | setWillNotDraw(false); 327 | mDecelerateInterpolator = new DecelerateInterpolator(DECELERATE_INTERPOLATION_FACTOR); 328 | 329 | final DisplayMetrics metrics = getResources().getDisplayMetrics(); 330 | mCircleDiameter = (int) (CIRCLE_DIAMETER * metrics.density); 331 | 332 | createProgressView(); 333 | ViewCompat.setChildrenDrawingOrderEnabled(this, true); 334 | // the absolute offset has to take into account that the circle starts at an offset 335 | mSpinnerFinalOffset = DEFAULT_CIRCLE_TARGET * metrics.density; 336 | mTotalDragDistance = mSpinnerFinalOffset; 337 | mNestedScrollingParentHelper = new NestedScrollingParentHelper(this); 338 | 339 | mNestedScrollingChildHelper = new NestedScrollingChildHelper(this); 340 | setNestedScrollingEnabled(true); 341 | 342 | mOriginalOffsetTop = mCurrentTargetOffsetTop = -mCircleDiameter; 343 | moveToStart(1.0f); 344 | 345 | final TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS); 346 | setEnabled(a.getBoolean(0, true)); 347 | a.recycle(); 348 | } 349 | 350 | @Override 351 | protected int getChildDrawingOrder(int childCount, int i) { 352 | if (mCircleViewIndex < 0) { 353 | return i; 354 | } else if (i == childCount - 1) { 355 | // Draw the selected child last 356 | return mCircleViewIndex; 357 | } else if (i >= mCircleViewIndex) { 358 | // Move the children after the selected child earlier one 359 | return i + 1; 360 | } else { 361 | // Keep the children before the selected child the same 362 | return i; 363 | } 364 | } 365 | 366 | 367 | private void createProgressView() { 368 | mCircleView = new CircleImageView(getContext(), CIRCLE_BG_LIGHT); 369 | mProgress = new SmileProgressDrawable(getContext(), this); 370 | mProgress.setBackgroundColor(CIRCLE_BG_LIGHT); 371 | mCircleView.setImageDrawable(mProgress); 372 | mCircleView.setVisibility(View.GONE); 373 | addView(mCircleView); 374 | } 375 | 376 | /** 377 | * Set the listener to be notified when a refresh is triggered via the swipe 378 | * gesture. 379 | */ 380 | public void setOnRefreshListener(OnRefreshListener listener) { 381 | mListener = listener; 382 | } 383 | 384 | /** 385 | * Pre API 11, alpha is used to make the progress circle appear instead of scale. 386 | */ 387 | private boolean isAlphaUsedForScale() { 388 | return android.os.Build.VERSION.SDK_INT < 11; 389 | } 390 | 391 | /** 392 | * Notify the widget that refresh state has changed. Do not call this when 393 | * refresh is triggered by a swipe gesture. 394 | * 395 | * @param refreshing Whether or not the view should show refresh progress. 396 | */ 397 | public void setRefreshing(boolean refreshing) { 398 | if (refreshing && mRefreshing != refreshing) { 399 | // scale and show 400 | mRefreshing = refreshing; 401 | int endTarget = 0; 402 | if (!mUsingCustomStart) { 403 | endTarget = (int) (mSpinnerFinalOffset + mOriginalOffsetTop); 404 | } else { 405 | endTarget = (int) mSpinnerFinalOffset; 406 | } 407 | setTargetOffsetTopAndBottom(endTarget - mCurrentTargetOffsetTop, 408 | true /* requires update */); 409 | mNotify = false; 410 | startScaleUpAnimation(mRefreshListener); 411 | } else { 412 | setRefreshing(refreshing, false /* notify */); 413 | } 414 | } 415 | 416 | private void startScaleUpAnimation(AnimationListener listener) { 417 | mCircleView.setVisibility(View.VISIBLE); 418 | if (android.os.Build.VERSION.SDK_INT >= 11) { 419 | // Pre API 11, alpha is used in place of scale up to show the 420 | // progress circle appearing. 421 | // Don't adjust the alpha during appearance otherwise. 422 | mProgress.setAlpha(MAX_ALPHA); 423 | } 424 | mScaleAnimation = new Animation() { 425 | @Override 426 | public void applyTransformation(float interpolatedTime, Transformation t) { 427 | setAnimationProgress(interpolatedTime); 428 | } 429 | }; 430 | mScaleAnimation.setDuration(mMediumAnimationDuration); 431 | if (listener != null) { 432 | mCircleView.setAnimationListener(listener); 433 | } 434 | mCircleView.clearAnimation(); 435 | mCircleView.startAnimation(mScaleAnimation); 436 | } 437 | 438 | /** 439 | * Pre API 11, this does an alpha animation. 440 | * 441 | * @param progress 442 | */ 443 | void setAnimationProgress(float progress) { 444 | if (isAlphaUsedForScale()) { 445 | setColorViewAlpha((int) (progress * MAX_ALPHA)); 446 | } else { 447 | ViewCompat.setScaleX(mCircleView, progress); 448 | ViewCompat.setScaleY(mCircleView, progress); 449 | } 450 | } 451 | 452 | private void setRefreshing(boolean refreshing, final boolean notify) { 453 | if (mRefreshing != refreshing) { 454 | mNotify = notify; 455 | ensureTarget(); 456 | mRefreshing = refreshing; 457 | if (mRefreshing) { 458 | animateOffsetToCorrectPosition(mCurrentTargetOffsetTop, mRefreshListener); 459 | } else { 460 | startScaleDownAnimation(mRefreshListener); 461 | } 462 | } 463 | } 464 | 465 | void startScaleDownAnimation(Animation.AnimationListener listener) { 466 | mScaleDownAnimation = new Animation() { 467 | @Override 468 | public void applyTransformation(float interpolatedTime, Transformation t) { 469 | setAnimationProgress(1 - interpolatedTime); 470 | } 471 | }; 472 | mScaleDownAnimation.setDuration(SCALE_DOWN_DURATION); 473 | mCircleView.setAnimationListener(listener); 474 | mCircleView.clearAnimation(); 475 | mCircleView.startAnimation(mScaleDownAnimation); 476 | } 477 | 478 | private void startProgressAlphaStartAnimation() { 479 | mAlphaStartAnimation = startAlphaAnimation(mProgress.getAlpha(), STARTING_PROGRESS_ALPHA); 480 | } 481 | 482 | private void startProgressAlphaMaxAnimation() { 483 | mAlphaMaxAnimation = startAlphaAnimation(mProgress.getAlpha(), MAX_ALPHA); 484 | } 485 | 486 | private Animation startAlphaAnimation(final int startingAlpha, final int endingAlpha) { 487 | // Pre API 11, alpha is used in place of scale. Don't also use it to 488 | // show the trigger point. 489 | if (mScale && isAlphaUsedForScale()) { 490 | return null; 491 | } 492 | Animation alpha = new Animation() { 493 | @Override 494 | public void applyTransformation(float interpolatedTime, Transformation t) { 495 | mProgress.setAlpha( 496 | (int) (startingAlpha + ((endingAlpha - startingAlpha) * interpolatedTime))); 497 | } 498 | }; 499 | alpha.setDuration(ALPHA_ANIMATION_DURATION); 500 | // Clear out the previous animation listeners. 501 | mCircleView.setAnimationListener(null); 502 | mCircleView.clearAnimation(); 503 | mCircleView.startAnimation(alpha); 504 | return alpha; 505 | } 506 | 507 | /** 508 | * 设置绘制的宽度 509 | * 510 | * @param strokeWidth 511 | */ 512 | public void setSmileStrokeWidth(float strokeWidth) { 513 | mProgress.setStrokeWidth(strokeWidth); 514 | } 515 | 516 | /** 517 | * @deprecated Use {@link #setProgressBackgroundColorSchemeResource(int)} 518 | */ 519 | @Deprecated 520 | public void setProgressBackgroundColor(int colorRes) { 521 | setProgressBackgroundColorSchemeResource(colorRes); 522 | } 523 | 524 | /** 525 | * Set the background color of the progress spinner disc. 526 | * 527 | * @param colorRes Resource id of the color. 528 | */ 529 | public void setProgressBackgroundColorSchemeResource(@ColorRes int colorRes) { 530 | setProgressBackgroundColorSchemeColor(getResources().getColor(colorRes)); 531 | } 532 | 533 | /** 534 | * Set the background color of the progress spinner disc. 535 | * 536 | * @param color 537 | */ 538 | public void setProgressBackgroundColorSchemeColor(@ColorInt int color) { 539 | mCircleView.setBackgroundColor(color); 540 | mProgress.setBackgroundColor(color); 541 | } 542 | 543 | /** 544 | * @deprecated Use {@link #setColorSchemeResources(int...)} 545 | */ 546 | @Deprecated 547 | public void setColorScheme(@ColorInt int... colors) { 548 | setColorSchemeResources(colors); 549 | } 550 | 551 | /** 552 | * Set the color resources used in the progress animation from color resources. 553 | * The first color will also be the color of the bar that grows in response 554 | * to a user swipe gesture. 555 | * 556 | * @param colorResIds 557 | */ 558 | public void setColorSchemeResources(@ColorRes int... colorResIds) { 559 | final Resources res = getResources(); 560 | int[] colorRes = new int[colorResIds.length]; 561 | for (int i = 0; i < colorResIds.length; i++) { 562 | colorRes[i] = res.getColor(colorResIds[i]); 563 | } 564 | setColorSchemeColors(colorRes); 565 | } 566 | 567 | /** 568 | * Set the colors used in the progress animation. The first 569 | * color will also be the color of the bar that grows in response to a user 570 | * swipe gesture. 571 | * 572 | * @param colors 573 | */ 574 | public void setColorSchemeColors(@ColorInt int... colors) { 575 | ensureTarget(); 576 | mProgress.setColorSchemeColors(colors); 577 | } 578 | 579 | public void setSmileAnimationDuration(int animationDuration){ 580 | mProgress.setAnimationDuration(animationDuration); 581 | } 582 | public void setSmileInterpolator(Interpolator interpolator){ 583 | mProgress.setInterpolator(interpolator); 584 | } 585 | /** 586 | * @return Whether the SwipeRefreshWidget is actively showing refresh 587 | * progress. 588 | */ 589 | public boolean isRefreshing() { 590 | return mRefreshing; 591 | } 592 | 593 | private void ensureTarget() { 594 | // Don't bother getting the parent height if the parent hasn't been laid 595 | // out yet. 596 | if (mTarget == null) { 597 | for (int i = 0; i < getChildCount(); i++) { 598 | View child = getChildAt(i); 599 | if (!child.equals(mCircleView)) { 600 | mTarget = child; 601 | break; 602 | } 603 | } 604 | } 605 | } 606 | 607 | /** 608 | * Set the distance to trigger a sync in dips 609 | * 610 | * @param distance 611 | */ 612 | public void setDistanceToTriggerSync(int distance) { 613 | mTotalDragDistance = distance; 614 | } 615 | 616 | @Override 617 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 618 | final int width = getMeasuredWidth(); 619 | final int height = getMeasuredHeight(); 620 | if (getChildCount() == 0) { 621 | return; 622 | } 623 | if (mTarget == null) { 624 | ensureTarget(); 625 | } 626 | if (mTarget == null) { 627 | return; 628 | } 629 | final View child = mTarget; 630 | final int childLeft = getPaddingLeft(); 631 | final int childTop = getPaddingTop(); 632 | final int childWidth = width - getPaddingLeft() - getPaddingRight(); 633 | final int childHeight = height - getPaddingTop() - getPaddingBottom(); 634 | child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight); 635 | int circleWidth = mCircleView.getMeasuredWidth(); 636 | int circleHeight = mCircleView.getMeasuredHeight(); 637 | mCircleView.layout((width / 2 - circleWidth / 2), mCurrentTargetOffsetTop, 638 | (width / 2 + circleWidth / 2), mCurrentTargetOffsetTop + circleHeight); 639 | } 640 | 641 | @Override 642 | public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 643 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 644 | if (mTarget == null) { 645 | ensureTarget(); 646 | } 647 | if (mTarget == null) { 648 | return; 649 | } 650 | mTarget.measure(MeasureSpec.makeMeasureSpec( 651 | getMeasuredWidth() - getPaddingLeft() - getPaddingRight(), 652 | MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec( 653 | getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), MeasureSpec.EXACTLY)); 654 | mCircleView.measure(MeasureSpec.makeMeasureSpec(mCircleDiameter, MeasureSpec.EXACTLY), 655 | MeasureSpec.makeMeasureSpec(mCircleDiameter, MeasureSpec.EXACTLY)); 656 | mCircleViewIndex = -1; 657 | // Get the index of the circleview. 658 | for (int index = 0; index < getChildCount(); index++) { 659 | if (getChildAt(index) == mCircleView) { 660 | mCircleViewIndex = index; 661 | break; 662 | } 663 | } 664 | } 665 | 666 | /** 667 | * Get the diameter of the progress circle that is displayed as part of the 668 | * swipe to refresh layout. 669 | * 670 | * @return Diameter in pixels of the progress circle view. 671 | */ 672 | public int getProgressCircleDiameter() { 673 | return mCircleDiameter; 674 | } 675 | 676 | /** 677 | * @return Whether it is possible for the child view of this layout to 678 | * scroll up. Override this if the child view is a custom view. 679 | */ 680 | public boolean canChildScrollUp() { 681 | if (mChildScrollUpCallback != null) { 682 | return mChildScrollUpCallback.canChildScrollUp(this, mTarget); 683 | } 684 | if (android.os.Build.VERSION.SDK_INT < 14) { 685 | if (mTarget instanceof AbsListView) { 686 | final AbsListView absListView = (AbsListView) mTarget; 687 | return absListView.getChildCount() > 0 688 | && (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0) 689 | .getTop() < absListView.getPaddingTop()); 690 | } else { 691 | return ViewCompat.canScrollVertically(mTarget, -1) || mTarget.getScrollY() > 0; 692 | } 693 | } else { 694 | return ViewCompat.canScrollVertically(mTarget, -1); 695 | } 696 | } 697 | 698 | /** 699 | * Set a callback to override {@link SwipeRefreshLayout#canChildScrollUp()} method. Non-null 700 | * callback will return the value provided by the callback and ignore all internal logic. 701 | * 702 | * @param callback Callback that should be called when canChildScrollUp() is called. 703 | */ 704 | public void setOnChildScrollUpCallback(@Nullable OnChildScrollUpCallback callback) { 705 | mChildScrollUpCallback = callback; 706 | } 707 | 708 | @Override 709 | public boolean onInterceptTouchEvent(MotionEvent ev) { 710 | ensureTarget(); 711 | 712 | final int action = MotionEventCompat.getActionMasked(ev); 713 | int pointerIndex; 714 | 715 | if (mReturningToStart && action == MotionEvent.ACTION_DOWN) { 716 | mReturningToStart = false; 717 | } 718 | 719 | if (!isEnabled() || mReturningToStart || canChildScrollUp() 720 | || mRefreshing || mNestedScrollInProgress) { 721 | // Fail fast if we're not in a state where a swipe is possible 722 | return false; 723 | } 724 | 725 | switch (action) { 726 | case MotionEvent.ACTION_DOWN: 727 | setTargetOffsetTopAndBottom(mOriginalOffsetTop - mCircleView.getTop(), true); 728 | mActivePointerId = ev.getPointerId(0); 729 | mIsBeingDragged = false; 730 | 731 | pointerIndex = ev.findPointerIndex(mActivePointerId); 732 | if (pointerIndex < 0) { 733 | return false; 734 | } 735 | mInitialDownY = ev.getY(pointerIndex); 736 | break; 737 | 738 | case MotionEvent.ACTION_MOVE: 739 | if (mActivePointerId == INVALID_POINTER) { 740 | Log.e(LOG_TAG, "Got ACTION_MOVE event but don't have an active pointer id."); 741 | return false; 742 | } 743 | 744 | pointerIndex = ev.findPointerIndex(mActivePointerId); 745 | if (pointerIndex < 0) { 746 | return false; 747 | } 748 | final float y = ev.getY(pointerIndex); 749 | startDragging(y); 750 | break; 751 | 752 | case MotionEventCompat.ACTION_POINTER_UP: 753 | onSecondaryPointerUp(ev); 754 | break; 755 | 756 | case MotionEvent.ACTION_UP: 757 | case MotionEvent.ACTION_CANCEL: 758 | mIsBeingDragged = false; 759 | mActivePointerId = INVALID_POINTER; 760 | break; 761 | } 762 | 763 | return mIsBeingDragged; 764 | } 765 | 766 | @Override 767 | public void requestDisallowInterceptTouchEvent(boolean b) { 768 | // if this is a List < L or another view that doesn't support nested 769 | // scrolling, ignore this request so that the vertical scroll event 770 | // isn't stolen 771 | if ((android.os.Build.VERSION.SDK_INT < 21 && mTarget instanceof AbsListView) 772 | || (mTarget != null && !ViewCompat.isNestedScrollingEnabled(mTarget))) { 773 | // Nope. 774 | } else { 775 | super.requestDisallowInterceptTouchEvent(b); 776 | } 777 | } 778 | 779 | // NestedScrollingParent 780 | 781 | @Override 782 | public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) { 783 | return isEnabled() && !mReturningToStart && !mRefreshing 784 | && (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0; 785 | } 786 | 787 | @Override 788 | public void onNestedScrollAccepted(View child, View target, int axes) { 789 | // Reset the counter of how much leftover scroll needs to be consumed. 790 | mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, axes); 791 | // Dispatch up to the nested parent 792 | startNestedScroll(axes & ViewCompat.SCROLL_AXIS_VERTICAL); 793 | mTotalUnconsumed = 0; 794 | mNestedScrollInProgress = true; 795 | } 796 | 797 | @Override 798 | public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) { 799 | // If we are in the middle of consuming, a scroll, then we want to move the spinner back up 800 | // before allowing the list to scroll 801 | if (dy > 0 && mTotalUnconsumed > 0) { 802 | if (dy > mTotalUnconsumed) { 803 | consumed[1] = dy - (int) mTotalUnconsumed; 804 | mTotalUnconsumed = 0; 805 | } else { 806 | mTotalUnconsumed -= dy; 807 | consumed[1] = dy; 808 | } 809 | moveSpinner(mTotalUnconsumed); 810 | } 811 | 812 | // If a client layout is using a custom start position for the circle 813 | // view, they mean to hide it again before scrolling the child view 814 | // If we get back to mTotalUnconsumed == 0 and there is more to go, hide 815 | // the circle so it isn't exposed if its blocking content is moved 816 | if (mUsingCustomStart && dy > 0 && mTotalUnconsumed == 0 817 | && Math.abs(dy - consumed[1]) > 0) { 818 | mCircleView.setVisibility(View.GONE); 819 | } 820 | 821 | // Now let our nested parent consume the leftovers 822 | final int[] parentConsumed = mParentScrollConsumed; 823 | if (dispatchNestedPreScroll(dx - consumed[0], dy - consumed[1], parentConsumed, null)) { 824 | consumed[0] += parentConsumed[0]; 825 | consumed[1] += parentConsumed[1]; 826 | } 827 | } 828 | 829 | @Override 830 | public int getNestedScrollAxes() { 831 | return mNestedScrollingParentHelper.getNestedScrollAxes(); 832 | } 833 | 834 | @Override 835 | public void onStopNestedScroll(View target) { 836 | mNestedScrollingParentHelper.onStopNestedScroll(target); 837 | mNestedScrollInProgress = false; 838 | // Finish the spinner for nested scrolling if we ever consumed any 839 | // unconsumed nested scroll 840 | if (mTotalUnconsumed > 0) { 841 | finishSpinner(mTotalUnconsumed); 842 | mTotalUnconsumed = 0; 843 | } 844 | // Dispatch up our nested parent 845 | stopNestedScroll(); 846 | } 847 | 848 | @Override 849 | public void onNestedScroll(final View target, final int dxConsumed, final int dyConsumed, 850 | final int dxUnconsumed, final int dyUnconsumed) { 851 | // Dispatch up to the nested parent first 852 | dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, 853 | mParentOffsetInWindow); 854 | 855 | // This is a bit of a hack. Nested scrolling works from the bottom up, and as we are 856 | // sometimes between two nested scrolling views, we need a way to be able to know when any 857 | // nested scrolling parent has stopped handling events. We do that by using the 858 | // 'offset in window 'functionality to see if we have been moved from the event. 859 | // This is a decent indication of whether we should take over the event stream or not. 860 | final int dy = dyUnconsumed + mParentOffsetInWindow[1]; 861 | if (dy < 0 && !canChildScrollUp()) { 862 | mTotalUnconsumed += Math.abs(dy); 863 | moveSpinner(mTotalUnconsumed); 864 | } 865 | } 866 | 867 | // NestedScrollingChild 868 | 869 | @Override 870 | public void setNestedScrollingEnabled(boolean enabled) { 871 | mNestedScrollingChildHelper.setNestedScrollingEnabled(enabled); 872 | } 873 | 874 | @Override 875 | public boolean isNestedScrollingEnabled() { 876 | return mNestedScrollingChildHelper.isNestedScrollingEnabled(); 877 | } 878 | 879 | @Override 880 | public boolean startNestedScroll(int axes) { 881 | return mNestedScrollingChildHelper.startNestedScroll(axes); 882 | } 883 | 884 | @Override 885 | public void stopNestedScroll() { 886 | mNestedScrollingChildHelper.stopNestedScroll(); 887 | } 888 | 889 | @Override 890 | public boolean hasNestedScrollingParent() { 891 | return mNestedScrollingChildHelper.hasNestedScrollingParent(); 892 | } 893 | 894 | @Override 895 | public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, 896 | int dyUnconsumed, int[] offsetInWindow) { 897 | return mNestedScrollingChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, 898 | dxUnconsumed, dyUnconsumed, offsetInWindow); 899 | } 900 | 901 | @Override 902 | public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) { 903 | return mNestedScrollingChildHelper.dispatchNestedPreScroll( 904 | dx, dy, consumed, offsetInWindow); 905 | } 906 | 907 | @Override 908 | public boolean onNestedPreFling(View target, float velocityX, 909 | float velocityY) { 910 | return dispatchNestedPreFling(velocityX, velocityY); 911 | } 912 | 913 | @Override 914 | public boolean onNestedFling(View target, float velocityX, float velocityY, 915 | boolean consumed) { 916 | return dispatchNestedFling(velocityX, velocityY, consumed); 917 | } 918 | 919 | @Override 920 | public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) { 921 | return mNestedScrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed); 922 | } 923 | 924 | @Override 925 | public boolean dispatchNestedPreFling(float velocityX, float velocityY) { 926 | return mNestedScrollingChildHelper.dispatchNestedPreFling(velocityX, velocityY); 927 | } 928 | 929 | private boolean isAnimationRunning(Animation animation) { 930 | return animation != null && animation.hasStarted() && !animation.hasEnded(); 931 | } 932 | 933 | private void moveSpinner(float overscrollTop) { 934 | 935 | float originalDragPercent = overscrollTop / mTotalDragDistance; 936 | 937 | float dragPercent = Math.min(1f, Math.abs(originalDragPercent)); 938 | float adjustedPercent = (float) Math.max(dragPercent - .4, 0) * 5 / 3;// 1 可以下拉刷新 939 | float extraOS = Math.abs(overscrollTop) - mTotalDragDistance; 940 | float slingshotDist = mUsingCustomStart ? mSpinnerFinalOffset - mOriginalOffsetTop 941 | : mSpinnerFinalOffset; 942 | float tensionSlingshotPercent = Math.max(0, Math.min(extraOS, slingshotDist * 2) 943 | / slingshotDist); 944 | float tensionPercent = (float) ((tensionSlingshotPercent / 4) - Math.pow( 945 | (tensionSlingshotPercent / 4), 2)) * 2f; 946 | float extraMove = (slingshotDist) * tensionPercent * 2; 947 | 948 | int targetY = mOriginalOffsetTop + (int) ((slingshotDist * dragPercent) + extraMove); 949 | // where 1.0f is a full circle 950 | if (mCircleView.getVisibility() != View.VISIBLE) { 951 | mCircleView.setVisibility(View.VISIBLE); 952 | } 953 | if (!mScale) { 954 | ViewCompat.setScaleX(mCircleView, 1f); 955 | ViewCompat.setScaleY(mCircleView, 1f); 956 | } 957 | 958 | if (mScale) { 959 | setAnimationProgress(Math.min(1f, overscrollTop / mTotalDragDistance)); 960 | } 961 | if (overscrollTop < mTotalDragDistance) { 962 | if (mProgress.getAlpha() > STARTING_PROGRESS_ALPHA 963 | && !isAnimationRunning(mAlphaStartAnimation)) { 964 | // Animate the alpha 965 | startProgressAlphaStartAnimation(); 966 | } 967 | } else { 968 | if (mProgress.getAlpha() < MAX_ALPHA && !isAnimationRunning(mAlphaMaxAnimation)) { 969 | // Animate the alpha 970 | startProgressAlphaMaxAnimation(); 971 | } 972 | } 973 | // float strokeStart = adjustedPercent * .8f;// TODO: 2016/10/13 0013 974 | // mProgress.setStartEndTrim(0f, Math.min(MAX_PROGRESS_ANGLE, strokeStart)); 975 | // mProgress.setArrowScale(Math.min(1f, adjustedPercent)); 976 | // 977 | // float rotation = (-0.25f + .4f * adjustedPercent + tensionPercent * 2) * .5f; 978 | // mProgress.setProgressRotation(rotation); 979 | mProgress.setPercentage(tensionPercent * 2); // 最高值0.5 980 | setTargetOffsetTopAndBottom(targetY - mCurrentTargetOffsetTop, true /* requires update */); 981 | } 982 | 983 | private void finishSpinner(float overscrollTop) { 984 | if (overscrollTop > mTotalDragDistance) { 985 | setRefreshing(true, true /* notify */); 986 | } else { 987 | // cancel refresh 988 | mRefreshing = false; 989 | // mProgress.setStartEndTrim(0f, 0f); // TODO: 2016/10/13 0013 990 | Animation.AnimationListener listener = null; 991 | if (!mScale) { 992 | listener = new Animation.AnimationListener() { 993 | 994 | @Override 995 | public void onAnimationStart(Animation animation) { 996 | } 997 | 998 | @Override 999 | public void onAnimationEnd(Animation animation) { 1000 | if (!mScale) { 1001 | startScaleDownAnimation(null); 1002 | } 1003 | } 1004 | 1005 | @Override 1006 | public void onAnimationRepeat(Animation animation) { 1007 | } 1008 | 1009 | }; 1010 | } 1011 | animateOffsetToStartPosition(mCurrentTargetOffsetTop, listener); 1012 | } 1013 | } 1014 | 1015 | @Override 1016 | public boolean onTouchEvent(MotionEvent ev) { 1017 | final int action = MotionEventCompat.getActionMasked(ev); 1018 | int pointerIndex = -1; 1019 | 1020 | if (mReturningToStart && action == MotionEvent.ACTION_DOWN) { 1021 | mReturningToStart = false; 1022 | } 1023 | 1024 | if (!isEnabled() || mReturningToStart || canChildScrollUp() 1025 | || mRefreshing || mNestedScrollInProgress) { 1026 | // Fail fast if we're not in a state where a swipe is possible 1027 | return false; 1028 | } 1029 | 1030 | switch (action) { 1031 | case MotionEvent.ACTION_DOWN: 1032 | mActivePointerId = ev.getPointerId(0); 1033 | mIsBeingDragged = false; 1034 | break; 1035 | 1036 | case MotionEvent.ACTION_MOVE: { 1037 | pointerIndex = ev.findPointerIndex(mActivePointerId); 1038 | if (pointerIndex < 0) { 1039 | Log.e(LOG_TAG, "Got ACTION_MOVE event but have an invalid active pointer id."); 1040 | return false; 1041 | } 1042 | 1043 | final float y = ev.getY(pointerIndex); 1044 | startDragging(y); 1045 | 1046 | if (mIsBeingDragged) { 1047 | final float overscrollTop = (y - mInitialMotionY) * DRAG_RATE; 1048 | if (overscrollTop > 0) { 1049 | moveSpinner(overscrollTop); 1050 | } else { 1051 | return false; 1052 | } 1053 | } 1054 | break; 1055 | } 1056 | case MotionEventCompat.ACTION_POINTER_DOWN: { 1057 | pointerIndex = MotionEventCompat.getActionIndex(ev); 1058 | if (pointerIndex < 0) { 1059 | Log.e(LOG_TAG, 1060 | "Got ACTION_POINTER_DOWN event but have an invalid action index."); 1061 | return false; 1062 | } 1063 | mActivePointerId = ev.getPointerId(pointerIndex); 1064 | break; 1065 | } 1066 | 1067 | case MotionEventCompat.ACTION_POINTER_UP: 1068 | onSecondaryPointerUp(ev); 1069 | break; 1070 | 1071 | case MotionEvent.ACTION_UP: { 1072 | pointerIndex = ev.findPointerIndex(mActivePointerId); 1073 | if (pointerIndex < 0) { 1074 | Log.e(LOG_TAG, "Got ACTION_UP event but don't have an active pointer id."); 1075 | return false; 1076 | } 1077 | 1078 | if (mIsBeingDragged) { 1079 | final float y = ev.getY(pointerIndex); 1080 | final float overscrollTop = (y - mInitialMotionY) * DRAG_RATE; 1081 | mIsBeingDragged = false; 1082 | finishSpinner(overscrollTop); 1083 | } 1084 | mActivePointerId = INVALID_POINTER; 1085 | return false; 1086 | } 1087 | case MotionEvent.ACTION_CANCEL: 1088 | return false; 1089 | } 1090 | 1091 | return true; 1092 | } 1093 | 1094 | private void startDragging(float y) { 1095 | final float yDiff = y - mInitialDownY; 1096 | if (yDiff > mTouchSlop && !mIsBeingDragged) { 1097 | mInitialMotionY = mInitialDownY + mTouchSlop; 1098 | mIsBeingDragged = true; 1099 | mProgress.setAlpha(STARTING_PROGRESS_ALPHA); 1100 | } 1101 | } 1102 | 1103 | private void animateOffsetToCorrectPosition(int from, AnimationListener listener) { 1104 | mFrom = from; 1105 | mAnimateToCorrectPosition.reset(); 1106 | mAnimateToCorrectPosition.setDuration(ANIMATE_TO_TRIGGER_DURATION); 1107 | mAnimateToCorrectPosition.setInterpolator(mDecelerateInterpolator); 1108 | if (listener != null) { 1109 | mCircleView.setAnimationListener(listener); 1110 | } 1111 | mCircleView.clearAnimation(); 1112 | mCircleView.startAnimation(mAnimateToCorrectPosition); 1113 | } 1114 | 1115 | private void animateOffsetToStartPosition(int from, AnimationListener listener) { 1116 | if (mScale) { 1117 | // Scale the item back down 1118 | startScaleDownReturnToStartAnimation(from, listener); 1119 | } else { 1120 | mFrom = from; 1121 | mAnimateToStartPosition.reset(); 1122 | mAnimateToStartPosition.setDuration(ANIMATE_TO_START_DURATION); 1123 | mAnimateToStartPosition.setInterpolator(mDecelerateInterpolator); 1124 | if (listener != null) { 1125 | mCircleView.setAnimationListener(listener); 1126 | } 1127 | mCircleView.clearAnimation(); 1128 | mCircleView.startAnimation(mAnimateToStartPosition); 1129 | } 1130 | } 1131 | 1132 | private final Animation mAnimateToCorrectPosition = new Animation() { 1133 | @Override 1134 | public void applyTransformation(float interpolatedTime, Transformation t) { 1135 | int targetTop = 0; 1136 | int endTarget = 0; 1137 | if (!mUsingCustomStart) { 1138 | endTarget = (int) (mSpinnerFinalOffset - Math.abs(mOriginalOffsetTop)); 1139 | } else { 1140 | endTarget = (int) mSpinnerFinalOffset; 1141 | } 1142 | targetTop = (mFrom + (int) ((endTarget - mFrom) * interpolatedTime)); 1143 | int offset = targetTop - mCircleView.getTop(); 1144 | setTargetOffsetTopAndBottom(offset, false /* requires update */); 1145 | } 1146 | }; 1147 | 1148 | void moveToStart(float interpolatedTime) { 1149 | int targetTop = 0; 1150 | targetTop = (mFrom + (int) ((mOriginalOffsetTop - mFrom) * interpolatedTime)); 1151 | int offset = targetTop - mCircleView.getTop(); 1152 | setTargetOffsetTopAndBottom(offset, false /* requires update */); 1153 | } 1154 | 1155 | private final Animation mAnimateToStartPosition = new Animation() { 1156 | @Override 1157 | public void applyTransformation(float interpolatedTime, Transformation t) { 1158 | moveToStart(interpolatedTime); 1159 | } 1160 | }; 1161 | 1162 | private void startScaleDownReturnToStartAnimation(int from, 1163 | Animation.AnimationListener listener) { 1164 | mFrom = from; 1165 | if (isAlphaUsedForScale()) { 1166 | mStartingScale = mProgress.getAlpha(); 1167 | } else { 1168 | mStartingScale = ViewCompat.getScaleX(mCircleView); 1169 | } 1170 | mScaleDownToStartAnimation = new Animation() { 1171 | @Override 1172 | public void applyTransformation(float interpolatedTime, Transformation t) { 1173 | float targetScale = (mStartingScale + (-mStartingScale * interpolatedTime)); 1174 | setAnimationProgress(targetScale); 1175 | moveToStart(interpolatedTime); 1176 | } 1177 | }; 1178 | mScaleDownToStartAnimation.setDuration(SCALE_DOWN_DURATION); 1179 | if (listener != null) { 1180 | mCircleView.setAnimationListener(listener); 1181 | } 1182 | mCircleView.clearAnimation(); 1183 | mCircleView.startAnimation(mScaleDownToStartAnimation); 1184 | } 1185 | 1186 | void setTargetOffsetTopAndBottom(int offset, boolean requiresUpdate) { 1187 | mCircleView.bringToFront(); 1188 | ViewCompat.offsetTopAndBottom(mCircleView, offset); 1189 | mCurrentTargetOffsetTop = mCircleView.getTop(); 1190 | if (requiresUpdate && android.os.Build.VERSION.SDK_INT < 11) { 1191 | invalidate(); 1192 | } 1193 | } 1194 | 1195 | private void onSecondaryPointerUp(MotionEvent ev) { 1196 | final int pointerIndex = MotionEventCompat.getActionIndex(ev); 1197 | final int pointerId = ev.getPointerId(pointerIndex); 1198 | if (pointerId == mActivePointerId) { 1199 | // This was our active pointer going up. Choose a new 1200 | // active pointer and adjust accordingly. 1201 | final int newPointerIndex = pointerIndex == 0 ? 1 : 0; 1202 | mActivePointerId = ev.getPointerId(newPointerIndex); 1203 | } 1204 | } 1205 | 1206 | /** 1207 | * Classes that wish to be notified when the swipe gesture correctly 1208 | * triggers a refresh should implement this interface. 1209 | */ 1210 | public interface OnRefreshListener { 1211 | /** 1212 | * Called when a swipe gesture triggers a refresh. 1213 | */ 1214 | void onRefresh(); 1215 | } 1216 | 1217 | /** 1218 | * Classes that wish to override {@link SwipeRefreshLayout#canChildScrollUp()} method 1219 | * behavior should implement this interface. 1220 | */ 1221 | public interface OnChildScrollUpCallback { 1222 | /** 1223 | * Callback that will be called when {@link SwipeRefreshLayout#canChildScrollUp()} method 1224 | * is called to allow the implementer to override its behavior. 1225 | * 1226 | * @param parent SwipeRefreshLayout that this callback is overriding. 1227 | * @param child The child view of SwipeRefreshLayout. 1228 | * @return Whether it is possible for the child view of parent layout to scroll up. 1229 | */ 1230 | boolean canChildScrollUp(PullToRefreshView parent, @Nullable View child); 1231 | } 1232 | } 1233 | --------------------------------------------------------------------------------