├── .buckconfig ├── .gitignore ├── .travis.yml ├── 01.gif ├── BUCK ├── LICENSE ├── PATENTS ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── project.properties ├── rebound-android-example ├── BUCK ├── build.gradle ├── debug.keystore ├── debug.keystore.properties ├── proguard-project.txt ├── project.properties └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── facebook │ │ └── rebound │ │ └── example │ │ └── MainActivity.java │ └── res │ ├── drawable-hdpi │ ├── landscape.png │ └── rebound_icon.png │ ├── drawable-mdpi │ ├── landscape.png │ └── rebound_icon.png │ ├── drawable-xhdpi │ ├── landscape.png │ └── rebound_icon.png │ ├── drawable-xxhdpi │ ├── landscape.png │ └── rebound_icon.png │ └── layout │ └── main.xml ├── rebound-android-playground ├── BUCK ├── build.gradle ├── debug.keystore ├── debug.keystore.properties ├── gradle.properties └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── facebook │ │ └── rebound │ │ └── playground │ │ ├── app │ │ ├── ExampleContainerView.java │ │ ├── PlaygroundActivity.java │ │ ├── RowView.java │ │ └── Util.java │ │ └── examples │ │ ├── BallExample.java │ │ ├── OrigamiExample.java │ │ ├── PhotoGalleryExample.java │ │ ├── SimpleExample.java │ │ ├── SpringChainExample.java │ │ ├── SpringScrollViewExample.java │ │ └── scrollview │ │ ├── ExampleRowView.java │ │ ├── SpringOverScroller.java │ │ └── SpringScrollView.java │ └── res │ ├── drawable-hdpi │ ├── ic_launcher.png │ ├── landscape.png │ └── rebound_icon.png │ ├── drawable-mdpi │ ├── ic_launcher.png │ ├── landscape.png │ └── rebound_icon.png │ ├── drawable-nodpi │ ├── d1.jpg │ ├── d10.jpg │ ├── d11.jpg │ ├── d12.jpg │ ├── d2.jpg │ ├── d3.jpg │ ├── d4.jpg │ ├── d5.jpg │ ├── d6.jpg │ ├── d7.jpg │ ├── d8.jpg │ └── d9.jpg │ ├── drawable-xhdpi │ ├── feedback.png │ ├── grid.png │ ├── ic_launcher.png │ ├── landscape.png │ ├── rebound_icon.png │ └── selected_photo.png │ ├── drawable-xxhdpi │ ├── ic_launcher.png │ ├── landscape.png │ └── rebound_icon.png │ ├── drawable │ ├── rebound_tiles.xml │ └── row_background.xml │ ├── layout │ ├── activity_playground.xml │ ├── cascade_effect.xml │ ├── example_row_view.xml │ ├── origami_example.xml │ ├── photo_scale_example.xml │ ├── row_view.xml │ └── spring_scroll_view_example.xml │ ├── menu │ └── playground.xml │ ├── values-w820dp │ └── dimens.xml │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── rebound-android ├── BUCK ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── facebook │ └── rebound │ ├── AndroidSpringLooperFactory.java │ ├── AnimationQueue.java │ ├── ChoreographerCompat.java │ ├── SpringChain.java │ ├── SpringSystem.java │ └── ui │ ├── SpringConfiguratorView.java │ └── Util.java ├── rebound-core ├── BUCK ├── build.gradle ├── libs │ ├── hamcrest-core-1.3.jar │ ├── junit-4.11.jar │ └── mockito-all-1.9.5.jar └── src │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── com │ │ └── facebook │ │ └── rebound │ │ ├── BaseSpringSystem.java │ │ ├── BouncyConversion.java │ │ ├── OrigamiValueConverter.java │ │ ├── SimpleSpringListener.java │ │ ├── Spring.java │ │ ├── SpringConfig.java │ │ ├── SpringConfigRegistry.java │ │ ├── SpringListener.java │ │ ├── SpringLooper.java │ │ ├── SpringSystemListener.java │ │ ├── SpringUtil.java │ │ ├── SteppingLooper.java │ │ └── SynchronousLooper.java │ └── test │ └── java │ └── com │ └── facebook │ └── rebound │ ├── SpringConfigRegistryTest.java │ ├── SpringSystemTest.java │ └── SpringTest.java ├── release_process.txt └── settings.gradle /.buckconfig: -------------------------------------------------------------------------------- 1 | [alias] 2 | android = //rebound-android:src 3 | app = //rebound-android-example:bin 4 | core = //rebound-core:src 5 | playground = //rebound-android-playground:bin 6 | rebound = //:rebound 7 | test = //rebound-core:test 8 | 9 | [project] 10 | ignore = .git -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .idea 3 | .gradle 4 | gen 5 | local.properties 6 | buck-* 7 | .buckd 8 | out 9 | build 10 | .DS_Store 11 | _site 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: oraclejdk7 3 | env: 4 | matrix: 5 | - ANDROID_SDKS=android-22,sysimg-22 ANDROID_TARGET=android-22 ANDROID_ABI=armeabi-v7a 6 | before_install: 7 | # Install base Android SDK. 8 | - sudo apt-get update -qq 9 | - if [ `uname -m` = x86_64 ]; then sudo apt-get install -qq --force-yes libgd2-xpm ia32-libs ia32-libs-multiarch > /dev/null; fi 10 | - wget http://dl.google.com/android/android-sdk_r24.3.3-linux.tgz 11 | - tar xzf android-sdk_r24.3.3-linux.tgz 12 | - export ANDROID_HOME=$PWD/android-sdk-linux 13 | - export PATH=${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools 14 | 15 | # Gradle 16 | - wget http://services.gradle.org/distributions/gradle-2.4-bin.zip 17 | - unzip gradle-2.4-bin.zip 18 | - export GRADLE_HOME=$PWD/gradle-2.4 19 | - export PATH=$GRADLE_HOME/bin:$PATH 20 | 21 | # install android build tools. 22 | - wget https://dl-ssl.google.com/android/repository/build-tools_r22.0.1-linux.zip 23 | - unzip build-tools_r22.0.1-linux.zip -d $ANDROID_HOME 24 | - mkdir -p $ANDROID_HOME/build-tools/ 25 | - mv $ANDROID_HOME/android-5.1 $ANDROID_HOME/build-tools/22.0.1 26 | - ls -la $ANDROID_HOME/build-tools/22.0.1 27 | - echo $ANDROID_HOME 28 | 29 | # Install required components. 30 | # For a full list, run `android list sdk -a --extended` 31 | # Note that sysimg-22 downloads the ARM, x86 and MIPS images (we should optimize this). 32 | # Other relevant API's 33 | - echo yes | android update sdk --filter platform-tools --no-ui --force > /dev/null 34 | - echo yes | android update sdk --filter android-18 --no-ui --force > /dev/null 35 | - echo yes | android update sdk --filter android-22 --no-ui --force > /dev/null 36 | - echo yes | android update sdk --filter sysimg-22 --no-ui --force > /dev/null 37 | - echo yes | android update sdk --filter extra-android-support --no-ui --force > /dev/null 38 | - echo yes | android update sdk --filter extra-android-m2repository --no-ui --force > /dev/null 39 | 40 | script: ./gradlew smokeTest --stacktrace 41 | -------------------------------------------------------------------------------- /01.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/rebound/cf877b0d747473e8786fc73283b4f372aab1b99f/01.gif -------------------------------------------------------------------------------- /BUCK: -------------------------------------------------------------------------------- 1 | # A special build that depends on rebound-android:src and packages into a jar file for distribution. 2 | java_binary( 3 | name = 'rebound', 4 | deps = ['//rebound-android:src'], 5 | visibility = ['PUBLIC'], 6 | ) 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD License 2 | 3 | For Rebound software 4 | 5 | Copyright (c) 2013, Facebook, Inc. 6 | All rights reserved. 7 | 8 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following 11 | disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following 14 | disclaimer in the documentation and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 17 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 19 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 22 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /PATENTS: -------------------------------------------------------------------------------- 1 | Additional Grant of Patent Rights Version 2 2 | 3 | "Software means the Rebound software distributed by Facebook, Inc. 4 | 5 | Facebook, Inc. ("Facebook") hereby grants to each recipient of the Software 6 | ("you") a perpetual, worldwide, royalty-free, non-exclusive, irrevocable 7 | (subject to the termination provision below) license under any Necessary Claims, 8 | to make, have made, use, sell, offer to sell, import, and otherwise transfer the 9 | Software. For avoidance of doubt, no license is granted under Facebook’s rights 10 | in any patent claims that are infringed by (i) modifications to the Software 11 | made by you or any third party or (ii) the Software in combination with any 12 | software or other technology. 13 | 14 | The license granted hereunder will terminate, automatically and without notice, 15 | if you (or any of your subsidiaries, corporate affiliates or agents) initiate 16 | directly or indirectly, or take a direct financial interest in, any Patent 17 | Assertion: (i) against Facebook or any of its subsidiaries or corporate 18 | affiliates, (ii) against any party if such Patent Assertion arises in whole or 19 | in part from any software, technology, product or service of Facebook or any of 20 | its subsidiaries or corporate affiliates, or (iii) against any party relating to 21 | the Software. Notwithstanding the foregoing, if Facebook or any of its 22 | subsidiaries or corporate affiliates files a lawsuit alleging patent 23 | infringement against you in the first instance, and you respond by filing a 24 | patent infringement counterclaim in that lawsuit against that party that is 25 | unrelated to the Software, the license granted hereunder will not terminate 26 | under section (i) of this paragraph due to such counterclaim. 27 | 28 | A "Necessary Claim" is a claim of a patent owned by Facebook that is necessarily 29 | infringed by the Software standing alone. 30 | 31 | A "Patent Assertion" is any lawsuit or other action alleging direct, indirect, 32 | or contributory infringement or inducement to infringe any patent, including a 33 | cross-claim or counterclaim. 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ## facebook的Rebound动库,弹性效果挺好的,估计太老了,之前一直跑不起来,修改了各种gradle配置后总算可以了 3 | 4 | GIF效果有效掉帧,类似这种弹性好过挺好的: 5 | 6 | ![](https://github.com/CarGuo/rebound/blob/master/01.gif) 7 | 8 | #Rebound 9 | [![Build Status](https://travis-ci.org/facebook/rebound.png?branch=master)](https://travis-ci.org/facebook/rebound) 10 | [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-Rebound-blue.svg?style=flat)](https://android-arsenal.com/details/1/30) 11 | 12 | ##About 13 | 14 | Rebound is a java library that 15 | models spring dynamics. Rebound spring models can be used to create animations 16 | that feel natural by introducing real world physics to your application. 17 | 18 | Rebound is not a general purpose physics library; however, spring dynamics 19 | can be used to drive a wide variety of animations. The simplicity of Rebound 20 | makes it easy to integrate and use as a building block for creating more 21 | complex components like pagers, toggles, and scrollers. 22 | 23 | For examples and usage instructions head over to: 24 | 25 | [facebook.github.io/rebound](http://facebook.github.io/rebound) 26 | 27 | If you are looking to build springy animations for the web, check out the [Javascript version](https://github.com/facebook/rebound-js). 28 | 29 | ##License 30 | 31 | BSD License 32 | 33 | For Rebound software 34 | 35 | Copyright (c) 2013, Facebook, Inc. 36 | All rights reserved. 37 | 38 | Redistribution and use in source and binary forms, with or without 39 | modification, are permitted provided that the following conditions are met: 40 | 41 | * Redistributions of source code must retain the above copyright notice, 42 | this list of conditions and the following disclaimer. 43 | 44 | * Redistributions in binary form must reproduce the above copyright notice, 45 | this list of conditions and the following disclaimer in the documentation 46 | and/or other materials provided with the distribution. 47 | 48 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 49 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 50 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 51 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 52 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 53 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 54 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 55 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 56 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 57 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 58 | POSSIBILITY OF SUCH DAMAGE. 59 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | mavenCentral() 4 | } 5 | 6 | dependencies { 7 | classpath 'com.android.tools.build:gradle:2.2.2' 8 | } 9 | } 10 | 11 | apply plugin: 'com.android.library' 12 | import com.android.builder.core.BuilderConstants 13 | 14 | android { 15 | compileOptions { 16 | sourceCompatibility JavaVersion.VERSION_1_7 17 | targetCompatibility JavaVersion.VERSION_1_7 18 | } 19 | 20 | compileSdkVersion Integer.parseInt(project.ANDROID_COMPILE_SDK_VERSION) 21 | buildToolsVersion project.ANDROID_BUILD_TOOLS_VERSION 22 | 23 | defaultConfig { 24 | minSdkVersion 11 25 | targetSdkVersion Integer.parseInt(project.ANDROID_TARGET_SDK_VERSION) 26 | } 27 | 28 | sourceSets { 29 | main { 30 | manifest.srcFile 'rebound-android/src/main/AndroidManifest.xml' 31 | java.srcDirs = ['rebound-core/src/main/java', 'rebound-android/src/main/java'] 32 | } 33 | } 34 | } 35 | 36 | // Create jar for distribution. 37 | android.libraryVariants.all { variant -> 38 | def name = variant.buildType.name 39 | if (name.equals(BuilderConstants.RELEASE)) { 40 | // Distribution Jar Task 41 | def distJarTask = task reboundDistJar(type: Jar, dependsOn: variant.javaCompile) { 42 | from variant.javaCompile.destinationDir 43 | } 44 | artifacts.add('archives', distJarTask) 45 | 46 | // Javadocs Task 47 | def javadocsTask = task androidJavadocs(type: Javadoc, dependsOn: variant.javaCompile) { 48 | source = variant.javaCompile.source 49 | def androidJar = "${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar" 50 | classpath = files(variant.javaCompile.classpath.files, androidJar) 51 | options { 52 | links "http://docs.oracle.com/javase/7/docs/api/" 53 | linksOffline "http://d.android.com/reference","${android.sdkDirectory}/docs/reference" 54 | } 55 | exclude '**/BuildConfig.java' 56 | exclude '**/R.java' 57 | } 58 | def javadocsJarTask = task androidJavadocsJar(type: Jar, dependsOn: javadocsTask) { 59 | classifier = 'javadoc' 60 | from javadocsTask.destinationDir 61 | } 62 | artifacts.add('archives', javadocsJarTask) 63 | 64 | // Sources Jar Task 65 | def sourcesJarTask = task androidSourcesJar(type: Jar) { 66 | classifier = 'sources' 67 | from variant.javaCompile.source 68 | } 69 | artifacts.add('archives', sourcesJarTask) 70 | } 71 | } 72 | 73 | task installExample(dependsOn: 'rebound-android-example:installDebug') 74 | task installPlayground(dependsOn: 'rebound-android-playground:installDebug') 75 | task smokeTest(dependsOn: [ 76 | 'rebound-core:assemble', 77 | 'rebound-core:check', 78 | 'rebound-android:assemble', 79 | 'rebound-android:check', 80 | 'rebound-android-example:assembleDebug', 81 | 'rebound-android-playground:assembleDebug', 82 | 'reboundDistJar' 83 | ]) 84 | 85 | // Configure gradle.properties to do maven builds 86 | if (!project.hasProperty('sonatypeRepo') || 87 | !project.hasProperty('sonatypeUsername') || 88 | !project.hasProperty('sonatypePassword')) { 89 | return; 90 | } 91 | 92 | // Maven Push 93 | 94 | apply plugin: 'maven' 95 | apply plugin: 'signing' 96 | 97 | version = "0.3.8" 98 | group = "com.facebook.rebound" 99 | 100 | signing { 101 | required { has("release") && gradle.taskGraph.hasTask("uploadArchives") } 102 | sign configurations.archives 103 | } 104 | 105 | uploadArchives { 106 | configuration = configurations.archives 107 | repositories.mavenDeployer { 108 | beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } 109 | 110 | repository(url: sonatypeRepo) { 111 | authentication(userName: sonatypeUsername, password: sonatypePassword) 112 | } 113 | 114 | pom.project { 115 | name 'Rebound' 116 | packaging 'jar' 117 | description 'Rebound is a simple spring dynamics animation library for Java and Android applications.' 118 | url 'http://facebook.github.io/rebound' 119 | 120 | scm { 121 | url 'scm:git@github.com:facebook/rebound.git' 122 | connection 'scm:git@github.com:facebook/rebound.git' 123 | developerConnection 'scm:git@github.com:facebook/rebound.git' 124 | } 125 | 126 | licenses { 127 | license { 128 | name 'BSD 2-Clause License' 129 | url 'https://github.com/facebook/rebound/blob/master/LICENSE' 130 | distribution 'repo' 131 | } 132 | } 133 | 134 | developers { 135 | developer { 136 | id 'will.bailey' 137 | name 'Will Bailey' 138 | email 'will.bailey@gmail.com' 139 | } 140 | } 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | VERSION_NAME=1.0 2 | VERSION_CODE=1 3 | 4 | ANDROID_BUILD_TOOLS_VERSION=22.0.1 5 | ANDROID_COMPILE_SDK_VERSION=22 6 | ANDROID_TARGET_SDK_VERSION=22 7 | ANDROID_MIN_SDK=14 -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/rebound/cf877b0d747473e8786fc73283b4f372aab1b99f/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Nov 25 11:58:14 GMT+08:00 2016 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.14.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" : '.*-> \(.*\)$'` 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 | -------------------------------------------------------------------------------- /project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system edit 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | # 10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): 11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt 12 | 13 | android.library=false 14 | # Project target. 15 | target=android-10 16 | -------------------------------------------------------------------------------- /rebound-android-example/BUCK: -------------------------------------------------------------------------------- 1 | keystore( 2 | name = 'debug_keystore', 3 | store = 'debug.keystore', 4 | properties = 'debug.keystore.properties', 5 | ) 6 | 7 | android_resource( 8 | name = 'res', 9 | res = 'src/main/res', 10 | package = 'com.facebook.rebound.example', 11 | visibility = ['//rebound-android-example:src'], 12 | ) 13 | 14 | android_library( 15 | name = 'src', 16 | srcs = glob(['src/main/java/**/*.java']), 17 | deps = [ 18 | ':res', 19 | '//rebound-core:src', 20 | '//rebound-android:src', 21 | ], 22 | ) 23 | 24 | android_binary( 25 | name = 'bin', 26 | manifest = 'src/main/AndroidManifest.xml', 27 | target = 'Google Inc.:Google APIs:19', 28 | keystore = ':debug_keystore', 29 | deps = [ 30 | ':res', 31 | ':src', 32 | ], 33 | ) 34 | 35 | project_config( 36 | src_target = ':bin', 37 | src_roots = ['src/main/java'], 38 | ) 39 | -------------------------------------------------------------------------------- /rebound-android-example/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | dependencies { 4 | compile project(':rebound-android') 5 | } 6 | 7 | android { 8 | 9 | compileOptions { 10 | sourceCompatibility JavaVersion.VERSION_1_7 11 | targetCompatibility JavaVersion.VERSION_1_7 12 | } 13 | 14 | compileSdkVersion Integer.parseInt(project.ANDROID_COMPILE_SDK_VERSION) 15 | buildToolsVersion project.ANDROID_BUILD_TOOLS_VERSION 16 | 17 | defaultConfig { 18 | minSdkVersion Integer.parseInt(project.ANDROID_MIN_SDK) 19 | targetSdkVersion Integer.parseInt(project.ANDROID_TARGET_SDK_VERSION) 20 | } 21 | 22 | signingConfigs { 23 | debug { 24 | storeFile file("debug.keystore") 25 | storePassword "android" 26 | keyAlias "androiddebugkey" 27 | keyPassword "android" 28 | } 29 | } 30 | 31 | buildTypes { 32 | debug { 33 | signingConfig signingConfigs.debug 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /rebound-android-example/debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/rebound/cf877b0d747473e8786fc73283b4f372aab1b99f/rebound-android-example/debug.keystore -------------------------------------------------------------------------------- /rebound-android-example/debug.keystore.properties: -------------------------------------------------------------------------------- 1 | key.store=debug.keystore 2 | key.alias=androiddebugkey 3 | key.store.password=android 4 | key.alias.password=android 5 | -------------------------------------------------------------------------------- /rebound-android-example/proguard-project.txt: -------------------------------------------------------------------------------- 1 | # To enable ProGuard in your project, edit project.properties 2 | # to define the proguard.config property as described in that file. 3 | # 4 | # Add project specific ProGuard rules here. 5 | # By default, the flags in this file are appended to flags specified 6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt 7 | # You can edit the include path and order by changing the ProGuard 8 | # include property in project.properties. 9 | # 10 | # For more details, see 11 | # http://developer.android.com/guide/developing/tools/proguard.html 12 | 13 | # Add any project specific keep options here: 14 | 15 | # If your project uses WebView with JS, uncomment the following 16 | # and specify the fully qualified class name to the JavaScript interface 17 | # class: 18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 19 | # public *; 20 | #} 21 | -------------------------------------------------------------------------------- /rebound-android-example/project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system edit 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | # 10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): 11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt 12 | 13 | # Project target. 14 | target=android-19 15 | android.library.reference.1=../rebound-android 16 | -------------------------------------------------------------------------------- /rebound-android-example/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 9 | 15 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /rebound-android-example/src/main/java/com/facebook/rebound/example/MainActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file provided by Facebook is for non-commercial testing and evaluation purposes only. 3 | * Facebook reserves all rights not expressly granted. 4 | * 5 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 6 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 7 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 8 | * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 9 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 10 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | 13 | package com.facebook.rebound.example; 14 | 15 | import android.app.Activity; 16 | import android.os.Bundle; 17 | import android.view.MotionEvent; 18 | import android.view.View; 19 | import android.widget.FrameLayout; 20 | 21 | import com.facebook.rebound.BaseSpringSystem; 22 | import com.facebook.rebound.SimpleSpringListener; 23 | import com.facebook.rebound.Spring; 24 | import com.facebook.rebound.SpringSystem; 25 | import com.facebook.rebound.SpringUtil; 26 | 27 | /** 28 | * This Activity presents an ImageView that scales down when pressed and returns to full size when 29 | * released. This demonstrates a very simple integrates a very Simple integration of a Rebound 30 | * Spring model to drive a bouncy animation as the photo scales up and down. You can control the 31 | * Spring configuration by tapping on the blue nub at the bottom of the screen to reveal the 32 | * SpringConfiguratorView. From this view you can adjust the tension and friction of the animation 33 | * spring and observe the effect these values have on the animation. 34 | */ 35 | public class MainActivity extends Activity { 36 | 37 | private final BaseSpringSystem mSpringSystem = SpringSystem.create(); 38 | private final ExampleSpringListener mSpringListener = new ExampleSpringListener(); 39 | private FrameLayout mRootView; 40 | private Spring mScaleSpring; 41 | private View mImageView; 42 | 43 | @Override 44 | public void onCreate(Bundle savedInstanceState) { 45 | super.onCreate(savedInstanceState); 46 | 47 | setContentView(R.layout.main); 48 | mRootView = (FrameLayout) findViewById(R.id.root_view); 49 | mImageView = mRootView.findViewById(R.id.image_view); 50 | 51 | // Create the animation spring. 52 | mScaleSpring = mSpringSystem.createSpring(); 53 | 54 | // Add an OnTouchListener to the root view. 55 | mRootView.setOnTouchListener(new View.OnTouchListener() { 56 | @Override 57 | public boolean onTouch(View v, MotionEvent event) { 58 | switch (event.getAction()) { 59 | case MotionEvent.ACTION_DOWN: 60 | // When pressed start solving the spring to 1. 61 | mScaleSpring.setEndValue(1); 62 | break; 63 | case MotionEvent.ACTION_UP: 64 | case MotionEvent.ACTION_CANCEL: 65 | // When released start solving the spring to 0. 66 | mScaleSpring.setEndValue(0); 67 | break; 68 | } 69 | return true; 70 | } 71 | }); 72 | } 73 | 74 | @Override 75 | public void onResume() { 76 | super.onResume(); 77 | // Add a listener to the spring when the Activity resumes. 78 | mScaleSpring.addListener(mSpringListener); 79 | } 80 | 81 | @Override 82 | public void onPause() { 83 | super.onPause(); 84 | // Remove the listener to the spring when the Activity pauses. 85 | mScaleSpring.removeListener(mSpringListener); 86 | } 87 | 88 | private class ExampleSpringListener extends SimpleSpringListener { 89 | @Override 90 | public void onSpringUpdate(Spring spring) { 91 | // On each update of the spring value, we adjust the scale of the image view to match the 92 | // springs new value. We use the SpringUtil linear interpolation function mapValueFromRangeToRange 93 | // to translate the spring's 0 to 1 scale to a 100% to 50% scale range and apply that to the View 94 | // with setScaleX/Y. Note that rendering is an implementation detail of the application and not 95 | // Rebound itself. If you need Gingerbread compatibility consider using NineOldAndroids to update 96 | // your view properties in a backwards compatible manner. 97 | float mappedValue = (float) SpringUtil.mapValueFromRangeToRange(spring.getCurrentValue(), 0, 1, 1, 0.5); 98 | mImageView.setScaleX(mappedValue); 99 | mImageView.setScaleY(mappedValue); 100 | } 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /rebound-android-example/src/main/res/drawable-hdpi/landscape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/rebound/cf877b0d747473e8786fc73283b4f372aab1b99f/rebound-android-example/src/main/res/drawable-hdpi/landscape.png -------------------------------------------------------------------------------- /rebound-android-example/src/main/res/drawable-hdpi/rebound_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/rebound/cf877b0d747473e8786fc73283b4f372aab1b99f/rebound-android-example/src/main/res/drawable-hdpi/rebound_icon.png -------------------------------------------------------------------------------- /rebound-android-example/src/main/res/drawable-mdpi/landscape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/rebound/cf877b0d747473e8786fc73283b4f372aab1b99f/rebound-android-example/src/main/res/drawable-mdpi/landscape.png -------------------------------------------------------------------------------- /rebound-android-example/src/main/res/drawable-mdpi/rebound_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/rebound/cf877b0d747473e8786fc73283b4f372aab1b99f/rebound-android-example/src/main/res/drawable-mdpi/rebound_icon.png -------------------------------------------------------------------------------- /rebound-android-example/src/main/res/drawable-xhdpi/landscape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/rebound/cf877b0d747473e8786fc73283b4f372aab1b99f/rebound-android-example/src/main/res/drawable-xhdpi/landscape.png -------------------------------------------------------------------------------- /rebound-android-example/src/main/res/drawable-xhdpi/rebound_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/rebound/cf877b0d747473e8786fc73283b4f372aab1b99f/rebound-android-example/src/main/res/drawable-xhdpi/rebound_icon.png -------------------------------------------------------------------------------- /rebound-android-example/src/main/res/drawable-xxhdpi/landscape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/rebound/cf877b0d747473e8786fc73283b4f372aab1b99f/rebound-android-example/src/main/res/drawable-xxhdpi/landscape.png -------------------------------------------------------------------------------- /rebound-android-example/src/main/res/drawable-xxhdpi/rebound_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/rebound/cf877b0d747473e8786fc73283b4f372aab1b99f/rebound-android-example/src/main/res/drawable-xxhdpi/rebound_icon.png -------------------------------------------------------------------------------- /rebound-android-example/src/main/res/layout/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 15 | 21 | 22 | -------------------------------------------------------------------------------- /rebound-android-playground/BUCK: -------------------------------------------------------------------------------- 1 | keystore( 2 | name = 'debug_keystore', 3 | store = 'debug.keystore', 4 | properties = 'debug.keystore.properties', 5 | ) 6 | 7 | android_resource( 8 | name = 'res', 9 | res = 'src/main/res', 10 | package = 'com.facebook.rebound.playground', 11 | visibility = ['//rebound-android-playground:src'], 12 | ) 13 | 14 | android_library( 15 | name = 'src', 16 | srcs = glob(['src/main/java/**/*.java']), 17 | deps = [ 18 | ':res', 19 | '//rebound-core:src', 20 | '//rebound-android:src', 21 | ], 22 | ) 23 | 24 | android_binary( 25 | name = 'bin', 26 | manifest = 'src/main/AndroidManifest.xml', 27 | target = 'Google Inc.:Google APIs:19', 28 | keystore = ':debug_keystore', 29 | deps = [ 30 | ':res', 31 | ':src', 32 | ], 33 | ) 34 | 35 | project_config( 36 | src_target = ':bin', 37 | src_roots = ['src/main/java'], 38 | ) 39 | -------------------------------------------------------------------------------- /rebound-android-playground/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | dependencies { 4 | compile fileTree(dir: 'libs', include: ['*.jar']) 5 | compile project(':rebound-android') 6 | } 7 | 8 | android { 9 | compileOptions { 10 | sourceCompatibility JavaVersion.VERSION_1_7 11 | targetCompatibility JavaVersion.VERSION_1_7 12 | } 13 | compileSdkVersion Integer.parseInt(project.ANDROID_COMPILE_SDK_VERSION) 14 | buildToolsVersion project.ANDROID_BUILD_TOOLS_VERSION 15 | 16 | defaultConfig { 17 | applicationId "com.facebook.rebound.androidplayground" 18 | versionCode Integer.parseInt(project.VERSION_CODE) 19 | versionName project.VERSION_NAME 20 | minSdkVersion 19 21 | targetSdkVersion Integer.parseInt(project.ANDROID_TARGET_SDK_VERSION) 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /rebound-android-playground/debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/rebound/cf877b0d747473e8786fc73283b4f372aab1b99f/rebound-android-playground/debug.keystore -------------------------------------------------------------------------------- /rebound-android-playground/debug.keystore.properties: -------------------------------------------------------------------------------- 1 | key.store=debug.keystore 2 | key.alias=androiddebugkey 3 | key.store.password=android 4 | key.alias.password=android 5 | -------------------------------------------------------------------------------- /rebound-android-playground/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Settings specified in this file will override any Gradle settings 5 | # configured through the IDE. 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 -------------------------------------------------------------------------------- /rebound-android-playground/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 13 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /rebound-android-playground/src/main/java/com/facebook/rebound/playground/app/ExampleContainerView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file provided by Facebook is for non-commercial testing and evaluation purposes only. 3 | * Facebook reserves all rights not expressly granted. 4 | * 5 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 6 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 7 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 8 | * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 9 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 10 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | 13 | package com.facebook.rebound.playground.app; 14 | 15 | import android.content.Context; 16 | import android.graphics.Color; 17 | import android.util.AttributeSet; 18 | import android.view.ViewGroup; 19 | import android.view.ViewTreeObserver; 20 | import android.widget.FrameLayout; 21 | 22 | import com.facebook.rebound.Spring; 23 | import com.facebook.rebound.SpringListener; 24 | import com.facebook.rebound.SpringSystem; 25 | import com.facebook.rebound.SpringUtil; 26 | 27 | public class ExampleContainerView extends FrameLayout implements SpringListener { 28 | private final SpringSystem mSpringSystem; 29 | private final Spring mTransitionSpring; 30 | private Callback mCallback; 31 | 32 | public void clearCallback() { 33 | mCallback = null; 34 | } 35 | 36 | public interface Callback { 37 | void onProgress(double progress); 38 | void onEnd(); 39 | } 40 | 41 | public ExampleContainerView(Context context) { 42 | this(context, null); 43 | } 44 | 45 | public ExampleContainerView(Context context, AttributeSet attrs) { 46 | this(context, attrs, 0); 47 | } 48 | 49 | public ExampleContainerView(Context context, AttributeSet attrs, int defStyle) { 50 | super(context, attrs, defStyle); 51 | mSpringSystem = SpringSystem.create(); 52 | mTransitionSpring = mSpringSystem.createSpring(); 53 | setClickable(true); 54 | setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); 55 | getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { 56 | @Override 57 | public void onGlobalLayout() { 58 | mTransitionSpring.setCurrentValue(1).setAtRest(); 59 | getViewTreeObserver().removeOnGlobalLayoutListener(this); 60 | } 61 | }); 62 | setBackgroundColor(Color.WHITE); 63 | } 64 | 65 | @Override 66 | protected void onAttachedToWindow() { 67 | super.onAttachedToWindow(); 68 | mTransitionSpring.addListener(this); 69 | } 70 | 71 | @Override 72 | protected void onDetachedFromWindow() { 73 | super.onDetachedFromWindow(); 74 | mTransitionSpring.removeListener(this); 75 | } 76 | 77 | public void reveal(boolean animated, Callback callback) { 78 | if (animated) { 79 | mTransitionSpring.setEndValue(0); 80 | } else { 81 | mTransitionSpring.setCurrentValue(0).setAtRest(); 82 | } 83 | mCallback = callback; 84 | } 85 | 86 | public void hide(boolean animated, Callback callback) { 87 | if (animated) { 88 | mTransitionSpring.setEndValue(1); 89 | } else { 90 | mTransitionSpring.setCurrentValue(1).setAtRest(); 91 | } 92 | mCallback = callback; 93 | } 94 | 95 | @Override 96 | public void onSpringUpdate(Spring spring) { 97 | double val = spring.getCurrentValue(); 98 | float xlat = (float) SpringUtil.mapValueFromRangeToRange(val, 0, 1, 0, getWidth()); 99 | setTranslationX(xlat); 100 | if (mCallback != null) { 101 | mCallback.onProgress(spring.getCurrentValue()); 102 | } 103 | } 104 | 105 | @Override 106 | public void onSpringAtRest(Spring spring) { 107 | if (mCallback != null) { 108 | mCallback.onEnd(); 109 | } 110 | } 111 | 112 | @Override 113 | public void onSpringActivate(Spring spring) { 114 | } 115 | 116 | @Override 117 | public void onSpringEndStateChange(Spring spring) { 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /rebound-android-playground/src/main/java/com/facebook/rebound/playground/app/PlaygroundActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file provided by Facebook is for non-commercial testing and evaluation purposes only. 3 | * Facebook reserves all rights not expressly granted. 4 | * 5 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 6 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 7 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 8 | * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 9 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 10 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | 13 | package com.facebook.rebound.playground.app; 14 | 15 | import android.app.Activity; 16 | import android.content.Context; 17 | import android.database.DataSetObserver; 18 | import android.os.Bundle; 19 | import android.view.LayoutInflater; 20 | import android.view.Menu; 21 | import android.view.MenuItem; 22 | import android.view.View; 23 | import android.view.ViewGroup; 24 | import android.widget.AdapterView; 25 | import android.widget.ListAdapter; 26 | import android.widget.ListView; 27 | 28 | import com.facebook.rebound.SpringUtil; 29 | import com.facebook.rebound.playground.R; 30 | import com.facebook.rebound.playground.examples.BallExample; 31 | import com.facebook.rebound.playground.examples.PhotoGalleryExample; 32 | import com.facebook.rebound.playground.examples.SpringChainExample; 33 | import com.facebook.rebound.playground.examples.OrigamiExample; 34 | import com.facebook.rebound.playground.examples.SimpleExample; 35 | import com.facebook.rebound.playground.examples.SpringScrollViewExample; 36 | 37 | import java.lang.reflect.Constructor; 38 | import java.lang.reflect.InvocationTargetException; 39 | import java.util.ArrayList; 40 | import java.util.List; 41 | 42 | public class PlaygroundActivity extends Activity implements AdapterView.OnItemClickListener { 43 | 44 | private static final List SAMPLES = new ArrayList(); 45 | 46 | static { 47 | SAMPLES.add(new Sample(SimpleExample.class, "Simple Example", "Scale a photo when you press and release")); 48 | SAMPLES.add(new Sample(SpringScrollViewExample.class, "Scroll View", "A scroll view with spring physics")); 49 | SAMPLES.add(new Sample(SpringChainExample.class, "SpringChain", "Drag any row in the list.")); 50 | SAMPLES.add(new Sample(PhotoGalleryExample.class, "Photo Gallery", "Tap on a photo to enlarge or minimize.")); 51 | SAMPLES.add(new Sample(BallExample.class, "Inertia Ball", "Toss the ball around the screen and watch it settle")); 52 | SAMPLES.add(new Sample(OrigamiExample.class, "Origami Example", "Rebound port of an Origami composition")); 53 | } 54 | 55 | private ListView mListView; 56 | private View mRootContainer; 57 | private ExampleListAdapter mAdapter; 58 | private ViewGroup mRootView; 59 | private ExampleContainerView mCurrentExample; 60 | private boolean mAnimating; 61 | private LayoutInflater mInflater; 62 | 63 | @Override 64 | protected void onCreate(Bundle savedInstanceState) { 65 | super.onCreate(savedInstanceState); 66 | setContentView(R.layout.activity_playground); 67 | mInflater = LayoutInflater.from(this); 68 | mRootView = (ViewGroup) findViewById(R.id.root); 69 | mRootContainer = findViewById(R.id.root_container); 70 | mListView = (ListView) findViewById(R.id.list_view); 71 | mAdapter = new ExampleListAdapter(); 72 | mListView.setAdapter(mAdapter); 73 | 74 | mListView.setOnItemClickListener(this); 75 | } 76 | 77 | @Override 78 | public boolean onCreateOptionsMenu(Menu menu) { 79 | getMenuInflater().inflate(R.menu.playground, menu); 80 | return true; 81 | } 82 | 83 | @Override 84 | public boolean onOptionsItemSelected(MenuItem item) { 85 | int id = item.getItemId(); 86 | if (id == R.id.action_settings) { 87 | return true; 88 | } 89 | return super.onOptionsItemSelected(item); 90 | } 91 | 92 | @Override 93 | public void onItemClick(AdapterView parent, View view, int position, long id) { 94 | if (mAnimating) { 95 | return; 96 | } 97 | 98 | Class clazz = SAMPLES.get(position).viewClass; 99 | View sampleView = null; 100 | try { 101 | Constructor ctor = clazz.getConstructor(Context.class); 102 | sampleView = ctor.newInstance(PlaygroundActivity.this); 103 | } catch (NoSuchMethodException e) { 104 | e.printStackTrace(); 105 | } catch (InvocationTargetException e) { 106 | e.printStackTrace(); 107 | } catch (InstantiationException e) { 108 | e.printStackTrace(); 109 | } catch (IllegalAccessException e) { 110 | e.printStackTrace(); 111 | } 112 | 113 | if (sampleView == null) { 114 | return; 115 | } 116 | mAnimating = true; 117 | 118 | mCurrentExample = new ExampleContainerView(this); 119 | mCurrentExample.addView(sampleView); 120 | mRootView.addView(mCurrentExample); 121 | 122 | mCurrentExample.postDelayed(new Runnable() { 123 | @Override 124 | public void run() { 125 | mCurrentExample.reveal(true, new ExampleContainerView.Callback() { 126 | @Override 127 | public void onProgress(double progress) { 128 | float scale = (float) SpringUtil.mapValueFromRangeToRange(progress, 0, 1, 0.8, 1); 129 | mRootContainer.setScaleX(scale); 130 | mRootContainer.setScaleY(scale); 131 | mRootContainer.setAlpha((float) progress); 132 | } 133 | 134 | @Override 135 | public void onEnd() { 136 | mAnimating = false; 137 | } 138 | }); 139 | } 140 | }, 100); 141 | } 142 | 143 | @Override 144 | public void onBackPressed() { 145 | if (mAnimating || mCurrentExample == null) { 146 | return; 147 | } 148 | mAnimating = true; 149 | mCurrentExample.hide(true, new ExampleContainerView.Callback() { 150 | @Override 151 | public void onProgress(double progress) { 152 | float scale = (float) SpringUtil.mapValueFromRangeToRange(progress, 0, 1, 0.8, 1); 153 | mRootContainer.setScaleX(scale); 154 | mRootContainer.setScaleY(scale); 155 | mRootContainer.setAlpha((float) progress); 156 | } 157 | 158 | @Override 159 | public void onEnd() { 160 | mAnimating = false; 161 | mCurrentExample.clearCallback(); 162 | mRootView.removeView(mCurrentExample); 163 | mCurrentExample = null; 164 | } 165 | }); 166 | } 167 | 168 | private class ExampleListAdapter implements ListAdapter { 169 | 170 | @Override 171 | public void registerDataSetObserver(DataSetObserver observer) {} 172 | 173 | @Override 174 | public void unregisterDataSetObserver(DataSetObserver observer) { 175 | } 176 | 177 | @Override 178 | public int getCount() { 179 | return SAMPLES.size(); 180 | } 181 | 182 | @Override 183 | public Object getItem(int position) { 184 | return SAMPLES.get(position); 185 | } 186 | 187 | @Override 188 | public long getItemId(int position) { 189 | return position; 190 | } 191 | 192 | @Override 193 | public boolean hasStableIds() { 194 | return true; 195 | } 196 | 197 | @Override 198 | public View getView(int position, View convertView, ViewGroup parent) { 199 | RowView rowView; 200 | if (convertView != null) { 201 | rowView = (RowView) convertView; 202 | } else { 203 | rowView = new RowView(PlaygroundActivity.this); 204 | } 205 | rowView.setText(SAMPLES.get(position).text); 206 | rowView.setSubtext(SAMPLES.get(position).subtext); 207 | return rowView; 208 | } 209 | 210 | @Override 211 | public int getItemViewType(int position) { 212 | return 0; 213 | } 214 | 215 | @Override 216 | public int getViewTypeCount() { 217 | return 1; 218 | } 219 | 220 | @Override 221 | public boolean isEmpty() { 222 | return SAMPLES.isEmpty(); 223 | } 224 | 225 | @Override 226 | public boolean areAllItemsEnabled() { 227 | return true; 228 | } 229 | 230 | @Override 231 | public boolean isEnabled(int position) { 232 | return true; 233 | } 234 | } 235 | 236 | private static class Sample { 237 | public Class viewClass; 238 | public String text; 239 | public String subtext; 240 | 241 | public Sample(Class viewClass, String text, String subtext) { 242 | this.viewClass = viewClass; 243 | this.text = text; 244 | this.subtext = subtext; 245 | } 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /rebound-android-playground/src/main/java/com/facebook/rebound/playground/app/RowView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file provided by Facebook is for non-commercial testing and evaluation purposes only. 3 | * Facebook reserves all rights not expressly granted. 4 | * 5 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 6 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 7 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 8 | * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 9 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 10 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | 13 | package com.facebook.rebound.playground.app; 14 | 15 | import android.content.Context; 16 | import android.util.AttributeSet; 17 | import android.view.LayoutInflater; 18 | import android.view.View; 19 | import android.widget.FrameLayout; 20 | import android.widget.TextView; 21 | 22 | import com.facebook.rebound.playground.R; 23 | 24 | public class RowView extends FrameLayout{ 25 | private final TextView mTextView; 26 | private final TextView mSubTextView; 27 | 28 | public RowView(Context context) { 29 | this(context, null); 30 | } 31 | 32 | public RowView(Context context, AttributeSet attrs) { 33 | this(context, attrs, 0); 34 | } 35 | 36 | public RowView(Context context, AttributeSet attrs, int defStyle) { 37 | super(context, attrs, defStyle); 38 | LayoutInflater inflater = LayoutInflater.from(context); 39 | View view = inflater.inflate(R.layout.row_view, this, false); 40 | mTextView = (TextView) view.findViewById(R.id.text_view); 41 | mSubTextView = (TextView) view.findViewById(R.id.subtext_view); 42 | setBackgroundResource(R.drawable.row_background); 43 | addView(view); 44 | } 45 | 46 | public void setText(String text) { 47 | mTextView.setText(text); 48 | } 49 | 50 | public void setSubtext(String text) { 51 | mSubTextView.setText(text); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /rebound-android-playground/src/main/java/com/facebook/rebound/playground/app/Util.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file provided by Facebook is for non-commercial testing and evaluation purposes only. 3 | * Facebook reserves all rights not expressly granted. 4 | * 5 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 6 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 7 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 8 | * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 9 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 10 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | 13 | package com.facebook.rebound.playground.app; 14 | 15 | import android.graphics.Color; 16 | 17 | import java.util.ArrayList; 18 | import java.util.List; 19 | import java.util.Random; 20 | 21 | public abstract class Util { 22 | 23 | public static final List COLORS = new ArrayList(); 24 | static { 25 | for (int i = 0; i < 10; i++) { 26 | COLORS.add(randomColor()); 27 | } 28 | } 29 | 30 | public static int randomColor() { 31 | Random random = new Random(); 32 | return Color.argb( 33 | 255, 34 | random.nextInt(255), 35 | random.nextInt(255), 36 | random.nextInt(255)); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /rebound-android-playground/src/main/java/com/facebook/rebound/playground/examples/BallExample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file provided by Facebook is for non-commercial testing and evaluation purposes only. 3 | * Facebook reserves all rights not expressly granted. 4 | * 5 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 6 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 7 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 8 | * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 9 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 10 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | 13 | package com.facebook.rebound.playground.examples; 14 | 15 | import android.animation.ArgbEvaluator; 16 | import android.content.Context; 17 | import android.graphics.Canvas; 18 | import android.graphics.Color; 19 | import android.graphics.Paint; 20 | import android.graphics.PointF; 21 | import android.util.AttributeSet; 22 | import android.view.MotionEvent; 23 | import android.view.VelocityTracker; 24 | import android.view.ViewTreeObserver; 25 | import android.widget.FrameLayout; 26 | 27 | import com.facebook.rebound.BaseSpringSystem; 28 | import com.facebook.rebound.Spring; 29 | import com.facebook.rebound.SpringConfig; 30 | import com.facebook.rebound.SpringListener; 31 | import com.facebook.rebound.SpringSystem; 32 | import com.facebook.rebound.SpringSystemListener; 33 | import com.facebook.rebound.playground.app.Util; 34 | 35 | import java.util.ArrayList; 36 | import java.util.List; 37 | 38 | public class BallExample extends FrameLayout implements SpringListener, SpringSystemListener { 39 | 40 | private final Spring xSpring; 41 | private final Spring ySpring; 42 | private final SpringSystem springSystem; 43 | private final SpringConfig COASTING; 44 | private float x; 45 | private float y; 46 | private Paint paint; 47 | private boolean dragging; 48 | private float radius = 100; 49 | private float downX; 50 | private float downY; 51 | private float lastX; 52 | private float lastY; 53 | private VelocityTracker velocityTracker; 54 | private float centerX; 55 | private float centerY; 56 | private float attractionThreshold = 200; 57 | private SpringConfig CONVERGING = SpringConfig.fromOrigamiTensionAndFriction(20, 3); 58 | private List points = new ArrayList(); 59 | private ArgbEvaluator colorEvaluator = new ArgbEvaluator(); 60 | private Integer startColor = Color.argb(255, 0, 255, 48); 61 | private Integer endColor = Color.argb(255, 0, 228, 255); 62 | 63 | public BallExample(Context context) { 64 | this(context, null); 65 | } 66 | 67 | public BallExample(Context context, AttributeSet attrs) { 68 | this(context, attrs, 0); 69 | } 70 | 71 | public BallExample(Context context, AttributeSet attrs, int defStyle) { 72 | super(context, attrs, defStyle); 73 | COASTING = SpringConfig.fromOrigamiTensionAndFriction(0, 0.5); 74 | COASTING.tension = 0; 75 | setBackgroundColor(Color.WHITE); 76 | 77 | springSystem = SpringSystem.create(); 78 | springSystem.addListener(this); 79 | xSpring = springSystem.createSpring(); 80 | ySpring = springSystem.createSpring(); 81 | xSpring.addListener(this); 82 | ySpring.addListener(this); 83 | paint = new Paint(Paint.ANTI_ALIAS_FLAG); 84 | 85 | getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { 86 | @Override 87 | public void onGlobalLayout() { 88 | centerX = getWidth() / 2f; 89 | centerY = getHeight() / 2f; 90 | 91 | xSpring.setCurrentValue(centerX).setAtRest(); 92 | ySpring.setCurrentValue(centerY).setAtRest(); 93 | getViewTreeObserver().removeOnGlobalLayoutListener(this); 94 | 95 | int offsetH = (int) ((getHeight() - (2 * radius)) % 800) / 2; 96 | int offsetW = (int) ((getWidth() - (2 * radius)) % 800) / 2; 97 | for (float i = offsetH + radius; i < getHeight() - offsetH; i += 400) { 98 | for (float j = offsetW + radius; j < getWidth() - offsetW; j += 400) { 99 | points.add(new PointF(j, i)); 100 | } 101 | } 102 | } 103 | }); 104 | } 105 | 106 | @Override 107 | protected void onDraw(Canvas canvas) { 108 | super.onDraw(canvas); 109 | 110 | int bg = Color.argb(255, 240, 240, 240); 111 | canvas.drawColor(bg); 112 | 113 | int i = 0; 114 | for (PointF point : points) { 115 | paint.setColor(Color.argb(255, 255, 255, 255)); 116 | paint.setStyle(Paint.Style.FILL); 117 | canvas.drawCircle(point.x, point.y, attractionThreshold - 80, paint); 118 | Integer color = (Integer) colorEvaluator.evaluate( 119 | (i + 1) / (float) points.size(), startColor, endColor); 120 | paint.setColor(color); 121 | paint.setStyle(Paint.Style.STROKE); 122 | paint.setStrokeWidth(20); 123 | canvas.drawCircle(point.x, point.y, attractionThreshold - 80, paint); 124 | i++; 125 | } 126 | 127 | paint.setColor(Color.argb(200, 255, 0, 0)); 128 | paint.setStyle(Paint.Style.FILL); 129 | canvas.drawCircle(x, y, radius, paint); 130 | paint.setColor(Color.WHITE); 131 | paint.setTextSize(36); 132 | paint.setTextAlign(Paint.Align.CENTER); 133 | canvas.drawText("TOUCH", x, y + 10, paint); 134 | } 135 | 136 | @Override 137 | public boolean onTouchEvent(MotionEvent event) { 138 | float touchX = event.getRawX(); 139 | float touchY = event.getRawY(); 140 | boolean ret = false; 141 | 142 | switch (event.getAction()) { 143 | case MotionEvent.ACTION_DOWN: 144 | downX = touchX; 145 | downY = touchY; 146 | lastX = downX; 147 | lastY = downY; 148 | velocityTracker = VelocityTracker.obtain(); 149 | velocityTracker.addMovement(event); 150 | if (downX > x - radius && downX < x + radius && downY > y - radius && downY < y + radius) { 151 | dragging = true; 152 | ret = true; 153 | } 154 | break; 155 | case MotionEvent.ACTION_MOVE: 156 | if (!dragging) { 157 | break; 158 | } 159 | velocityTracker.addMovement(event); 160 | float offsetX = lastX - touchX; 161 | float offsetY = lastY - touchY; 162 | xSpring.setCurrentValue(xSpring.getCurrentValue() - offsetX).setAtRest(); 163 | ySpring.setCurrentValue(ySpring.getCurrentValue() - offsetY).setAtRest(); 164 | checkConstraints(); 165 | ret = true; 166 | break; 167 | case MotionEvent.ACTION_UP: 168 | case MotionEvent.ACTION_CANCEL: 169 | if (!dragging) { 170 | break; 171 | } 172 | velocityTracker.addMovement(event); 173 | velocityTracker.computeCurrentVelocity(1000); 174 | dragging = false; 175 | ySpring.setSpringConfig(COASTING); 176 | xSpring.setSpringConfig(COASTING); 177 | downX = 0; 178 | downY = 0; 179 | xSpring.setVelocity(velocityTracker.getXVelocity()); 180 | ySpring.setVelocity(velocityTracker.getYVelocity()); 181 | ret = true; 182 | } 183 | 184 | lastX = touchX; 185 | lastY = touchY; 186 | return ret; 187 | } 188 | 189 | @Override 190 | public void onSpringUpdate(Spring spring) { 191 | x = (float) xSpring.getCurrentValue(); 192 | y = (float) ySpring.getCurrentValue(); 193 | invalidate(); 194 | } 195 | 196 | @Override 197 | public void onSpringAtRest(Spring spring) { 198 | 199 | } 200 | 201 | @Override 202 | public void onSpringActivate(Spring spring) { 203 | 204 | } 205 | 206 | @Override 207 | public void onSpringEndStateChange(Spring spring) { 208 | 209 | } 210 | 211 | @Override 212 | public void onBeforeIntegrate(BaseSpringSystem springSystem) { 213 | } 214 | 215 | @Override 216 | public void onAfterIntegrate(BaseSpringSystem springSystem) { 217 | checkConstraints(); 218 | } 219 | 220 | private void checkConstraints() { 221 | if (x + radius >= getWidth()) { 222 | xSpring.setVelocity(-xSpring.getVelocity()); 223 | xSpring.setCurrentValue(xSpring.getCurrentValue() - (x + radius - getWidth()), false); 224 | } 225 | if (x - radius <= 0) { 226 | xSpring.setVelocity(-xSpring.getVelocity()); 227 | xSpring.setCurrentValue(xSpring.getCurrentValue() - (x - radius), false); 228 | } 229 | if (y + radius >= getHeight()) { 230 | ySpring.setVelocity(-ySpring.getVelocity()); 231 | ySpring.setCurrentValue(ySpring.getCurrentValue() - (y + radius - getHeight()), false); 232 | } 233 | if (y - radius <= 0) { 234 | ySpring.setVelocity(-ySpring.getVelocity()); 235 | ySpring.setCurrentValue(ySpring.getCurrentValue() - (y - radius), false); 236 | } 237 | 238 | for (PointF point : points) { 239 | if (dist(x, y, point.x, point.y) < attractionThreshold && 240 | Math.abs(xSpring.getVelocity()) < 900 && 241 | Math.abs(ySpring.getVelocity()) < 900 && 242 | !dragging) { 243 | xSpring.setSpringConfig(CONVERGING); 244 | xSpring.setEndValue(point.x); 245 | ySpring.setSpringConfig(CONVERGING); 246 | ySpring.setEndValue(point.y); 247 | } 248 | } 249 | } 250 | 251 | private float dist(double posX, double posY, double pos2X, double pos2Y) { 252 | return (float) Math.sqrt(Math.pow(pos2X - posX, 2) + Math.pow(pos2Y - posY, 2)); 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /rebound-android-playground/src/main/java/com/facebook/rebound/playground/examples/OrigamiExample.java: -------------------------------------------------------------------------------- 1 | package com.facebook.rebound.playground.examples; 2 | 3 | import android.content.Context; 4 | import android.content.res.Resources; 5 | import android.util.AttributeSet; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.view.ViewTreeObserver; 10 | import android.widget.FrameLayout; 11 | 12 | import com.facebook.rebound.SimpleSpringListener; 13 | import com.facebook.rebound.Spring; 14 | import com.facebook.rebound.SpringConfig; 15 | import com.facebook.rebound.SpringConfigRegistry; 16 | import com.facebook.rebound.SpringSystem; 17 | import com.facebook.rebound.SpringUtil; 18 | import com.facebook.rebound.playground.R; 19 | import com.facebook.rebound.ui.SpringConfiguratorView; 20 | import com.facebook.rebound.ui.Util; 21 | 22 | public class OrigamiExample extends FrameLayout { 23 | 24 | // Create a spring configuration based on Origami values from the Photo Grid example. 25 | private static final SpringConfig ORIGAMI_SPRING_CONFIG = SpringConfig.fromOrigamiTensionAndFriction(40, 7); 26 | 27 | private final Spring mSpring; 28 | private final View mSelectedPhoto; 29 | private final View mPhotoGrid; 30 | private final View mFeedbackBar; 31 | private final SpringConfiguratorView mSpringConfiguratorView; 32 | 33 | public OrigamiExample(Context context) { 34 | this(context, null); 35 | } 36 | 37 | public OrigamiExample(Context context, AttributeSet attrs) { 38 | this(context, attrs, 0); 39 | } 40 | 41 | public OrigamiExample(Context context, AttributeSet attrs, int defStyle) { 42 | super(context, attrs, defStyle); 43 | 44 | // Inflate the layout. 45 | LayoutInflater inflater = LayoutInflater.from(context); 46 | ViewGroup root = (ViewGroup) inflater.inflate(R.layout.origami_example, this, false); 47 | addView(root); 48 | 49 | // Listen for clicks on the root view. 50 | root.setOnClickListener(new OnClickListener() { 51 | @Override 52 | public void onClick(View v) { 53 | handleClick(v); 54 | } 55 | }); 56 | 57 | // Get references to our views. 58 | mPhotoGrid = root.findViewById(R.id.grid); 59 | mSelectedPhoto = root.findViewById(R.id.selection); 60 | mFeedbackBar = root.findViewById(R.id.feedback); 61 | mSpringConfiguratorView = (SpringConfiguratorView) root.findViewById(R.id.spring_configurator); 62 | 63 | // Setup the Spring by creating a SpringSystem adding a SimpleListener that renders the 64 | // animation whenever the spring is updated. 65 | mSpring = SpringSystem 66 | .create() 67 | .createSpring() 68 | .setSpringConfig(ORIGAMI_SPRING_CONFIG) 69 | .addListener(new SimpleSpringListener() { 70 | @Override 71 | public void onSpringUpdate(Spring spring) { 72 | // Just tell the UI to update based on the springs current state. 73 | render(); 74 | } 75 | }); 76 | 77 | 78 | // Here we just wait until the first layout pass finishes and call our render method to update 79 | // the animation to the initial resting state of the spring. 80 | mPhotoGrid.getViewTreeObserver().addOnGlobalLayoutListener( 81 | new ViewTreeObserver.OnGlobalLayoutListener() { 82 | @Override 83 | public void onGlobalLayout() { 84 | render(); 85 | mPhotoGrid.getViewTreeObserver().removeOnGlobalLayoutListener(this); 86 | } 87 | }); 88 | 89 | /** Optional - Live Spring Tuning **/ 90 | 91 | // Put our config into a registry. This is optional, but it gives you the ability to live tune 92 | // the spring using the SpringConfiguratorView which will show up at the bottom of the screen. 93 | SpringConfigRegistry.getInstance().addSpringConfig(ORIGAMI_SPRING_CONFIG, "origami animation spring"); 94 | // Tell the SpringConfiguratorView that we've updated the registry to allow you to live tune the animation spring. 95 | mSpringConfiguratorView.refreshSpringConfigurations(); 96 | 97 | // Uncomment this line to actually show the SpringConfiguratorView allowing you to live tune 98 | // the Spring constants as you manipulate the UI. 99 | mSpringConfiguratorView.setVisibility(View.VISIBLE); 100 | } 101 | 102 | /** 103 | * On click we just move the springs end state from 0 to 1. This allows the Spring to act much 104 | * like an Origami switch. 105 | */ 106 | public void handleClick(View view) { 107 | if (mSpring.getEndValue() == 0) { 108 | mSpring.setEndValue(1); 109 | } else { 110 | mSpring.setEndValue(0); 111 | } 112 | } 113 | 114 | /** 115 | * This method takes the current state of the spring and maps it to all the values for each UI 116 | * element that is animated on this spring. This allows the Spring to act as a common timing 117 | * function for the animation ensuring that all element transitions are synchronized. 118 | * 119 | * You can think of these mappings as similiar to Origami transitions. 120 | * SpringUtil#mapValueFromRangeToRange converts the spring's 0 to 1 transition and maps it to the 121 | * range of animation for a property on a view such as translation, scale, rotation, and alpha. 122 | */ 123 | private void render() { 124 | Resources resources = getResources(); 125 | // Get the current spring value. 126 | double value = mSpring.getCurrentValue(); 127 | 128 | // Map the spring to the feedback bar position so that its hidden off screen and bounces in on tap. 129 | float barPosition = 130 | (float) SpringUtil.mapValueFromRangeToRange(value, 0, 1, mFeedbackBar.getHeight(), 0); 131 | mFeedbackBar.setTranslationY(barPosition); 132 | 133 | // Map the spring to the selected photo scale as it moves into and out of the grid. 134 | float selectedPhotoScale = 135 | (float) SpringUtil.mapValueFromRangeToRange(value, 0, 1, 0.33, 1); 136 | selectedPhotoScale = Math.max(selectedPhotoScale, 0); // Clamp the value so we don't go below 0. 137 | mSelectedPhoto.setScaleX(selectedPhotoScale); 138 | mSelectedPhoto.setScaleY(selectedPhotoScale); 139 | 140 | // Map the spring to the selected photo translation from its position in the grid 141 | float selectedPhotoTranslateX = (float) SpringUtil.mapValueFromRangeToRange(value, 0, 1, Util.dpToPx(-106.667f, resources), 0); 142 | float selectedPhotoTranslateY = (float) SpringUtil.mapValueFromRangeToRange(value, 0, 1, Util.dpToPx(46.667f, resources), 0); 143 | mSelectedPhoto.setTranslationX(selectedPhotoTranslateX); 144 | mSelectedPhoto.setTranslationY(selectedPhotoTranslateY); 145 | 146 | // Map the spring to the photo grid alpha as it fades to black when the photo is selected. 147 | float gridAlpha = 148 | (float) SpringUtil.mapValueFromRangeToRange(value, 0, 1, 1, 0); 149 | mPhotoGrid.setAlpha(gridAlpha); 150 | 151 | 152 | // Map the spring to the photo grid scale so that it scales down slightly as the selected photo // zooms in. 153 | float gridScale = 154 | (float) SpringUtil.mapValueFromRangeToRange(value, 0, 1, 1, 0.95); 155 | gridScale = Math.max(gridScale, 0); // Clamp the value so we don't go below 0. 156 | mPhotoGrid.setScaleX(gridScale); 157 | mPhotoGrid.setScaleY(gridScale); 158 | } 159 | 160 | } 161 | -------------------------------------------------------------------------------- /rebound-android-playground/src/main/java/com/facebook/rebound/playground/examples/PhotoGalleryExample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file provided by Facebook is for non-commercial testing and evaluation purposes only. 3 | * Facebook reserves all rights not expressly granted. 4 | * 5 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 6 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 7 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 8 | * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 9 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 10 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | 13 | package com.facebook.rebound.playground.examples; 14 | 15 | import android.content.Context; 16 | import android.content.res.Resources; 17 | import android.graphics.Point; 18 | import android.util.TypedValue; 19 | import android.view.View; 20 | import android.view.ViewTreeObserver; 21 | import android.widget.FrameLayout; 22 | import android.widget.ImageView; 23 | 24 | import com.facebook.rebound.SimpleSpringListener; 25 | import com.facebook.rebound.Spring; 26 | import com.facebook.rebound.SpringChain; 27 | import com.facebook.rebound.SpringConfig; 28 | import com.facebook.rebound.SpringListener; 29 | import com.facebook.rebound.SpringSystem; 30 | import com.facebook.rebound.SpringUtil; 31 | import com.facebook.rebound.playground.app.Util; 32 | 33 | import java.util.ArrayList; 34 | import java.util.List; 35 | 36 | public class PhotoGalleryExample extends FrameLayout implements SpringListener { 37 | 38 | private static final int ROWS = 5; 39 | private static final int COLS = 4; 40 | 41 | private final List mImageViews = new ArrayList(); 42 | private final List mPositions = new ArrayList(); 43 | private final SpringChain mSpringChain = SpringChain.create(); 44 | private final Spring mSpring = SpringSystem 45 | .create() 46 | .createSpring() 47 | .addListener(this) 48 | .setSpringConfig(SpringConfig.fromOrigamiTensionAndFriction(40, 6)); 49 | 50 | private int mActiveIndex; 51 | private int mPadding; 52 | 53 | public PhotoGalleryExample(Context context) { 54 | super(context); 55 | 56 | int viewCount = ROWS * COLS; 57 | 58 | for (int i = 0; i < viewCount; i++) { 59 | final int j = i; 60 | 61 | // Create the View. 62 | final ImageView imageView = new ImageView(context); 63 | mImageViews.add(imageView); 64 | addView(imageView); 65 | imageView.setAlpha(0f); 66 | imageView.setBackgroundColor(Util.randomColor()); 67 | imageView.setLayerType(LAYER_TYPE_HARDWARE, null); 68 | 69 | // Add an image for each view. 70 | int res = getResources().getIdentifier("d" + (i % 11 + 1), "drawable", context.getPackageName()); 71 | imageView.setScaleType(ImageView.ScaleType.CENTER_CROP); 72 | imageView.setImageResource(res); 73 | 74 | // Add a click listener to handle scaling up the view. 75 | imageView.setOnClickListener(new OnClickListener() { 76 | @Override 77 | public void onClick(View v) { 78 | int endValue = mSpring.getEndValue() == 0 ? 1 : 0; 79 | imageView.bringToFront(); 80 | mActiveIndex = j; 81 | mSpring.setEndValue(endValue); 82 | } 83 | }); 84 | 85 | // Add a spring to the SpringChain to do an entry animation. 86 | mSpringChain.addSpring(new SimpleSpringListener() { 87 | @Override 88 | public void onSpringUpdate(Spring spring) { 89 | render(); 90 | } 91 | }); 92 | } 93 | 94 | // Wait for layout. 95 | getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { 96 | @Override 97 | public void onGlobalLayout() { 98 | layout(); 99 | getViewTreeObserver().removeOnGlobalLayoutListener(this); 100 | 101 | postOnAnimationDelayed(new Runnable() { 102 | @Override 103 | public void run() { 104 | mSpringChain.setControlSpringIndex(0).getControlSpring().setEndValue(1); 105 | } 106 | }, 500); 107 | } 108 | }); 109 | 110 | } 111 | 112 | private void render() { 113 | for (int i = 0; i < mImageViews.size(); i++) { 114 | ImageView imageView = mImageViews.get(i); 115 | if (mSpring.isAtRest() && mSpring.getCurrentValue() == 0) { 116 | // Performing the initial entry transition animation. 117 | Spring spring = mSpringChain.getAllSprings().get(i); 118 | float val = (float) spring.getCurrentValue(); 119 | imageView.setScaleX(val); 120 | imageView.setScaleY(val); 121 | imageView.setAlpha(val); 122 | Point pos = mPositions.get(i); 123 | imageView.setTranslationX(pos.x); 124 | imageView.setTranslationY(pos.y); 125 | } else { 126 | // Scaling up a photo to fullscreen size. 127 | Point pos = mPositions.get(i); 128 | if (i == mActiveIndex) { 129 | float ww = imageView.getWidth(); 130 | float hh = imageView.getHeight(); 131 | float sx = getWidth() / ww; 132 | float sy = getHeight() / hh; 133 | float s = sx > sy ? sx : sy; 134 | float xlatX = (float) SpringUtil.mapValueFromRangeToRange(mSpring.getCurrentValue(), 0, 1, pos.x, 0); 135 | float xlatY = (float) SpringUtil.mapValueFromRangeToRange(mSpring.getCurrentValue(), 0, 1, pos.y, 0); 136 | imageView.setPivotX(0); 137 | imageView.setPivotY(0); 138 | imageView.setTranslationX(xlatX); 139 | imageView.setTranslationY(xlatY); 140 | 141 | float ss = (float) SpringUtil.mapValueFromRangeToRange(mSpring.getCurrentValue(), 0, 1, 1, s); 142 | imageView.setScaleX(ss); 143 | imageView.setScaleY(ss); 144 | } else { 145 | float val = (float) Math.max(0, 1 - mSpring.getCurrentValue()); 146 | imageView.setAlpha(val); 147 | } 148 | } 149 | } 150 | } 151 | 152 | private void layout() { 153 | float width = getWidth(); 154 | float height = getHeight(); 155 | 156 | // Determine the size for each image given the screen dimensions. 157 | Resources res = getResources(); 158 | mPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3, res.getDisplayMetrics()); 159 | int colWidth = (int) Math.ceil((width - 2 * mPadding) / COLS) - 2 * mPadding; 160 | int rowHeight = (int) Math.ceil((height - 2 * mPadding) / ROWS) - 2 * mPadding; 161 | 162 | // Determine the resting position for each view. 163 | int k = 0; 164 | int py = 0; 165 | for (int i = 0; i < ROWS; i++) { 166 | int px = 0; 167 | py += mPadding * 2; 168 | for (int j = 0; j < COLS; j++) { 169 | px += mPadding * 2; 170 | ImageView imageView = mImageViews.get(k); 171 | imageView.setLayoutParams(new LayoutParams(colWidth, rowHeight)); 172 | mPositions.add(new Point(px, py)); 173 | px += colWidth; 174 | k++; 175 | } 176 | py += rowHeight; 177 | } 178 | } 179 | 180 | @Override 181 | public void onSpringUpdate(Spring spring) { 182 | render(); 183 | } 184 | 185 | @Override 186 | public void onSpringAtRest(Spring spring) { 187 | 188 | } 189 | 190 | @Override 191 | public void onSpringActivate(Spring spring) { 192 | 193 | } 194 | 195 | @Override 196 | public void onSpringEndStateChange(Spring spring) { 197 | 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /rebound-android-playground/src/main/java/com/facebook/rebound/playground/examples/SimpleExample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file provided by Facebook is for non-commercial testing and evaluation purposes only. 3 | * Facebook reserves all rights not expressly granted. 4 | * 5 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 6 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 7 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 8 | * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 9 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 10 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | 13 | package com.facebook.rebound.playground.examples; 14 | 15 | import android.content.Context; 16 | import android.util.AttributeSet; 17 | import android.view.LayoutInflater; 18 | import android.view.MotionEvent; 19 | import android.view.View; 20 | import android.widget.FrameLayout; 21 | 22 | import com.facebook.rebound.BaseSpringSystem; 23 | import com.facebook.rebound.SimpleSpringListener; 24 | import com.facebook.rebound.Spring; 25 | import com.facebook.rebound.SpringConfig; 26 | import com.facebook.rebound.SpringSystem; 27 | import com.facebook.rebound.SpringUtil; 28 | import com.facebook.rebound.playground.R; 29 | 30 | public class SimpleExample extends FrameLayout { 31 | 32 | private final BaseSpringSystem mSpringSystem = SpringSystem.create(); 33 | private final ExampleSpringListener mSpringListener = new ExampleSpringListener(); 34 | private final FrameLayout mRootView; 35 | private final Spring mScaleSpring; 36 | private final View mImageView; 37 | 38 | public SimpleExample(Context context) { 39 | this(context, null); 40 | } 41 | 42 | public SimpleExample(Context context, AttributeSet attrs) { 43 | this(context, attrs, 0); 44 | } 45 | 46 | public SimpleExample(Context context, AttributeSet attrs, int defStyle) { 47 | super(context, attrs, defStyle); 48 | mScaleSpring = mSpringSystem.createSpring(); 49 | LayoutInflater inflater = LayoutInflater.from(context); 50 | mRootView = (FrameLayout) inflater.inflate(R.layout.photo_scale_example, this, false); 51 | mImageView = mRootView.findViewById(R.id.image_view); 52 | mRootView.setOnTouchListener(new View.OnTouchListener() { 53 | @Override 54 | public boolean onTouch(View v, MotionEvent event) { 55 | switch (event.getAction()) { 56 | case MotionEvent.ACTION_DOWN: 57 | mScaleSpring.setEndValue(1); 58 | break; 59 | case MotionEvent.ACTION_UP: 60 | case MotionEvent.ACTION_CANCEL: 61 | mScaleSpring.setEndValue(0); 62 | break; 63 | } 64 | return true; 65 | } 66 | }); 67 | addView(mRootView); 68 | } 69 | 70 | @Override 71 | protected void onAttachedToWindow() { 72 | mScaleSpring.addListener(mSpringListener); 73 | } 74 | 75 | @Override 76 | protected void onDetachedFromWindow() { 77 | mScaleSpring.removeListener(mSpringListener); 78 | } 79 | 80 | private class ExampleSpringListener extends SimpleSpringListener { 81 | @Override 82 | public void onSpringUpdate(Spring spring) { 83 | float mappedValue = (float) SpringUtil.mapValueFromRangeToRange(spring.getCurrentValue(), 0, 1, 1, 0.5); 84 | mImageView.setScaleX(mappedValue); 85 | mImageView.setScaleY(mappedValue); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /rebound-android-playground/src/main/java/com/facebook/rebound/playground/examples/SpringChainExample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file provided by Facebook is for non-commercial testing and evaluation purposes only. 3 | * Facebook reserves all rights not expressly granted. 4 | * 5 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 6 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 7 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 8 | * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 9 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 10 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | 13 | package com.facebook.rebound.playground.examples; 14 | 15 | import android.animation.ArgbEvaluator; 16 | import android.content.Context; 17 | import android.graphics.Color; 18 | import android.view.LayoutInflater; 19 | import android.view.MotionEvent; 20 | import android.view.VelocityTracker; 21 | import android.view.View; 22 | import android.view.ViewGroup; 23 | import android.view.ViewTreeObserver; 24 | import android.widget.FrameLayout; 25 | import android.widget.TableLayout; 26 | 27 | import com.facebook.rebound.SimpleSpringListener; 28 | import com.facebook.rebound.Spring; 29 | import com.facebook.rebound.SpringChain; 30 | import com.facebook.rebound.playground.R; 31 | 32 | import java.util.ArrayList; 33 | import java.util.List; 34 | 35 | public class SpringChainExample extends FrameLayout { 36 | 37 | private final SpringChain mSpringChain = SpringChain.create(); 38 | 39 | private final List mViews = new ArrayList(); 40 | private float mLastDownX; 41 | 42 | /** Touch handling **/ 43 | private View mLastDraggingView; 44 | private float mLastDownXlat; 45 | private int mActivePointerId; 46 | private VelocityTracker mVelocityTracker; 47 | 48 | public SpringChainExample(Context context) { 49 | super(context); 50 | 51 | LayoutInflater inflater = LayoutInflater.from(context); 52 | ViewGroup container = (ViewGroup) inflater.inflate(R.layout.cascade_effect, this, false); 53 | addView(container); 54 | ViewGroup rootView = (ViewGroup) container.findViewById(R.id.root); 55 | int bgColor = Color.argb(255, 17, 148, 231); 56 | setBackgroundColor(bgColor); 57 | rootView.setBackgroundResource(R.drawable.rebound_tiles); 58 | 59 | int startColor = Color.argb(255, 255, 64, 230); 60 | int endColor = Color.argb(255, 255, 230, 64); 61 | ArgbEvaluator evaluator = new ArgbEvaluator(); 62 | int viewCount = 10; 63 | for (int i = 0; i < viewCount; i++) { 64 | final View view = new View(context); 65 | view.setLayoutParams( 66 | new TableLayout.LayoutParams( 67 | ViewGroup.LayoutParams.MATCH_PARENT, 68 | ViewGroup.LayoutParams.WRAP_CONTENT, 69 | 1f)); 70 | mSpringChain.addSpring(new SimpleSpringListener() { 71 | @Override 72 | public void onSpringUpdate(Spring spring) { 73 | float value = (float) spring.getCurrentValue(); 74 | view.setTranslationX(value); 75 | } 76 | }); 77 | int color = (Integer) evaluator.evaluate((float) i / (float) viewCount, startColor, endColor); 78 | view.setBackgroundColor(color); 79 | view.setOnTouchListener(new OnTouchListener() { 80 | @Override 81 | public boolean onTouch(View v, MotionEvent event) { 82 | return handleRowTouch(v, event); 83 | } 84 | }); 85 | mViews.add(view); 86 | rootView.addView(view); 87 | } 88 | 89 | getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { 90 | @Override 91 | public void onGlobalLayout() { 92 | getViewTreeObserver().removeOnGlobalLayoutListener(this); 93 | List springs = mSpringChain.getAllSprings(); 94 | for (int i = 0; i < springs.size(); i++) { 95 | springs.get(i).setCurrentValue(-mViews.get(i).getWidth()); 96 | } 97 | postDelayed(new Runnable() { 98 | @Override 99 | public void run() { 100 | mSpringChain 101 | .setControlSpringIndex(0) 102 | .getControlSpring() 103 | .setEndValue(0); 104 | } 105 | }, 500); 106 | } 107 | }); 108 | } 109 | 110 | private boolean handleRowTouch(View view, MotionEvent event) { 111 | int action = event.getAction(); 112 | switch (action & MotionEvent.ACTION_MASK) { 113 | case MotionEvent.ACTION_DOWN: 114 | 115 | mActivePointerId = event.getPointerId(0); 116 | mLastDownXlat = view.getTranslationX(); 117 | mLastDraggingView = view; 118 | mLastDownX = event.getRawX(); 119 | 120 | mVelocityTracker = VelocityTracker.obtain(); 121 | mVelocityTracker.addMovement(event); 122 | 123 | int idx = mViews.indexOf(mLastDraggingView); 124 | mSpringChain 125 | .setControlSpringIndex(idx) 126 | .getControlSpring() 127 | .setCurrentValue(mLastDownXlat); 128 | break; 129 | case MotionEvent.ACTION_MOVE: { 130 | final int pointerIndex = event.findPointerIndex(mActivePointerId); 131 | if (pointerIndex != -1) { 132 | final int location[] = {0, 0}; 133 | view.getLocationOnScreen(location); 134 | float x = event.getX(pointerIndex) + location[0]; 135 | float offset = x - mLastDownX + mLastDownXlat; 136 | mSpringChain 137 | .getControlSpring() 138 | .setCurrentValue(offset); 139 | mVelocityTracker.addMovement(event); 140 | } 141 | break; 142 | } 143 | case MotionEvent.ACTION_CANCEL: 144 | case MotionEvent.ACTION_UP: 145 | final int pointerIndex = event.findPointerIndex(mActivePointerId); 146 | if (pointerIndex != -1) { 147 | mVelocityTracker.addMovement(event); 148 | mVelocityTracker.computeCurrentVelocity(1000); 149 | mSpringChain 150 | .getControlSpring() 151 | .setVelocity(mVelocityTracker.getXVelocity()) 152 | .setEndValue(0); 153 | } 154 | break; 155 | } 156 | return true; 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /rebound-android-playground/src/main/java/com/facebook/rebound/playground/examples/SpringScrollViewExample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file provided by Facebook is for non-commercial testing and evaluation purposes only. 3 | * Facebook reserves all rights not expressly granted. 4 | * 5 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 6 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 7 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 8 | * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 9 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 10 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | 13 | package com.facebook.rebound.playground.examples; 14 | 15 | import android.animation.ArgbEvaluator; 16 | import android.content.Context; 17 | import android.graphics.Color; 18 | import android.view.LayoutInflater; 19 | import android.view.ViewGroup; 20 | import android.widget.FrameLayout; 21 | 22 | import com.facebook.rebound.playground.R; 23 | import com.facebook.rebound.playground.examples.scrollview.ExampleRowView; 24 | 25 | public class SpringScrollViewExample extends FrameLayout { 26 | 27 | private final int ROW_COUNT = 50; 28 | 29 | public SpringScrollViewExample(Context context) { 30 | super(context); 31 | 32 | LayoutInflater inflater = LayoutInflater.from(context); 33 | ViewGroup root = (ViewGroup) inflater.inflate(R.layout.spring_scroll_view_example, this, false); 34 | ViewGroup content = (ViewGroup) root.findViewById(R.id.content_view); 35 | addView(root); 36 | setBackgroundColor(Color.argb(255, 50, 50, 50)); 37 | 38 | int startColor = Color.argb(255, 255, 64, 230); 39 | int endColor = Color.argb(255, 0, 174, 255); 40 | ArgbEvaluator evaluator = new ArgbEvaluator(); 41 | for (int i = 0; i < ROW_COUNT; i++) { 42 | ExampleRowView exampleRowView = new ExampleRowView(context); 43 | Integer color = (Integer) evaluator.evaluate((float) i / (float) ROW_COUNT, startColor, endColor); 44 | exampleRowView.setText("Row " + i); 45 | exampleRowView.setBackgroundColor(color); 46 | content.addView(exampleRowView); 47 | } 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /rebound-android-playground/src/main/java/com/facebook/rebound/playground/examples/scrollview/ExampleRowView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file provided by Facebook is for non-commercial testing and evaluation purposes only. 3 | * Facebook reserves all rights not expressly granted. 4 | * 5 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 6 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 7 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 8 | * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 9 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 10 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | 13 | package com.facebook.rebound.playground.examples.scrollview; 14 | 15 | import android.content.Context; 16 | import android.view.LayoutInflater; 17 | import android.view.ViewGroup; 18 | import android.widget.FrameLayout; 19 | import android.widget.TextView; 20 | 21 | import com.facebook.rebound.playground.R; 22 | 23 | public class ExampleRowView extends FrameLayout { 24 | private final TextView mTextView; 25 | 26 | public ExampleRowView(Context context) { 27 | super(context); 28 | LayoutInflater inflater = LayoutInflater.from(context); 29 | ViewGroup view = (ViewGroup) inflater.inflate(R.layout.example_row_view, this, false); 30 | mTextView = (TextView) view.findViewById(R.id.text_view); 31 | addView(view); 32 | } 33 | 34 | public void setText(String text) { 35 | mTextView.setText(text); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /rebound-android-playground/src/main/java/com/facebook/rebound/playground/examples/scrollview/SpringOverScroller.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file provided by Facebook is for non-commercial testing and evaluation purposes only. 3 | * Facebook reserves all rights not expressly granted. 4 | * 5 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 6 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 7 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 8 | * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 9 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 10 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | 13 | package com.facebook.rebound.playground.examples.scrollview; 14 | 15 | import android.content.Context; 16 | import android.util.Log; 17 | import android.view.animation.Interpolator; 18 | 19 | import com.facebook.rebound.Spring; 20 | import com.facebook.rebound.SpringConfig; 21 | import com.facebook.rebound.SpringConfigRegistry; 22 | import com.facebook.rebound.SpringListener; 23 | import com.facebook.rebound.SpringSystem; 24 | 25 | public class SpringOverScroller implements SpringListener { 26 | 27 | private final SpringSystem mSpringSystem; 28 | private final Spring mSpringX; 29 | private final Spring mSpringY; 30 | 31 | private static final SpringConfig COASTING_CONFIG = 32 | SpringConfig.fromOrigamiTensionAndFriction(0, 0.5); 33 | private static final SpringConfig RUBBERBANDING_CONFIG = 34 | SpringConfig.fromOrigamiTensionAndFriction(20, 9); 35 | 36 | public SpringOverScroller(Context context) { 37 | this(context, null); 38 | } 39 | 40 | public SpringOverScroller(Context context, Interpolator interpolator) { 41 | this(context, interpolator, true); 42 | } 43 | 44 | public SpringOverScroller(Context context, Interpolator interpolator, 45 | float bounceCoefficientX, float bounceCoefficientY) { 46 | this(context, interpolator, true); 47 | } 48 | 49 | public SpringOverScroller(Context context, Interpolator interpolator, 50 | float bounceCoefficientX, float bounceCoefficientY, boolean flywheel) { 51 | this(context, interpolator, flywheel); 52 | } 53 | 54 | public SpringOverScroller(Context context, Interpolator interpolator, boolean flywheel) { 55 | mSpringSystem = SpringSystem.create(); 56 | mSpringX = mSpringSystem 57 | .createSpring() 58 | .addListener(this); 59 | mSpringY = mSpringSystem 60 | .createSpring() 61 | .addListener(this); 62 | SpringConfigRegistry 63 | .getInstance() 64 | .addSpringConfig(RUBBERBANDING_CONFIG, "rubber-banding"); 65 | SpringConfigRegistry 66 | .getInstance() 67 | .addSpringConfig(COASTING_CONFIG, "coasting"); 68 | } 69 | 70 | public final void setFriction(float friction) { 71 | } 72 | 73 | public final boolean isFinished() { 74 | return mSpringX.isAtRest() && mSpringY.isAtRest(); 75 | } 76 | 77 | public final int getCurrX() { 78 | return (int) Math.round(mSpringX.getCurrentValue()); 79 | } 80 | 81 | public final int getCurrY() { 82 | return (int) Math.round(mSpringY.getCurrentValue()); 83 | } 84 | 85 | public float getCurrVelocity() { 86 | double velX = mSpringX.getVelocity(); 87 | double velY = mSpringX.getVelocity(); 88 | return (int) Math.sqrt(velX * velX + velY * velY); 89 | } 90 | 91 | public final int getStartX() { 92 | return (int) Math.round(mSpringX.getStartValue()); 93 | } 94 | 95 | public final int getStartY() { 96 | return (int) Math.round(mSpringY.getStartValue()); 97 | } 98 | 99 | public final int getFinalX() { 100 | return (int) Math.round(mSpringX.getEndValue()); 101 | } 102 | 103 | public final int getFinalY() { 104 | return (int) Math.round(mSpringY.getEndValue()); 105 | } 106 | 107 | public boolean computeScrollOffset() { 108 | return !(mSpringX.isAtRest() && mSpringY.isAtRest()); 109 | } 110 | 111 | public void startScroll(int startX, int startY, int dx, int dy) { 112 | mSpringX.setCurrentValue(startX).setEndValue(dx); 113 | mSpringY.setCurrentValue(startY).setEndValue(dy); 114 | } 115 | 116 | public boolean springBack(int startX, int startY, int minX, int maxX, int minY, int maxY) { 117 | mSpringX.setCurrentValue(startX, false); 118 | mSpringY.setCurrentValue(startY, false); 119 | if (startX > maxX || startX < minX) { 120 | if (startX > maxX) { 121 | mSpringX.setEndValue(maxX); 122 | } else if (startX < minX) { 123 | mSpringX.setEndValue(minX); 124 | } 125 | mSpringX.setSpringConfig(RUBBERBANDING_CONFIG); 126 | return true; 127 | } 128 | if (startY > maxY || startY < minY) { 129 | if (startY > maxY) { 130 | mSpringY.setEndValue(maxY); 131 | } else if (startY < minY) { 132 | mSpringY.setEndValue(minY); 133 | } 134 | mSpringY.setSpringConfig(RUBBERBANDING_CONFIG); 135 | return true; 136 | } 137 | return true; 138 | } 139 | 140 | public void fling(int startX, int startY, int velocityX, int velocityY, 141 | int minX, int maxX, int minY, int maxY, int overX, int overY) { 142 | mSpringX 143 | .setSpringConfig(COASTING_CONFIG) 144 | .setCurrentValue(startX) 145 | .setVelocity(velocityX); 146 | mSpringY 147 | .setSpringConfig(COASTING_CONFIG) 148 | .setCurrentValue(startY) 149 | .setVelocity(velocityY); 150 | } 151 | 152 | public void notifyHorizontalEdgeReached(int startX, int finalX, int overX) { 153 | } 154 | 155 | public void notifyVerticalEdgeReached(int startY, int finalY, int overY) { 156 | } 157 | 158 | public void abortAnimation() { 159 | mSpringX.setAtRest(); 160 | mSpringY.setAtRest(); 161 | } 162 | 163 | @Override 164 | public void onSpringUpdate(Spring spring) { 165 | Log.d("WSB", "cv:" + spring.getCurrentValue()); 166 | } 167 | 168 | @Override 169 | public void onSpringAtRest(Spring spring) { 170 | 171 | } 172 | 173 | @Override 174 | public void onSpringActivate(Spring spring) { 175 | 176 | } 177 | 178 | @Override 179 | public void onSpringEndStateChange(Spring spring) { 180 | 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /rebound-android-playground/src/main/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/rebound/cf877b0d747473e8786fc73283b4f372aab1b99f/rebound-android-playground/src/main/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /rebound-android-playground/src/main/res/drawable-hdpi/landscape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/rebound/cf877b0d747473e8786fc73283b4f372aab1b99f/rebound-android-playground/src/main/res/drawable-hdpi/landscape.png -------------------------------------------------------------------------------- /rebound-android-playground/src/main/res/drawable-hdpi/rebound_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/rebound/cf877b0d747473e8786fc73283b4f372aab1b99f/rebound-android-playground/src/main/res/drawable-hdpi/rebound_icon.png -------------------------------------------------------------------------------- /rebound-android-playground/src/main/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/rebound/cf877b0d747473e8786fc73283b4f372aab1b99f/rebound-android-playground/src/main/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /rebound-android-playground/src/main/res/drawable-mdpi/landscape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/rebound/cf877b0d747473e8786fc73283b4f372aab1b99f/rebound-android-playground/src/main/res/drawable-mdpi/landscape.png -------------------------------------------------------------------------------- /rebound-android-playground/src/main/res/drawable-mdpi/rebound_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/rebound/cf877b0d747473e8786fc73283b4f372aab1b99f/rebound-android-playground/src/main/res/drawable-mdpi/rebound_icon.png -------------------------------------------------------------------------------- /rebound-android-playground/src/main/res/drawable-nodpi/d1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/rebound/cf877b0d747473e8786fc73283b4f372aab1b99f/rebound-android-playground/src/main/res/drawable-nodpi/d1.jpg -------------------------------------------------------------------------------- /rebound-android-playground/src/main/res/drawable-nodpi/d10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/rebound/cf877b0d747473e8786fc73283b4f372aab1b99f/rebound-android-playground/src/main/res/drawable-nodpi/d10.jpg -------------------------------------------------------------------------------- /rebound-android-playground/src/main/res/drawable-nodpi/d11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/rebound/cf877b0d747473e8786fc73283b4f372aab1b99f/rebound-android-playground/src/main/res/drawable-nodpi/d11.jpg -------------------------------------------------------------------------------- /rebound-android-playground/src/main/res/drawable-nodpi/d12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/rebound/cf877b0d747473e8786fc73283b4f372aab1b99f/rebound-android-playground/src/main/res/drawable-nodpi/d12.jpg -------------------------------------------------------------------------------- /rebound-android-playground/src/main/res/drawable-nodpi/d2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/rebound/cf877b0d747473e8786fc73283b4f372aab1b99f/rebound-android-playground/src/main/res/drawable-nodpi/d2.jpg -------------------------------------------------------------------------------- /rebound-android-playground/src/main/res/drawable-nodpi/d3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/rebound/cf877b0d747473e8786fc73283b4f372aab1b99f/rebound-android-playground/src/main/res/drawable-nodpi/d3.jpg -------------------------------------------------------------------------------- /rebound-android-playground/src/main/res/drawable-nodpi/d4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/rebound/cf877b0d747473e8786fc73283b4f372aab1b99f/rebound-android-playground/src/main/res/drawable-nodpi/d4.jpg -------------------------------------------------------------------------------- /rebound-android-playground/src/main/res/drawable-nodpi/d5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/rebound/cf877b0d747473e8786fc73283b4f372aab1b99f/rebound-android-playground/src/main/res/drawable-nodpi/d5.jpg -------------------------------------------------------------------------------- /rebound-android-playground/src/main/res/drawable-nodpi/d6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/rebound/cf877b0d747473e8786fc73283b4f372aab1b99f/rebound-android-playground/src/main/res/drawable-nodpi/d6.jpg -------------------------------------------------------------------------------- /rebound-android-playground/src/main/res/drawable-nodpi/d7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/rebound/cf877b0d747473e8786fc73283b4f372aab1b99f/rebound-android-playground/src/main/res/drawable-nodpi/d7.jpg -------------------------------------------------------------------------------- /rebound-android-playground/src/main/res/drawable-nodpi/d8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/rebound/cf877b0d747473e8786fc73283b4f372aab1b99f/rebound-android-playground/src/main/res/drawable-nodpi/d8.jpg -------------------------------------------------------------------------------- /rebound-android-playground/src/main/res/drawable-nodpi/d9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/rebound/cf877b0d747473e8786fc73283b4f372aab1b99f/rebound-android-playground/src/main/res/drawable-nodpi/d9.jpg -------------------------------------------------------------------------------- /rebound-android-playground/src/main/res/drawable-xhdpi/feedback.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/rebound/cf877b0d747473e8786fc73283b4f372aab1b99f/rebound-android-playground/src/main/res/drawable-xhdpi/feedback.png -------------------------------------------------------------------------------- /rebound-android-playground/src/main/res/drawable-xhdpi/grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/rebound/cf877b0d747473e8786fc73283b4f372aab1b99f/rebound-android-playground/src/main/res/drawable-xhdpi/grid.png -------------------------------------------------------------------------------- /rebound-android-playground/src/main/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/rebound/cf877b0d747473e8786fc73283b4f372aab1b99f/rebound-android-playground/src/main/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /rebound-android-playground/src/main/res/drawable-xhdpi/landscape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/rebound/cf877b0d747473e8786fc73283b4f372aab1b99f/rebound-android-playground/src/main/res/drawable-xhdpi/landscape.png -------------------------------------------------------------------------------- /rebound-android-playground/src/main/res/drawable-xhdpi/rebound_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/rebound/cf877b0d747473e8786fc73283b4f372aab1b99f/rebound-android-playground/src/main/res/drawable-xhdpi/rebound_icon.png -------------------------------------------------------------------------------- /rebound-android-playground/src/main/res/drawable-xhdpi/selected_photo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/rebound/cf877b0d747473e8786fc73283b4f372aab1b99f/rebound-android-playground/src/main/res/drawable-xhdpi/selected_photo.png -------------------------------------------------------------------------------- /rebound-android-playground/src/main/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/rebound/cf877b0d747473e8786fc73283b4f372aab1b99f/rebound-android-playground/src/main/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /rebound-android-playground/src/main/res/drawable-xxhdpi/landscape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/rebound/cf877b0d747473e8786fc73283b4f372aab1b99f/rebound-android-playground/src/main/res/drawable-xxhdpi/landscape.png -------------------------------------------------------------------------------- /rebound-android-playground/src/main/res/drawable-xxhdpi/rebound_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/rebound/cf877b0d747473e8786fc73283b4f372aab1b99f/rebound-android-playground/src/main/res/drawable-xxhdpi/rebound_icon.png -------------------------------------------------------------------------------- /rebound-android-playground/src/main/res/drawable/rebound_tiles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | -------------------------------------------------------------------------------- /rebound-android-playground/src/main/res/drawable/row_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /rebound-android-playground/src/main/res/layout/activity_playground.xml: -------------------------------------------------------------------------------- 1 | 10 | 17 | 23 | 29 | 30 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /rebound-android-playground/src/main/res/layout/cascade_effect.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 14 | 19 | 20 | -------------------------------------------------------------------------------- /rebound-android-playground/src/main/res/layout/example_row_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 14 | -------------------------------------------------------------------------------- /rebound-android-playground/src/main/res/layout/origami_example.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 16 | 23 | 30 | 37 | -------------------------------------------------------------------------------- /rebound-android-playground/src/main/res/layout/photo_scale_example.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 15 | 21 | 22 | -------------------------------------------------------------------------------- /rebound-android-playground/src/main/res/layout/row_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 19 | 27 | -------------------------------------------------------------------------------- /rebound-android-playground/src/main/res/layout/spring_scroll_view_example.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 11 | 17 | 18 | 23 | -------------------------------------------------------------------------------- /rebound-android-playground/src/main/res/menu/playground.xml: -------------------------------------------------------------------------------- 1 | 4 | 8 | 9 | -------------------------------------------------------------------------------- /rebound-android-playground/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /rebound-android-playground/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #efefef 4 | -------------------------------------------------------------------------------- /rebound-android-playground/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /rebound-android-playground/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Playground 5 | Settings 6 | 7 | 8 | -------------------------------------------------------------------------------- /rebound-android-playground/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /rebound-android/BUCK: -------------------------------------------------------------------------------- 1 | android_library( 2 | name = 'src', 3 | srcs = glob(['src/main/java/**/*.java']), 4 | deps = ['//rebound-core:src'], 5 | visibility = ['PUBLIC'], 6 | ) 7 | 8 | project_config( 9 | src_target = '//rebound-android:src', 10 | src_roots = ['src/main/java'], 11 | ) 12 | -------------------------------------------------------------------------------- /rebound-android/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | dependencies { 4 | compile project(':rebound-core') 5 | } 6 | 7 | android { 8 | compileOptions { 9 | sourceCompatibility JavaVersion.VERSION_1_7 10 | targetCompatibility JavaVersion.VERSION_1_7 11 | } 12 | compileSdkVersion Integer.parseInt(project.ANDROID_COMPILE_SDK_VERSION) 13 | buildToolsVersion project.ANDROID_BUILD_TOOLS_VERSION 14 | 15 | defaultConfig { 16 | minSdkVersion 11 17 | targetSdkVersion Integer.parseInt(project.ANDROID_TARGET_SDK_VERSION) 18 | } 19 | 20 | lintOptions { 21 | abortOnError false 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /rebound-android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /rebound-android/src/main/java/com/facebook/rebound/AndroidSpringLooperFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | package com.facebook.rebound; 12 | 13 | import android.annotation.TargetApi; 14 | import android.os.Build; 15 | import android.os.Handler; 16 | import android.os.SystemClock; 17 | import android.view.Choreographer; 18 | 19 | /** 20 | * Android version of the spring looper that uses the most appropriate frame callback mechanism 21 | * available. It uses Android's {@link Choreographer} when available, otherwise it uses a 22 | * {@link Handler}. 23 | */ 24 | abstract class AndroidSpringLooperFactory { 25 | 26 | /** 27 | * Create an Android {@link com.facebook.rebound.SpringLooper} for the detected Android platform. 28 | * @return a SpringLooper 29 | */ 30 | public static SpringLooper createSpringLooper() { 31 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 32 | return ChoreographerAndroidSpringLooper.create(); 33 | } else { 34 | return LegacyAndroidSpringLooper.create(); 35 | } 36 | } 37 | 38 | /** 39 | * The base implementation of the Android spring looper, using a {@link Handler} for the 40 | * frame callbacks. 41 | */ 42 | private static class LegacyAndroidSpringLooper extends SpringLooper { 43 | 44 | private final Handler mHandler; 45 | private final Runnable mLooperRunnable; 46 | private boolean mStarted; 47 | private long mLastTime; 48 | 49 | /** 50 | * @return an Android spring looper using a new {@link Handler} instance 51 | */ 52 | public static SpringLooper create() { 53 | return new LegacyAndroidSpringLooper(new Handler()); 54 | } 55 | 56 | public LegacyAndroidSpringLooper(Handler handler) { 57 | mHandler = handler; 58 | mLooperRunnable = new Runnable() { 59 | @Override 60 | public void run() { 61 | if (!mStarted || mSpringSystem == null) { 62 | return; 63 | } 64 | long currentTime = SystemClock.uptimeMillis(); 65 | mSpringSystem.loop(currentTime - mLastTime); 66 | mLastTime = currentTime; 67 | mHandler.post(mLooperRunnable); 68 | } 69 | }; 70 | } 71 | 72 | @Override 73 | public void start() { 74 | if (mStarted) { 75 | return; 76 | } 77 | mStarted = true; 78 | mLastTime = SystemClock.uptimeMillis(); 79 | mHandler.removeCallbacks(mLooperRunnable); 80 | mHandler.post(mLooperRunnable); 81 | } 82 | 83 | @Override 84 | public void stop() { 85 | mStarted = false; 86 | mHandler.removeCallbacks(mLooperRunnable); 87 | } 88 | } 89 | 90 | /** 91 | * The Jelly Bean and up implementation of the spring looper that uses Android's 92 | * {@link Choreographer} instead of a {@link Handler} 93 | */ 94 | @TargetApi(Build.VERSION_CODES.JELLY_BEAN) 95 | private static class ChoreographerAndroidSpringLooper extends SpringLooper { 96 | 97 | private final Choreographer mChoreographer; 98 | private final Choreographer.FrameCallback mFrameCallback; 99 | private boolean mStarted; 100 | private long mLastTime; 101 | 102 | /** 103 | * @return an Android spring choreographer using the system {@link Choreographer} 104 | */ 105 | public static ChoreographerAndroidSpringLooper create() { 106 | return new ChoreographerAndroidSpringLooper(Choreographer.getInstance()); 107 | } 108 | 109 | public ChoreographerAndroidSpringLooper(Choreographer choreographer) { 110 | mChoreographer = choreographer; 111 | mFrameCallback = new Choreographer.FrameCallback() { 112 | @Override 113 | public void doFrame(long frameTimeNanos) { 114 | if (!mStarted || mSpringSystem == null) { 115 | return; 116 | } 117 | long currentTime = SystemClock.uptimeMillis(); 118 | mSpringSystem.loop(currentTime - mLastTime); 119 | mLastTime = currentTime; 120 | mChoreographer.postFrameCallback(mFrameCallback); 121 | } 122 | }; 123 | } 124 | 125 | @Override 126 | public void start() { 127 | if (mStarted) { 128 | return; 129 | } 130 | mStarted = true; 131 | mLastTime = SystemClock.uptimeMillis(); 132 | mChoreographer.removeFrameCallback(mFrameCallback); 133 | mChoreographer.postFrameCallback(mFrameCallback); 134 | } 135 | 136 | @Override 137 | public void stop() { 138 | mStarted = false; 139 | mChoreographer.removeFrameCallback(mFrameCallback); 140 | } 141 | } 142 | } -------------------------------------------------------------------------------- /rebound-android/src/main/java/com/facebook/rebound/AnimationQueue.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | package com.facebook.rebound; 11 | 12 | import java.util.ArrayList; 13 | import java.util.Collection; 14 | import java.util.LinkedList; 15 | import java.util.List; 16 | import java.util.Queue; 17 | 18 | /** 19 | * AnimationQueue provides a way to trigger a delayed stream of animations off of a stream of 20 | * values. Each callback that is added the AnimationQueue will be process the stream delayed by 21 | * the number of animation frames equal to its position in the callback list. This makes it easy 22 | * to build cascading animations. 23 | * 24 | * TODO: Add options for changing the delay after which a callback receives a value from the 25 | * animation queue value stream. 26 | */ 27 | public class AnimationQueue { 28 | 29 | /** 30 | * AnimationQueue.Callback receives the value from the stream that it should use in its onFrame 31 | * method. 32 | */ 33 | public interface Callback { 34 | void onFrame(Double value); 35 | } 36 | 37 | private final ChoreographerCompat mChoreographer; 38 | private final Queue mPendingQueue = new LinkedList(); 39 | private final Queue mAnimationQueue = new LinkedList(); 40 | private final List mCallbacks = new ArrayList(); 41 | private final ArrayList mTempValues = new ArrayList(); 42 | private final ChoreographerCompat.FrameCallback mChoreographerCallback; 43 | private boolean mRunning; 44 | 45 | public AnimationQueue() { 46 | mChoreographer = ChoreographerCompat.getInstance(); 47 | mChoreographerCallback = new ChoreographerCompat.FrameCallback() { 48 | @Override 49 | public void doFrame(long frameTimeNanos) { 50 | onFrame(frameTimeNanos); 51 | } 52 | }; 53 | } 54 | 55 | /* Values */ 56 | 57 | /** 58 | * Add a single value to the pending animation queue. 59 | * @param value the single value to add 60 | */ 61 | public void addValue(Double value) { 62 | mPendingQueue.add(value); 63 | runIfIdle(); 64 | } 65 | 66 | /** 67 | * Add a collection of values to the pending animation value queue 68 | * @param values the collection of values to add 69 | */ 70 | public void addAllValues(Collection values) { 71 | mPendingQueue.addAll(values); 72 | runIfIdle(); 73 | } 74 | 75 | /** 76 | * Clear all pending animation values. 77 | */ 78 | public void clearValues() { 79 | mPendingQueue.clear(); 80 | } 81 | 82 | /* Callbacks */ 83 | 84 | /** 85 | * Add a callback to the AnimationQueue. 86 | * @param callback the callback to add 87 | */ 88 | public void addCallback(Callback callback) { 89 | mCallbacks.add(callback); 90 | } 91 | 92 | /** 93 | * Remove the specified callback from the AnimationQueue. 94 | * @param callback the callback to remove 95 | */ 96 | public void removeCallback(Callback callback) { 97 | mCallbacks.remove(callback); 98 | } 99 | 100 | /** 101 | * Remove any callbacks from the AnimationQueue. 102 | */ 103 | public void clearCallbacks() { 104 | mCallbacks.clear(); 105 | } 106 | 107 | /** 108 | * Start the animation loop if it is not currently running. 109 | */ 110 | private void runIfIdle() { 111 | if (!mRunning) { 112 | mRunning = true; 113 | mChoreographer.postFrameCallback(mChoreographerCallback); 114 | } 115 | } 116 | 117 | /** 118 | * Called every time a new frame is ready to be rendered. 119 | * 120 | * Values are processed FIFO and each callback is given a chance to handle each value when its 121 | * turn comes before a value is poll'd off the AnimationQueue. 122 | * 123 | * @param frameTimeNanos The time in nanoseconds when the frame started being rendered, in the 124 | * nanoTime() timebase. Divide this value by 1000000 to convert it to the 125 | * uptimeMillis() time base. 126 | */ 127 | private void onFrame(long frameTimeNanos) { 128 | Double nextPendingValue = mPendingQueue.poll(); 129 | 130 | int drainingOffset; 131 | if (nextPendingValue != null) { 132 | mAnimationQueue.offer(nextPendingValue); 133 | drainingOffset = 0; 134 | } else { 135 | drainingOffset = Math.max(mCallbacks.size() - mAnimationQueue.size(), 0); 136 | } 137 | 138 | // Copy the values into a temporary ArrayList for processing. 139 | mTempValues.addAll(mAnimationQueue); 140 | for (int i = mTempValues.size() - 1; i > -1; i--) { 141 | Double val = mTempValues.get(i); 142 | int cbIdx = mTempValues.size() - 1 - i + drainingOffset; 143 | if (mCallbacks.size() > cbIdx) { 144 | mCallbacks.get(cbIdx).onFrame(val); 145 | } 146 | } 147 | mTempValues.clear(); 148 | 149 | while (mAnimationQueue.size() + drainingOffset >= mCallbacks.size()) { 150 | mAnimationQueue.poll(); 151 | } 152 | 153 | if (mAnimationQueue.isEmpty() && mPendingQueue.isEmpty()) { 154 | mRunning = false; 155 | } else { 156 | mChoreographer.postFrameCallback(mChoreographerCallback); 157 | } 158 | } 159 | 160 | } 161 | 162 | -------------------------------------------------------------------------------- /rebound-android/src/main/java/com/facebook/rebound/ChoreographerCompat.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | package com.facebook.rebound; 12 | 13 | import android.annotation.TargetApi; 14 | import android.os.Build; 15 | import android.os.Handler; 16 | import android.os.Looper; 17 | import android.view.Choreographer; 18 | 19 | /** 20 | * Wrapper class for abstracting away availability of the JellyBean Choreographer. If Choreographer 21 | * is unavailable we fallback to using a normal Handler. 22 | */ 23 | public class ChoreographerCompat { 24 | 25 | private static final long ONE_FRAME_MILLIS = 17; 26 | private static final boolean IS_JELLYBEAN_OR_HIGHER = 27 | Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN; 28 | private static final ChoreographerCompat INSTANCE = new ChoreographerCompat(); 29 | 30 | private Handler mHandler; 31 | private Choreographer mChoreographer; 32 | 33 | public static ChoreographerCompat getInstance() { 34 | return INSTANCE; 35 | } 36 | 37 | private ChoreographerCompat() { 38 | if (IS_JELLYBEAN_OR_HIGHER) { 39 | mChoreographer = getChoreographer(); 40 | } else { 41 | mHandler = new Handler(Looper.getMainLooper()); 42 | } 43 | } 44 | 45 | public void postFrameCallback(FrameCallback callbackWrapper) { 46 | if (IS_JELLYBEAN_OR_HIGHER) { 47 | choreographerPostFrameCallback(callbackWrapper.getFrameCallback()); 48 | } else { 49 | mHandler.postDelayed(callbackWrapper.getRunnable(), 0); 50 | } 51 | } 52 | 53 | public void postFrameCallbackDelayed(FrameCallback callbackWrapper, long delayMillis) { 54 | if (IS_JELLYBEAN_OR_HIGHER) { 55 | choreographerPostFrameCallbackDelayed(callbackWrapper.getFrameCallback(), delayMillis); 56 | } else { 57 | mHandler.postDelayed(callbackWrapper.getRunnable(), delayMillis + ONE_FRAME_MILLIS); 58 | } 59 | } 60 | 61 | public void removeFrameCallback(FrameCallback callbackWrapper) { 62 | if (IS_JELLYBEAN_OR_HIGHER) { 63 | choreographerRemoveFrameCallback(callbackWrapper.getFrameCallback()); 64 | } else { 65 | mHandler.removeCallbacks(callbackWrapper.getRunnable()); 66 | } 67 | } 68 | 69 | @TargetApi(Build.VERSION_CODES.JELLY_BEAN) 70 | private Choreographer getChoreographer() { 71 | return Choreographer.getInstance(); 72 | } 73 | 74 | @TargetApi(Build.VERSION_CODES.JELLY_BEAN) 75 | private void choreographerPostFrameCallback(Choreographer.FrameCallback frameCallback) { 76 | mChoreographer.postFrameCallback(frameCallback); 77 | } 78 | 79 | @TargetApi(Build.VERSION_CODES.JELLY_BEAN) 80 | private void choreographerPostFrameCallbackDelayed( 81 | Choreographer.FrameCallback frameCallback, 82 | long delayMillis) { 83 | mChoreographer.postFrameCallbackDelayed(frameCallback, delayMillis); 84 | } 85 | 86 | @TargetApi(Build.VERSION_CODES.JELLY_BEAN) 87 | private void choreographerRemoveFrameCallback(Choreographer.FrameCallback frameCallback) { 88 | mChoreographer.removeFrameCallback(frameCallback); 89 | } 90 | 91 | /** 92 | * This class provides a compatibility wrapper around the JellyBean FrameCallback with methods 93 | * to access cached wrappers for submitting a real FrameCallback to a Choreographer or a Runnable 94 | * to a Handler. 95 | */ 96 | public static abstract class FrameCallback { 97 | 98 | private Runnable mRunnable; 99 | private Choreographer.FrameCallback mFrameCallback; 100 | 101 | @TargetApi(Build.VERSION_CODES.JELLY_BEAN) 102 | Choreographer.FrameCallback getFrameCallback() { 103 | if (mFrameCallback == null) { 104 | mFrameCallback = new Choreographer.FrameCallback() { 105 | @Override 106 | public void doFrame(long frameTimeNanos) { 107 | FrameCallback.this.doFrame(frameTimeNanos); 108 | } 109 | }; 110 | } 111 | return mFrameCallback; 112 | } 113 | 114 | Runnable getRunnable() { 115 | if (mRunnable == null) { 116 | mRunnable = new Runnable() { 117 | @Override 118 | public void run() { 119 | doFrame(System.nanoTime()); 120 | } 121 | }; 122 | } 123 | return mRunnable; 124 | } 125 | 126 | public abstract void doFrame(long frameTimeNanos); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /rebound-android/src/main/java/com/facebook/rebound/SpringChain.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | package com.facebook.rebound; 11 | 12 | import java.util.List; 13 | import java.util.concurrent.CopyOnWriteArrayList; 14 | 15 | /** 16 | * SpringChain is a helper class for creating spring animations with multiple springs in a chain. 17 | * Chains of springs can be used to create cascading animations that maintain individual physics 18 | * state for each member of the chain. One spring in the chain is chosen to be the control spring. 19 | * Springs before and after the control spring in the chain are pulled along by their predecessor. 20 | * You can change which spring is the control spring at any point by calling 21 | * {@link SpringChain#setControlSpringIndex(int)}. 22 | */ 23 | public class SpringChain implements SpringListener { 24 | 25 | /** 26 | * Add these spring configs to the registry to support live tuning through the 27 | * {@link com.facebook.rebound.ui.SpringConfiguratorView} 28 | */ 29 | private static final SpringConfigRegistry registry = SpringConfigRegistry.getInstance(); 30 | private static final int DEFAULT_MAIN_TENSION = 40; 31 | private static final int DEFAULT_MAIN_FRICTION = 6; 32 | private static final int DEFAULT_ATTACHMENT_TENSION = 70; 33 | private static final int DEFAULT_ATTACHMENT_FRICTION = 10; 34 | private static int id = 0; 35 | 36 | 37 | /** 38 | * Factory method for creating a new SpringChain with default SpringConfig. 39 | * @return the newly created SpringChain 40 | */ 41 | public static SpringChain create() { 42 | return new SpringChain(); 43 | } 44 | 45 | /** 46 | * Factory method for creating a new SpringChain with the provided SpringConfig. 47 | * @param mainTension tension for the main spring 48 | * @param mainFriction friction for the main spring 49 | * @param attachmentTension tension for the attachment spring 50 | * @param attachmentFriction friction for the attachment spring 51 | * @return the newly created SpringChain 52 | */ 53 | public static SpringChain create( 54 | int mainTension, 55 | int mainFriction, 56 | int attachmentTension, 57 | int attachmentFriction) { 58 | return new SpringChain(mainTension, mainFriction, attachmentTension, attachmentFriction); 59 | } 60 | 61 | private final SpringSystem mSpringSystem = SpringSystem.create(); 62 | private final CopyOnWriteArrayList mListeners = 63 | new CopyOnWriteArrayList(); 64 | private final CopyOnWriteArrayList mSprings = new CopyOnWriteArrayList(); 65 | private int mControlSpringIndex = -1; 66 | 67 | // The main spring config defines the tension and friction for the control spring. Keeping these 68 | // values separate allows the behavior of the trailing springs to be different than that of the 69 | // control point. 70 | private final SpringConfig mMainSpringConfig; 71 | 72 | // The attachment spring config defines the tension and friction for the rest of the springs in 73 | // the chain. 74 | private final SpringConfig mAttachmentSpringConfig; 75 | 76 | private SpringChain() { 77 | this( 78 | DEFAULT_MAIN_TENSION, 79 | DEFAULT_MAIN_FRICTION, 80 | DEFAULT_ATTACHMENT_TENSION, 81 | DEFAULT_ATTACHMENT_FRICTION); 82 | } 83 | 84 | private SpringChain( 85 | int mainTension, 86 | int mainFriction, 87 | int attachmentTension, 88 | int attachmentFriction) { 89 | mMainSpringConfig = SpringConfig.fromOrigamiTensionAndFriction(mainTension, mainFriction); 90 | mAttachmentSpringConfig = 91 | SpringConfig.fromOrigamiTensionAndFriction(attachmentTension, attachmentFriction); 92 | registry.addSpringConfig(mMainSpringConfig, "main spring " + id++); 93 | registry.addSpringConfig(mAttachmentSpringConfig, "attachment spring " + id++); 94 | } 95 | 96 | public SpringConfig getMainSpringConfig() { 97 | return mMainSpringConfig; 98 | } 99 | 100 | public SpringConfig getAttachmentSpringConfig() { 101 | return mAttachmentSpringConfig; 102 | } 103 | 104 | /** 105 | * Add a spring to the chain that will callback to the provided listener. 106 | * @param listener the listener to notify for this Spring in the chain 107 | * @return this SpringChain for chaining 108 | */ 109 | public SpringChain addSpring(final SpringListener listener) { 110 | // We listen to each spring added to the SpringChain and dynamically chain the springs together 111 | // whenever the control spring state is modified. 112 | Spring spring = mSpringSystem 113 | .createSpring() 114 | .addListener(this) 115 | .setSpringConfig(mAttachmentSpringConfig); 116 | mSprings.add(spring); 117 | mListeners.add(listener); 118 | return this; 119 | } 120 | 121 | /** 122 | * Set the index of the control spring. This spring will drive the positions of all the springs 123 | * before and after it in the list when moved. 124 | * @param i the index to use for the control spring 125 | * @return this SpringChain 126 | */ 127 | public SpringChain setControlSpringIndex(int i) { 128 | mControlSpringIndex = i; 129 | Spring controlSpring = mSprings.get(mControlSpringIndex); 130 | if (controlSpring == null) { 131 | return null; 132 | } 133 | for (Spring spring : mSpringSystem.getAllSprings()) { 134 | spring.setSpringConfig(mAttachmentSpringConfig); 135 | } 136 | getControlSpring().setSpringConfig(mMainSpringConfig); 137 | return this; 138 | } 139 | 140 | /** 141 | * Retrieve the control spring so you can manipulate it to drive the positions of the other 142 | * springs. 143 | * @return the control spring. 144 | */ 145 | public Spring getControlSpring() { 146 | return mSprings.get(mControlSpringIndex); 147 | } 148 | 149 | /** 150 | * Retrieve the list of springs in the chain. 151 | * @return the list of springs 152 | */ 153 | public List getAllSprings() { 154 | return mSprings; 155 | } 156 | 157 | @Override 158 | public void onSpringUpdate(Spring spring) { 159 | // Get the control spring index and update the endValue of each spring above and below it in the 160 | // spring collection triggering a cascading effect. 161 | int idx = mSprings.indexOf(spring); 162 | SpringListener listener = mListeners.get(idx); 163 | int above = -1; 164 | int below = -1; 165 | if (idx == mControlSpringIndex) { 166 | below = idx - 1; 167 | above = idx + 1; 168 | } else if (idx < mControlSpringIndex) { 169 | below = idx - 1; 170 | } else if (idx > mControlSpringIndex) { 171 | above = idx + 1; 172 | } 173 | if (above > -1 && above < mSprings.size()) { 174 | mSprings.get(above).setEndValue(spring.getCurrentValue()); 175 | } 176 | if (below > -1 && below < mSprings.size()) { 177 | mSprings.get(below).setEndValue(spring.getCurrentValue()); 178 | } 179 | listener.onSpringUpdate(spring); 180 | } 181 | 182 | @Override 183 | public void onSpringAtRest(Spring spring) { 184 | int idx = mSprings.indexOf(spring); 185 | mListeners.get(idx).onSpringAtRest(spring); 186 | } 187 | 188 | @Override 189 | public void onSpringActivate(Spring spring) { 190 | int idx = mSprings.indexOf(spring); 191 | mListeners.get(idx).onSpringActivate(spring); 192 | } 193 | 194 | @Override 195 | public void onSpringEndStateChange(Spring spring) { 196 | int idx = mSprings.indexOf(spring); 197 | mListeners.get(idx).onSpringEndStateChange(spring); 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /rebound-android/src/main/java/com/facebook/rebound/SpringSystem.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | package com.facebook.rebound; 12 | 13 | /** 14 | * This is a wrapper for BaseSpringSystem that provides the convenience of automatically providing 15 | * the AndroidSpringLooper dependency in {@link SpringSystem#create}. 16 | */ 17 | public class SpringSystem extends BaseSpringSystem { 18 | 19 | /** 20 | * Create a new SpringSystem providing the appropriate constructor parameters to work properly 21 | * in an Android environment. 22 | * @return the SpringSystem 23 | */ 24 | public static SpringSystem create() { 25 | return new SpringSystem(AndroidSpringLooperFactory.createSpringLooper()); 26 | } 27 | 28 | private SpringSystem(SpringLooper springLooper) { 29 | super(springLooper); 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /rebound-android/src/main/java/com/facebook/rebound/ui/SpringConfiguratorView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | package com.facebook.rebound.ui; 12 | 13 | import android.annotation.TargetApi; 14 | import android.content.Context; 15 | import android.content.res.Resources; 16 | import android.graphics.Color; 17 | import android.os.Build; 18 | import android.util.AttributeSet; 19 | import android.view.Gravity; 20 | import android.view.MotionEvent; 21 | import android.view.View; 22 | import android.view.ViewGroup; 23 | import android.widget.AbsListView; 24 | import android.widget.AdapterView; 25 | import android.widget.BaseAdapter; 26 | import android.widget.FrameLayout; 27 | import android.widget.LinearLayout; 28 | import android.widget.SeekBar; 29 | import android.widget.Spinner; 30 | import android.widget.TableLayout; 31 | import android.widget.TextView; 32 | 33 | import com.facebook.rebound.OrigamiValueConverter; 34 | import com.facebook.rebound.Spring; 35 | import com.facebook.rebound.SpringConfig; 36 | import com.facebook.rebound.SpringConfigRegistry; 37 | import com.facebook.rebound.SpringListener; 38 | import com.facebook.rebound.SpringSystem; 39 | 40 | import java.text.DecimalFormat; 41 | import java.util.ArrayList; 42 | import java.util.List; 43 | import java.util.Map; 44 | 45 | import static com.facebook.rebound.ui.Util.*; 46 | 47 | /** 48 | * The SpringConfiguratorView provides a reusable view for live-editing all registered springs 49 | * within an Application. Each registered Spring can be accessed by its id and its tension and 50 | * friction properties can be edited while the user tests the effected UI live. 51 | */ 52 | public class SpringConfiguratorView extends FrameLayout { 53 | 54 | private static final int MAX_SEEKBAR_VAL = 100000; 55 | private static final float MIN_TENSION = 0; 56 | private static final float MAX_TENSION = 200; 57 | private static final float MIN_FRICTION = 0; 58 | private static final float MAX_FRICTION = 50; 59 | private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("#.#"); 60 | 61 | private final SpinnerAdapter spinnerAdapter; 62 | private final List mSpringConfigs = new ArrayList(); 63 | private final Spring mRevealerSpring; 64 | private final float mStashPx; 65 | private final float mRevealPx; 66 | private final SpringConfigRegistry springConfigRegistry; 67 | private final int mTextColor = Color.argb(255, 225, 225, 225); 68 | private SeekBar mTensionSeekBar; 69 | private SeekBar mFrictionSeekBar; 70 | private Spinner mSpringSelectorSpinner; 71 | private TextView mFrictionLabel; 72 | private TextView mTensionLabel; 73 | private SpringConfig mSelectedSpringConfig; 74 | 75 | public SpringConfiguratorView(Context context) { 76 | this(context, null); 77 | } 78 | 79 | public SpringConfiguratorView(Context context, AttributeSet attrs) { 80 | this(context, attrs, 0); 81 | } 82 | 83 | @TargetApi(Build.VERSION_CODES.HONEYCOMB) 84 | public SpringConfiguratorView(Context context, AttributeSet attrs, int defStyle) { 85 | super(context, attrs, defStyle); 86 | 87 | SpringSystem springSystem = SpringSystem.create(); 88 | springConfigRegistry = SpringConfigRegistry.getInstance(); 89 | spinnerAdapter = new SpinnerAdapter(context); 90 | 91 | Resources resources = getResources(); 92 | mRevealPx = dpToPx(40, resources); 93 | mStashPx = dpToPx(280, resources); 94 | 95 | mRevealerSpring = springSystem.createSpring(); 96 | SpringListener revealerSpringListener = new RevealerSpringListener(); 97 | mRevealerSpring 98 | .setCurrentValue(1) 99 | .setEndValue(1) 100 | .addListener(revealerSpringListener); 101 | 102 | addView(generateHierarchy(context)); 103 | 104 | SeekbarListener seekbarListener = new SeekbarListener(); 105 | mTensionSeekBar.setMax(MAX_SEEKBAR_VAL); 106 | mTensionSeekBar.setOnSeekBarChangeListener(seekbarListener); 107 | 108 | mFrictionSeekBar.setMax(MAX_SEEKBAR_VAL); 109 | mFrictionSeekBar.setOnSeekBarChangeListener(seekbarListener); 110 | 111 | mSpringSelectorSpinner.setAdapter(spinnerAdapter); 112 | mSpringSelectorSpinner.setOnItemSelectedListener(new SpringSelectedListener()); 113 | refreshSpringConfigurations(); 114 | 115 | this.setTranslationY(mStashPx); 116 | } 117 | 118 | /** 119 | * Programmatically build up the view hierarchy to avoid the need for resources. 120 | * @return View hierarchy 121 | */ 122 | private View generateHierarchy(Context context) { 123 | Resources resources = getResources(); 124 | 125 | FrameLayout.LayoutParams params; 126 | int fivePx = dpToPx(5, resources); 127 | int tenPx = dpToPx(10, resources); 128 | int twentyPx = dpToPx(20, resources); 129 | TableLayout.LayoutParams tableLayoutParams = new TableLayout.LayoutParams( 130 | 0, 131 | ViewGroup.LayoutParams.WRAP_CONTENT, 132 | 1f); 133 | tableLayoutParams.setMargins(0, 0, fivePx, 0); 134 | LinearLayout seekWrapper; 135 | 136 | FrameLayout root = new FrameLayout(context); 137 | params = createLayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, dpToPx(300, resources)); 138 | root.setLayoutParams(params); 139 | 140 | FrameLayout container = new FrameLayout(context); 141 | params = createMatchParams(); 142 | params.setMargins(0, twentyPx, 0, 0); 143 | container.setLayoutParams(params); 144 | container.setBackgroundColor(Color.argb(100, 0, 0, 0)); 145 | root.addView(container); 146 | 147 | mSpringSelectorSpinner = new Spinner(context, Spinner.MODE_DIALOG); 148 | params = createMatchWrapParams(); 149 | params.gravity = Gravity.TOP; 150 | params.setMargins(tenPx, tenPx, tenPx, 0); 151 | mSpringSelectorSpinner.setLayoutParams(params); 152 | container.addView(mSpringSelectorSpinner); 153 | 154 | LinearLayout linearLayout = new LinearLayout(context); 155 | params = createMatchWrapParams(); 156 | params.setMargins(0, 0, 0, dpToPx(80, resources)); 157 | params.gravity = Gravity.BOTTOM; 158 | linearLayout.setLayoutParams(params); 159 | linearLayout.setOrientation(LinearLayout.VERTICAL); 160 | container.addView(linearLayout); 161 | 162 | seekWrapper = new LinearLayout(context); 163 | params = createMatchWrapParams(); 164 | params.setMargins(tenPx, tenPx, tenPx, twentyPx); 165 | seekWrapper.setPadding(tenPx, tenPx, tenPx, tenPx); 166 | seekWrapper.setLayoutParams(params); 167 | seekWrapper.setOrientation(LinearLayout.HORIZONTAL); 168 | linearLayout.addView(seekWrapper); 169 | 170 | mTensionSeekBar = new SeekBar(context); 171 | mTensionSeekBar.setLayoutParams(tableLayoutParams); 172 | seekWrapper.addView(mTensionSeekBar); 173 | 174 | mTensionLabel = new TextView(getContext()); 175 | mTensionLabel.setTextColor(mTextColor); 176 | params = createLayoutParams( 177 | dpToPx(50, resources), 178 | ViewGroup.LayoutParams.MATCH_PARENT); 179 | mTensionLabel.setGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT); 180 | mTensionLabel.setLayoutParams(params); 181 | mTensionLabel.setMaxLines(1); 182 | seekWrapper.addView(mTensionLabel); 183 | 184 | seekWrapper = new LinearLayout(context); 185 | params = createMatchWrapParams(); 186 | params.setMargins(tenPx, tenPx, tenPx, twentyPx); 187 | seekWrapper.setPadding(tenPx, tenPx, tenPx, tenPx); 188 | seekWrapper.setLayoutParams(params); 189 | seekWrapper.setOrientation(LinearLayout.HORIZONTAL); 190 | linearLayout.addView(seekWrapper); 191 | 192 | mFrictionSeekBar = new SeekBar(context); 193 | mFrictionSeekBar.setLayoutParams(tableLayoutParams); 194 | seekWrapper.addView(mFrictionSeekBar); 195 | 196 | mFrictionLabel = new TextView(getContext()); 197 | mFrictionLabel.setTextColor(mTextColor); 198 | params = createLayoutParams(dpToPx(50, resources), ViewGroup.LayoutParams.MATCH_PARENT); 199 | mFrictionLabel.setGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT); 200 | mFrictionLabel.setLayoutParams(params); 201 | mFrictionLabel.setMaxLines(1); 202 | seekWrapper.addView(mFrictionLabel); 203 | 204 | View nub = new View(context); 205 | params = createLayoutParams(dpToPx(60, resources), dpToPx(40, resources)); 206 | params.gravity = Gravity.TOP | Gravity.CENTER; 207 | nub.setLayoutParams(params); 208 | nub.setOnTouchListener(new OnNubTouchListener()); 209 | nub.setBackgroundColor(Color.argb(255, 0, 164, 209)); 210 | root.addView(nub); 211 | 212 | return root; 213 | } 214 | 215 | /** 216 | * remove the configurator from its parent and clean up springs and listeners 217 | */ 218 | public void destroy() { 219 | ViewGroup parent = (ViewGroup) getParent(); 220 | if (parent != null) { 221 | parent.removeView(this); 222 | } 223 | mRevealerSpring.destroy(); 224 | } 225 | 226 | /** 227 | * reload the springs from the registry and update the UI 228 | */ 229 | public void refreshSpringConfigurations() { 230 | Map springConfigMap = springConfigRegistry.getAllSpringConfig(); 231 | 232 | spinnerAdapter.clear(); 233 | mSpringConfigs.clear(); 234 | 235 | for (Map.Entry entry : springConfigMap.entrySet()) { 236 | if (entry.getKey() == SpringConfig.defaultConfig) { 237 | continue; 238 | } 239 | mSpringConfigs.add(entry.getKey()); 240 | spinnerAdapter.add(entry.getValue()); 241 | } 242 | // Add the default config in last. 243 | mSpringConfigs.add(SpringConfig.defaultConfig); 244 | spinnerAdapter.add(springConfigMap.get(SpringConfig.defaultConfig)); 245 | spinnerAdapter.notifyDataSetChanged(); 246 | if (mSpringConfigs.size() > 0) { 247 | mSpringSelectorSpinner.setSelection(0); 248 | } 249 | } 250 | 251 | private class SpringSelectedListener implements AdapterView.OnItemSelectedListener { 252 | 253 | @Override 254 | public void onItemSelected(AdapterView adapterView, View view, int i, long l) { 255 | mSelectedSpringConfig = mSpringConfigs.get(i); 256 | updateSeekBarsForSpringConfig(mSelectedSpringConfig); 257 | } 258 | 259 | @Override 260 | public void onNothingSelected(AdapterView adapterView) { 261 | } 262 | } 263 | 264 | /** 265 | * listen to events on seekbars and update registered springs accordingly 266 | */ 267 | private class SeekbarListener implements SeekBar.OnSeekBarChangeListener { 268 | 269 | @Override 270 | public void onProgressChanged(SeekBar seekBar, int val, boolean b) { 271 | float tensionRange = MAX_TENSION - MIN_TENSION; 272 | float frictionRange = MAX_FRICTION - MIN_FRICTION; 273 | 274 | if (seekBar == mTensionSeekBar) { 275 | float scaledTension = ((val) * tensionRange) / MAX_SEEKBAR_VAL + MIN_TENSION; 276 | mSelectedSpringConfig.tension = 277 | OrigamiValueConverter.tensionFromOrigamiValue(scaledTension); 278 | String roundedTensionLabel = DECIMAL_FORMAT.format(scaledTension); 279 | mTensionLabel.setText("T:" + roundedTensionLabel); 280 | } 281 | 282 | if (seekBar == mFrictionSeekBar) { 283 | float scaledFriction = ((val) * frictionRange) / MAX_SEEKBAR_VAL + MIN_FRICTION; 284 | mSelectedSpringConfig.friction = 285 | OrigamiValueConverter.frictionFromOrigamiValue(scaledFriction); 286 | String roundedFrictionLabel = DECIMAL_FORMAT.format(scaledFriction); 287 | mFrictionLabel.setText("F:" + roundedFrictionLabel); 288 | } 289 | } 290 | 291 | @Override 292 | public void onStartTrackingTouch(SeekBar seekBar) { 293 | } 294 | 295 | @Override 296 | public void onStopTrackingTouch(SeekBar seekBar) { 297 | } 298 | } 299 | 300 | /** 301 | * update the position of the seekbars based on the spring value; 302 | * @param springConfig current editing spring 303 | */ 304 | private void updateSeekBarsForSpringConfig(SpringConfig springConfig) { 305 | float tension = (float) OrigamiValueConverter.origamiValueFromTension(springConfig.tension); 306 | float tensionRange = MAX_TENSION - MIN_TENSION; 307 | int scaledTension = Math.round(((tension - MIN_TENSION) * MAX_SEEKBAR_VAL) / tensionRange); 308 | 309 | float friction = (float) OrigamiValueConverter.origamiValueFromFriction(springConfig.friction); 310 | float frictionRange = MAX_FRICTION - MIN_FRICTION; 311 | int scaledFriction = Math.round(((friction - MIN_FRICTION) * MAX_SEEKBAR_VAL) / frictionRange); 312 | 313 | mTensionSeekBar.setProgress(scaledTension); 314 | mFrictionSeekBar.setProgress(scaledFriction); 315 | } 316 | 317 | /** 318 | * toggle visibility when the nub is tapped. 319 | */ 320 | private class OnNubTouchListener implements View.OnTouchListener { 321 | @Override 322 | public boolean onTouch(View view, MotionEvent motionEvent) { 323 | if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) { 324 | togglePosition(); 325 | } 326 | return true; 327 | } 328 | } 329 | 330 | private void togglePosition() { 331 | double currentValue = mRevealerSpring.getEndValue(); 332 | mRevealerSpring 333 | .setEndValue(currentValue == 1 ? 0 : 1); 334 | } 335 | 336 | private class RevealerSpringListener implements SpringListener { 337 | 338 | @Override 339 | public void onSpringUpdate(Spring spring) { 340 | float val = (float) spring.getCurrentValue(); 341 | float minTranslate = mRevealPx; 342 | float maxTranslate = mStashPx; 343 | float range = maxTranslate - minTranslate; 344 | float yTranslate = (val * range) + minTranslate; 345 | SpringConfiguratorView.this.setTranslationY(yTranslate); 346 | } 347 | 348 | @Override 349 | public void onSpringAtRest(Spring spring) { 350 | } 351 | 352 | @Override 353 | public void onSpringActivate(Spring spring) { 354 | } 355 | 356 | @Override 357 | public void onSpringEndStateChange(Spring spring) { 358 | } 359 | } 360 | 361 | private class SpinnerAdapter extends BaseAdapter { 362 | 363 | private final Context mContext; 364 | private final List mStrings; 365 | 366 | public SpinnerAdapter(Context context) { 367 | mContext = context; 368 | mStrings = new ArrayList(); 369 | } 370 | 371 | @Override 372 | public int getCount() { 373 | return mStrings.size(); 374 | } 375 | 376 | @Override 377 | public Object getItem(int position) { 378 | return mStrings.get(position); 379 | } 380 | 381 | @Override 382 | public long getItemId(int position) { 383 | return position; 384 | } 385 | 386 | public void add(String string) { 387 | mStrings.add(string); 388 | notifyDataSetChanged(); 389 | } 390 | 391 | /** 392 | * Remove all elements from the list. 393 | */ 394 | public void clear() { 395 | mStrings.clear(); 396 | notifyDataSetChanged(); 397 | } 398 | 399 | @Override 400 | public View getView(int position, View convertView, ViewGroup parent) { 401 | TextView textView; 402 | if (convertView == null) { 403 | textView = new TextView(mContext); 404 | AbsListView.LayoutParams params = new AbsListView.LayoutParams( 405 | ViewGroup.LayoutParams.MATCH_PARENT, 406 | ViewGroup.LayoutParams.MATCH_PARENT); 407 | textView.setLayoutParams(params); 408 | int twelvePx = dpToPx(12, getResources()); 409 | textView.setPadding(twelvePx, twelvePx, twelvePx, twelvePx); 410 | textView.setTextColor(mTextColor); 411 | } else { 412 | textView = (TextView) convertView; 413 | } 414 | textView.setText(mStrings.get(position)); 415 | return textView; 416 | } 417 | } 418 | } 419 | 420 | -------------------------------------------------------------------------------- /rebound-android/src/main/java/com/facebook/rebound/ui/Util.java: -------------------------------------------------------------------------------- 1 | package com.facebook.rebound.ui; 2 | 3 | import android.content.res.Resources; 4 | import android.util.TypedValue; 5 | import android.view.ViewGroup; 6 | import android.widget.FrameLayout; 7 | 8 | /** 9 | * Utilities for generating view hierarchies without using resources. 10 | */ 11 | public abstract class Util { 12 | 13 | public static int dpToPx(float dp, Resources res) { 14 | return (int) TypedValue.applyDimension( 15 | TypedValue.COMPLEX_UNIT_DIP, 16 | dp, 17 | res.getDisplayMetrics()); 18 | } 19 | 20 | public static FrameLayout.LayoutParams createLayoutParams(int width, int height) { 21 | return new FrameLayout.LayoutParams(width, height); 22 | } 23 | 24 | public static FrameLayout.LayoutParams createMatchParams() { 25 | return createLayoutParams( 26 | ViewGroup.LayoutParams.MATCH_PARENT, 27 | ViewGroup.LayoutParams.MATCH_PARENT); 28 | } 29 | 30 | public static FrameLayout.LayoutParams createWrapParams() { 31 | return createLayoutParams( 32 | ViewGroup.LayoutParams.WRAP_CONTENT, 33 | ViewGroup.LayoutParams.WRAP_CONTENT); 34 | } 35 | 36 | public static FrameLayout.LayoutParams createWrapMatchParams() { 37 | return createLayoutParams( 38 | ViewGroup.LayoutParams.WRAP_CONTENT, 39 | ViewGroup.LayoutParams.MATCH_PARENT); 40 | } 41 | 42 | public static FrameLayout.LayoutParams createMatchWrapParams() { 43 | return createLayoutParams( 44 | ViewGroup.LayoutParams.MATCH_PARENT, 45 | ViewGroup.LayoutParams.WRAP_CONTENT); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /rebound-core/BUCK: -------------------------------------------------------------------------------- 1 | prebuilt_jar( 2 | name = 'mockito', 3 | binary_jar = 'libs/mockito-all-1.9.5.jar', 4 | ) 5 | 6 | prebuilt_jar( 7 | name = 'hamcrest-core', 8 | binary_jar = 'libs/hamcrest-core-1.3.jar', 9 | ) 10 | 11 | prebuilt_jar( 12 | name = 'junit', 13 | binary_jar = 'libs/junit-4.11.jar', 14 | ) 15 | 16 | java_test( 17 | name = 'test', 18 | srcs = glob(['src/test/java/**/*Test.java']), 19 | deps = [ 20 | '//rebound-core:mockito', 21 | '//rebound-core:hamcrest-core', 22 | '//rebound-core:junit', 23 | '//rebound-core:src' 24 | ], 25 | source_under_test = ['//rebound-core:src'], 26 | ) 27 | 28 | android_library( 29 | name = 'src', 30 | srcs = glob(['src/main/java/**/*.java']), 31 | visibility = ['PUBLIC'], 32 | ) 33 | 34 | project_config( 35 | src_target = '//rebound-core:src', 36 | test_target = '//rebound-core:test', 37 | src_roots = ['src/main/java'], 38 | test_roots = ['src/test/java'], 39 | ) 40 | -------------------------------------------------------------------------------- /rebound-core/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | 4 | android { 5 | compileOptions { 6 | sourceCompatibility JavaVersion.VERSION_1_7 7 | targetCompatibility JavaVersion.VERSION_1_7 8 | } 9 | compileSdkVersion Integer.parseInt(project.ANDROID_COMPILE_SDK_VERSION) 10 | buildToolsVersion project.ANDROID_BUILD_TOOLS_VERSION 11 | 12 | defaultConfig { 13 | versionCode Integer.parseInt(project.VERSION_CODE) 14 | versionName project.VERSION_NAME 15 | minSdkVersion 11 16 | targetSdkVersion Integer.parseInt(project.ANDROID_TARGET_SDK_VERSION) 17 | } 18 | 19 | } 20 | 21 | dependencies { 22 | testCompile files( 23 | 'libs/hamcrest-core-1.3.jar', 24 | 'libs/junit-4.11.jar', 25 | 'libs/mockito-all-1.9.5.jar') 26 | } -------------------------------------------------------------------------------- /rebound-core/libs/hamcrest-core-1.3.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/rebound/cf877b0d747473e8786fc73283b4f372aab1b99f/rebound-core/libs/hamcrest-core-1.3.jar -------------------------------------------------------------------------------- /rebound-core/libs/junit-4.11.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/rebound/cf877b0d747473e8786fc73283b4f372aab1b99f/rebound-core/libs/junit-4.11.jar -------------------------------------------------------------------------------- /rebound-core/libs/mockito-all-1.9.5.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/rebound/cf877b0d747473e8786fc73283b4f372aab1b99f/rebound-core/libs/mockito-all-1.9.5.jar -------------------------------------------------------------------------------- /rebound-core/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /rebound-core/src/main/java/com/facebook/rebound/BaseSpringSystem.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | package com.facebook.rebound; 12 | 13 | import java.util.ArrayList; 14 | import java.util.Collection; 15 | import java.util.Collections; 16 | import java.util.HashMap; 17 | import java.util.List; 18 | import java.util.Map; 19 | import java.util.Set; 20 | import java.util.concurrent.CopyOnWriteArraySet; 21 | 22 | /** 23 | * BaseSpringSystem maintains the set of springs within an Application context. It is responsible for 24 | * Running the spring integration loop and maintains a registry of all the Springs it solves for. 25 | * In addition to listening to physics events on the individual Springs in the system, listeners 26 | * can be added to the BaseSpringSystem itself to provide pre and post integration setup. 27 | */ 28 | public class BaseSpringSystem { 29 | 30 | private final Map mSpringRegistry = new HashMap(); 31 | private final Set mActiveSprings = new CopyOnWriteArraySet(); 32 | private final SpringLooper mSpringLooper; 33 | private final CopyOnWriteArraySet mListeners = new CopyOnWriteArraySet(); 34 | private boolean mIdle = true; 35 | 36 | /** 37 | * create a new BaseSpringSystem 38 | * @param springLooper parameterized springLooper to allow testability of the 39 | * physics loop 40 | */ 41 | public BaseSpringSystem(SpringLooper springLooper) { 42 | if (springLooper == null) { 43 | throw new IllegalArgumentException("springLooper is required"); 44 | } 45 | mSpringLooper = springLooper; 46 | mSpringLooper.setSpringSystem(this); 47 | } 48 | 49 | /** 50 | * check if the system is idle 51 | * @return is the system idle 52 | */ 53 | public boolean getIsIdle() { 54 | return mIdle; 55 | } 56 | 57 | /** 58 | * create a spring with a random uuid for its name. 59 | * @return the spring 60 | */ 61 | public Spring createSpring() { 62 | Spring spring = new Spring(this); 63 | registerSpring(spring); 64 | return spring; 65 | } 66 | 67 | /** 68 | * get a spring by name 69 | * @param id id of the spring to retrieve 70 | * @return Spring with the specified key 71 | */ 72 | public Spring getSpringById(String id) { 73 | if (id == null) { 74 | throw new IllegalArgumentException("id is required"); 75 | } 76 | return mSpringRegistry.get(id); 77 | } 78 | 79 | /** 80 | * return all the springs in the simulator 81 | * @return all the springs 82 | */ 83 | public List getAllSprings() { 84 | Collection collection = mSpringRegistry.values(); 85 | List list; 86 | if (collection instanceof List) { 87 | list = (List)collection; 88 | } else { 89 | list = new ArrayList(collection); 90 | } 91 | return Collections.unmodifiableList(list); 92 | } 93 | 94 | /** 95 | * Registers a Spring to this BaseSpringSystem so it can be iterated if active. 96 | * @param spring the Spring to register 97 | */ 98 | void registerSpring(Spring spring) { 99 | if (spring == null) { 100 | throw new IllegalArgumentException("spring is required"); 101 | } 102 | if (mSpringRegistry.containsKey(spring.getId())) { 103 | throw new IllegalArgumentException("spring is already registered"); } 104 | mSpringRegistry.put(spring.getId(), spring); 105 | } 106 | 107 | /** 108 | * Deregisters a Spring from this BaseSpringSystem, so it won't be iterated anymore. The Spring should 109 | * not be used anymore after doing this. 110 | * 111 | * @param spring the Spring to deregister 112 | */ 113 | void deregisterSpring(Spring spring) { 114 | if (spring == null) { 115 | throw new IllegalArgumentException("spring is required"); 116 | } 117 | mActiveSprings.remove(spring); 118 | mSpringRegistry.remove(spring.getId()); 119 | } 120 | 121 | /** 122 | * update the springs in the system 123 | * @param deltaTime delta since last update in millis 124 | */ 125 | void advance(double deltaTime) { 126 | for (Spring spring : mActiveSprings) { 127 | // advance time in seconds 128 | if (spring.systemShouldAdvance()) { 129 | spring.advance(deltaTime / 1000.0); 130 | } else { 131 | mActiveSprings.remove(spring); 132 | } 133 | } 134 | } 135 | 136 | /** 137 | * loop the system until idle 138 | * @param elapsedMillis elapsed milliseconds 139 | */ 140 | public void loop(double elapsedMillis) { 141 | for (SpringSystemListener listener : mListeners) { 142 | listener.onBeforeIntegrate(this); 143 | } 144 | advance(elapsedMillis); 145 | if (mActiveSprings.isEmpty()) { 146 | mIdle = true; 147 | } 148 | for (SpringSystemListener listener : mListeners) { 149 | listener.onAfterIntegrate(this); 150 | } 151 | if (mIdle) { 152 | mSpringLooper.stop(); 153 | } 154 | } 155 | 156 | /** 157 | * This is used internally by the {@link Spring}s created by this {@link BaseSpringSystem} to notify 158 | * it has reached a state where it needs to be iterated. This will add the spring to the list of 159 | * active springs on this system and start the iteration if the system was idle before this call. 160 | * @param springId the id of the Spring to be activated 161 | */ 162 | void activateSpring(String springId) { 163 | Spring spring = mSpringRegistry.get(springId); 164 | if (spring == null) { 165 | throw new IllegalArgumentException("springId " + springId + " does not reference a registered spring"); 166 | } 167 | mActiveSprings.add(spring); 168 | if (getIsIdle()) { 169 | mIdle = false; 170 | mSpringLooper.start(); 171 | } 172 | } 173 | 174 | /** listeners **/ 175 | 176 | /** 177 | * Add new listener object. 178 | * @param newListener listener 179 | */ 180 | public void addListener(SpringSystemListener newListener) { 181 | if (newListener == null) { 182 | throw new IllegalArgumentException("newListener is required"); 183 | } 184 | mListeners.add(newListener); 185 | } 186 | 187 | /** 188 | * Remove listener object. 189 | * @param listenerToRemove listener 190 | */ 191 | public void removeListener(SpringSystemListener listenerToRemove) { 192 | if (listenerToRemove == null) { 193 | throw new IllegalArgumentException("listenerToRemove is required"); 194 | } 195 | mListeners.remove(listenerToRemove); 196 | } 197 | 198 | /** 199 | * Remove all listeners. 200 | */ 201 | public void removeAllListeners() { 202 | mListeners.clear(); 203 | } 204 | } 205 | 206 | 207 | -------------------------------------------------------------------------------- /rebound-core/src/main/java/com/facebook/rebound/BouncyConversion.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | package com.facebook.rebound; 12 | 13 | /** 14 | * This class converts values from the Quartz Composer Bouncy patch into Bouncy QC tension and 15 | * friction values. 16 | */ 17 | public class BouncyConversion { 18 | 19 | private final double mBouncyTension; 20 | private final double mBouncyFriction; 21 | private final double mSpeed; 22 | private final double mBounciness; 23 | 24 | public BouncyConversion(double speed, double bounciness) { 25 | mSpeed = speed; 26 | mBounciness = bounciness; 27 | double b = normalize(bounciness / 1.7, 0, 20.); 28 | b = project_normal(b, 0.0, 0.8); 29 | double s = normalize(speed / 1.7, 0, 20.); 30 | mBouncyTension = project_normal(s, 0.5, 200); 31 | mBouncyFriction = quadratic_out_interpolation(b, b3_nobounce(mBouncyTension), 0.01); 32 | } 33 | 34 | public double getSpeed() { 35 | return mSpeed; 36 | } 37 | 38 | public double getBounciness() { 39 | return mBounciness; 40 | } 41 | 42 | public double getBouncyTension() { 43 | return mBouncyTension; 44 | } 45 | 46 | public double getBouncyFriction() { 47 | return mBouncyFriction; 48 | } 49 | 50 | private double normalize(double value, double startValue, double endValue) { 51 | return (value - startValue) / (endValue - startValue); 52 | } 53 | 54 | private double project_normal(double n, double start, double end) { 55 | return start + (n * (end - start)); 56 | } 57 | 58 | private double linear_interpolation(double t, double start, double end) { 59 | return t * end + (1.f - t) * start; 60 | } 61 | 62 | private double quadratic_out_interpolation(double t, double start, double end) { 63 | return linear_interpolation(2*t - t*t, start, end); 64 | } 65 | 66 | private double b3_friction1(double x) { 67 | return (0.0007 * Math.pow(x, 3)) - (0.031 * Math.pow(x, 2)) + 0.64 * x + 1.28; 68 | } 69 | 70 | private double b3_friction2(double x) { 71 | return (0.000044 * Math.pow(x, 3)) - (0.006 * Math.pow(x, 2)) + 0.36 * x + 2.; 72 | } 73 | 74 | private double b3_friction3(double x) { 75 | return (0.00000045 * Math.pow(x, 3)) - (0.000332 * Math.pow(x, 2)) + 0.1078 * x + 5.84; 76 | } 77 | 78 | private double b3_nobounce(double tension) { 79 | double friction = 0; 80 | if (tension <= 18) { 81 | friction = b3_friction1(tension); 82 | } else if (tension > 18 && tension <= 44) { 83 | friction = b3_friction2(tension); 84 | } else if (tension > 44) { 85 | friction = b3_friction3(tension); 86 | } else { 87 | assert(false); 88 | } 89 | return friction; 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /rebound-core/src/main/java/com/facebook/rebound/OrigamiValueConverter.java: -------------------------------------------------------------------------------- 1 | package com.facebook.rebound; 2 | 3 | /** 4 | * Helper math util to convert tension & friction values from the Origami design tool to values 5 | * that the spring system needs. 6 | */ 7 | public class OrigamiValueConverter { 8 | 9 | public static double tensionFromOrigamiValue(double oValue) { 10 | return oValue == 0 ? 0 : (oValue - 30.0) * 3.62 + 194.0; 11 | } 12 | 13 | public static double origamiValueFromTension(double tension) { 14 | return tension == 0 ? 0 : (tension - 194.0) / 3.62 + 30.0; 15 | } 16 | 17 | public static double frictionFromOrigamiValue(double oValue) { 18 | return oValue == 0 ? 0 : (oValue - 8.0) * 3.0 + 25.0; 19 | } 20 | 21 | public static double origamiValueFromFriction(double friction) { 22 | return friction == 0 ? 0 : (friction - 25.0) / 3.0 + 8.0; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /rebound-core/src/main/java/com/facebook/rebound/SimpleSpringListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | package com.facebook.rebound; 12 | 13 | public class SimpleSpringListener implements SpringListener { 14 | @Override 15 | public void onSpringUpdate(Spring spring) { 16 | } 17 | 18 | @Override 19 | public void onSpringAtRest(Spring spring) { 20 | } 21 | 22 | @Override 23 | public void onSpringActivate(Spring spring) { 24 | } 25 | 26 | @Override 27 | public void onSpringEndStateChange(Spring spring) { 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /rebound-core/src/main/java/com/facebook/rebound/Spring.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | package com.facebook.rebound; 12 | 13 | import java.util.concurrent.CopyOnWriteArraySet; 14 | 15 | /** 16 | * Classical spring implementing Hooke's law with configurable friction and tension. 17 | */ 18 | public class Spring { 19 | 20 | // unique incrementer id for springs 21 | private static int ID = 0; 22 | 23 | // maximum amount of time to simulate per physics iteration in seconds (4 frames at 60 FPS) 24 | private static final double MAX_DELTA_TIME_SEC = 0.064; 25 | // fixed timestep to use in the physics solver in seconds 26 | private static final double SOLVER_TIMESTEP_SEC = 0.001; 27 | private SpringConfig mSpringConfig; 28 | private boolean mOvershootClampingEnabled; 29 | 30 | // storage for the current and prior physics state while integration is occurring 31 | private static class PhysicsState { 32 | double position; 33 | double velocity; 34 | } 35 | 36 | // unique id for the spring in the system 37 | private final String mId; 38 | // all physics simulation objects are final and reused in each processing pass 39 | private final PhysicsState mCurrentState = new PhysicsState(); 40 | private final PhysicsState mPreviousState = new PhysicsState(); 41 | private final PhysicsState mTempState = new PhysicsState(); 42 | private double mStartValue; 43 | private double mEndValue; 44 | private boolean mWasAtRest = true; 45 | // thresholds for determining when the spring is at rest 46 | private double mRestSpeedThreshold = 0.005; 47 | private double mDisplacementFromRestThreshold = 0.005; 48 | private double mTimeAccumulator = 0; 49 | private final CopyOnWriteArraySet mListeners = 50 | new CopyOnWriteArraySet(); 51 | 52 | private final BaseSpringSystem mSpringSystem; 53 | 54 | /** 55 | * create a new spring 56 | */ 57 | Spring(BaseSpringSystem springSystem) { 58 | if (springSystem == null) { 59 | throw new IllegalArgumentException("Spring cannot be created outside of a BaseSpringSystem"); 60 | } 61 | mSpringSystem = springSystem; 62 | mId = "spring:" + ID++; 63 | setSpringConfig(SpringConfig.defaultConfig); 64 | } 65 | 66 | /** 67 | * Destroys this Spring, meaning that it will be deregistered from its BaseSpringSystem so it won't be 68 | * iterated anymore and will clear its set of listeners. Do not use the Spring after calling this, 69 | * doing so may just cause an exception to be thrown. 70 | */ 71 | public void destroy() { 72 | mListeners.clear(); 73 | mSpringSystem.deregisterSpring(this); 74 | } 75 | 76 | /** 77 | * get the unique id for this spring 78 | * @return the unique id 79 | */ 80 | public String getId() { 81 | return mId; 82 | } 83 | 84 | /** 85 | * set the config class 86 | * @param springConfig config class for the spring 87 | * @return this Spring instance for chaining 88 | */ 89 | public Spring setSpringConfig(SpringConfig springConfig) { 90 | if (springConfig == null) { 91 | throw new IllegalArgumentException("springConfig is required"); 92 | } 93 | mSpringConfig = springConfig; 94 | return this; 95 | } 96 | 97 | /** 98 | * retrieve the spring config for this spring 99 | * @return the SpringConfig applied to this spring 100 | */ 101 | public SpringConfig getSpringConfig() { 102 | return mSpringConfig; 103 | } 104 | 105 | /** 106 | * Set the displaced value to determine the displacement for the spring from the rest value. 107 | * This value is retained and used to calculate the displacement ratio. 108 | * The default signature also sets the Spring at rest to facilitate the common behavior of moving 109 | * a spring to a new position. 110 | * @param currentValue the new start and current value for the spring 111 | * @return the spring for chaining 112 | */ 113 | public Spring setCurrentValue(double currentValue) { 114 | return setCurrentValue(currentValue, true); 115 | } 116 | 117 | /** 118 | * The full signature for setCurrentValue includes the option of not setting the spring at rest 119 | * after updating its currentValue. Passing setAtRest false means that if the endValue of the 120 | * spring is not equal to the currentValue, the physics system will start iterating to resolve 121 | * the spring to the end value. This is almost never the behavior that you want, so the default 122 | * setCurrentValue signature passes true. 123 | * @param currentValue the new start and current value for the spring 124 | * @param setAtRest optionally set the spring at rest after updating its current value. 125 | * see {@link com.facebook.rebound.Spring#setAtRest()} 126 | * @return the spring for chaining 127 | */ 128 | public Spring setCurrentValue(double currentValue, boolean setAtRest) { 129 | mStartValue = currentValue; 130 | mCurrentState.position = currentValue; 131 | mSpringSystem.activateSpring(this.getId()); 132 | for (SpringListener listener : mListeners) { 133 | listener.onSpringUpdate(this); 134 | } 135 | if (setAtRest) { 136 | setAtRest(); 137 | } 138 | return this; 139 | } 140 | 141 | /** 142 | * Get the displacement value from the last time setCurrentValue was called. 143 | * @return displacement value 144 | */ 145 | public double getStartValue() { 146 | return mStartValue; 147 | } 148 | 149 | /** 150 | * Get the current 151 | * @return current value 152 | */ 153 | public double getCurrentValue() { 154 | return mCurrentState.position; 155 | } 156 | 157 | /** 158 | * get the displacement of the springs current value from its rest value. 159 | * @return the distance displaced by 160 | */ 161 | public double getCurrentDisplacementDistance() { 162 | return getDisplacementDistanceForState(mCurrentState); 163 | } 164 | 165 | /** 166 | * get the displacement from rest for a given physics state 167 | * @param state the state to measure from 168 | * @return the distance displaced by 169 | */ 170 | private double getDisplacementDistanceForState(PhysicsState state) { 171 | return Math.abs(mEndValue - state.position); 172 | } 173 | 174 | /** 175 | * set the rest value to determine the displacement for the spring 176 | * @param endValue the endValue for the spring 177 | * @return the spring for chaining 178 | */ 179 | public Spring setEndValue(double endValue) { 180 | if (mEndValue == endValue && isAtRest()) { 181 | return this; 182 | } 183 | mStartValue = getCurrentValue(); 184 | mEndValue = endValue; 185 | mSpringSystem.activateSpring(this.getId()); 186 | for (SpringListener listener : mListeners) { 187 | listener.onSpringEndStateChange(this); 188 | } 189 | return this; 190 | } 191 | 192 | /** 193 | * get the rest value used for determining the displacement of the spring 194 | * @return the rest value for the spring 195 | */ 196 | public double getEndValue() { 197 | return mEndValue; 198 | } 199 | 200 | /** 201 | * set the velocity on the spring in pixels per second 202 | * @param velocity velocity value 203 | * @return the spring for chaining 204 | */ 205 | public Spring setVelocity(double velocity) { 206 | if (velocity == mCurrentState.velocity) { 207 | return this; 208 | } 209 | mCurrentState.velocity = velocity; 210 | mSpringSystem.activateSpring(this.getId()); 211 | return this; 212 | } 213 | 214 | /** 215 | * get the velocity of the spring 216 | * @return the current velocity 217 | */ 218 | public double getVelocity() { 219 | return mCurrentState.velocity; 220 | } 221 | 222 | /** 223 | * Sets the speed at which the spring should be considered at rest. 224 | * @param restSpeedThreshold speed pixels per second 225 | * @return the spring for chaining 226 | */ 227 | public Spring setRestSpeedThreshold(double restSpeedThreshold) { 228 | mRestSpeedThreshold = restSpeedThreshold; 229 | return this; 230 | } 231 | 232 | /** 233 | * Returns the speed at which the spring should be considered at rest in pixels per second 234 | * @return speed in pixels per second 235 | */ 236 | public double getRestSpeedThreshold() { 237 | return mRestSpeedThreshold; 238 | } 239 | 240 | /** 241 | * set the threshold of displacement from rest below which the spring should be considered at rest 242 | * @param displacementFromRestThreshold displacement to consider resting below 243 | * @return the spring for chaining 244 | */ 245 | public Spring setRestDisplacementThreshold(double displacementFromRestThreshold) { 246 | mDisplacementFromRestThreshold = displacementFromRestThreshold; 247 | return this; 248 | } 249 | 250 | /** 251 | * get the threshold of displacement from rest below which the spring should be considered at rest 252 | * @return displacement to consider resting below 253 | */ 254 | public double getRestDisplacementThreshold() { 255 | return mDisplacementFromRestThreshold; 256 | } 257 | 258 | /** 259 | * Force the spring to clamp at its end value to avoid overshooting the target value. 260 | * @param overshootClampingEnabled whether or not to enable overshoot clamping 261 | * @return the spring for chaining 262 | */ 263 | public Spring setOvershootClampingEnabled(boolean overshootClampingEnabled) { 264 | mOvershootClampingEnabled = overshootClampingEnabled; 265 | return this; 266 | } 267 | 268 | /** 269 | * Check if overshoot clamping is enabled. 270 | * @return is overshoot clamping enabled 271 | */ 272 | public boolean isOvershootClampingEnabled() { 273 | return mOvershootClampingEnabled; 274 | } 275 | 276 | /** 277 | * Check if the spring is overshooting beyond its target. 278 | * @return true if the spring is overshooting its target 279 | */ 280 | public boolean isOvershooting() { 281 | return mSpringConfig.tension > 0 && 282 | ((mStartValue < mEndValue && getCurrentValue() > mEndValue) || 283 | (mStartValue > mEndValue && getCurrentValue() < mEndValue)); 284 | } 285 | 286 | /** 287 | * advance the physics simulation in SOLVER_TIMESTEP_SEC sized chunks to fulfill the required 288 | * realTimeDelta. 289 | * The math is inlined inside the loop since it made a huge performance impact when there are 290 | * several springs being advanced. 291 | * @param realDeltaTime clock drift 292 | */ 293 | void advance(double realDeltaTime) { 294 | 295 | boolean isAtRest = isAtRest(); 296 | 297 | if (isAtRest && mWasAtRest) { 298 | /* begin debug 299 | Log.d(TAG, "bailing out because we are at rest:" + getName()); 300 | end debug */ 301 | return; 302 | } 303 | 304 | // clamp the amount of realTime to simulate to avoid stuttering in the UI. We should be able 305 | // to catch up in a subsequent advance if necessary. 306 | double adjustedDeltaTime = realDeltaTime; 307 | if (realDeltaTime > MAX_DELTA_TIME_SEC) { 308 | adjustedDeltaTime = MAX_DELTA_TIME_SEC; 309 | } 310 | 311 | /* begin debug 312 | long startTime = System.currentTimeMillis(); 313 | int iterations = 0; 314 | end debug */ 315 | 316 | mTimeAccumulator += adjustedDeltaTime; 317 | 318 | double tension = mSpringConfig.tension; 319 | double friction = mSpringConfig.friction; 320 | 321 | double position = mCurrentState.position; 322 | double velocity = mCurrentState.velocity; 323 | double tempPosition = mTempState.position; 324 | double tempVelocity = mTempState.velocity; 325 | 326 | double aVelocity, aAcceleration; 327 | double bVelocity, bAcceleration; 328 | double cVelocity, cAcceleration; 329 | double dVelocity, dAcceleration; 330 | 331 | double dxdt, dvdt; 332 | 333 | // iterate over the true time 334 | while (mTimeAccumulator >= SOLVER_TIMESTEP_SEC) { 335 | /* begin debug 336 | iterations++; 337 | end debug */ 338 | mTimeAccumulator -= SOLVER_TIMESTEP_SEC; 339 | 340 | if (mTimeAccumulator < SOLVER_TIMESTEP_SEC) { 341 | // This will be the last iteration. Remember the previous state in case we need to 342 | // interpolate 343 | mPreviousState.position = position; 344 | mPreviousState.velocity = velocity; 345 | } 346 | 347 | // Perform an RK4 integration to provide better detection of the acceleration curve via 348 | // sampling of Euler integrations at 4 intervals feeding each derivative into the calculation 349 | // of the next and taking a weighted sum of the 4 derivatives as the final output. 350 | 351 | // This math was inlined since it made for big performance improvements when advancing several 352 | // springs in one pass of the BaseSpringSystem. 353 | 354 | // The initial derivative is based on the current velocity and the calculated acceleration 355 | aVelocity = velocity; 356 | aAcceleration = (tension * (mEndValue - tempPosition)) - friction * velocity; 357 | 358 | // Calculate the next derivatives starting with the last derivative and integrating over the 359 | // timestep 360 | tempPosition = position + aVelocity * SOLVER_TIMESTEP_SEC * 0.5; 361 | tempVelocity = velocity + aAcceleration * SOLVER_TIMESTEP_SEC * 0.5; 362 | bVelocity = tempVelocity; 363 | bAcceleration = (tension * (mEndValue - tempPosition)) - friction * tempVelocity; 364 | 365 | tempPosition = position + bVelocity * SOLVER_TIMESTEP_SEC * 0.5; 366 | tempVelocity = velocity + bAcceleration * SOLVER_TIMESTEP_SEC * 0.5; 367 | cVelocity = tempVelocity; 368 | cAcceleration = (tension * (mEndValue - tempPosition)) - friction * tempVelocity; 369 | 370 | tempPosition = position + cVelocity * SOLVER_TIMESTEP_SEC; 371 | tempVelocity = velocity + cAcceleration * SOLVER_TIMESTEP_SEC; 372 | dVelocity = tempVelocity; 373 | dAcceleration = (tension * (mEndValue - tempPosition)) - friction * tempVelocity; 374 | 375 | // Take the weighted sum of the 4 derivatives as the final output. 376 | dxdt = 1.0/6.0 * (aVelocity + 2.0 * (bVelocity + cVelocity) + dVelocity); 377 | dvdt = 1.0/6.0 * (aAcceleration + 2.0 * (bAcceleration + cAcceleration) + dAcceleration); 378 | 379 | position += dxdt * SOLVER_TIMESTEP_SEC; 380 | velocity += dvdt * SOLVER_TIMESTEP_SEC; 381 | } 382 | 383 | mTempState.position = tempPosition; 384 | mTempState.velocity = tempVelocity; 385 | 386 | mCurrentState.position = position; 387 | mCurrentState.velocity = velocity; 388 | 389 | if (mTimeAccumulator > 0) { 390 | interpolate(mTimeAccumulator / SOLVER_TIMESTEP_SEC); 391 | } 392 | 393 | // End the spring immediately if it is overshooting and overshoot clamping is enabled. 394 | // Also make sure that if the spring was considered within a resting threshold that it's now 395 | // snapped to its end value. 396 | if (isAtRest() || (mOvershootClampingEnabled && isOvershooting())) { 397 | // Don't call setCurrentValue because that forces a call to onSpringUpdate 398 | if (tension > 0) { 399 | mStartValue = mEndValue; 400 | mCurrentState.position = mEndValue; 401 | } else { 402 | mEndValue = mCurrentState.position; 403 | mStartValue = mEndValue; 404 | } 405 | setVelocity(0); 406 | isAtRest = true; 407 | } 408 | 409 | /* begin debug 410 | long endTime = System.currentTimeMillis(); 411 | long elapsedMillis = endTime - startTime; 412 | Log.d(TAG, 413 | "iterations:" + iterations + 414 | " iterationTime:" + elapsedMillis + 415 | " position:" + mCurrentState.position + 416 | " velocity:" + mCurrentState.velocity + 417 | " realDeltaTime:" + realDeltaTime + 418 | " adjustedDeltaTime:" + adjustedDeltaTime + 419 | " isAtRest:" + isAtRest + 420 | " wasAtRest:" + mWasAtRest); 421 | end debug */ 422 | 423 | // NB: do these checks outside the loop so all listeners are properly notified of the state 424 | // transition 425 | boolean notifyActivate = false; 426 | if (mWasAtRest) { 427 | mWasAtRest = false; 428 | notifyActivate = true; 429 | } 430 | boolean notifyAtRest = false; 431 | if (isAtRest) { 432 | mWasAtRest = true; 433 | notifyAtRest = true; 434 | } 435 | for (SpringListener listener : mListeners) { 436 | // starting to move 437 | if (notifyActivate) { 438 | listener.onSpringActivate(this); 439 | } 440 | 441 | // updated 442 | listener.onSpringUpdate(this); 443 | 444 | // coming to rest 445 | if (notifyAtRest) { 446 | listener.onSpringAtRest(this); 447 | } 448 | } 449 | } 450 | 451 | /** 452 | * Check if this spring should be advanced by the system. * The rule is if the spring is 453 | * currently at rest and it was at rest in the previous advance, the system can skip this spring 454 | * @return should the system process this spring 455 | */ 456 | public boolean systemShouldAdvance() { 457 | return !isAtRest() || !wasAtRest(); 458 | } 459 | 460 | /** 461 | * Check if the spring was at rest in the prior iteration. This is used for ensuring the ending 462 | * callbacks are fired as the spring comes to a rest. 463 | * @return true if the spring was at rest in the prior iteration 464 | */ 465 | public boolean wasAtRest() { 466 | return mWasAtRest; 467 | } 468 | 469 | /** 470 | * check if the current state is at rest 471 | * @return is the spring at rest 472 | */ 473 | public boolean isAtRest() { 474 | return Math.abs(mCurrentState.velocity) <= mRestSpeedThreshold && 475 | (getDisplacementDistanceForState(mCurrentState) <= mDisplacementFromRestThreshold || 476 | mSpringConfig.tension == 0); 477 | } 478 | 479 | /** 480 | * Set the spring to be at rest by making its end value equal to its current value and setting 481 | * velocity to 0. 482 | * @return this object 483 | */ 484 | public Spring setAtRest() { 485 | mEndValue = mCurrentState.position; 486 | mTempState.position = mCurrentState.position; 487 | mCurrentState.velocity = 0; 488 | return this; 489 | } 490 | 491 | /** 492 | * linear interpolation between the previous and current physics state based on the amount of 493 | * timestep remaining after processing the rendering delta time in timestep sized chunks. 494 | * @param alpha from 0 to 1, where 0 is the previous state, 1 is the current state 495 | */ 496 | private void interpolate(double alpha) { 497 | mCurrentState.position = mCurrentState.position * alpha + mPreviousState.position *(1-alpha); 498 | mCurrentState.velocity = mCurrentState.velocity * alpha + mPreviousState.velocity *(1-alpha); 499 | } 500 | 501 | /** listeners **/ 502 | 503 | /** 504 | * add a listener 505 | * @param newListener to add 506 | * @return the spring for chaining 507 | */ 508 | public Spring addListener(SpringListener newListener) { 509 | if (newListener == null) { 510 | throw new IllegalArgumentException("newListener is required"); 511 | } 512 | mListeners.add(newListener); 513 | return this; 514 | } 515 | 516 | /** 517 | * remove a listener 518 | * @param listenerToRemove to remove 519 | * @return the spring for chaining 520 | */ 521 | public Spring removeListener(SpringListener listenerToRemove) { 522 | if (listenerToRemove == null) { 523 | throw new IllegalArgumentException("listenerToRemove is required"); 524 | } 525 | mListeners.remove(listenerToRemove); 526 | return this; 527 | } 528 | 529 | /** 530 | * remove all of the listeners 531 | * @return the spring for chaining 532 | */ 533 | public Spring removeAllListeners() { 534 | mListeners.clear(); 535 | return this; 536 | } 537 | 538 | /** 539 | * This method checks to see that the current spring displacement value is equal to the input, 540 | * accounting for the spring's rest displacement threshold. 541 | * @param value The value to compare the spring value to 542 | * @return Whether the displacement value from the spring is within the bounds of the compare 543 | * value, accounting for threshold 544 | */ 545 | public boolean currentValueIsApproximately(double value) { 546 | return Math.abs(getCurrentValue() - value) <= getRestDisplacementThreshold(); 547 | } 548 | 549 | } 550 | 551 | -------------------------------------------------------------------------------- /rebound-core/src/main/java/com/facebook/rebound/SpringConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | package com.facebook.rebound; 12 | 13 | /** 14 | * Data structure for storing spring configuration. 15 | */ 16 | public class SpringConfig { 17 | public double friction; 18 | public double tension; 19 | 20 | public static SpringConfig defaultConfig = SpringConfig.fromOrigamiTensionAndFriction(40, 7); 21 | 22 | /** 23 | * constructor for the SpringConfig 24 | * @param tension tension value for the SpringConfig 25 | * @param friction friction value for the SpringConfig 26 | */ 27 | public SpringConfig(double tension, double friction) { 28 | this.tension = tension; 29 | this.friction = friction; 30 | } 31 | 32 | /** 33 | * A helper to make creating a SpringConfig easier with values mapping to the Origami values. 34 | * @param qcTension tension as defined in the Quartz Composition 35 | * @param qcFriction friction as defined in the Quartz Composition 36 | * @return a SpringConfig that maps to these values 37 | */ 38 | public static SpringConfig fromOrigamiTensionAndFriction(double qcTension, double qcFriction) { 39 | return new SpringConfig( 40 | OrigamiValueConverter.tensionFromOrigamiValue(qcTension), 41 | OrigamiValueConverter.frictionFromOrigamiValue(qcFriction) 42 | ); 43 | } 44 | 45 | /** 46 | * Map values from the Origami POP Animation patch, which are based on a bounciness and speed 47 | * value. 48 | * @param bounciness bounciness of the POP Animation 49 | * @param speed speed of the POP Animation 50 | * @return a SpringConfig mapping to the specified POP Animation values. 51 | */ 52 | public static SpringConfig fromBouncinessAndSpeed(double bounciness, double speed) { 53 | BouncyConversion bouncyConversion = new BouncyConversion(speed, bounciness); 54 | return fromOrigamiTensionAndFriction( 55 | bouncyConversion.getBouncyTension(), 56 | bouncyConversion.getBouncyFriction()); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /rebound-core/src/main/java/com/facebook/rebound/SpringConfigRegistry.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | package com.facebook.rebound; 12 | 13 | import java.util.Collections; 14 | import java.util.HashMap; 15 | import java.util.Map; 16 | 17 | /** 18 | * class for maintaining a registry of all spring configs 19 | */ 20 | public class SpringConfigRegistry { 21 | 22 | private static final SpringConfigRegistry INSTANCE = new SpringConfigRegistry(true); 23 | 24 | public static SpringConfigRegistry getInstance() { 25 | return INSTANCE; 26 | } 27 | 28 | private final Map mSpringConfigMap; 29 | 30 | /** 31 | * constructor for the SpringConfigRegistry 32 | */ 33 | SpringConfigRegistry(boolean includeDefaultEntry) { 34 | mSpringConfigMap = new HashMap(); 35 | if (includeDefaultEntry) { 36 | addSpringConfig(SpringConfig.defaultConfig, "default config"); 37 | } 38 | } 39 | 40 | /** 41 | * add a SpringConfig to the registry 42 | * 43 | * @param springConfig SpringConfig to add to the registry 44 | * @param configName name to give the SpringConfig in the registry 45 | * @return true if the SpringConfig was added, false if a config with that name is already 46 | * present. 47 | */ 48 | public boolean addSpringConfig(SpringConfig springConfig, String configName) { 49 | if (springConfig == null) { 50 | throw new IllegalArgumentException("springConfig is required"); 51 | } 52 | if (configName == null) { 53 | throw new IllegalArgumentException("configName is required"); 54 | } 55 | if (mSpringConfigMap.containsKey(springConfig)) { 56 | return false; 57 | } 58 | mSpringConfigMap.put(springConfig, configName); 59 | return true; 60 | } 61 | 62 | /** 63 | * remove a specific SpringConfig from the registry 64 | * @param springConfig the of the SpringConfig to remove 65 | * @return true if the SpringConfig was removed, false if it was not present. 66 | */ 67 | public boolean removeSpringConfig(SpringConfig springConfig) { 68 | if (springConfig == null) { 69 | throw new IllegalArgumentException("springConfig is required"); 70 | } 71 | return mSpringConfigMap.remove(springConfig) != null; 72 | } 73 | 74 | /** 75 | * retrieve all SpringConfig in the registry 76 | * @return a list of all SpringConfig 77 | */ 78 | public Map getAllSpringConfig() { 79 | return Collections.unmodifiableMap(mSpringConfigMap); 80 | } 81 | 82 | /** 83 | * clear all SpringConfig in the registry 84 | */ 85 | public void removeAllSpringConfig() { 86 | mSpringConfigMap.clear(); 87 | } 88 | } 89 | 90 | -------------------------------------------------------------------------------- /rebound-core/src/main/java/com/facebook/rebound/SpringListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | package com.facebook.rebound; 12 | 13 | public interface SpringListener { 14 | 15 | /** 16 | * called whenever the spring is updated 17 | * @param spring the Spring sending the update 18 | */ 19 | void onSpringUpdate(Spring spring); 20 | 21 | /** 22 | * called whenever the spring achieves a resting state 23 | * @param spring the spring that's now resting 24 | */ 25 | void onSpringAtRest(Spring spring); 26 | 27 | /** 28 | * called whenever the spring leaves its resting state 29 | * @param spring the spring that has left its resting state 30 | */ 31 | void onSpringActivate(Spring spring); 32 | 33 | /** 34 | * called whenever the spring notifies of displacement state changes 35 | * @param spring the spring whose end state has changed 36 | */ 37 | void onSpringEndStateChange(Spring spring); 38 | } 39 | 40 | -------------------------------------------------------------------------------- /rebound-core/src/main/java/com/facebook/rebound/SpringLooper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | package com.facebook.rebound; 12 | 13 | /** 14 | * The spring looper is an interface for implementing platform-dependent run loops. 15 | */ 16 | public abstract class SpringLooper { 17 | 18 | protected BaseSpringSystem mSpringSystem; 19 | 20 | /** 21 | * Set the BaseSpringSystem that the SpringLooper will call back to. 22 | * @param springSystem the spring system to call loop on. 23 | */ 24 | public void setSpringSystem(BaseSpringSystem springSystem) { 25 | mSpringSystem = springSystem; 26 | } 27 | 28 | /** 29 | * The BaseSpringSystem has requested that the looper begins running this {@link Runnable} 30 | * on every frame. The {@link Runnable} will continue running on every frame until 31 | * {@link #stop()} is called. 32 | * If an existing {@link Runnable} had been started on this looper, it will be cancelled. 33 | */ 34 | public abstract void start(); 35 | 36 | /** 37 | * The looper will no longer run the {@link Runnable}. 38 | */ 39 | public abstract void stop(); 40 | } -------------------------------------------------------------------------------- /rebound-core/src/main/java/com/facebook/rebound/SpringSystemListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | package com.facebook.rebound; 12 | 13 | /** 14 | * SpringSystemListener provides an interface for listening to events before and after each Physics 15 | * solving loop the BaseSpringSystem runs. 16 | */ 17 | public interface SpringSystemListener { 18 | 19 | /** 20 | * Runs before each pass through the physics integration loop providing an opportunity to do any 21 | * setup or alterations to the Physics state before integrating. 22 | * @param springSystem the BaseSpringSystem listened to 23 | */ 24 | void onBeforeIntegrate(BaseSpringSystem springSystem); 25 | 26 | /** 27 | * Runs after each pass through the physics integration loop providing an opportunity to do any 28 | * setup or alterations to the Physics state after integrating. 29 | * @param springSystem the BaseSpringSystem listened to 30 | */ 31 | void onAfterIntegrate(BaseSpringSystem springSystem); 32 | } 33 | 34 | -------------------------------------------------------------------------------- /rebound-core/src/main/java/com/facebook/rebound/SpringUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | package com.facebook.rebound; 12 | 13 | public class SpringUtil { 14 | 15 | /** 16 | * Map a value within a given range to another range. 17 | * @param value the value to map 18 | * @param fromLow the low end of the range the value is within 19 | * @param fromHigh the high end of the range the value is within 20 | * @param toLow the low end of the range to map to 21 | * @param toHigh the high end of the range to map to 22 | * @return the mapped value 23 | */ 24 | public static double mapValueFromRangeToRange( 25 | double value, 26 | double fromLow, 27 | double fromHigh, 28 | double toLow, 29 | double toHigh) { 30 | double fromRangeSize = fromHigh - fromLow; 31 | double toRangeSize = toHigh - toLow; 32 | double valueScale = (value - fromLow) / fromRangeSize; 33 | return toLow + (valueScale * toRangeSize); 34 | } 35 | 36 | /** 37 | * Clamp a value to be within the provided range. 38 | * @param value the value to clamp 39 | * @param low the low end of the range 40 | * @param high the high end of the range 41 | * @return the clamped value 42 | */ 43 | public static double clamp(double value, double low, double high) { 44 | return Math.min(Math.max(value, low), high); 45 | } 46 | } 47 | 48 | -------------------------------------------------------------------------------- /rebound-core/src/main/java/com/facebook/rebound/SteppingLooper.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | package com.facebook.rebound; 11 | 12 | public class SteppingLooper extends SpringLooper { 13 | 14 | private boolean mStarted; 15 | private long mLastTime; 16 | 17 | @Override 18 | public void start() { 19 | mStarted = true; 20 | mLastTime = 0; 21 | } 22 | 23 | public boolean step(long interval) { 24 | if (mSpringSystem == null || !mStarted) { 25 | return false; 26 | } 27 | long currentTime = mLastTime + interval; 28 | mSpringSystem.loop(currentTime); 29 | mLastTime = currentTime; 30 | return mSpringSystem.getIsIdle(); 31 | } 32 | 33 | @Override 34 | public void stop() { 35 | mStarted = false; 36 | } 37 | } 38 | 39 | -------------------------------------------------------------------------------- /rebound-core/src/main/java/com/facebook/rebound/SynchronousLooper.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | package com.facebook.rebound; 11 | 12 | public class SynchronousLooper extends SpringLooper { 13 | 14 | public static final double SIXTY_FPS = 16.6667; 15 | private double mTimeStep; 16 | private boolean mRunning; 17 | 18 | public SynchronousLooper() { 19 | mTimeStep = SIXTY_FPS; 20 | } 21 | 22 | public double getTimeStep() { 23 | return mTimeStep; 24 | } 25 | 26 | public void setTimeStep(double timeStep) { 27 | mTimeStep = timeStep; 28 | } 29 | 30 | @Override 31 | public void start() { 32 | mRunning = true; 33 | while (!mSpringSystem.getIsIdle()) { 34 | if (mRunning == false) { 35 | break; 36 | } 37 | mSpringSystem.loop(mTimeStep); 38 | } 39 | } 40 | 41 | @Override 42 | public void stop() { 43 | mRunning = false; 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /rebound-core/src/test/java/com/facebook/rebound/SpringConfigRegistryTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | package com.facebook.rebound; 12 | 13 | import org.junit.Before; 14 | import org.junit.Test; 15 | 16 | import java.util.Map; 17 | 18 | import static org.junit.Assert.assertEquals; 19 | import static org.junit.Assert.assertTrue; 20 | import static org.mockito.Mockito.spy; 21 | 22 | public class SpringConfigRegistryTest { 23 | 24 | private static final String CONFIG_NAME = "test config"; 25 | private static final double TENSION = SpringConfig.defaultConfig.tension; 26 | private static final double FRICTION = SpringConfig.defaultConfig.friction; 27 | 28 | private SpringConfigRegistry mSpringConfigRegistrySpy; 29 | 30 | @Before 31 | public void beforeEach() { 32 | mSpringConfigRegistrySpy = spy(new SpringConfigRegistry(false)); 33 | } 34 | 35 | @Test 36 | public void testAddSpringConfig() { 37 | SpringConfig config = new SpringConfig(TENSION, FRICTION); 38 | mSpringConfigRegistrySpy.addSpringConfig(config, CONFIG_NAME); 39 | Map configs = mSpringConfigRegistrySpy.getAllSpringConfig(); 40 | 41 | assertEquals(1, configs.size()); 42 | assertTrue(configs.containsKey(config)); 43 | String configName = configs.get(config); 44 | assertEquals(configName, CONFIG_NAME); 45 | } 46 | 47 | @Test 48 | public void testRemoveSpringConfig() { 49 | SpringConfig config = new SpringConfig(TENSION, FRICTION); 50 | mSpringConfigRegistrySpy.addSpringConfig(config, CONFIG_NAME); 51 | 52 | Map configs = mSpringConfigRegistrySpy.getAllSpringConfig(); 53 | assertEquals(1, configs.size()); 54 | mSpringConfigRegistrySpy.removeSpringConfig(config); 55 | 56 | configs = mSpringConfigRegistrySpy.getAllSpringConfig(); 57 | assertEquals(0, configs.size()); 58 | } 59 | 60 | @Test 61 | public void testRemoveAllSpringConfig() { 62 | SpringConfig configA = new SpringConfig(0, 0); 63 | SpringConfig configB = new SpringConfig(0, 0); 64 | SpringConfig configC = new SpringConfig(0, 0); 65 | 66 | mSpringConfigRegistrySpy.addSpringConfig(configA, "a"); 67 | mSpringConfigRegistrySpy.addSpringConfig(configB, "b"); 68 | mSpringConfigRegistrySpy.addSpringConfig(configC, "c"); 69 | 70 | Map configs = mSpringConfigRegistrySpy.getAllSpringConfig(); 71 | assertEquals(3, configs.size()); 72 | 73 | mSpringConfigRegistrySpy.removeAllSpringConfig(); 74 | configs = mSpringConfigRegistrySpy.getAllSpringConfig(); 75 | assertEquals(0, configs.size()); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /rebound-core/src/test/java/com/facebook/rebound/SpringSystemTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | package com.facebook.rebound; 12 | 13 | import org.junit.Before; 14 | import org.junit.Test; 15 | import org.mockito.InOrder; 16 | 17 | import static org.junit.Assert.*; 18 | import static org.mockito.Mockito.*; 19 | 20 | public class SpringSystemTest { 21 | 22 | private BaseSpringSystem mSpringSystemSpy; 23 | private SynchronousLooper mSynchronousLooper; 24 | private Spring mMockSpring; 25 | 26 | @Before 27 | public void beforeEach() { 28 | mSynchronousLooper = new SynchronousLooper(); 29 | mSpringSystemSpy = spy(new BaseSpringSystem(mSynchronousLooper)); 30 | mSynchronousLooper.setSpringSystem(mSpringSystemSpy); 31 | // NB: make sure the runnable calls the spy 32 | mMockSpring = mock(Spring.class); 33 | when(mMockSpring.getId()).thenReturn("spring_id"); 34 | } 35 | 36 | @Test 37 | public void testRegisterSpringOnCreation() { 38 | Spring spring = mSpringSystemSpy.createSpring(); 39 | verify(mSpringSystemSpy).registerSpring(spring); 40 | } 41 | 42 | @Test 43 | public void testCreateSpringWithoutName() { 44 | Spring spring = mSpringSystemSpy.createSpring(); 45 | assertNotNull(spring.getId()); 46 | assertEquals(spring, mSpringSystemSpy.getSpringById(spring.getId())); 47 | assertEquals(1, mSpringSystemSpy.getAllSprings().size()); 48 | } 49 | 50 | @Test 51 | public void testLoop() { 52 | mSpringSystemSpy.registerSpring(mMockSpring); 53 | when(mMockSpring.systemShouldAdvance()).thenReturn(true, false); 54 | mSpringSystemSpy.activateSpring(mMockSpring.getId()); 55 | verify(mSpringSystemSpy, times(2)).advance(mSynchronousLooper.getTimeStep()); 56 | verify(mMockSpring, times(1)).advance(mSynchronousLooper.getTimeStep() / 1000); 57 | assertTrue(mSpringSystemSpy.getIsIdle()); 58 | } 59 | 60 | @Test 61 | public void testLoopWithMultiplePassesRunsAndTerminates() { 62 | InOrder inOrder = inOrder(mMockSpring, mSpringSystemSpy); 63 | when(mMockSpring.systemShouldAdvance()).thenReturn(true, true, true, false); 64 | 65 | mSpringSystemSpy.registerSpring(mMockSpring); 66 | mSpringSystemSpy.activateSpring(mMockSpring.getId()); 67 | 68 | double stepMillis = mSynchronousLooper.getTimeStep(); 69 | double stepSeconds = mSynchronousLooper.getTimeStep() / 1000; 70 | 71 | inOrder.verify(mSpringSystemSpy, times(1)).advance(stepMillis); 72 | inOrder.verify(mMockSpring, times(1)).advance(stepSeconds); 73 | inOrder.verify(mSpringSystemSpy, times(1)).advance(stepMillis); 74 | inOrder.verify(mMockSpring, times(1)).advance(stepSeconds); 75 | inOrder.verify(mSpringSystemSpy, times(1)).advance(stepMillis); 76 | inOrder.verify(mMockSpring, times(1)).advance(stepSeconds); 77 | inOrder.verify(mSpringSystemSpy, times(1)).advance(stepMillis); // one extra pass through the system 78 | 79 | assertTrue(mSpringSystemSpy.getIsIdle()); 80 | } 81 | 82 | @Test 83 | public void testSpringSystemListener() { 84 | SpringSystemListener listener = spy(new SpringSystemListener() { 85 | @Override 86 | public void onBeforeIntegrate(BaseSpringSystem springSystem) { 87 | } 88 | 89 | @Override 90 | public void onAfterIntegrate(BaseSpringSystem springSystem) { 91 | } 92 | }); 93 | 94 | InOrder inOrder = inOrder(listener); 95 | mSpringSystemSpy.registerSpring(mMockSpring); 96 | when(mMockSpring.systemShouldAdvance()).thenReturn(true, false); 97 | 98 | mSpringSystemSpy.addListener(listener); 99 | mSpringSystemSpy.activateSpring(mMockSpring.getId()); 100 | 101 | inOrder.verify(listener).onBeforeIntegrate(mSpringSystemSpy); 102 | inOrder.verify(listener).onAfterIntegrate(mSpringSystemSpy); 103 | inOrder.verify(listener).onBeforeIntegrate(mSpringSystemSpy); 104 | inOrder.verify(listener).onAfterIntegrate(mSpringSystemSpy); 105 | 106 | mSpringSystemSpy.removeListener(listener); 107 | 108 | mSpringSystemSpy.activateSpring(mMockSpring.getId()); 109 | inOrder.verify(listener, never()).onBeforeIntegrate(mSpringSystemSpy); 110 | inOrder.verify(listener, never()).onAfterIntegrate(mSpringSystemSpy); 111 | } 112 | 113 | @Test 114 | public void testActivatingAndDeactivatingSpring() { 115 | when(mMockSpring.systemShouldAdvance()).thenReturn(true, false, false); 116 | InOrder inOrder = inOrder(mMockSpring, mSpringSystemSpy); 117 | mSpringSystemSpy.registerSpring(mMockSpring); 118 | assertTrue(mSpringSystemSpy.getIsIdle()); 119 | 120 | double stepMillis = mSynchronousLooper.getTimeStep(); 121 | double stepSeconds = mSynchronousLooper.getTimeStep() / 1000; 122 | 123 | mSpringSystemSpy.activateSpring(mMockSpring.getId()); 124 | inOrder.verify(mSpringSystemSpy).advance(stepMillis); 125 | inOrder.verify(mMockSpring).systemShouldAdvance(); 126 | inOrder.verify(mMockSpring).advance(stepSeconds); 127 | 128 | inOrder.verify(mSpringSystemSpy).advance(stepMillis); 129 | inOrder.verify(mMockSpring).systemShouldAdvance(); 130 | inOrder.verify(mMockSpring, never()).advance(stepSeconds); 131 | assertTrue(mSpringSystemSpy.getIsIdle()); 132 | 133 | mSpringSystemSpy.loop(stepMillis); 134 | inOrder.verify(mSpringSystemSpy).advance(stepMillis); 135 | inOrder.verify(mMockSpring, never()).systemShouldAdvance(); 136 | inOrder.verify(mMockSpring, never()).advance(stepSeconds); 137 | assertTrue(mSpringSystemSpy.getIsIdle()); 138 | 139 | mSpringSystemSpy.activateSpring(mMockSpring.getId()); 140 | inOrder.verify(mSpringSystemSpy).advance(stepMillis); 141 | inOrder.verify(mMockSpring).systemShouldAdvance(); 142 | inOrder.verify(mMockSpring, never()).advance(stepSeconds); 143 | assertTrue(mSpringSystemSpy.getIsIdle()); 144 | 145 | mSpringSystemSpy.loop(stepMillis); 146 | inOrder.verify(mSpringSystemSpy).advance(stepMillis); 147 | inOrder.verify(mMockSpring, never()).systemShouldAdvance(); 148 | inOrder.verify(mMockSpring, never()).advance(stepSeconds); 149 | assertTrue(mSpringSystemSpy.getIsIdle()); 150 | } 151 | 152 | @Test 153 | public void testCanAddListenersWhileIterating() { 154 | when(mMockSpring.systemShouldAdvance()).thenReturn(true, false); 155 | mSpringSystemSpy.addListener(new SimpleSpringSystemListener() { 156 | @Override 157 | public void onAfterIntegrate(BaseSpringSystem springSystem) { 158 | springSystem.addListener(new SimpleSpringSystemListener()); 159 | } 160 | }); 161 | mSpringSystemSpy.addListener(new SimpleSpringSystemListener()); 162 | mSpringSystemSpy.loop(1); 163 | } 164 | 165 | @Test 166 | public void testCanRemoveListenersWhileIterating() { 167 | when(mMockSpring.systemShouldAdvance()).thenReturn(true, true, false); 168 | final SimpleSpringSystemListener nextListener = new SimpleSpringSystemListener(); 169 | mSpringSystemSpy 170 | .addListener(new SimpleSpringSystemListener() { 171 | @Override 172 | public void onAfterIntegrate(BaseSpringSystem springSystem) { 173 | springSystem.removeListener(nextListener); 174 | } 175 | }); 176 | mSpringSystemSpy 177 | .addListener(nextListener); 178 | mSpringSystemSpy.loop(1); 179 | mSpringSystemSpy.loop(1); 180 | mSpringSystemSpy.loop(1); 181 | } 182 | 183 | private class SimpleSpringSystemListener implements SpringSystemListener { 184 | @Override 185 | public void onBeforeIntegrate(BaseSpringSystem springSystem) { 186 | 187 | } 188 | 189 | @Override 190 | public void onAfterIntegrate(BaseSpringSystem springSystem) { 191 | 192 | } 193 | } 194 | } 195 | 196 | -------------------------------------------------------------------------------- /release_process.txt: -------------------------------------------------------------------------------- 1 | How to Release a new version: 2 | ----------------------------- 3 | - Review all commits that will be in the new version. 4 | - Run all tests and build/install sample apps to verify that everything looks sane 5 | - Update the version number in build.gradle to the new version number 6 | - Create a new git tag for the version in the format v0.0.0 7 | - build the distribution jar 8 | ./gradlew reboundDistJar 9 | - Draft a new release on github include release notes of all the changes and the 10 | jar generated at build/libs/rebound-0.0.0.jar 11 | - publish to maven central 12 | - ./gradlew uploadArchives 13 | - login to https://oss.sonatype.org 14 | - Go to staging repositories and look at the bottom of the list for comfacebook-0000 this will be your 15 | staging repo. Verify that the new release is in the content. 16 | - press Close at the top of the first horizontal pane while this staging repo is selected 17 | - hit refresh and see that the status is now Closed 18 | - hit release to submit the artifacts for publication to central. The sync may take some time to complete 19 | as well as the update of the maven search index. 20 | - once the sync is complete update the gh-pages to reflect that a new version is available on central 21 | - update the published javadocs with 22 | ./gradlew javadoc 23 | upload the docs generated at rebound-core/build to gh-pages 24 | - tweet that there is an update -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':rebound-core' 2 | include ':rebound-android' 3 | include ':rebound-android-example' 4 | include ':rebound-android-playground' --------------------------------------------------------------------------------