├── .gitignore ├── .idea ├── .name ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── gradle.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── FlyRefresh.iml ├── LICENSE ├── README.md ├── app ├── .gitignore ├── app.iml ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── race604 │ │ └── flyrefresh │ │ └── ApplicationTest.java │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── race604 │ │ └── flyrefresh │ │ └── sample │ │ ├── ItemData.java │ │ ├── MainActivity.java │ │ └── SampleItemAnimator.java │ └── res │ ├── layout │ ├── activity_main.xml │ └── view_list_item.xml │ ├── menu │ └── menu_main.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ ├── ic_assessment_white_24dp.png │ ├── ic_folder_white_24dp.png │ ├── ic_info_grey600_18dp.png │ ├── ic_launcher.png │ ├── ic_search_white_24dp.png │ └── ic_smartphone_white_24dp.png │ ├── values-w820dp │ └── dimens.xml │ └── values │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── images ├── designed.gif └── flyrefresh.gif ├── library ├── .gitignore ├── build.gradle ├── library.iml ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── race604 │ │ └── flyrefresh │ │ └── ApplicationTest.java │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── race604 │ │ ├── flyrefresh │ │ ├── FlyRefreshLayout.java │ │ ├── HeaderController.java │ │ ├── IPullHeader.java │ │ ├── PullHeaderLayout.java │ │ └── internal │ │ │ ├── ElasticOutInterpolator.java │ │ │ ├── MountainSceneDrawable.java │ │ │ ├── MountanScenceView.java │ │ │ ├── RotatableDrawable.java │ │ │ ├── SimpleAnimatorListener.java │ │ │ └── TreeDrawable.java │ │ └── utils │ │ └── UIUtils.java │ └── res │ ├── mipmap-xxhdpi │ └── ic_send.png │ └── values │ ├── attrs.xml │ ├── colors.xml │ └── strings.xml └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /local.properties 3 | /.idea/workspace.xml 4 | /.idea/libraries 5 | .DS_Store 6 | /build 7 | /captures 8 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | FlyRefresh -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <project version="4"> 3 | <component name="CompilerConfiguration"> 4 | <resourceExtensions /> 5 | <wildcardResourcePatterns> 6 | <entry name="!?*.java" /> 7 | <entry name="!?*.form" /> 8 | <entry name="!?*.class" /> 9 | <entry name="!?*.groovy" /> 10 | <entry name="!?*.scala" /> 11 | <entry name="!?*.flex" /> 12 | <entry name="!?*.kt" /> 13 | <entry name="!?*.clj" /> 14 | <entry name="!?*.aj" /> 15 | </wildcardResourcePatterns> 16 | <annotationProcessing> 17 | <profile default="true" name="Default" enabled="false"> 18 | <processorPath useClasspath="true" /> 19 | </profile> 20 | </annotationProcessing> 21 | </component> 22 | </project> -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | <component name="CopyrightManager"> 2 | <settings default="" /> 3 | </component> -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <project version="4"> 3 | <component name="GradleSettings"> 4 | <option name="linkedExternalProjectsSettings"> 5 | <GradleProjectSettings> 6 | <option name="distributionType" value="DEFAULT_WRAPPED" /> 7 | <option name="externalProjectPath" value="$PROJECT_DIRquot; /> 8 | <option name="gradleJvm" value="1.7" /> 9 | <option name="modules"> 10 | <set> 11 | <option value="$PROJECT_DIRquot; /> 12 | <option value="$PROJECT_DIR$/app" /> 13 | <option value="$PROJECT_DIR$/library" /> 14 | </set> 15 | </option> 16 | </GradleProjectSettings> 17 | </option> 18 | </component> 19 | </project> -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <project version="4"> 3 | <component name="EntryPointsManager"> 4 | <entry_points version="2.0" /> 5 | </component> 6 | <component name="NullableNotNullManager"> 7 | <option name="myDefaultNullable" value="android.support.annotation.Nullable" /> 8 | <option name="myDefaultNotNull" value="android.support.annotation.NonNull" /> 9 | <option name="myNullables"> 10 | <value> 11 | <list size="4"> 12 | <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" /> 13 | <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" /> 14 | <item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" /> 15 | <item index="3" class="java.lang.String" itemvalue="android.support.annotation.Nullable" /> 16 | </list> 17 | </value> 18 | </option> 19 | <option name="myNotNulls"> 20 | <value> 21 | <list size="4"> 22 | <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" /> 23 | <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" /> 24 | <item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" /> 25 | <item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" /> 26 | </list> 27 | </value> 28 | </option> 29 | </component> 30 | <component name="ProjectLevelVcsManager" settingsEditedManually="false"> 31 | <OptionsSetting value="true" id="Add" /> 32 | <OptionsSetting value="true" id="Remove" /> 33 | <OptionsSetting value="true" id="Checkout" /> 34 | <OptionsSetting value="true" id="Update" /> 35 | <OptionsSetting value="true" id="Status" /> 36 | <OptionsSetting value="true" id="Edit" /> 37 | <ConfirmationsSetting value="0" id="Add" /> 38 | <ConfirmationsSetting value="0" id="Remove" /> 39 | </component> 40 | <component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" default="true" assert-keyword="true" jdk-15="true" project-jdk-name="1.7" project-jdk-type="JavaSDK"> 41 | <output url="file://$PROJECT_DIR$/build/classes" /> 42 | </component> 43 | <component name="ProjectType"> 44 | <option name="id" value="Android" /> 45 | </component> 46 | <component name="masterDetails"> 47 | <states> 48 | <state key="ProjectJDKs.UI"> 49 | <settings> 50 | <last-edited>1.7</last-edited> 51 | <splitter-proportions> 52 | <option name="proportions"> 53 | <list> 54 | <option value="0.2" /> 55 | </list> 56 | </option> 57 | </splitter-proportions> 58 | </settings> 59 | </state> 60 | </states> 61 | </component> 62 | </project> -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <project version="4"> 3 | <component name="ProjectModuleManager"> 4 | <modules> 5 | <module fileurl="file://$PROJECT_DIR$/FlyRefresh.iml" filepath="$PROJECT_DIR$/FlyRefresh.iml" /> 6 | <module fileurl="file://$PROJECT_DIR$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" /> 7 | <module fileurl="file://$PROJECT_DIR$/library/library.iml" filepath="$PROJECT_DIR$/library/library.iml" /> 8 | </modules> 9 | </component> 10 | </project> -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <project version="4"> 3 | <component name="RunConfigurationProducerService"> 4 | <option name="ignoredProducers"> 5 | <set> 6 | <option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" /> 7 | <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" /> 8 | <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" /> 9 | </set> 10 | </option> 11 | </component> 12 | </project> -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <project version="4"> 3 | <component name="VcsDirectoryMappings"> 4 | <mapping directory="$PROJECT_DIRquot; vcs="Git" /> 5 | </component> 6 | </project> -------------------------------------------------------------------------------- /FlyRefresh.iml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <module external.linked.project.id="FlyRefresh" external.linked.project.path="$MODULE_DIRquot; external.root.project.path="$MODULE_DIRquot; external.system.id="GRADLE" external.system.module.group="" external.system.module.version="unspecified" type="JAVA_MODULE" version="4"> 3 | <component name="FacetManager"> 4 | <facet type="java-gradle" name="Java-Gradle"> 5 | <configuration> 6 | <option name="BUILD_FOLDER_PATH" value="$MODULE_DIR$/build" /> 7 | <option name="BUILDABLE" value="false" /> 8 | </configuration> 9 | </facet> 10 | </component> 11 | <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7" inherit-compiler-output="true"> 12 | <exclude-output /> 13 | <content url="file://$MODULE_DIRquot;> 14 | <excludeFolder url="file://$MODULE_DIR$/.gradle" /> 15 | </content> 16 | <orderEntry type="inheritedJdk" /> 17 | <orderEntry type="sourceFolder" forTests="false" /> 18 | </component> 19 | </module> -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 race604 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FlyRefresh 2 | The Android implementation of [Replace](https://dribbble.com/shots/2067564-Replace), designed by [Zee Youn](https://dribbble.com/zeeyoung). 3 | I implement this as a **FlyRefresh** layout. The content of the layout can be any `NestedScrollingChild`, such as a RecyclerView, NestedScrollView, VerticalGridView, etc. 4 | This library can also work with `NestedScrollingParent` as parent, such as CoordinatorLayout. 5 | 6 | # How it looks 7 |  8 | 9 | # Features 10 | * Work with all [NestedScrollingParent](https://developer.android.com/reference/android/support/v4/view/NestedScrollingParent.html) and [NestedScrollingChild](https://developer.android.com/reference/android/support/v4/view/NestedScrollingChild.html) 11 | * Default minimize configuration for [Replace](https://dribbble.com/shots/2067564-Replace) animation 12 | * Expendable/Shrinkable header 13 | * Support custom header view 14 | * Support custom refresh animation 15 | 16 | # How to use 17 | 18 | Add Gradle dependency: 19 | 20 | ```gradle 21 | dependencies { 22 | compile 'com.race604.flyrefresh:library:2.0.0' 23 | } 24 | ``` 25 | 26 | An example of basic usage in `layout.xml`: 27 | 28 | ```xml 29 | <com.race604.flyrefresh.FlyRefreshLayout 30 | android:id="@+id/fly_layout" 31 | android:layout_width="match_parent" 32 | android:layout_height="match_parent"> 33 | 34 | <android.support.v7.widget.RecyclerView 35 | android:id="@+id/list" 36 | android:layout_width="match_parent" 37 | android:layout_height="match_parent" 38 | android:paddingTop="24dp" 39 | android:background="#FFFFFF"/> 40 | </com.race604.flyrefresh.FlyRefreshLayout> 41 | ``` 42 | 43 | Or you can use `PullHeaderLayout` for more configurations, you can set custom attributes as shown below: 44 | 45 | ```xml 46 | <declare-styleable name="PullHeaderLayout"> 47 | <!-- hader size --> 48 | <attr name="phl_header_height" format="dimension" /> 49 | <attr name="phl_header_expand_height" format="dimension" /> 50 | <attr name="phl_header_shrink_height" format="dimension" /> 51 | <!-- header view id --> 52 | <attr name="phl_header" format="reference" /> 53 | <!-- content view id --> 54 | <attr name="phl_content" format="reference" /> 55 | <!-- Float action button icon --> 56 | <attr name="phl_action" format="reference" /> 57 | </declare-styleable> 58 | ``` 59 | For more, please turn to the source code. 60 | 61 | # License 62 | `FlyRefresh` is available under the MIT license. 63 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/app.iml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <module external.linked.project.id=":app" external.linked.project.path="$MODULE_DIRquot; external.root.project.path="$MODULE_DIR$/.." external.system.id="GRADLE" external.system.module.group="FlyRefresh" external.system.module.version="unspecified" type="JAVA_MODULE" version="4"> 3 | <component name="FacetManager"> 4 | <facet type="android-gradle" name="Android-Gradle"> 5 | <configuration> 6 | <option name="GRADLE_PROJECT_PATH" value=":app" /> 7 | </configuration> 8 | </facet> 9 | <facet type="android" name="Android"> 10 | <configuration> 11 | <option name="SELECTED_BUILD_VARIANT" value="debug" /> 12 | <option name="SELECTED_TEST_ARTIFACT" value="_android_test_" /> 13 | <option name="ASSEMBLE_TASK_NAME" value="assembleDebug" /> 14 | <option name="COMPILE_JAVA_TASK_NAME" value="compileDebugSources" /> 15 | <option name="ASSEMBLE_TEST_TASK_NAME" value="assembleDebugAndroidTest" /> 16 | <option name="COMPILE_JAVA_TEST_TASK_NAME" value="compileDebugAndroidTestSources" /> 17 | <afterSyncTasks> 18 | <task>generateDebugAndroidTestSources</task> 19 | <task>generateDebugSources</task> 20 | </afterSyncTasks> 21 | <option name="ALLOW_USER_CONFIGURATION" value="false" /> 22 | <option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" /> 23 | <option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" /> 24 | <option name="RES_FOLDERS_RELATIVE_PATH" value="file://$MODULE_DIR$/src/main/res" /> 25 | <option name="ASSETS_FOLDER_RELATIVE_PATH" value="/src/main/assets" /> 26 | </configuration> 27 | </facet> 28 | </component> 29 | <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7" inherit-compiler-output="false"> 30 | <output url="file://$MODULE_DIR$/build/intermediates/classes/debug" /> 31 | <output-test url="file://$MODULE_DIR$/build/intermediates/classes/androidTest/debug" /> 32 | <exclude-output /> 33 | <content url="file://$MODULE_DIRquot;> 34 | <sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/debug" isTestSource="false" generated="true" /> 35 | <sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/debug" isTestSource="false" generated="true" /> 36 | <sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/debug" isTestSource="false" generated="true" /> 37 | <sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/debug" isTestSource="false" generated="true" /> 38 | <sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/debug" type="java-resource" /> 39 | <sourceFolder url="file://$MODULE_DIR$/build/generated/res/generated/debug" type="java-resource" /> 40 | <sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/androidTest/debug" isTestSource="true" generated="true" /> 41 | <sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/androidTest/debug" isTestSource="true" generated="true" /> 42 | <sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/androidTest/debug" isTestSource="true" generated="true" /> 43 | <sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/androidTest/debug" isTestSource="true" generated="true" /> 44 | <sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/androidTest/debug" type="java-test-resource" /> 45 | <sourceFolder url="file://$MODULE_DIR$/build/generated/res/generated/androidTest/debug" type="java-test-resource" /> 46 | <sourceFolder url="file://$MODULE_DIR$/src/debug/res" type="java-resource" /> 47 | <sourceFolder url="file://$MODULE_DIR$/src/debug/resources" type="java-resource" /> 48 | <sourceFolder url="file://$MODULE_DIR$/src/debug/assets" type="java-resource" /> 49 | <sourceFolder url="file://$MODULE_DIR$/src/debug/aidl" isTestSource="false" /> 50 | <sourceFolder url="file://$MODULE_DIR$/src/debug/java" isTestSource="false" /> 51 | <sourceFolder url="file://$MODULE_DIR$/src/debug/jni" isTestSource="false" /> 52 | <sourceFolder url="file://$MODULE_DIR$/src/debug/rs" isTestSource="false" /> 53 | <sourceFolder url="file://$MODULE_DIR$/src/main/res" type="java-resource" /> 54 | <sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" /> 55 | <sourceFolder url="file://$MODULE_DIR$/src/main/assets" type="java-resource" /> 56 | <sourceFolder url="file://$MODULE_DIR$/src/main/aidl" isTestSource="false" /> 57 | <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" /> 58 | <sourceFolder url="file://$MODULE_DIR$/src/main/jni" isTestSource="false" /> 59 | <sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" /> 60 | <sourceFolder url="file://$MODULE_DIR$/src/androidTest/res" type="java-test-resource" /> 61 | <sourceFolder url="file://$MODULE_DIR$/src/androidTest/resources" type="java-test-resource" /> 62 | <sourceFolder url="file://$MODULE_DIR$/src/androidTest/assets" type="java-test-resource" /> 63 | <sourceFolder url="file://$MODULE_DIR$/src/androidTest/aidl" isTestSource="true" /> 64 | <sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" /> 65 | <sourceFolder url="file://$MODULE_DIR$/src/androidTest/jni" isTestSource="true" /> 66 | <sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" /> 67 | <excludeFolder url="file://$MODULE_DIR$/build/intermediates/assets" /> 68 | <excludeFolder url="file://$MODULE_DIR$/build/intermediates/bundles" /> 69 | <excludeFolder url="file://$MODULE_DIR$/build/intermediates/classes" /> 70 | <excludeFolder url="file://$MODULE_DIR$/build/intermediates/coverage-instrumented-classes" /> 71 | <excludeFolder url="file://$MODULE_DIR$/build/intermediates/dependency-cache" /> 72 | <excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex" /> 73 | <excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex-cache" /> 74 | <excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/appcompat-v7/23.0.0/jars" /> 75 | <excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/recyclerview-v7/23.0.0/jars" /> 76 | <excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/support-v4/23.0.0/jars" /> 77 | <excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/jp.wasabeef/recyclerview-animators/1.2.0/jars" /> 78 | <excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" /> 79 | <excludeFolder url="file://$MODULE_DIR$/build/intermediates/jacoco" /> 80 | <excludeFolder url="file://$MODULE_DIR$/build/intermediates/javaResources" /> 81 | <excludeFolder url="file://$MODULE_DIR$/build/intermediates/libs" /> 82 | <excludeFolder url="file://$MODULE_DIR$/build/intermediates/lint" /> 83 | <excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifests" /> 84 | <excludeFolder url="file://$MODULE_DIR$/build/intermediates/ndk" /> 85 | <excludeFolder url="file://$MODULE_DIR$/build/intermediates/pre-dexed" /> 86 | <excludeFolder url="file://$MODULE_DIR$/build/intermediates/proguard" /> 87 | <excludeFolder url="file://$MODULE_DIR$/build/intermediates/res" /> 88 | <excludeFolder url="file://$MODULE_DIR$/build/intermediates/rs" /> 89 | <excludeFolder url="file://$MODULE_DIR$/build/intermediates/symbols" /> 90 | <excludeFolder url="file://$MODULE_DIR$/build/outputs" /> 91 | <excludeFolder url="file://$MODULE_DIR$/build/tmp" /> 92 | </content> 93 | <orderEntry type="jdk" jdkName="Android API 23 Platform" jdkType="Android SDK" /> 94 | <orderEntry type="sourceFolder" forTests="false" /> 95 | <orderEntry type="library" exported="" name="appcompat-v7-23.0.0" level="project" /> 96 | <orderEntry type="library" exported="" name="recyclerview-v7-23.0.0" level="project" /> 97 | <orderEntry type="library" exported="" name="recyclerview-animators-1.2.0" level="project" /> 98 | <orderEntry type="library" exported="" name="support-v4-23.0.0" level="project" /> 99 | <orderEntry type="library" exported="" name="support-annotations-23.0.0" level="project" /> 100 | <orderEntry type="module" module-name="library" exported="" /> 101 | </component> 102 | </module> -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "22.0.1" 6 | 7 | defaultConfig { 8 | applicationId "com.race604.flyrefresh" 9 | minSdkVersion 11 10 | targetSdkVersion 23 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(include: ['*.jar'], dir: 'libs') 24 | compile 'com.android.support:appcompat-v7:23.0.0' 25 | compile project(':library') 26 | compile 'com.android.support:recyclerview-v7:23.0.0' 27 | compile 'jp.wasabeef:recyclerview-animators:1.2.0@aar' 28 | } 29 | -------------------------------------------------------------------------------- /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 /home/jing/Tools/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/androidTest/java/com/race604/flyrefresh/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.race604.flyrefresh; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a> 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase<Application> { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <manifest xmlns:android="http://schemas.android.com/apk/res/android" 3 | package="com.race604.flyrefresh.sample" > 4 | 5 | <application 6 | android:allowBackup="true" 7 | android:icon="@mipmap/ic_launcher" 8 | android:label="@string/app_name" 9 | android:theme="@style/AppTheme" > 10 | <activity 11 | android:name=".MainActivity" 12 | android:label="@string/app_name" > 13 | <intent-filter> 14 | <action android:name="android.intent.action.MAIN" /> 15 | 16 | <category android:name="android.intent.category.LAUNCHER" /> 17 | </intent-filter> 18 | </activity> 19 | </application> 20 | 21 | </manifest> 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/race604/flyrefresh/sample/ItemData.java: -------------------------------------------------------------------------------- 1 | package com.race604.flyrefresh.sample; 2 | 3 | import android.graphics.Color; 4 | 5 | import java.util.Date; 6 | 7 | /** 8 | * Created by Jing on 15/5/27. 9 | */ 10 | public class ItemData { 11 | int color; 12 | public int icon; 13 | public String title; 14 | public Date time; 15 | 16 | public ItemData(int color, int icon, String title, Date time) { 17 | this.color = color; 18 | this.icon = icon; 19 | this.title = title; 20 | this.time = time; 21 | } 22 | 23 | public ItemData(int icon, String title) { 24 | this(Color.DKGRAY, icon, title, new Date()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/race604/flyrefresh/sample/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.race604.flyrefresh.sample; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorSet; 5 | import android.animation.ObjectAnimator; 6 | import android.content.Context; 7 | import android.graphics.Color; 8 | import android.graphics.drawable.ShapeDrawable; 9 | import android.graphics.drawable.shapes.OvalShape; 10 | import android.os.Bundle; 11 | import android.os.Handler; 12 | import android.support.v7.app.AppCompatActivity; 13 | import android.support.v7.widget.LinearLayoutManager; 14 | import android.support.v7.widget.RecyclerView; 15 | import android.support.v7.widget.Toolbar; 16 | import android.view.LayoutInflater; 17 | import android.view.Menu; 18 | import android.view.MenuItem; 19 | import android.view.View; 20 | import android.view.ViewGroup; 21 | import android.view.animation.AccelerateInterpolator; 22 | import android.widget.ImageView; 23 | import android.widget.TextView; 24 | 25 | import com.race604.flyrefresh.FlyRefreshLayout; 26 | 27 | import java.text.DateFormat; 28 | import java.text.SimpleDateFormat; 29 | import java.util.ArrayList; 30 | import java.util.Date; 31 | import java.util.Locale; 32 | 33 | public class MainActivity extends AppCompatActivity implements FlyRefreshLayout.OnPullRefreshListener { 34 | 35 | private FlyRefreshLayout mFlylayout; 36 | private RecyclerView mListView; 37 | 38 | private ItemAdapter mAdapter; 39 | 40 | private ArrayList<ItemData> mDataSet = new ArrayList<>(); 41 | private Handler mHandler = new Handler(); 42 | private LinearLayoutManager mLayoutManager; 43 | 44 | @Override 45 | protected void onCreate(Bundle savedInstanceState) { 46 | super.onCreate(savedInstanceState); 47 | initDataSet(); 48 | setContentView(R.layout.activity_main); 49 | 50 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 51 | setSupportActionBar(toolbar); 52 | getSupportActionBar().setDisplayShowTitleEnabled(false); 53 | 54 | mFlylayout = (FlyRefreshLayout) findViewById(R.id.fly_layout); 55 | 56 | mFlylayout.setOnPullRefreshListener(this); 57 | 58 | mListView = (RecyclerView) findViewById(R.id.list); 59 | 60 | mLayoutManager = new LinearLayoutManager(this); 61 | mListView.setLayoutManager(mLayoutManager); 62 | mAdapter = new ItemAdapter(this); 63 | 64 | mListView.setAdapter(mAdapter); 65 | 66 | mListView.setItemAnimator(new SampleItemAnimator()); 67 | 68 | View actionButton = mFlylayout.getHeaderActionButton(); 69 | if (actionButton != null) { 70 | actionButton.setOnClickListener(new View.OnClickListener() { 71 | @Override 72 | public void onClick(View v) { 73 | mFlylayout.startRefresh(); 74 | } 75 | }); 76 | } 77 | } 78 | 79 | private void initDataSet() { 80 | mDataSet.add(new ItemData(Color.parseColor("#76A9FC"), R.mipmap.ic_assessment_white_24dp, "Meeting Minutes", new Date(2014 - 1900, 2, 9))); 81 | mDataSet.add(new ItemData(Color.GRAY, R.mipmap.ic_folder_white_24dp, "Favorites Photos", new Date(2014 - 1900, 1, 3))); 82 | mDataSet.add(new ItemData(Color.GRAY, R.mipmap.ic_folder_white_24dp, "Photos", new Date(2014 - 1900, 0, 9))); 83 | } 84 | 85 | private void addItemData() { 86 | ItemData itemData = new ItemData(Color.parseColor("#FFC970"), R.mipmap.ic_smartphone_white_24dp, "Magic Cube Show", new Date()); 87 | mDataSet.add(0, itemData); 88 | mAdapter.notifyItemInserted(0); 89 | mLayoutManager.scrollToPosition(0); 90 | } 91 | 92 | @Override 93 | public boolean onCreateOptionsMenu(Menu menu) { 94 | getMenuInflater().inflate(R.menu.menu_main, menu); 95 | return true; 96 | } 97 | 98 | @Override 99 | public boolean onOptionsItemSelected(MenuItem item) { 100 | int id = item.getItemId(); 101 | 102 | if (id == R.id.action_settings) { 103 | return true; 104 | } 105 | 106 | return super.onOptionsItemSelected(item); 107 | } 108 | 109 | @Override 110 | public void onRefresh(FlyRefreshLayout view) { 111 | View child = mListView.getChildAt(0); 112 | if (child != null) { 113 | bounceAnimateView(child.findViewById(R.id.icon)); 114 | } 115 | 116 | mHandler.postDelayed(new Runnable() { 117 | @Override 118 | public void run() { 119 | mFlylayout.onRefreshFinish(); 120 | } 121 | }, 2000); 122 | } 123 | 124 | private void bounceAnimateView(View view) { 125 | if (view == null) { 126 | return; 127 | } 128 | 129 | Animator swing = ObjectAnimator.ofFloat(view, "rotationX", 0, 30, -20, 0); 130 | swing.setDuration(400); 131 | swing.setInterpolator(new AccelerateInterpolator()); 132 | swing.start(); 133 | } 134 | 135 | @Override 136 | public void onRefreshAnimationEnd(FlyRefreshLayout view) { 137 | addItemData(); 138 | } 139 | 140 | private class ItemAdapter extends RecyclerView.Adapter<ItemViewHolder> { 141 | 142 | private LayoutInflater mInflater; 143 | private DateFormat dateFormat; 144 | 145 | public ItemAdapter(Context context) { 146 | mInflater = LayoutInflater.from(context); 147 | dateFormat = SimpleDateFormat.getDateInstance(DateFormat.DEFAULT, Locale.ENGLISH); 148 | } 149 | 150 | @Override 151 | public ItemViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) { 152 | View view = mInflater.inflate(R.layout.view_list_item, viewGroup, false); 153 | return new ItemViewHolder(view); 154 | } 155 | 156 | @Override 157 | public void onBindViewHolder(ItemViewHolder itemViewHolder, int i) { 158 | final ItemData data = mDataSet.get(i); 159 | ShapeDrawable drawable = new ShapeDrawable(new OvalShape()); 160 | drawable.getPaint().setColor(data.color); 161 | itemViewHolder.icon.setBackgroundDrawable(drawable); 162 | itemViewHolder.icon.setImageResource(data.icon); 163 | itemViewHolder.title.setText(data.title); 164 | itemViewHolder.subTitle.setText(dateFormat.format(data.time)); 165 | } 166 | 167 | @Override 168 | public int getItemCount() { 169 | return mDataSet.size(); 170 | } 171 | } 172 | 173 | private static class ItemViewHolder extends RecyclerView.ViewHolder { 174 | 175 | ImageView icon; 176 | TextView title; 177 | TextView subTitle; 178 | 179 | public ItemViewHolder(View itemView) { 180 | super(itemView); 181 | icon = (ImageView) itemView.findViewById(R.id.icon); 182 | title = (TextView) itemView.findViewById(R.id.title); 183 | subTitle = (TextView) itemView.findViewById(R.id.subtitle); 184 | } 185 | 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /app/src/main/java/com/race604/flyrefresh/sample/SampleItemAnimator.java: -------------------------------------------------------------------------------- 1 | package com.race604.flyrefresh.sample; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorSet; 5 | import android.animation.ObjectAnimator; 6 | import android.support.v4.view.ViewCompat; 7 | import android.support.v7.widget.RecyclerView; 8 | import android.view.View; 9 | import android.view.animation.DecelerateInterpolator; 10 | import android.view.animation.OvershootInterpolator; 11 | 12 | import jp.wasabeef.recyclerview.animators.BaseItemAnimator; 13 | 14 | /** 15 | * Created by jing on 15-5-28. 16 | */ 17 | public class SampleItemAnimator extends BaseItemAnimator { 18 | 19 | @Override 20 | protected void preAnimateAddImpl(RecyclerView.ViewHolder holder) { 21 | View icon = holder.itemView.findViewById(R.id.icon); 22 | icon.setRotationX(30); 23 | View right = holder.itemView.findViewById(R.id.right); 24 | right.setPivotX(0); 25 | right.setPivotY(0); 26 | right.setRotationY(90); 27 | } 28 | 29 | @Override 30 | protected void animateRemoveImpl(RecyclerView.ViewHolder viewHolder) { 31 | } 32 | 33 | @Override 34 | protected void animateAddImpl(final RecyclerView.ViewHolder holder) { 35 | View target = holder.itemView; 36 | View icon = target.findViewById(R.id.icon); 37 | Animator swing = ObjectAnimator.ofFloat(icon, "rotationX", 45, 0); 38 | swing.setInterpolator(new OvershootInterpolator(5)); 39 | 40 | View right = holder.itemView.findViewById(R.id.right); 41 | Animator rotateIn = ObjectAnimator.ofFloat(right, "rotationY", 90, 0); 42 | rotateIn.setInterpolator(new DecelerateInterpolator()); 43 | 44 | AnimatorSet animator = new AnimatorSet(); 45 | animator.setDuration(getAddDuration()); 46 | animator.playTogether(swing, rotateIn); 47 | 48 | animator.start(); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 | xmlns:app="http://schemas.android.com/apk/res-auto" 3 | xmlns:tools="http://schemas.android.com/tools" 4 | android:layout_width="match_parent" 5 | android:layout_height="match_parent"> 6 | <com.race604.flyrefresh.FlyRefreshLayout 7 | android:id="@+id/fly_layout" 8 | android:layout_width="match_parent" 9 | android:layout_height="match_parent" 10 | tools:context=".MainActivity"> 11 | 12 | <android.support.v7.widget.RecyclerView 13 | android:id="@+id/list" 14 | android:layout_width="match_parent" 15 | android:layout_height="match_parent" 16 | android:paddingTop="24dp" 17 | android:background="#FFFFFF"/> 18 | </com.race604.flyrefresh.FlyRefreshLayout> 19 | 20 | <android.support.v7.widget.Toolbar 21 | android:id="@+id/toolbar" 22 | android:layout_width="match_parent" 23 | android:layout_height="?attr/actionBarSize" 24 | android:background="@android:color/transparent" 25 | android:elevation="4dp" 26 | android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" 27 | app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/> 28 | </RelativeLayout> -------------------------------------------------------------------------------- /app/src/main/res/layout/view_list_item.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 | android:layout_width="match_parent" 4 | android:layout_height="wrap_content" 5 | android:gravity="center_vertical" 6 | android:orientation="horizontal" 7 | android:paddingBottom="4dp" 8 | android:paddingLeft="16dp" 9 | android:paddingRight="16dp" 10 | android:paddingTop="4dp"> 11 | 12 | <ImageView 13 | android:id="@+id/icon" 14 | android:layout_width="40dp" 15 | android:layout_height="40dp" 16 | android:layout_margin="4dp" 17 | android:scaleType="centerInside" /> 18 | 19 | <RelativeLayout 20 | android:id="@+id/right" 21 | android:layout_width="0dp" 22 | android:layout_height="wrap_content" 23 | android:layout_margin="4dp" 24 | android:layout_weight="1" 25 | android:orientation="vertical"> 26 | 27 | <TextView 28 | android:id="@+id/title" 29 | android:layout_width="wrap_content" 30 | android:layout_height="wrap_content" 31 | android:textAppearance="?attr/titleTextAppearance" 32 | android:textSize="18sp" /> 33 | 34 | <TextView 35 | android:id="@+id/subtitle" 36 | android:layout_width="wrap_content" 37 | android:layout_height="wrap_content" 38 | android:layout_below="@id/title" 39 | android:layout_marginTop="4dp" 40 | android:textColor="#888888" 41 | android:textSize="16sp" /> 42 | 43 | <ImageView 44 | android:layout_width="30dp" 45 | android:layout_height="30dp" 46 | android:layout_alignParentRight="true" 47 | android:layout_centerVertical="true" 48 | android:scaleType="centerInside" 49 | android:src="@mipmap/ic_info_grey600_18dp" /> 50 | </RelativeLayout> 51 | 52 | 53 | </LinearLayout> -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_main.xml: -------------------------------------------------------------------------------- 1 | <menu xmlns:android="http://schemas.android.com/apk/res/android" 2 | xmlns:app="http://schemas.android.com/apk/res-auto" 3 | xmlns:tools="http://schemas.android.com/tools" tools:context=".MainActivity"> 4 | <item android:id="@+id/action_search" 5 | android:icon="@mipmap/ic_search_white_24dp" android:title="@string/action_search" 6 | android:orderInCategory="100" app:showAsAction="always" /> 7 | <item android:id="@+id/action_settings" android:title="@string/action_settings" 8 | android:orderInCategory="100" app:showAsAction="never" /> 9 | </menu> 10 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/race604/FlyRefresh/b3049ab0ec2053b23c6a921ddd23bb6e40820ce9/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/race604/FlyRefresh/b3049ab0ec2053b23c6a921ddd23bb6e40820ce9/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/race604/FlyRefresh/b3049ab0ec2053b23c6a921ddd23bb6e40820ce9/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_assessment_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/race604/FlyRefresh/b3049ab0ec2053b23c6a921ddd23bb6e40820ce9/app/src/main/res/mipmap-xxhdpi/ic_assessment_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_folder_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/race604/FlyRefresh/b3049ab0ec2053b23c6a921ddd23bb6e40820ce9/app/src/main/res/mipmap-xxhdpi/ic_folder_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_info_grey600_18dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/race604/FlyRefresh/b3049ab0ec2053b23c6a921ddd23bb6e40820ce9/app/src/main/res/mipmap-xxhdpi/ic_info_grey600_18dp.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/race604/FlyRefresh/b3049ab0ec2053b23c6a921ddd23bb6e40820ce9/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_search_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/race604/FlyRefresh/b3049ab0ec2053b23c6a921ddd23bb6e40820ce9/app/src/main/res/mipmap-xxhdpi/ic_search_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_smartphone_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/race604/FlyRefresh/b3049ab0ec2053b23c6a921ddd23bb6e40820ce9/app/src/main/res/mipmap-xxhdpi/ic_smartphone_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | <resources> 2 | <!-- Example customization of dimensions originally defined in res/values/dimens.xml 3 | (such as screen margins) for screens with more than 820dp of available width. This 4 | would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). --> 5 | <dimen name="activity_horizontal_margin">64dp</dimen> 6 | </resources> 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | <resources> 2 | <!-- Default screen margins, per the Android Design guidelines. --> 3 | <dimen name="activity_horizontal_margin">16dp</dimen> 4 | <dimen name="activity_vertical_margin">16dp</dimen> 5 | </resources> 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | <resources> 2 | <string name="app_name">FlyRefresh</string> 3 | 4 | <string name="hello_world">Hello world!</string> 5 | <string name="action_settings">Settings</string> 6 | <string name="action_search">Search</string> 7 | </resources> 8 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | <resources> 2 | 3 | <!-- Base application theme. --> 4 | <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar"> 5 | <!-- Customize your theme here. --> 6 | <item name="colorPrimary">#00BCD4</item> 7 | <item name="colorPrimaryDark">#0097A7</item> 8 | <item name="colorAccent">#61B3DD</item> 9 | </style> 10 | 11 | </resources> 12 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:1.2.3' 9 | classpath 'com.github.dcendents:android-maven-plugin:1.2' 10 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.0' 11 | 12 | // NOTE: Do not place your application dependencies here; they belong 13 | // in the individual module build.gradle files 14 | } 15 | } 16 | 17 | allprojects { 18 | repositories { 19 | jcenter() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/race604/FlyRefresh/b3049ab0ec2053b23c6a921ddd23bb6e40820ce9/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Apr 10 15:27:10 PDT 2013 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)#39;` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /images/designed.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/race604/FlyRefresh/b3049ab0ec2053b23c6a921ddd23bb6e40820ce9/images/designed.gif -------------------------------------------------------------------------------- /images/flyrefresh.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/race604/FlyRefresh/b3049ab0ec2053b23c6a921ddd23bb6e40820ce9/images/flyrefresh.gif -------------------------------------------------------------------------------- /library/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /library/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'com.github.dcendents.android-maven' 3 | apply plugin: 'com.jfrog.bintray' 4 | 5 | version = "2.0.0" 6 | 7 | android { 8 | compileSdkVersion 23 9 | buildToolsVersion "22.0.1" 10 | resourcePrefix "flyrefresh__" 11 | 12 | defaultConfig { 13 | minSdkVersion 11 14 | targetSdkVersion 23 15 | versionCode 4 16 | versionName version 17 | } 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | } 25 | 26 | dependencies { 27 | compile fileTree(dir: 'libs', include: ['*.jar']) 28 | compile 'com.android.support:appcompat-v7:23.0.0' 29 | compile 'com.android.support:design:23.0.0' 30 | } 31 | 32 | def siteUrl = 'https://github.com/race604/FlyRefresh' 33 | def gitUrl = 'https://github.com/race604/FlyRefresh.git' 34 | group = "com.race604.flyrefresh" 35 | 36 | install { 37 | repositories.mavenInstaller { 38 | // This generates POM.xml with proper parameters 39 | pom { 40 | project { 41 | packaging 'aar' 42 | // Add your description here 43 | name 'A awsome pull to refresh widget' 44 | url siteUrl 45 | // Set your license 46 | licenses { 47 | license { 48 | name 'The MIT License (MIT)' 49 | url 'http://opensource.org/licenses/MIT' 50 | } 51 | } 52 | developers { 53 | developer { 54 | id 'race604' 55 | name 'Race604' 56 | email 'race604@gmail.com' 57 | } 58 | } 59 | scm { 60 | connection gitUrl 61 | developerConnection gitUrl 62 | url siteUrl 63 | } 64 | } 65 | } 66 | } 67 | } 68 | task sourcesJar(type: Jar) { 69 | from android.sourceSets.main.java.srcDirs 70 | classifier = 'sources' 71 | } 72 | task javadoc(type: Javadoc) { 73 | source = android.sourceSets.main.java.srcDirs 74 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) 75 | } 76 | task javadocJar(type: Jar, dependsOn: javadoc) { 77 | classifier = 'javadoc' 78 | from javadoc.destinationDir 79 | } 80 | artifacts { 81 | archives javadocJar 82 | archives sourcesJar 83 | } 84 | 85 | Properties properties = new Properties() 86 | properties.load(project.rootProject.file('local.properties').newDataInputStream()) 87 | bintray { 88 | user = properties.getProperty("bintray.user") 89 | key = properties.getProperty("bintray.apikey") 90 | configurations = ['archives'] 91 | pkg { 92 | repo = "maven" 93 | name = "FlyRefresh" 94 | websiteUrl = siteUrl 95 | vcsUrl = gitUrl 96 | licenses = ["MIT"] 97 | publish = true 98 | } 99 | } -------------------------------------------------------------------------------- /library/library.iml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <module external.linked.project.id=":library" external.linked.project.path="$MODULE_DIRquot; external.root.project.path="$MODULE_DIR$/.." external.system.id="GRADLE" external.system.module.group="com.race604.flyrefresh" external.system.module.version="2.0.0" type="JAVA_MODULE" version="4"> 3 | <component name="FacetManager"> 4 | <facet type="android-gradle" name="Android-Gradle"> 5 | <configuration> 6 | <option name="GRADLE_PROJECT_PATH" value=":library" /> 7 | </configuration> 8 | </facet> 9 | <facet type="android" name="Android"> 10 | <configuration> 11 | <option name="SELECTED_BUILD_VARIANT" value="debug" /> 12 | <option name="SELECTED_TEST_ARTIFACT" value="_android_test_" /> 13 | <option name="ASSEMBLE_TASK_NAME" value="assembleDebug" /> 14 | <option name="COMPILE_JAVA_TASK_NAME" value="compileDebugSources" /> 15 | <option name="ASSEMBLE_TEST_TASK_NAME" value="assembleDebugAndroidTest" /> 16 | <option name="COMPILE_JAVA_TEST_TASK_NAME" value="compileDebugAndroidTestSources" /> 17 | <afterSyncTasks> 18 | <task>generateDebugAndroidTestSources</task> 19 | <task>generateDebugSources</task> 20 | </afterSyncTasks> 21 | <option name="ALLOW_USER_CONFIGURATION" value="false" /> 22 | <option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" /> 23 | <option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" /> 24 | <option name="RES_FOLDERS_RELATIVE_PATH" value="file://$MODULE_DIR$/src/main/res" /> 25 | <option name="ASSETS_FOLDER_RELATIVE_PATH" value="/src/main/assets" /> 26 | <option name="LIBRARY_PROJECT" value="true" /> 27 | </configuration> 28 | </facet> 29 | </component> 30 | <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7" inherit-compiler-output="false"> 31 | <output url="file://$MODULE_DIR$/build/intermediates/classes/debug" /> 32 | <output-test url="file://$MODULE_DIR$/build/intermediates/classes/androidTest/debug" /> 33 | <exclude-output /> 34 | <content url="file://$MODULE_DIRquot;> 35 | <sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/debug" isTestSource="false" generated="true" /> 36 | <sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/debug" isTestSource="false" generated="true" /> 37 | <sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/debug" isTestSource="false" generated="true" /> 38 | <sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/debug" isTestSource="false" generated="true" /> 39 | <sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/debug" type="java-resource" /> 40 | <sourceFolder url="file://$MODULE_DIR$/build/generated/res/generated/debug" type="java-resource" /> 41 | <sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/androidTest/debug" isTestSource="true" generated="true" /> 42 | <sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/androidTest/debug" isTestSource="true" generated="true" /> 43 | <sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/androidTest/debug" isTestSource="true" generated="true" /> 44 | <sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/androidTest/debug" isTestSource="true" generated="true" /> 45 | <sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/androidTest/debug" type="java-test-resource" /> 46 | <sourceFolder url="file://$MODULE_DIR$/build/generated/res/generated/androidTest/debug" type="java-test-resource" /> 47 | <sourceFolder url="file://$MODULE_DIR$/src/debug/res" type="java-resource" /> 48 | <sourceFolder url="file://$MODULE_DIR$/src/debug/resources" type="java-resource" /> 49 | <sourceFolder url="file://$MODULE_DIR$/src/debug/assets" type="java-resource" /> 50 | <sourceFolder url="file://$MODULE_DIR$/src/debug/aidl" isTestSource="false" /> 51 | <sourceFolder url="file://$MODULE_DIR$/src/debug/java" isTestSource="false" /> 52 | <sourceFolder url="file://$MODULE_DIR$/src/debug/jni" isTestSource="false" /> 53 | <sourceFolder url="file://$MODULE_DIR$/src/debug/rs" isTestSource="false" /> 54 | <sourceFolder url="file://$MODULE_DIR$/src/main/res" type="java-resource" /> 55 | <sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" /> 56 | <sourceFolder url="file://$MODULE_DIR$/src/main/assets" type="java-resource" /> 57 | <sourceFolder url="file://$MODULE_DIR$/src/main/aidl" isTestSource="false" /> 58 | <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" /> 59 | <sourceFolder url="file://$MODULE_DIR$/src/main/jni" isTestSource="false" /> 60 | <sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" /> 61 | <sourceFolder url="file://$MODULE_DIR$/src/androidTest/res" type="java-test-resource" /> 62 | <sourceFolder url="file://$MODULE_DIR$/src/androidTest/resources" type="java-test-resource" /> 63 | <sourceFolder url="file://$MODULE_DIR$/src/androidTest/assets" type="java-test-resource" /> 64 | <sourceFolder url="file://$MODULE_DIR$/src/androidTest/aidl" isTestSource="true" /> 65 | <sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" /> 66 | <sourceFolder url="file://$MODULE_DIR$/src/androidTest/jni" isTestSource="true" /> 67 | <sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" /> 68 | <excludeFolder url="file://$MODULE_DIR$/build/intermediates/assets" /> 69 | <excludeFolder url="file://$MODULE_DIR$/build/intermediates/bundles" /> 70 | <excludeFolder url="file://$MODULE_DIR$/build/intermediates/classes" /> 71 | <excludeFolder url="file://$MODULE_DIR$/build/intermediates/coverage-instrumented-classes" /> 72 | <excludeFolder url="file://$MODULE_DIR$/build/intermediates/dependency-cache" /> 73 | <excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex" /> 74 | <excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex-cache" /> 75 | <excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" /> 76 | <excludeFolder url="file://$MODULE_DIR$/build/intermediates/jacoco" /> 77 | <excludeFolder url="file://$MODULE_DIR$/build/intermediates/javaResources" /> 78 | <excludeFolder url="file://$MODULE_DIR$/build/intermediates/libs" /> 79 | <excludeFolder url="file://$MODULE_DIR$/build/intermediates/lint" /> 80 | <excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifests" /> 81 | <excludeFolder url="file://$MODULE_DIR$/build/intermediates/ndk" /> 82 | <excludeFolder url="file://$MODULE_DIR$/build/intermediates/pre-dexed" /> 83 | <excludeFolder url="file://$MODULE_DIR$/build/intermediates/proguard" /> 84 | <excludeFolder url="file://$MODULE_DIR$/build/intermediates/res" /> 85 | <excludeFolder url="file://$MODULE_DIR$/build/intermediates/rs" /> 86 | <excludeFolder url="file://$MODULE_DIR$/build/intermediates/symbols" /> 87 | <excludeFolder url="file://$MODULE_DIR$/build/outputs" /> 88 | <excludeFolder url="file://$MODULE_DIR$/build/tmp" /> 89 | </content> 90 | <orderEntry type="jdk" jdkName="Android API 23 Platform" jdkType="Android SDK" /> 91 | <orderEntry type="sourceFolder" forTests="false" /> 92 | <orderEntry type="library" exported="" name="appcompat-v7-23.0.0" level="project" /> 93 | <orderEntry type="library" exported="" name="support-v4-23.0.0" level="project" /> 94 | <orderEntry type="library" exported="" name="design-23.0.0" level="project" /> 95 | <orderEntry type="library" exported="" name="support-annotations-23.0.0" level="project" /> 96 | </component> 97 | </module> -------------------------------------------------------------------------------- /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 /home/jing/Tools/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 | -------------------------------------------------------------------------------- /library/src/androidTest/java/com/race604/flyrefresh/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.race604.flyrefresh; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a> 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase<Application> { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | <manifest xmlns:android="http://schemas.android.com/apk/res/android" 2 | package="com.race604.flyrefresh"> 3 | 4 | <application android:allowBackup="true" android:label="@string/app_name"> 5 | 6 | </application> 7 | 8 | </manifest> 9 | -------------------------------------------------------------------------------- /library/src/main/java/com/race604/flyrefresh/FlyRefreshLayout.java: -------------------------------------------------------------------------------- 1 | package com.race604.flyrefresh; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorSet; 5 | import android.animation.ObjectAnimator; 6 | import android.content.Context; 7 | import android.support.v4.view.animation.PathInterpolatorCompat; 8 | import android.util.AttributeSet; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | import android.view.animation.AccelerateInterpolator; 12 | import android.view.animation.DecelerateInterpolator; 13 | 14 | import com.race604.flyrefresh.internal.MountanScenceView; 15 | import com.race604.flyrefresh.internal.SimpleAnimatorListener; 16 | import com.race604.utils.UIUtils; 17 | 18 | /** 19 | * Created by jing on 15-5-27. 20 | */ 21 | public class FlyRefreshLayout extends PullHeaderLayout { 22 | 23 | private AnimatorSet mFlyAnimator = null; 24 | private OnPullRefreshListener mListener; 25 | 26 | public FlyRefreshLayout(Context context) { 27 | super(context); 28 | init(context); 29 | } 30 | 31 | public FlyRefreshLayout(Context context, AttributeSet attrs) { 32 | super(context, attrs); 33 | init(context); 34 | } 35 | 36 | public FlyRefreshLayout(Context context, AttributeSet attrs, int defStyleAttr) { 37 | super(context, attrs, defStyleAttr); 38 | init(context); 39 | } 40 | 41 | private void init(Context context) { 42 | MountanScenceView headerView = new MountanScenceView(getContext()); 43 | LayoutParams lp = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, mHeaderController.getMaxHeight()); 44 | setHeaderView(headerView, lp); 45 | } 46 | 47 | @Override 48 | protected void onFinishInflate() { 49 | super.onFinishInflate(); 50 | 51 | // Set default action icon if user not override 52 | if (getIconView() == null) { 53 | setActionDrawable(getResources().getDrawable(R.mipmap.ic_send)); 54 | } 55 | } 56 | 57 | @Override 58 | public void startRefresh() { 59 | 60 | if (mFlyAnimator != null) { 61 | mFlyAnimator.end(); 62 | } 63 | 64 | final View iconView = getIconView(); 65 | UIUtils.clearAnimator(iconView); 66 | 67 | AnimatorSet flyUpAnim = new AnimatorSet(); 68 | flyUpAnim.setDuration(800); 69 | 70 | ObjectAnimator transX = ObjectAnimator.ofFloat(iconView, "translationX", 0, getWidth()); 71 | ObjectAnimator transY = ObjectAnimator.ofFloat(iconView, "translationY", 0, -mHeaderController.getHeight()); 72 | transY.setInterpolator(PathInterpolatorCompat.create(0.7f, 1f)); 73 | ObjectAnimator rotation = ObjectAnimator.ofFloat(iconView, "rotation", -45, 0); 74 | rotation.setInterpolator(new DecelerateInterpolator()); 75 | ObjectAnimator rotationX = ObjectAnimator.ofFloat(iconView, "rotationX", 0, 60); 76 | rotationX.setInterpolator(new DecelerateInterpolator()); 77 | 78 | flyUpAnim.playTogether(transX, transY, rotationX, 79 | ObjectAnimator.ofFloat(iconView, "scaleX", 1, 0.5f), 80 | ObjectAnimator.ofFloat(iconView, "scaleY", 1, 0.5f), 81 | rotation 82 | ); 83 | 84 | mFlyAnimator = flyUpAnim; 85 | mFlyAnimator.start(); 86 | 87 | if (mListener != null) { 88 | mListener.onRefresh(FlyRefreshLayout.this); 89 | } 90 | } 91 | 92 | public void setOnPullRefreshListener(OnPullRefreshListener listener) { 93 | mListener = listener; 94 | } 95 | 96 | public void onRefreshFinish() { 97 | if (mFlyAnimator != null) { 98 | mFlyAnimator.cancel(); 99 | } 100 | 101 | final View iconView = getIconView(); 102 | UIUtils.clearAnimator(iconView); 103 | 104 | final int offDistX = -iconView.getRight(); 105 | final int offDistY = -UIUtils.dpToPx(10); 106 | AnimatorSet flyDownAnim = new AnimatorSet(); 107 | flyDownAnim.setDuration(800); 108 | ObjectAnimator transX1 = ObjectAnimator.ofFloat(iconView, "translationX", getWidth(), offDistX); 109 | ObjectAnimator transY1 = ObjectAnimator.ofFloat(iconView, "translationY", -mHeaderController.getHeight(), offDistY); 110 | transY1.setInterpolator(PathInterpolatorCompat.create(0.1f, 1f)); 111 | ObjectAnimator rotation1 = ObjectAnimator.ofFloat(iconView, "rotation", iconView.getRotation(), 0); 112 | rotation1.setInterpolator(new AccelerateInterpolator()); 113 | flyDownAnim.playTogether(transX1, transY1, 114 | ObjectAnimator.ofFloat(iconView, "scaleX", 0.5f, 0.9f), 115 | ObjectAnimator.ofFloat(iconView, "scaleY", 0.5f, 0.9f), 116 | rotation1 117 | ); 118 | flyDownAnim.addListener(new SimpleAnimatorListener() { 119 | @Override 120 | public void onAnimationStart(Animator animation) { 121 | iconView.setRotationY(180); 122 | } 123 | }); 124 | 125 | AnimatorSet flyInAnim = new AnimatorSet(); 126 | flyInAnim.setDuration(400); 127 | flyInAnim.setInterpolator(new DecelerateInterpolator()); 128 | ObjectAnimator tranX2 = ObjectAnimator.ofFloat(iconView, "translationX", offDistX, 0); 129 | ObjectAnimator tranY2 = ObjectAnimator.ofFloat(iconView, "translationY", offDistY, 0); 130 | ObjectAnimator rotationX2 = ObjectAnimator.ofFloat(iconView, "rotationX", 30, 0); 131 | flyInAnim.playTogether(tranX2, tranY2, rotationX2, 132 | ObjectAnimator.ofFloat(iconView, "scaleX", 0.9f, 1f), 133 | ObjectAnimator.ofFloat(iconView, "scaleY", 0.9f, 1f)); 134 | flyInAnim.setStartDelay(100); 135 | flyInAnim.addListener(new SimpleAnimatorListener() { 136 | @Override 137 | public void onAnimationStart(Animator animation) { 138 | iconView.setRotationY(0); 139 | } 140 | }); 141 | 142 | mFlyAnimator = new AnimatorSet(); 143 | mFlyAnimator.playSequentially(flyDownAnim, flyInAnim); 144 | mFlyAnimator.addListener(new SimpleAnimatorListener() { 145 | @Override 146 | public void onAnimationEnd(Animator animation) { 147 | if (mListener != null) { 148 | mListener.onRefreshAnimationEnd(FlyRefreshLayout.this); 149 | } 150 | } 151 | }); 152 | mFlyAnimator.start(); 153 | } 154 | 155 | @Override 156 | protected void onMoveHeader(int state, float progress) { 157 | super.onMoveHeader(state, progress); 158 | if (mHeaderController.isOverHeight()) { 159 | getIconView().setRotation((-45) * progress); 160 | } 161 | } 162 | 163 | public interface OnPullRefreshListener { 164 | void onRefresh(FlyRefreshLayout view); 165 | void onRefreshAnimationEnd(FlyRefreshLayout view); 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /library/src/main/java/com/race604/flyrefresh/HeaderController.java: -------------------------------------------------------------------------------- 1 | package com.race604.flyrefresh; 2 | 3 | /** 4 | * Created by jing on 15-5-19. 5 | */ 6 | public class HeaderController { 7 | 8 | private int mHeight; 9 | private int mMaxHegiht; 10 | private int mMinHegiht; 11 | private float mOverDistance; 12 | 13 | private float mResistance = 0.5f; 14 | private boolean mIsInTouch = false; 15 | private float mScroll = 0; 16 | private int mMaxScroll = 0; 17 | private int mMinScroll = 0; 18 | 19 | public HeaderController(int height, int maxHeight, int minHeight) { 20 | 21 | if (maxHeight <= 0) { 22 | throw new IllegalArgumentException("maxHeight must > 0"); 23 | } 24 | 25 | setSize(height, maxHeight, minHeight); 26 | } 27 | 28 | public void setSize(int height, int maxHeight, int minHeight) { 29 | mHeight = Math.max(0, height); 30 | mMaxHegiht = Math.max(0, maxHeight); 31 | mMinHegiht = Math.max(0, minHeight); 32 | mOverDistance = mMaxHegiht - mHeight; 33 | 34 | mScroll = 0; 35 | mMaxScroll = mHeight - mMinHegiht; 36 | mMinScroll = mHeight - mMaxHegiht; 37 | } 38 | 39 | public int getMaxHeight() { 40 | return mMaxHegiht; 41 | } 42 | 43 | public int getMinHeight() { 44 | return mMinHegiht; 45 | } 46 | 47 | public int getHeight() { 48 | return mHeight; 49 | } 50 | 51 | public int getScroll() { 52 | return (int) mScroll; 53 | } 54 | 55 | public int getMaxScroll() { 56 | return mMaxScroll; 57 | } 58 | 59 | public int getMinScroll() { 60 | return mMinScroll; 61 | } 62 | 63 | public int getCurPosition() { 64 | return (int) (mHeight - mScroll); 65 | } 66 | 67 | public boolean isInTouch() { 68 | return mIsInTouch; 69 | } 70 | 71 | /** 72 | * Check if can scroll down to show top 73 | * @return 74 | */ 75 | public boolean canScrollDown() { 76 | return mScroll > mMinScroll; 77 | } 78 | 79 | /** 80 | * Check if can scroll up to show bottom 81 | * @return 82 | */ 83 | public boolean canScrollUp() { 84 | return mScroll < mMaxScroll; 85 | } 86 | 87 | public int move(float deltaY) { 88 | float willTo; 89 | float consumed = deltaY; 90 | if (mScroll >= 0) { 91 | willTo = mScroll + deltaY; 92 | if (willTo < 0) { 93 | willTo = willTo * mResistance; 94 | if (willTo < mMinScroll) { 95 | consumed -= (willTo - mMinScroll) / mResistance; 96 | willTo = mMinScroll; 97 | } 98 | } else if (willTo > mMaxScroll) { 99 | consumed -= willTo - mMaxScroll; 100 | willTo = mMaxScroll; 101 | } 102 | } else { 103 | willTo = mScroll + deltaY * mResistance; 104 | if (willTo > 0) { 105 | willTo = willTo / mResistance; 106 | if (willTo > mMaxScroll) { 107 | consumed -= willTo - mMaxScroll; 108 | willTo = mMaxScroll; 109 | } 110 | } else if (willTo < mMinScroll) { 111 | consumed -= willTo - mMinScroll; 112 | willTo = mMinScroll; 113 | } 114 | } 115 | 116 | mScroll = willTo; 117 | return (int) consumed; 118 | } 119 | 120 | public boolean isOverHeight() { 121 | return mScroll < 0; 122 | } 123 | 124 | public float getMovePercentage() { 125 | return -mScroll / mOverDistance; 126 | } 127 | 128 | public boolean needSendRefresh() { 129 | return getMovePercentage() > 0.9f; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /library/src/main/java/com/race604/flyrefresh/IPullHeader.java: -------------------------------------------------------------------------------- 1 | package com.race604.flyrefresh; 2 | 3 | /** 4 | * Created by jing on 15-5-19. 5 | */ 6 | public interface IPullHeader { 7 | void onPullProgress(PullHeaderLayout parent, int state, float progress); 8 | } 9 | -------------------------------------------------------------------------------- /library/src/main/java/com/race604/flyrefresh/PullHeaderLayout.java: -------------------------------------------------------------------------------- 1 | package com.race604.flyrefresh; 2 | 3 | import android.animation.Animator; 4 | import android.animation.ObjectAnimator; 5 | import android.animation.ValueAnimator; 6 | import android.content.Context; 7 | import android.content.res.TypedArray; 8 | import android.graphics.drawable.Drawable; 9 | import android.graphics.drawable.ShapeDrawable; 10 | import android.graphics.drawable.shapes.OvalShape; 11 | import android.support.annotation.Nullable; 12 | import android.support.design.widget.FloatingActionButton; 13 | import android.support.v4.view.MotionEventCompat; 14 | import android.support.v4.view.NestedScrollingChild; 15 | import android.support.v4.view.NestedScrollingChildHelper; 16 | import android.support.v4.view.NestedScrollingParent; 17 | import android.support.v4.view.NestedScrollingParentHelper; 18 | import android.support.v4.view.VelocityTrackerCompat; 19 | import android.support.v4.view.ViewCompat; 20 | import android.support.v4.widget.ScrollerCompat; 21 | import android.util.AttributeSet; 22 | import android.util.Log; 23 | import android.view.MotionEvent; 24 | import android.view.VelocityTracker; 25 | import android.view.View; 26 | import android.view.ViewConfiguration; 27 | import android.view.ViewGroup; 28 | import android.view.ViewParent; 29 | import android.widget.ImageView; 30 | 31 | import com.race604.flyrefresh.internal.ElasticOutInterpolator; 32 | import com.race604.flyrefresh.internal.SimpleAnimatorListener; 33 | import com.race604.utils.UIUtils; 34 | 35 | /** 36 | * Created by Jing on 15/5/18. 37 | */ 38 | public class PullHeaderLayout extends ViewGroup implements NestedScrollingParent, NestedScrollingChild { 39 | 40 | private static final String TAG = PullHeaderLayout.class.getCanonicalName(); 41 | private static final boolean D = true; 42 | 43 | public static final int STATE_IDLE = 0; 44 | public static final int STATE_DRAGE = 1; 45 | public static final int STATE_FLING = 2; 46 | public static final int STATE_BOUNCE = 3; 47 | 48 | final static int ACTION_BUTTON_CENTER = UIUtils.dpToPx(40); 49 | final static int ACTION_ICON_SIZE = UIUtils.dpToPx(32); 50 | private final static int DEFAULT_EXPAND = UIUtils.dpToPx(300); 51 | private final static int DEFAULT_HEIGHT = UIUtils.dpToPx(240); 52 | private final static int DEFAULT_SHRINK = UIUtils.dpToPx(48); 53 | 54 | private int mHeaderId = 0; 55 | private int mContentId = 0; 56 | 57 | private Drawable mActionDrawable; 58 | private FloatingActionButton mActionView; 59 | private ImageView mFlyView; 60 | private View mHeaderView; 61 | private IPullHeader mPullHeaderView; 62 | protected View mContent; 63 | protected HeaderController mHeaderController; 64 | 65 | private VelocityTracker mVelocityTracker; 66 | private ValueAnimator mBounceAnim; 67 | private int mPullState = STATE_IDLE; 68 | 69 | private static final int INVALID_POINTER = -1; 70 | private int mActivePointerId = INVALID_POINTER; 71 | private boolean mIsBeingDragged = false; 72 | private int mLastMotionY = 0; 73 | 74 | private int mTouchSlop; 75 | private int mMinimumVelocity; 76 | private int mMaximumVelocity; 77 | private int mNestedYOffset; 78 | 79 | private final int[] mScrollOffset = new int[2]; 80 | private final int[] mScrollConsumed = new int[2]; 81 | 82 | private final NestedScrollingParentHelper mParentHelper; 83 | private final NestedScrollingChildHelper mChildHelper; 84 | 85 | private ScrollerCompat mScroller; 86 | 87 | private OnPullListener mPullListener; 88 | 89 | public PullHeaderLayout(Context context) { 90 | this(context, null); 91 | } 92 | 93 | public PullHeaderLayout(Context context, AttributeSet attrs) { 94 | this(context, attrs, 0); 95 | } 96 | 97 | public PullHeaderLayout(Context context, AttributeSet attrs, int defStyleAttr) { 98 | super(context, attrs, defStyleAttr); 99 | 100 | int headerHeight = DEFAULT_HEIGHT; 101 | int headerExpandHeight = DEFAULT_EXPAND; 102 | int headerShrinkHeight = DEFAULT_SHRINK; 103 | 104 | if (attrs != null) { 105 | TypedArray arr = context.obtainStyledAttributes(attrs, R.styleable.PullHeaderLayout); 106 | headerHeight = arr.getDimensionPixelOffset(R.styleable.PullHeaderLayout_phl_header_height, 107 | DEFAULT_HEIGHT); 108 | headerExpandHeight = arr.getDimensionPixelOffset(R.styleable.PullHeaderLayout_phl_header_expand_height, 109 | DEFAULT_EXPAND); 110 | headerShrinkHeight = arr.getDimensionPixelOffset(R.styleable.PullHeaderLayout_phl_header_shrink_height, 111 | DEFAULT_SHRINK); 112 | 113 | mHeaderId = arr.getResourceId(R.styleable.PullHeaderLayout_phl_header, mHeaderId); 114 | mContentId = arr.getResourceId(R.styleable.PullHeaderLayout_phl_content, mContentId); 115 | 116 | mActionDrawable = arr.getDrawable(R.styleable.PullHeaderLayout_phl_action); 117 | 118 | arr.recycle(); 119 | } 120 | 121 | mHeaderController = new HeaderController(headerHeight, headerExpandHeight, headerShrinkHeight); 122 | 123 | final ViewConfiguration conf = ViewConfiguration.get(getContext()); 124 | 125 | mParentHelper = new NestedScrollingParentHelper(this); 126 | mChildHelper = new NestedScrollingChildHelper(this); 127 | 128 | setNestedScrollingEnabled(true); 129 | 130 | init(); 131 | } 132 | 133 | private void init() { 134 | mScroller = ScrollerCompat.create(getContext()); 135 | final ViewConfiguration configuration = ViewConfiguration.get(getContext()); 136 | mTouchSlop = configuration.getScaledTouchSlop(); 137 | mMinimumVelocity = configuration.getScaledMinimumFlingVelocity(); 138 | mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); 139 | } 140 | 141 | // NestedScrollingChild 142 | 143 | @Override 144 | public void setNestedScrollingEnabled(boolean enabled) { 145 | mChildHelper.setNestedScrollingEnabled(enabled); 146 | } 147 | 148 | @Override 149 | public boolean isNestedScrollingEnabled() { 150 | return mChildHelper.isNestedScrollingEnabled(); 151 | } 152 | 153 | @Override 154 | public boolean startNestedScroll(int axes) { 155 | return mChildHelper.startNestedScroll(axes); 156 | } 157 | 158 | @Override 159 | public void stopNestedScroll() { 160 | mChildHelper.stopNestedScroll(); 161 | } 162 | 163 | @Override 164 | public boolean hasNestedScrollingParent() { 165 | return mChildHelper.hasNestedScrollingParent(); 166 | } 167 | 168 | @Override 169 | public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, 170 | int dyUnconsumed, int[] offsetInWindow) { 171 | return mChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, 172 | offsetInWindow); 173 | } 174 | 175 | @Override 176 | public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) { 177 | return mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow); 178 | } 179 | 180 | @Override 181 | public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) { 182 | return mChildHelper.dispatchNestedFling(velocityX, velocityY, consumed); 183 | } 184 | 185 | @Override 186 | public boolean dispatchNestedPreFling(float velocityX, float velocityY) { 187 | return mChildHelper.dispatchNestedPreFling(velocityX, velocityY); 188 | } 189 | 190 | // NestedScrollingParent 191 | 192 | @Override 193 | public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) { 194 | return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0; 195 | } 196 | 197 | @Override 198 | public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) { 199 | mParentHelper.onNestedScrollAccepted(child, target, nestedScrollAxes); 200 | startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL); 201 | } 202 | 203 | @Override 204 | public void onStopNestedScroll(View target) { 205 | stopNestedScroll(); 206 | } 207 | 208 | @Override 209 | public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, 210 | int dyUnconsumed) { 211 | final int myConsumed = moveBy(dyUnconsumed); 212 | final int myUnconsumed = dyUnconsumed - myConsumed; 213 | dispatchNestedScroll(0, myConsumed, 0, myUnconsumed, null); 214 | } 215 | 216 | @Override 217 | public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) { 218 | if (dy > 0 && mHeaderController.canScrollUp()) { 219 | final int delta = moveBy(dy); 220 | consumed[0] = 0; 221 | consumed[1] = delta; 222 | //dispatchNestedScroll(0, myConsumed, 0, consumed[1], null); 223 | } 224 | } 225 | 226 | @Override 227 | public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) { 228 | if (!consumed) { 229 | flingWithNestedDispatch((int) velocityY); 230 | return true; 231 | } 232 | return false; 233 | } 234 | 235 | private boolean flingWithNestedDispatch(int velocityY) { 236 | final boolean canFling = (mHeaderController.canScrollUp() && velocityY > 0) || 237 | (mHeaderController.canScrollDown() && velocityY < 0); 238 | if (!dispatchNestedPreFling(0, velocityY)) { 239 | dispatchNestedFling(0, velocityY, canFling); 240 | if (canFling) { 241 | fling(velocityY); 242 | } 243 | } 244 | return canFling; 245 | } 246 | 247 | @Override 248 | public boolean onNestedPreFling(View target, float velocityX, float velocityY) { 249 | return flingWithNestedDispatch((int) velocityY); 250 | } 251 | 252 | @Override 253 | public int getNestedScrollAxes() { 254 | return mParentHelper.getNestedScrollAxes(); 255 | } 256 | 257 | public void setHeaderSize(int height, int maxHeight, int minHeight) { 258 | mHeaderController.setSize(height, maxHeight, minHeight); 259 | if (isLayoutRequested()) { 260 | requestLayout(); 261 | } 262 | } 263 | 264 | public void setOnPullListener(OnPullListener listener) { 265 | mPullListener = listener; 266 | } 267 | 268 | public void setActionDrawable(Drawable actionDrawable) { 269 | mActionDrawable = actionDrawable; 270 | if (mActionDrawable != null) { 271 | if (mActionView == null) { 272 | final int bgColor = UIUtils.getThemeColorFromAttrOrRes(getContext(), R.attr.colorAccent, R.color.accent); 273 | final int pressedColor = UIUtils.darkerColor(bgColor, 0.8f); 274 | 275 | final ShapeDrawable bgDrawable = new ShapeDrawable(new OvalShape()); 276 | bgDrawable.getPaint().setColor(bgColor); 277 | mActionView = new FloatingActionButton(getContext()); 278 | mActionView.setRippleColor(pressedColor); 279 | mActionView.setBackgroundDrawable(bgDrawable); 280 | addView(mActionView, new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); 281 | } 282 | 283 | if (mFlyView == null) { 284 | mFlyView = new ImageView(getContext()); 285 | mFlyView.setScaleType(ImageView.ScaleType.FIT_XY); 286 | addView(mFlyView, new LayoutParams(ACTION_ICON_SIZE, ACTION_ICON_SIZE)); 287 | mFlyView.bringToFront(); 288 | float elevation = ViewCompat.getElevation(mActionView); 289 | ViewCompat.setElevation(mFlyView, elevation + 1); 290 | } 291 | mFlyView.setImageDrawable(mActionDrawable); 292 | 293 | } else { 294 | if (mActionView != null) { 295 | removeView(mActionView); 296 | removeView(mFlyView); 297 | mActionView = null; 298 | mFlyView = null; 299 | } 300 | } 301 | } 302 | 303 | @Nullable 304 | public View getIconView() { 305 | return mFlyView; 306 | } 307 | 308 | @Nullable 309 | public FloatingActionButton getHeaderActionButton() { 310 | return mActionView; 311 | } 312 | 313 | public void setHeaderView(View headerView, LayoutParams lp) { 314 | if (mHeaderView != null) { 315 | removeView(mHeaderView); 316 | mPullHeaderView = null; 317 | } 318 | 319 | addView(headerView, 0, lp); 320 | mHeaderView = headerView; 321 | 322 | if (mHeaderView instanceof IPullHeader) { 323 | mPullHeaderView = (IPullHeader) mHeaderView; 324 | } 325 | } 326 | 327 | @Override 328 | protected void onFinishInflate() { 329 | final int childCount = getChildCount(); 330 | if (childCount > 2) { 331 | throw new IllegalStateException("FlyRefreshLayout only can host 2 elements"); 332 | } else if (childCount == 2) { 333 | if (mHeaderId != 0 && mHeaderView == null) { 334 | mHeaderView = findViewById(mHeaderId); 335 | } 336 | 337 | if (mContentId != 0 && mContent == null) { 338 | mContent = findViewById(mContentId); 339 | } 340 | 341 | // not specify header or content 342 | if (mContent == null || mHeaderView == null) { 343 | 344 | View child1 = getChildAt(0); 345 | View child2 = getChildAt(1); 346 | if (child1 instanceof IPullHeader) { 347 | mHeaderView = child1; 348 | mContent = child2; 349 | mPullHeaderView = (IPullHeader) mHeaderView; 350 | } else if (child2 instanceof IPullHeader) { 351 | mHeaderView = child2; 352 | mContent = child1; 353 | mPullHeaderView = (IPullHeader) mHeaderView; 354 | } else { 355 | // both are not specified 356 | if (mContent == null && mHeaderView == null) { 357 | mHeaderView = child1; 358 | mContent = child2; 359 | } 360 | // only one is specified 361 | else { 362 | if (mHeaderView == null) { 363 | mHeaderView = mContent == child1 ? child2 : child1; 364 | } else { 365 | mContent = mHeaderView == child1 ? child2 : child1; 366 | } 367 | } 368 | } 369 | } 370 | } else if (childCount == 1) { 371 | mContent = getChildAt(0); 372 | } 373 | 374 | setActionDrawable(mActionDrawable); 375 | 376 | super.onFinishInflate(); 377 | } 378 | 379 | @Override 380 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 381 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 382 | 383 | if (mHeaderView != null) { 384 | measureChildWithMargins(mHeaderView, widthMeasureSpec, 0, heightMeasureSpec, 0); 385 | } 386 | 387 | if (mContent != null) { 388 | measureChildWithMargins(mContent, widthMeasureSpec, 0, heightMeasureSpec, mHeaderController.getMinHeight()); 389 | } 390 | 391 | if (mActionView != null) { 392 | measureChild(mActionView, widthMeasureSpec, heightMeasureSpec); 393 | measureChild(mFlyView, widthMeasureSpec, heightMeasureSpec); 394 | } 395 | } 396 | 397 | @Override 398 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 399 | layoutChildren(); 400 | } 401 | 402 | private void layoutChildren() { 403 | int offsetY = mHeaderController.getCurPosition(); 404 | int paddingLeft = getPaddingLeft(); 405 | int paddingTop = getPaddingTop(); 406 | 407 | if (mHeaderView != null) { 408 | MarginLayoutParams lp = (MarginLayoutParams) mHeaderView.getLayoutParams(); 409 | final int left = paddingLeft + lp.leftMargin; 410 | final int top = paddingTop + lp.topMargin; 411 | final int right = left + mHeaderView.getMeasuredWidth(); 412 | final int bottom = top + mHeaderView.getMeasuredHeight(); 413 | mHeaderView.layout(left, top, right, bottom); 414 | } 415 | 416 | if (mContent != null) { 417 | MarginLayoutParams lp = (MarginLayoutParams) mContent.getLayoutParams(); 418 | final int left = paddingLeft + lp.leftMargin; 419 | final int top = paddingTop + lp.topMargin + offsetY; 420 | final int right = left + mContent.getMeasuredWidth(); 421 | final int bottom = top + mContent.getMeasuredHeight(); 422 | mContent.layout(left, top, right, bottom); 423 | } 424 | 425 | if (mActionView != null) { 426 | final int center = ACTION_BUTTON_CENTER; 427 | int halfWidth = (mActionView.getMeasuredWidth() + 1) / 2; 428 | int halfHeight = (mActionView.getMeasuredHeight() + 1) / 2; 429 | 430 | mActionView.layout(center - halfWidth , offsetY - halfHeight, 431 | center + halfWidth, offsetY + halfHeight); 432 | 433 | halfWidth = (mFlyView.getMeasuredWidth() + 1) / 2; 434 | halfHeight = (mFlyView.getMeasuredHeight() + 1) / 2; 435 | mFlyView.layout(center - halfWidth, offsetY - halfHeight, 436 | center + halfWidth, offsetY + halfHeight); 437 | } 438 | 439 | } 440 | 441 | private void obtainVelocityTracker(MotionEvent event) { 442 | if (mVelocityTracker == null) { 443 | mVelocityTracker = VelocityTracker.obtain(); 444 | } 445 | mVelocityTracker.addMovement(event); 446 | } 447 | 448 | private void initOrResetVelocityTracker() { 449 | if (mVelocityTracker == null) { 450 | mVelocityTracker = VelocityTracker.obtain(); 451 | } else { 452 | mVelocityTracker.clear(); 453 | } 454 | } 455 | 456 | private void releaseVelocityTracker() { 457 | if (mVelocityTracker != null) { 458 | mVelocityTracker.recycle(); 459 | mVelocityTracker = null; 460 | } 461 | } 462 | 463 | private void onSecondaryPointerUp(MotionEvent ev) { 464 | final int pointerIndex = (ev.getAction() & MotionEventCompat.ACTION_POINTER_INDEX_MASK) >> 465 | MotionEventCompat.ACTION_POINTER_INDEX_SHIFT; 466 | final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex); 467 | if (pointerId == mActivePointerId) { 468 | // This was our active pointer going up. Choose a new 469 | // active pointer and adjust accordingly. 470 | // TODO: Make this decision more intelligent. 471 | final int newPointerIndex = pointerIndex == 0 ? 1 : 0; 472 | mLastMotionY = (int) MotionEventCompat.getY(ev, newPointerIndex); 473 | mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex); 474 | if (mVelocityTracker != null) { 475 | mVelocityTracker.clear(); 476 | } 477 | } 478 | } 479 | 480 | private void endDrag() { 481 | mIsBeingDragged = false; 482 | releaseVelocityTracker(); 483 | } 484 | 485 | @Override 486 | public boolean onInterceptTouchEvent(MotionEvent ev) { 487 | final int action = ev.getAction(); 488 | if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) { 489 | return true; 490 | } 491 | 492 | if(!isEnabled()) { 493 | return false; 494 | } 495 | 496 | switch (action & MotionEventCompat.ACTION_MASK) { 497 | case MotionEvent.ACTION_MOVE: { 498 | final int activePointerId = mActivePointerId; 499 | if (activePointerId == INVALID_POINTER) { 500 | // If we don't have a valid id, the touch down wasn't on content. 501 | break; 502 | } 503 | 504 | final int pointerIndex = MotionEventCompat.findPointerIndex(ev, activePointerId); 505 | if (pointerIndex == -1) { 506 | Log.e(TAG, "Invalid pointerId=" + activePointerId 507 | + " in onInterceptTouchEvent"); 508 | break; 509 | } 510 | 511 | final int y = (int) MotionEventCompat.getY(ev, pointerIndex); 512 | final int yDiff = Math.abs(y - mLastMotionY); 513 | if (yDiff > mTouchSlop 514 | && (getNestedScrollAxes() & ViewCompat.SCROLL_AXIS_VERTICAL) == 0) { 515 | mIsBeingDragged = true; 516 | mLastMotionY = y; 517 | obtainVelocityTracker(ev); 518 | mNestedYOffset = 0; 519 | final ViewParent parent = getParent(); 520 | if (parent != null) { 521 | parent.requestDisallowInterceptTouchEvent(true); 522 | } 523 | } 524 | break; 525 | } 526 | 527 | case MotionEvent.ACTION_DOWN: { 528 | final int y = (int) ev.getY(); 529 | 530 | /* 531 | * Remember location of down touch. 532 | * ACTION_DOWN always refers to pointer index 0. 533 | */ 534 | mLastMotionY = y; 535 | mActivePointerId = MotionEventCompat.getPointerId(ev, 0); 536 | 537 | initOrResetVelocityTracker(); 538 | mVelocityTracker.addMovement(ev); 539 | /* 540 | * If being flinged and user touches the screen, initiate drag; 541 | * otherwise don't. mScroller.isFinished should be false when 542 | * being flinged. 543 | */ 544 | mIsBeingDragged = !mScroller.isFinished(); 545 | startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL); 546 | break; 547 | } 548 | 549 | case MotionEvent.ACTION_CANCEL: 550 | case MotionEvent.ACTION_UP: 551 | /* Release the drag */ 552 | mIsBeingDragged = false; 553 | mActivePointerId = INVALID_POINTER; 554 | endDrag(); 555 | stopNestedScroll(); 556 | break; 557 | case MotionEventCompat.ACTION_POINTER_UP: 558 | onSecondaryPointerUp(ev); 559 | break; 560 | } 561 | 562 | return mIsBeingDragged; 563 | } 564 | 565 | @Override 566 | public boolean dispatchTouchEvent(MotionEvent ev) { 567 | final int actionMasked = MotionEventCompat.getActionMasked(ev); 568 | if (actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_CANCEL) { 569 | tryBounceBack(); 570 | } 571 | return super.dispatchTouchEvent(ev); 572 | } 573 | 574 | @Override 575 | public boolean onTouchEvent(MotionEvent ev) { 576 | 577 | MotionEvent vtev = MotionEvent.obtain(ev); 578 | 579 | final int actionMasked = MotionEventCompat.getActionMasked(ev); 580 | 581 | if (actionMasked == MotionEvent.ACTION_DOWN) { 582 | mNestedYOffset = 0; 583 | } 584 | vtev.offsetLocation(0, mNestedYOffset); 585 | 586 | switch (actionMasked) { 587 | case MotionEvent.ACTION_DOWN: { 588 | if ((mIsBeingDragged = !mScroller.isFinished())) { 589 | final ViewParent parent = getParent(); 590 | if (parent != null) { 591 | parent.requestDisallowInterceptTouchEvent(true); 592 | } 593 | } 594 | 595 | /* 596 | * If being flinged and user touches, stop the fling. isFinished 597 | * will be false if being flinged. 598 | */ 599 | if (!mScroller.isFinished()) { 600 | mScroller.abortAnimation(); 601 | } 602 | 603 | // Remember where the motion event started 604 | mLastMotionY = (int) ev.getY(); 605 | mActivePointerId = MotionEventCompat.getPointerId(ev, 0); 606 | startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL); 607 | break; 608 | } 609 | case MotionEvent.ACTION_MOVE: 610 | final int activePointerIndex = MotionEventCompat.findPointerIndex(ev, 611 | mActivePointerId); 612 | if (activePointerIndex == -1) { 613 | Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent"); 614 | break; 615 | } 616 | 617 | final int y = (int) MotionEventCompat.getY(ev, activePointerIndex); 618 | int deltaY = mLastMotionY - y; 619 | if (dispatchNestedPreScroll(0, deltaY, mScrollConsumed, mScrollOffset)) { 620 | deltaY -= mScrollConsumed[1]; 621 | vtev.offsetLocation(0, mScrollOffset[1]); 622 | mNestedYOffset += mScrollOffset[1]; 623 | } 624 | if (!mIsBeingDragged && Math.abs(deltaY) > mTouchSlop) { 625 | final ViewParent parent = getParent(); 626 | if (parent != null) { 627 | parent.requestDisallowInterceptTouchEvent(true); 628 | } 629 | mIsBeingDragged = true; 630 | if (deltaY > 0) { 631 | deltaY -= mTouchSlop; 632 | } else { 633 | deltaY += mTouchSlop; 634 | } 635 | } 636 | if (mIsBeingDragged) { 637 | // Scroll to follow the motion event 638 | mLastMotionY = y - mScrollOffset[1]; 639 | 640 | final int scrolledDeltaY = moveBy(deltaY); 641 | final int unconsumedY = deltaY - scrolledDeltaY; 642 | if (dispatchNestedScroll(0, scrolledDeltaY, 0, unconsumedY, mScrollOffset)) { 643 | mLastMotionY -= mScrollOffset[1]; 644 | vtev.offsetLocation(0, mScrollOffset[1]); 645 | mNestedYOffset += mScrollOffset[1]; 646 | } 647 | } 648 | break; 649 | case MotionEvent.ACTION_UP: 650 | if (mIsBeingDragged) { 651 | final VelocityTracker velocityTracker = mVelocityTracker; 652 | velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 653 | int initialVelocity = (int) VelocityTrackerCompat.getYVelocity(velocityTracker, 654 | mActivePointerId); 655 | 656 | if ((Math.abs(initialVelocity) > mMinimumVelocity)) { 657 | flingWithNestedDispatch(-initialVelocity); 658 | } 659 | 660 | mActivePointerId = INVALID_POINTER; 661 | endDrag(); 662 | } 663 | break; 664 | case MotionEvent.ACTION_CANCEL: 665 | if (mIsBeingDragged && getChildCount() > 0) { 666 | mActivePointerId = INVALID_POINTER; 667 | endDrag(); 668 | } 669 | break; 670 | case MotionEventCompat.ACTION_POINTER_DOWN: { 671 | final int index = MotionEventCompat.getActionIndex(ev); 672 | mLastMotionY = (int) MotionEventCompat.getY(ev, index); 673 | mActivePointerId = MotionEventCompat.getPointerId(ev, index); 674 | break; 675 | } 676 | case MotionEventCompat.ACTION_POINTER_UP: 677 | onSecondaryPointerUp(ev); 678 | mLastMotionY = (int) MotionEventCompat.getY(ev, 679 | MotionEventCompat.findPointerIndex(ev, mActivePointerId)); 680 | break; 681 | } 682 | 683 | if (mVelocityTracker != null) { 684 | mVelocityTracker.addMovement(vtev); 685 | } 686 | vtev.recycle(); 687 | return true; 688 | } 689 | 690 | protected void onMoveHeader(int state, float progress) {} 691 | 692 | /** 693 | * Fling the scroll view 694 | * 695 | * @param velocityY The initial velocity in the Y direction. Positive 696 | * numbers mean that the finger/cursor is moving down the screen, 697 | * which means we want to scroll towards the top. 698 | */ 699 | public void fling(int velocityY) { 700 | mPullState = STATE_FLING; 701 | mScroller.abortAnimation(); 702 | mScroller.fling(0, mHeaderController.getScroll(), 0, velocityY, 0, 0, 703 | mHeaderController.getMinScroll(), mHeaderController.getMaxScroll(), 704 | 0, 0); 705 | ViewCompat.postInvalidateOnAnimation(this); 706 | } 707 | 708 | private int moveBy(float deltaY) { 709 | int oldScroll = mHeaderController.getScroll(); 710 | int consumed = mHeaderController.move(deltaY); 711 | 712 | final int delta = mHeaderController.getScroll() - oldScroll; 713 | if (delta == 0) { 714 | return 0; 715 | } 716 | 717 | if (mPullState != STATE_DRAGE) { 718 | mPullState = STATE_DRAGE; 719 | } 720 | 721 | if (mContent != null) { 722 | mContent.offsetTopAndBottom(-delta); 723 | } 724 | 725 | if (mActionView != null) { 726 | mActionView.offsetTopAndBottom(-delta); 727 | 728 | mFlyView.offsetTopAndBottom(-delta); 729 | 730 | float percentage = mHeaderController.getMovePercentage(); 731 | onMoveHeader(mPullState, percentage); 732 | 733 | if (mPullHeaderView != null) { 734 | mPullHeaderView.onPullProgress(this, mPullState, percentage); 735 | } 736 | 737 | if (mPullListener != null) { 738 | mPullListener.onPullProgress(this, mPullState, percentage); 739 | } 740 | 741 | } 742 | 743 | return consumed; 744 | } 745 | 746 | @Override 747 | public void computeScroll() { 748 | if (mScroller.computeScrollOffset()) { 749 | int oldY = mHeaderController.getScroll(); 750 | int y = mScroller.getCurrY(); 751 | 752 | if (oldY != y) { 753 | moveBy(y - oldY); 754 | } 755 | ViewCompat.postInvalidateOnAnimation(this); 756 | } else { 757 | tryBounceBack(); 758 | } 759 | } 760 | 761 | private void tryBounceBack() { 762 | if (mHeaderController.isOverHeight()) { 763 | mBounceAnim = ObjectAnimator.ofFloat(mHeaderController.getScroll(), 0); 764 | mBounceAnim.setInterpolator(new ElasticOutInterpolator()); 765 | mBounceAnim.setDuration(500); 766 | mBounceAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 767 | @Override 768 | public void onAnimationUpdate(ValueAnimator animation) { 769 | float deltaY = (float) animation.getAnimatedValue() - mHeaderController.getScroll(); 770 | moveBy(deltaY); 771 | } 772 | }); 773 | mBounceAnim.addListener(new SimpleAnimatorListener() { 774 | @Override 775 | public void onAnimationEnd(Animator animation) { 776 | mPullState = STATE_IDLE; 777 | } 778 | }); 779 | 780 | mBounceAnim.start(); 781 | mPullState = STATE_BOUNCE; 782 | 783 | if (mHeaderController.needSendRefresh()) { 784 | startRefresh(); 785 | } 786 | } else { 787 | mPullState = STATE_IDLE; 788 | } 789 | } 790 | 791 | public void startRefresh() {} 792 | 793 | @Override 794 | protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 795 | return p instanceof LayoutParams; 796 | } 797 | 798 | @Override 799 | protected ViewGroup.LayoutParams generateDefaultLayoutParams() { 800 | return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 801 | } 802 | 803 | @Override 804 | protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 805 | return new LayoutParams(p); 806 | } 807 | 808 | @Override 809 | public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { 810 | return new LayoutParams(getContext(), attrs); 811 | } 812 | 813 | public static class LayoutParams extends MarginLayoutParams { 814 | 815 | public LayoutParams(Context c, AttributeSet attrs) { 816 | super(c, attrs); 817 | } 818 | 819 | public LayoutParams(int width, int height) { 820 | super(width, height); 821 | } 822 | 823 | @SuppressWarnings({"unused"}) 824 | public LayoutParams(MarginLayoutParams source) { 825 | super(source); 826 | } 827 | 828 | public LayoutParams(ViewGroup.LayoutParams source) { 829 | super(source); 830 | } 831 | } 832 | 833 | public interface OnPullListener { 834 | void onPullProgress(PullHeaderLayout view, int state, float progress); 835 | } 836 | } 837 | -------------------------------------------------------------------------------- /library/src/main/java/com/race604/flyrefresh/internal/ElasticOutInterpolator.java: -------------------------------------------------------------------------------- 1 | package com.race604.flyrefresh.internal; 2 | 3 | import android.view.animation.Interpolator; 4 | 5 | /** 6 | * Created by jing on 15-5-22. 7 | */ 8 | public class ElasticOutInterpolator implements Interpolator { 9 | 10 | @Override 11 | public float getInterpolation(float t) { 12 | if (t == 0) return 0; 13 | if (t >= 1) return 1; 14 | float p=.3f; 15 | float s=p/4; 16 | return ((float)Math.pow(2,-10*t) * (float)Math.sin( (t-s)*(2*(float)Math.PI)/p) + 1); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /library/src/main/java/com/race604/flyrefresh/internal/MountainSceneDrawable.java: -------------------------------------------------------------------------------- 1 | package com.race604.flyrefresh.internal; 2 | 3 | import android.graphics.Canvas; 4 | import android.graphics.Color; 5 | import android.graphics.ColorFilter; 6 | import android.graphics.Matrix; 7 | import android.graphics.Paint; 8 | import android.graphics.Path; 9 | import android.graphics.PixelFormat; 10 | import android.graphics.RectF; 11 | import android.graphics.drawable.Drawable; 12 | import android.support.v4.view.animation.PathInterpolatorCompat; 13 | import android.view.animation.Interpolator; 14 | 15 | import com.race604.flyrefresh.PullHeaderLayout; 16 | 17 | /** 18 | * Created by Jing on 15/5/24. 19 | */ 20 | public class MountainSceneDrawable extends Drawable { 21 | 22 | private static final int COLOR_BACKGROUND = Color.parseColor("#7ECEC9"); 23 | private static final int COLOR_MOUNTAIN_1 = Color.parseColor("#86DAD7"); 24 | private static final int COLOR_MOUNTAIN_2 = Color.parseColor("#3C929C"); 25 | private static final int COLOR_MOUNTAIN_3 = Color.parseColor("#3E5F73"); 26 | private static final int COLOR_TREE_1_BRANCH = Color.parseColor("#1F7177"); 27 | private static final int COLOR_TREE_1_BTRUNK = Color.parseColor("#0C3E48"); 28 | private static final int COLOR_TREE_2_BRANCH = Color.parseColor("#34888F"); 29 | private static final int COLOR_TREE_2_BTRUNK = Color.parseColor("#1B6169"); 30 | private static final int COLOR_TREE_3_BRANCH = Color.parseColor("#57B1AE"); 31 | private static final int COLOR_TREE_3_BTRUNK = Color.parseColor("#62A4AD"); 32 | 33 | private static final int WIDTH = 240; 34 | private static final int HEIGHT = 180; 35 | 36 | private static final int TREE_WIDTH = 100; 37 | private static final int TREE_HEIGHT = 200; 38 | 39 | private Paint mMountPaint = new Paint(); 40 | private Paint mTrunkPaint = new Paint(); 41 | private Paint mBranchPaint = new Paint(); 42 | private Paint mBoarderPaint = new Paint(); 43 | 44 | private Path mMount1 = new Path(); 45 | private Path mMount2 = new Path(); 46 | private Path mMount3 = new Path(); 47 | private Path mTrunk = new Path(); 48 | private Path mBranch = new Path(); 49 | 50 | private float mScale = 5f; 51 | private float mMoveFactor = 0; 52 | private float mBounceMax = 1; 53 | private float mTreeBendFactor = Float.MAX_VALUE; 54 | private Matrix mTransMatrix = new Matrix(); 55 | 56 | public MountainSceneDrawable() { 57 | super(); 58 | 59 | mMountPaint.setAntiAlias(true); 60 | mMountPaint.setStyle(Paint.Style.FILL); 61 | 62 | mTrunkPaint.setAntiAlias(true); 63 | mBranchPaint.setAntiAlias(true); 64 | mBoarderPaint.setAntiAlias(true); 65 | mBoarderPaint.setStyle(Paint.Style.STROKE); 66 | mBoarderPaint.setStrokeWidth(2); 67 | mBoarderPaint.setStrokeJoin(Paint.Join.ROUND); 68 | 69 | updateMountainPath(mMoveFactor); 70 | updateTreePath(0); 71 | } 72 | 73 | private void updateMountainPath(float factor) { 74 | 75 | mTransMatrix.reset(); 76 | mTransMatrix.setScale(mScale, mScale); 77 | 78 | int offset1 = (int) (10 * factor); 79 | mMount1.reset(); 80 | mMount1.moveTo(0, 95 + offset1); 81 | mMount1.lineTo(55, 74 + offset1); 82 | mMount1.lineTo(146, 104 + offset1); 83 | mMount1.lineTo(227, 72 + offset1); 84 | mMount1.lineTo(WIDTH, 80 + offset1); 85 | mMount1.lineTo(WIDTH, HEIGHT); 86 | mMount1.lineTo(0, HEIGHT); 87 | mMount1.close(); 88 | mMount1.transform(mTransMatrix); 89 | 90 | int offset2 = (int) (20 * factor); 91 | mMount2.reset(); 92 | mMount2.moveTo(0, 103 + offset2); 93 | mMount2.lineTo(67, 90 + offset2); 94 | mMount2.lineTo(165, 115 + offset2); 95 | mMount2.lineTo(221, 87 + offset2); 96 | mMount2.lineTo(WIDTH, 100 + offset2); 97 | mMount2.lineTo(WIDTH, HEIGHT); 98 | mMount2.lineTo(0, HEIGHT); 99 | mMount2.close(); 100 | mMount2.transform(mTransMatrix); 101 | 102 | int offset3 = (int) (30 * factor); 103 | mMount3.reset(); 104 | mMount3.moveTo(0, 114 + offset3); 105 | mMount3.cubicTo(30, 106 + offset3, 196, 97 + offset3, WIDTH, 104 + offset3); 106 | mMount3.lineTo(WIDTH, HEIGHT); 107 | mMount3.lineTo(0, HEIGHT); 108 | mMount3.close(); 109 | mMount3.transform(mTransMatrix); 110 | } 111 | 112 | private void updateTreePath(float factor) { 113 | if (factor == mTreeBendFactor) { 114 | return; 115 | } 116 | 117 | final Interpolator interpolator = PathInterpolatorCompat.create(0.8f, -0.5f * factor); 118 | 119 | final float width = TREE_WIDTH; 120 | final float height = TREE_HEIGHT; 121 | 122 | final float maxMove = width * 0.3f * factor; 123 | final float trunkSize = width * 0.05f; 124 | final float branchSize = width * 0.2f; 125 | final float x0 = width / 2; 126 | final float y0 = height; 127 | 128 | final int N = 25; 129 | final float dp = 1f / N; 130 | final float dy = -dp * height; 131 | float y = y0; 132 | float p = 0; 133 | float[] xx = new float[N + 1]; 134 | float[] yy = new float[N + 1]; 135 | for (int i = 0; i <= N; i++) { 136 | xx[i] = interpolator.getInterpolation(p) * maxMove + x0; 137 | yy[i] = y; 138 | 139 | y += dy; 140 | p += dp; 141 | } 142 | 143 | mTrunk.reset(); 144 | mTrunk.moveTo(x0 - trunkSize, y0); 145 | int max = (int) (N * 0.7f); 146 | int max1 = (int) (max * 0.5f); 147 | float diff = max - max1; 148 | for (int i = 0; i < max; i++) { 149 | if (i < max1) { 150 | mTrunk.lineTo(xx[i] - trunkSize, yy[i]); 151 | } else { 152 | mTrunk.lineTo(xx[i] - trunkSize * (max - i) / diff, yy[i]); 153 | } 154 | } 155 | 156 | for (int i = max - 1; i >= 0; i--) { 157 | if (i < max1) { 158 | mTrunk.lineTo(xx[i] + trunkSize, yy[i]); 159 | } else { 160 | mTrunk.lineTo(xx[i] + trunkSize * (max - i) / diff, yy[i]); 161 | } 162 | } 163 | mTrunk.close(); 164 | 165 | mBranch.reset(); 166 | int min = (int) (N * 0.4f); 167 | diff = N - min; 168 | 169 | mBranch.moveTo(xx[min] - branchSize, yy[min]); 170 | mBranch.addArc(new RectF(xx[min] - branchSize, yy[min] - branchSize, xx[min] + branchSize, yy[min] + branchSize), 0f, 180f); 171 | for (int i = min; i <= N; i++) { 172 | float f = (i - min) / diff; 173 | mBranch.lineTo(xx[i] - branchSize + f * f * branchSize, yy[i]); 174 | } 175 | for (int i = N; i >= min; i--) { 176 | float f = (i - min) / diff; 177 | mBranch.lineTo(xx[i] + branchSize - f * f * branchSize, yy[i]); 178 | } 179 | 180 | } 181 | 182 | public void setMoveFactor(int state, float factor) { 183 | 184 | float bendFactor; 185 | if (state == PullHeaderLayout.STATE_BOUNCE) { 186 | if (factor < mBounceMax) { 187 | mBounceMax = factor; 188 | } 189 | bendFactor = factor; 190 | } else { 191 | mBounceMax = factor; 192 | bendFactor = Math.max(0, factor); 193 | } 194 | 195 | mMoveFactor = Math.max(0, mBounceMax); 196 | updateMountainPath(mMoveFactor); 197 | updateTreePath(bendFactor); 198 | } 199 | 200 | private void drawTree(Canvas canvas, float scale, float baseX, float baseY, 201 | int colorTrunk, int colorBranch) { 202 | canvas.save(); 203 | 204 | final float dx = baseX - TREE_WIDTH * scale / 2; 205 | final float dy = baseY - TREE_HEIGHT * scale; 206 | canvas.translate(dx, dy); 207 | canvas.scale(scale, scale); 208 | 209 | mBranchPaint.setColor(colorBranch); 210 | canvas.drawPath(mBranch, mBranchPaint); 211 | mTrunkPaint.setColor(colorTrunk); 212 | canvas.drawPath(mTrunk, mTrunkPaint); 213 | mBoarderPaint.setColor(colorTrunk); 214 | canvas.drawPath(mBranch, mBoarderPaint); 215 | 216 | canvas.restore(); 217 | } 218 | 219 | @Override 220 | public void draw(Canvas canvas) { 221 | canvas.drawColor(COLOR_BACKGROUND); 222 | 223 | mMountPaint.setColor(COLOR_MOUNTAIN_1); 224 | canvas.drawPath(mMount1, mMountPaint); 225 | 226 | canvas.save(); 227 | canvas.scale(-1, 1, getIntrinsicWidth() / 2, 0); 228 | drawTree(canvas, 0.12f * mScale, 180 * mScale, (93 + 20 * mMoveFactor) * mScale, 229 | COLOR_TREE_3_BTRUNK, COLOR_TREE_3_BRANCH); 230 | drawTree(canvas, 0.1f * mScale, 200 * mScale, (96 + 20 * mMoveFactor) * mScale, 231 | COLOR_TREE_3_BTRUNK, COLOR_TREE_3_BRANCH); 232 | canvas.restore(); 233 | mMountPaint.setColor(COLOR_MOUNTAIN_2); 234 | canvas.drawPath(mMount2, mMountPaint); 235 | 236 | drawTree(canvas, 0.2f * mScale, 160 * mScale, (105 + 30 * mMoveFactor) * mScale, 237 | COLOR_TREE_1_BTRUNK, COLOR_TREE_1_BRANCH); 238 | 239 | drawTree(canvas, 0.14f * mScale, 180 * mScale, (105 + 30 * mMoveFactor) * mScale, 240 | COLOR_TREE_2_BTRUNK ,COLOR_TREE_2_BRANCH); 241 | 242 | drawTree(canvas, 0.16f * mScale, 140 * mScale, (105 + 30 * mMoveFactor) * mScale, 243 | COLOR_TREE_2_BTRUNK ,COLOR_TREE_2_BRANCH); 244 | 245 | mMountPaint.setColor(COLOR_MOUNTAIN_3); 246 | canvas.drawPath(mMount3, mMountPaint); 247 | 248 | } 249 | 250 | @Override 251 | public void setAlpha(int alpha) { 252 | mMountPaint.setAlpha(alpha); 253 | mTrunkPaint.setAlpha(alpha); 254 | mBranchPaint.setAlpha(alpha); 255 | mBoarderPaint.setAlpha(alpha); 256 | } 257 | 258 | @Override 259 | public void setColorFilter(ColorFilter cf) { 260 | mMountPaint.setColorFilter(cf); 261 | mTrunkPaint.setColorFilter(cf); 262 | mBranchPaint.setColorFilter(cf); 263 | mBoarderPaint.setColorFilter(cf); 264 | } 265 | 266 | @Override 267 | public int getOpacity() { 268 | return PixelFormat.OPAQUE; 269 | } 270 | 271 | @Override 272 | public int getIntrinsicHeight() { 273 | return (int) (HEIGHT * mScale); 274 | } 275 | 276 | @Override 277 | public int getIntrinsicWidth() { 278 | return (int) (WIDTH * mScale); 279 | } 280 | } 281 | -------------------------------------------------------------------------------- /library/src/main/java/com/race604/flyrefresh/internal/MountanScenceView.java: -------------------------------------------------------------------------------- 1 | package com.race604.flyrefresh.internal; 2 | 3 | import android.annotation.TargetApi; 4 | import android.content.Context; 5 | import android.graphics.Canvas; 6 | import android.graphics.Color; 7 | import android.graphics.Matrix; 8 | import android.graphics.Paint; 9 | import android.graphics.Path; 10 | import android.graphics.RectF; 11 | import android.os.Build; 12 | import android.support.v4.view.animation.PathInterpolatorCompat; 13 | import android.util.AttributeSet; 14 | import android.view.View; 15 | import android.view.animation.Interpolator; 16 | 17 | import com.race604.flyrefresh.IPullHeader; 18 | import com.race604.flyrefresh.PullHeaderLayout; 19 | 20 | /** 21 | * Created by jing on 15-5-28. 22 | */ 23 | public class MountanScenceView extends View implements IPullHeader { 24 | 25 | private static final int COLOR_BACKGROUND = Color.parseColor("#7ECEC9"); 26 | private static final int COLOR_MOUNTAIN_1 = Color.parseColor("#86DAD7"); 27 | private static final int COLOR_MOUNTAIN_2 = Color.parseColor("#3C929C"); 28 | private static final int COLOR_MOUNTAIN_3 = Color.parseColor("#3E5F73"); 29 | private static final int COLOR_TREE_1_BRANCH = Color.parseColor("#1F7177"); 30 | private static final int COLOR_TREE_1_BTRUNK = Color.parseColor("#0C3E48"); 31 | private static final int COLOR_TREE_2_BRANCH = Color.parseColor("#34888F"); 32 | private static final int COLOR_TREE_2_BTRUNK = Color.parseColor("#1B6169"); 33 | private static final int COLOR_TREE_3_BRANCH = Color.parseColor("#57B1AE"); 34 | private static final int COLOR_TREE_3_BTRUNK = Color.parseColor("#62A4AD"); 35 | 36 | private static final int WIDTH = 240; 37 | private static final int HEIGHT = 180; 38 | 39 | private static final int TREE_WIDTH = 100; 40 | private static final int TREE_HEIGHT = 200; 41 | 42 | private Paint mMountPaint = new Paint(); 43 | private Paint mTrunkPaint = new Paint(); 44 | private Paint mBranchPaint = new Paint(); 45 | private Paint mBoarderPaint = new Paint(); 46 | 47 | private Path mMount1 = new Path(); 48 | private Path mMount2 = new Path(); 49 | private Path mMount3 = new Path(); 50 | private Path mTrunk = new Path(); 51 | private Path mBranch = new Path(); 52 | 53 | private float mScaleX = 5f; 54 | private float mScaleY = 5f; 55 | private float mMoveFactor = 0; 56 | private float mBounceMax = 1; 57 | private float mTreeBendFactor = Float.MAX_VALUE; 58 | private Matrix mTransMatrix = new Matrix(); 59 | 60 | public MountanScenceView(Context context) { 61 | super(context); 62 | init(); 63 | } 64 | 65 | public MountanScenceView(Context context, AttributeSet attrs) { 66 | super(context, attrs); 67 | init(); 68 | } 69 | 70 | public MountanScenceView(Context context, AttributeSet attrs, int defStyleAttr) { 71 | super(context, attrs, defStyleAttr); 72 | init(); 73 | } 74 | 75 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 76 | public MountanScenceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 77 | super(context, attrs, defStyleAttr, defStyleRes); 78 | init(); 79 | } 80 | 81 | @Override 82 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 83 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 84 | final float width = getMeasuredWidth(); 85 | final float height = getMeasuredHeight(); 86 | mScaleX = width / WIDTH; 87 | mScaleY = height / HEIGHT; 88 | 89 | updateMountainPath(mMoveFactor); 90 | updateTreePath(mMoveFactor, true); 91 | } 92 | 93 | private void init() { 94 | mMountPaint.setAntiAlias(true); 95 | mMountPaint.setStyle(Paint.Style.FILL); 96 | 97 | mTrunkPaint.setAntiAlias(true); 98 | mBranchPaint.setAntiAlias(true); 99 | mBoarderPaint.setAntiAlias(true); 100 | mBoarderPaint.setStyle(Paint.Style.STROKE); 101 | mBoarderPaint.setStrokeWidth(2); 102 | mBoarderPaint.setStrokeJoin(Paint.Join.ROUND); 103 | 104 | updateMountainPath(mMoveFactor); 105 | updateTreePath(mMoveFactor, true); 106 | } 107 | 108 | private void updateMountainPath(float factor) { 109 | 110 | mTransMatrix.reset(); 111 | mTransMatrix.setScale(mScaleX, mScaleY); 112 | 113 | int offset1 = (int) (10 * factor); 114 | mMount1.reset(); 115 | mMount1.moveTo(0, 95 + offset1); 116 | mMount1.lineTo(55, 74 + offset1); 117 | mMount1.lineTo(146, 104 + offset1); 118 | mMount1.lineTo(227, 72 + offset1); 119 | mMount1.lineTo(WIDTH, 80 + offset1); 120 | mMount1.lineTo(WIDTH, HEIGHT); 121 | mMount1.lineTo(0, HEIGHT); 122 | mMount1.close(); 123 | mMount1.transform(mTransMatrix); 124 | 125 | int offset2 = (int) (20 * factor); 126 | mMount2.reset(); 127 | mMount2.moveTo(0, 103 + offset2); 128 | mMount2.lineTo(67, 90 + offset2); 129 | mMount2.lineTo(165, 115 + offset2); 130 | mMount2.lineTo(221, 87 + offset2); 131 | mMount2.lineTo(WIDTH, 100 + offset2); 132 | mMount2.lineTo(WIDTH, HEIGHT); 133 | mMount2.lineTo(0, HEIGHT); 134 | mMount2.close(); 135 | mMount2.transform(mTransMatrix); 136 | 137 | int offset3 = (int) (30 * factor); 138 | mMount3.reset(); 139 | mMount3.moveTo(0, 114 + offset3); 140 | mMount3.cubicTo(30, 106 + offset3, 196, 97 + offset3, WIDTH, 104 + offset3); 141 | mMount3.lineTo(WIDTH, HEIGHT); 142 | mMount3.lineTo(0, HEIGHT); 143 | mMount3.close(); 144 | mMount3.transform(mTransMatrix); 145 | } 146 | 147 | private void updateTreePath(float factor, boolean force) { 148 | if (factor == mTreeBendFactor && !force) { 149 | return; 150 | } 151 | 152 | final Interpolator interpolator = PathInterpolatorCompat.create(0.8f, -0.5f * factor); 153 | 154 | final float width = TREE_WIDTH; 155 | final float height = TREE_HEIGHT; 156 | 157 | final float maxMove = width * 0.3f * factor; 158 | final float trunkSize = width * 0.05f; 159 | final float branchSize = width * 0.2f; 160 | final float x0 = width / 2; 161 | final float y0 = height; 162 | 163 | final int N = 25; 164 | final float dp = 1f / N; 165 | final float dy = -dp * height; 166 | float y = y0; 167 | float p = 0; 168 | float[] xx = new float[N + 1]; 169 | float[] yy = new float[N + 1]; 170 | for (int i = 0; i <= N; i++) { 171 | xx[i] = interpolator.getInterpolation(p) * maxMove + x0; 172 | yy[i] = y; 173 | 174 | y += dy; 175 | p += dp; 176 | } 177 | 178 | mTrunk.reset(); 179 | mTrunk.moveTo(x0 - trunkSize, y0); 180 | int max = (int) (N * 0.7f); 181 | int max1 = (int) (max * 0.5f); 182 | float diff = max - max1; 183 | for (int i = 0; i < max; i++) { 184 | if (i < max1) { 185 | mTrunk.lineTo(xx[i] - trunkSize, yy[i]); 186 | } else { 187 | mTrunk.lineTo(xx[i] - trunkSize * (max - i) / diff, yy[i]); 188 | } 189 | } 190 | 191 | for (int i = max - 1; i >= 0; i--) { 192 | if (i < max1) { 193 | mTrunk.lineTo(xx[i] + trunkSize, yy[i]); 194 | } else { 195 | mTrunk.lineTo(xx[i] + trunkSize * (max - i) / diff, yy[i]); 196 | } 197 | } 198 | mTrunk.close(); 199 | 200 | mBranch.reset(); 201 | int min = (int) (N * 0.4f); 202 | diff = N - min; 203 | 204 | mBranch.moveTo(xx[min] - branchSize, yy[min]); 205 | mBranch.addArc(new RectF(xx[min] - branchSize, yy[min] - branchSize, xx[min] + branchSize, yy[min] + branchSize), 0f, 180f); 206 | for (int i = min; i <= N; i++) { 207 | float f = (i - min) / diff; 208 | mBranch.lineTo(xx[i] - branchSize + f * f * branchSize, yy[i]); 209 | } 210 | for (int i = N; i >= min; i--) { 211 | float f = (i - min) / diff; 212 | mBranch.lineTo(xx[i] + branchSize - f * f * branchSize, yy[i]); 213 | } 214 | 215 | } 216 | 217 | @Override 218 | public void onPullProgress(PullHeaderLayout parent, int state, float factor) { 219 | 220 | float bendFactor; 221 | if (state == PullHeaderLayout.STATE_BOUNCE) { 222 | if (factor < mBounceMax) { 223 | mBounceMax = factor; 224 | } 225 | bendFactor = factor; 226 | } else { 227 | mBounceMax = factor; 228 | bendFactor = Math.max(0, factor); 229 | } 230 | 231 | mMoveFactor = Math.max(0, mBounceMax); 232 | updateMountainPath(mMoveFactor); 233 | updateTreePath(bendFactor, false); 234 | 235 | postInvalidate(); 236 | } 237 | 238 | private void drawTree(Canvas canvas, float scale, float baseX, float baseY, 239 | int colorTrunk, int colorBranch) { 240 | canvas.save(); 241 | 242 | final float dx = baseX - TREE_WIDTH * scale / 2; 243 | final float dy = baseY - TREE_HEIGHT * scale; 244 | canvas.translate(dx, dy); 245 | canvas.scale(scale, scale); 246 | 247 | mBranchPaint.setColor(colorBranch); 248 | canvas.drawPath(mBranch, mBranchPaint); 249 | mTrunkPaint.setColor(colorTrunk); 250 | canvas.drawPath(mTrunk, mTrunkPaint); 251 | mBoarderPaint.setColor(colorTrunk); 252 | canvas.drawPath(mBranch, mBoarderPaint); 253 | 254 | canvas.restore(); 255 | } 256 | 257 | @Override 258 | protected void onDraw(Canvas canvas) { 259 | super.onDraw(canvas); 260 | canvas.drawColor(COLOR_BACKGROUND); 261 | 262 | mMountPaint.setColor(COLOR_MOUNTAIN_1); 263 | canvas.drawPath(mMount1, mMountPaint); 264 | 265 | canvas.save(); 266 | canvas.scale(-1, 1, getWidth() / 2, 0); 267 | drawTree(canvas, 0.12f * mScaleX, 180 * mScaleX, (93 + 20 * mMoveFactor) * mScaleY, 268 | COLOR_TREE_3_BTRUNK, COLOR_TREE_3_BRANCH); 269 | drawTree(canvas, 0.1f * mScaleX, 200 * mScaleX, (96 + 20 * mMoveFactor) * mScaleY, 270 | COLOR_TREE_3_BTRUNK, COLOR_TREE_3_BRANCH); 271 | canvas.restore(); 272 | mMountPaint.setColor(COLOR_MOUNTAIN_2); 273 | canvas.drawPath(mMount2, mMountPaint); 274 | 275 | drawTree(canvas, 0.2f * mScaleX, 160 * mScaleX, (105 + 30 * mMoveFactor) * mScaleY, 276 | COLOR_TREE_1_BTRUNK, COLOR_TREE_1_BRANCH); 277 | 278 | drawTree(canvas, 0.14f * mScaleX, 180 * mScaleX, (105 + 30 * mMoveFactor) * mScaleY, 279 | COLOR_TREE_2_BTRUNK ,COLOR_TREE_2_BRANCH); 280 | 281 | drawTree(canvas, 0.16f * mScaleX, 140 * mScaleX, (105 + 30 * mMoveFactor) * mScaleY, 282 | COLOR_TREE_2_BTRUNK ,COLOR_TREE_2_BRANCH); 283 | 284 | mMountPaint.setColor(COLOR_MOUNTAIN_3); 285 | canvas.drawPath(mMount3, mMountPaint); 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /library/src/main/java/com/race604/flyrefresh/internal/RotatableDrawable.java: -------------------------------------------------------------------------------- 1 | package com.race604.flyrefresh.internal; 2 | 3 | import android.graphics.Canvas; 4 | import android.graphics.Rect; 5 | import android.graphics.drawable.Drawable; 6 | import android.graphics.drawable.LayerDrawable; 7 | 8 | /** 9 | * Created by jing on 15-5-21. 10 | */ 11 | public class RotatableDrawable extends LayerDrawable { 12 | 13 | private float mDegree = 0; 14 | 15 | /** 16 | * Create a new layer drawable with the list of specified layers. 17 | * 18 | * @param layers A list of drawables to use as layers in this new drawable. 19 | */ 20 | public RotatableDrawable(Drawable[] layers) { 21 | super(layers); 22 | } 23 | 24 | public RotatableDrawable(Drawable drawable) { 25 | this(new Drawable[]{drawable}); 26 | } 27 | 28 | public void setDegree(float degree) { 29 | mDegree = degree; 30 | } 31 | 32 | public float getDegree() { 33 | return mDegree; 34 | } 35 | 36 | @Override 37 | public void draw(Canvas canvas) { 38 | canvas.save(); 39 | Rect bounds = getBounds(); 40 | canvas.rotate(mDegree, bounds.centerX(), bounds.centerY()); 41 | super.draw(canvas); 42 | canvas.restore(); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /library/src/main/java/com/race604/flyrefresh/internal/SimpleAnimatorListener.java: -------------------------------------------------------------------------------- 1 | package com.race604.flyrefresh.internal; 2 | 3 | import android.animation.Animator; 4 | 5 | /** 6 | * Created by Jing on 15/5/23. 7 | */ 8 | public class SimpleAnimatorListener implements Animator.AnimatorListener { 9 | @Override 10 | public void onAnimationStart(Animator animation) { 11 | 12 | } 13 | 14 | @Override 15 | public void onAnimationEnd(Animator animation) { 16 | 17 | } 18 | 19 | @Override 20 | public void onAnimationCancel(Animator animation) { 21 | 22 | } 23 | 24 | @Override 25 | public void onAnimationRepeat(Animator animation) { 26 | 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /library/src/main/java/com/race604/flyrefresh/internal/TreeDrawable.java: -------------------------------------------------------------------------------- 1 | package com.race604.flyrefresh.internal; 2 | 3 | import android.graphics.Canvas; 4 | import android.graphics.Color; 5 | import android.graphics.ColorFilter; 6 | import android.graphics.Paint; 7 | import android.graphics.Path; 8 | import android.graphics.RectF; 9 | import android.graphics.drawable.Drawable; 10 | import android.support.v4.view.animation.PathInterpolatorCompat; 11 | import android.util.FloatMath; 12 | import android.view.animation.Interpolator; 13 | 14 | /** 15 | * Created by jing on 15-5-22. 16 | */ 17 | public class TreeDrawable extends Drawable { 18 | 19 | private static final int DEFAULT_WIDTH = 100; 20 | private static final int DEFAULT_HEIGHT = 200; 21 | private float mSizeFactor = 1; 22 | private float mBendScale = 0f; 23 | 24 | private Paint mTrunkPaint = new Paint(); 25 | private Paint mBranchPaint = new Paint(); 26 | private Path mTrunk = new Path(); 27 | private Path mBranch = new Path(); 28 | 29 | private Path mBaseLine = new Path(); 30 | private Paint mBaseLinePaint = new Paint(); 31 | 32 | public TreeDrawable() { 33 | super(); 34 | 35 | mTrunkPaint.setAntiAlias(true); 36 | mTrunkPaint.setStrokeWidth(0.1f); 37 | mTrunkPaint.setColor(Color.DKGRAY); 38 | 39 | mBranchPaint.setAntiAlias(true); 40 | mBranchPaint.setColor(Color.GREEN); 41 | 42 | mBaseLinePaint.setAntiAlias(true); 43 | mBaseLinePaint.setColor(Color.RED); 44 | mBaseLinePaint.setStrokeWidth(0.5f); 45 | mBaseLinePaint.setStyle(Paint.Style.STROKE); 46 | updateTree(); 47 | } 48 | 49 | public void setBendScale(float scale) { 50 | mBendScale = scale; 51 | updateTree(); 52 | } 53 | 54 | public void updateTree() { 55 | 56 | final Interpolator interpolator = PathInterpolatorCompat.create(0.8f, -0.6f * mBendScale); 57 | 58 | final float width = DEFAULT_WIDTH * mSizeFactor; 59 | final float height = DEFAULT_HEIGHT * mSizeFactor; 60 | 61 | final float maxMove = width * 0.45f * mBendScale; 62 | final float trunkSize = width * 0.05f; 63 | final float branchSize = width * 0.2f; 64 | final float x0 = width / 2; 65 | final float y0 = height; 66 | 67 | mBaseLine.reset(); 68 | mBaseLine.moveTo(x0, y0); 69 | final int N = 50; 70 | final float dp = 1f / N; 71 | final float dy = -dp * height; 72 | float y = y0; 73 | float p = 0; 74 | float[] xx = new float[N + 1]; 75 | float[] yy = new float[N + 1]; 76 | for (int i = 0; i <= N; i++) { 77 | xx[i] = interpolator.getInterpolation(p) * maxMove + x0; 78 | yy[i] = y; 79 | mBaseLine.lineTo(xx[i], yy[i]); 80 | 81 | y += dy; 82 | p += dp; 83 | } 84 | 85 | mTrunk.reset(); 86 | mTrunk.moveTo(x0 - trunkSize, y0); 87 | int max = (int) (N * 0.6f); 88 | int max1 = (int) (max * 0.6f); 89 | float diff = max - max1; 90 | for (int i = 0; i < max; i++) { 91 | if (i < max1) { 92 | mTrunk.lineTo(xx[i] - trunkSize, yy[i]); 93 | } else { 94 | mTrunk.lineTo(xx[i] - trunkSize * (max - i) / diff, yy[i]); 95 | } 96 | } 97 | 98 | for (int i = max - 1; i >= 0; i--) { 99 | if (i < max1) { 100 | mTrunk.lineTo(xx[i] + trunkSize, yy[i]); 101 | } else { 102 | mTrunk.lineTo(xx[i] + trunkSize * (max - i) / diff, yy[i]); 103 | } 104 | } 105 | mTrunk.close(); 106 | 107 | mBranch.reset(); 108 | int min = (int) (N * 0.4f); 109 | diff = N - min; 110 | 111 | mBranch.addArc(new RectF(xx[min] - branchSize, yy[min] - branchSize, xx[min] + branchSize, yy[min] + branchSize), 0f, 180f); 112 | for (int i = min; i <= N; i++) { 113 | mBranch.lineTo(xx[i] + branchSize - ((i - min) / diff) * branchSize, yy[i]); 114 | } 115 | for (int i = N; i >= min; i--) { 116 | mBranch.lineTo(xx[i] - branchSize + ((i - min) / diff) * branchSize, yy[i]); 117 | } 118 | 119 | mBranch.close(); 120 | } 121 | 122 | @Override 123 | public int getIntrinsicWidth() { 124 | return (int) (DEFAULT_WIDTH * mSizeFactor); 125 | } 126 | 127 | @Override 128 | public int getIntrinsicHeight() { 129 | return (int) (DEFAULT_HEIGHT * mSizeFactor); 130 | } 131 | 132 | @Override 133 | public void draw(Canvas canvas) { 134 | canvas.drawPath(mBranch, mBranchPaint); 135 | canvas.drawPath(mTrunk, mTrunkPaint); 136 | 137 | canvas.drawPath(mBaseLine, mBaseLinePaint); 138 | } 139 | 140 | @Override 141 | public void setAlpha(int alpha) { 142 | 143 | } 144 | 145 | @Override 146 | public void setColorFilter(ColorFilter cf) { 147 | 148 | } 149 | 150 | @Override 151 | public int getOpacity() { 152 | return 0; 153 | } 154 | 155 | private static float getQuadPoint(float x0, float y0, float x1, float y1, 156 | float x2, float y2, float x) { 157 | float t; 158 | float a = (x0 - 2 * x1 + x2); 159 | float b = 2 * (x1 - x0); 160 | float c = x1 - x; 161 | if (a < 1e-10) { 162 | t = (x1 - x) / (2 * (x1 - x0)); 163 | } else { 164 | t = (float) ((Math.sqrt(b * b - 4 * a * c) + b) / (2 * a)); 165 | } 166 | 167 | float t1 = 1 - t; 168 | return t1 * t1 * y0 + 2 * t * t1 * y1 + t * t * y2; 169 | } 170 | 171 | } 172 | -------------------------------------------------------------------------------- /library/src/main/java/com/race604/utils/UIUtils.java: -------------------------------------------------------------------------------- 1 | package com.race604.utils; 2 | 3 | import android.content.Context; 4 | import android.content.res.Resources; 5 | import android.graphics.Color; 6 | import android.support.v4.view.ViewCompat; 7 | import android.util.TypedValue; 8 | import android.view.View; 9 | 10 | /** 11 | * Created by Jing on 15/5/18. 12 | */ 13 | public class UIUtils { 14 | 15 | public static final int dpToPx(int dp) { 16 | return (int) (dp * Resources.getSystem().getDisplayMetrics().density); 17 | } 18 | 19 | public static int lighterColor(int color, float factor) { 20 | int red = (int) ((Color.red(color) * (1 - factor) / 255 + factor) * 255); 21 | int green = (int) ((Color.green(color) * (1 - factor) / 255 + factor) * 255); 22 | int blue = (int) ((Color.blue(color) * (1 - factor) / 255 + factor) * 255); 23 | return Color.argb(Color.alpha(color), red, green, blue); 24 | } 25 | 26 | public static int darkerColor(int color, float factor) { 27 | int a = Color.alpha( color ); 28 | int r = Color.red( color ); 29 | int g = Color.green( color ); 30 | int b = Color.blue( color ); 31 | 32 | return Color.argb( a, 33 | Math.max( (int)(r * factor), 0 ), 34 | Math.max( (int)(g * factor), 0 ), 35 | Math.max( (int)(b * factor), 0 ) ); 36 | } 37 | 38 | public static int getThemeColor(Context ctx, int attr) { 39 | TypedValue tv = new TypedValue(); 40 | if (ctx.getTheme().resolveAttribute(attr, tv, true)) { 41 | return tv.data; 42 | } 43 | return 0; 44 | } 45 | 46 | /** 47 | * helper method to get the color by attr (which is defined in the style) or by resource. 48 | * 49 | * @param ctx 50 | * @param attr 51 | * @param res 52 | * @return 53 | */ 54 | public static int getThemeColorFromAttrOrRes(Context ctx, int attr, int res) { 55 | int color = getThemeColor(ctx, attr); 56 | if (color == 0) { 57 | color = ctx.getResources().getColor(res); 58 | } 59 | return color; 60 | } 61 | 62 | public static void clearAnimator(View v) { 63 | ViewCompat.setAlpha(v, 1); 64 | ViewCompat.setScaleY(v, 1); 65 | ViewCompat.setScaleX(v, 1); 66 | ViewCompat.setTranslationY(v, 0); 67 | ViewCompat.setTranslationX(v, 0); 68 | ViewCompat.setRotation(v, 0); 69 | ViewCompat.setRotationY(v, 0); 70 | ViewCompat.setRotationX(v, 0); 71 | // @TODO https://code.google.com/p/android/issues/detail?id=80863 72 | // ViewCompat.setPivotY(v, v.getMeasuredHeight() / 2); 73 | v.setPivotY(v.getMeasuredHeight() / 2); 74 | ViewCompat.setPivotX(v, v.getMeasuredWidth() / 2); 75 | ViewCompat.animate(v).setInterpolator(null); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /library/src/main/res/mipmap-xxhdpi/ic_send.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/race604/FlyRefresh/b3049ab0ec2053b23c6a921ddd23bb6e40820ce9/library/src/main/res/mipmap-xxhdpi/ic_send.png -------------------------------------------------------------------------------- /library/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <resources> 3 | <declare-styleable name="PullHeaderLayout"> 4 | <attr name="phl_header_height" format="dimension" /> 5 | <attr name="phl_header_expand_height" format="dimension" /> 6 | <attr name="phl_header_shrink_height" format="dimension" /> 7 | <attr name="phl_header" format="reference" /> 8 | <attr name="phl_content" format="reference" /> 9 | <attr name="phl_action" format="reference" /> 10 | </declare-styleable> 11 | </resources> -------------------------------------------------------------------------------- /library/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <resources> 3 | <color name="primary">#00BCD4</color> 4 | <color name="primary_dark">#0097A7</color> 5 | <color name="primary_light">#B2EBF2</color> 6 | <color name="accent">#61B3DD</color> 7 | <color name="primary_text">#212121</color> 8 | <color name="secondary_text">#727272</color> 9 | <color name="icons">#FFFFFF</color> 10 | <color name="divider">#B6B6B6</color> 11 | </resources> -------------------------------------------------------------------------------- /library/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | <resources> 2 | <string name="app_name">Library</string> 3 | </resources> 4 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':library' 2 | --------------------------------------------------------------------------------