├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── ic_launcher-web.png
│ │ ├── res
│ │ │ ├── drawable-hdpi
│ │ │ │ └── ic_magic.png
│ │ │ ├── drawable-xhdpi
│ │ │ │ └── ic_magic.png
│ │ │ ├── drawable-xxhdpi
│ │ │ │ └── ic_magic.png
│ │ │ ├── drawable-xxxhdpi
│ │ │ │ └── ic_magic.png
│ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── drawable-nodpi
│ │ │ │ └── illustration.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── menu
│ │ │ │ └── activity_main.xml
│ │ │ ├── values
│ │ │ │ ├── colors.xml
│ │ │ │ ├── strings.xml
│ │ │ │ └── styles.xml
│ │ │ ├── drawable
│ │ │ │ ├── ic_check_black_16dp.xml
│ │ │ │ ├── ic_mode_edit_black_16dp.xml
│ │ │ │ └── il_comments.xml
│ │ │ └── layout
│ │ │ │ ├── activity_adjustment.xml
│ │ │ │ ├── layout_toolbar.xml
│ │ │ │ ├── activity_main.xml
│ │ │ │ ├── item_simple.xml
│ │ │ │ ├── fragment_square.xml
│ │ │ │ └── combo_slider.xml
│ │ ├── java
│ │ │ └── me
│ │ │ │ └── cyandev
│ │ │ │ └── springanimatordemo
│ │ │ │ ├── util
│ │ │ │ └── SimpleAnimatorListener.java
│ │ │ │ ├── preview
│ │ │ │ ├── BasePreviewFragment.java
│ │ │ │ ├── RecyclerViewPreviewFragment.java
│ │ │ │ └── TranslationPreviewFragment.java
│ │ │ │ ├── adjustment
│ │ │ │ ├── Rk4AdjustmentFragment.java
│ │ │ │ ├── DhoAdjustmentFragment.java
│ │ │ │ ├── BaseAdjustmentFragment.java
│ │ │ │ └── view
│ │ │ │ │ └── ComboSliderView.java
│ │ │ │ ├── ConfigurationResolver.java
│ │ │ │ └── MainActivity.java
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── me
│ │ │ └── cyandev
│ │ │ └── springanimator
│ │ │ └── ExampleUnitTest.java
│ └── androidTest
│ │ └── java
│ │ └── me
│ │ └── cyandev
│ │ └── springanimator
│ │ └── ExampleInstrumentedTest.java
├── proguard-rules.pro
└── build.gradle.kts
├── library
├── .gitignore
├── src
│ └── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── me
│ │ └── cyandev
│ │ └── springanimator
│ │ ├── DhoSpringAnimator.java
│ │ ├── Rk4SpringAnimator.java
│ │ ├── internal
│ │ └── AnimationHandler.java
│ │ └── AbsSpringAnimator.java
├── proguard-rules.pro
└── build.gradle.kts
├── .gitattributes
├── settings.gradle.kts
├── art
├── measure
│ ├── links
│ │ ├── page-1-active.html
│ │ ├── page-1-input.html
│ │ └── page-1-main.html
│ └── preview
│ │ ├── page-1-input.png
│ │ ├── page-1-main.png
│ │ └── page-1-active.png
├── screencast.gif
└── spring_animator.sketch
├── .idea
├── copyright
│ ├── profiles_settings.xml
│ └── Apache.xml
├── markdown-navigator
│ └── profiles_settings.xml
├── encodings.xml
├── statistic.xml
├── modules.xml
├── runConfigurations.xml
├── compiler.xml
├── gradle.xml
├── misc.xml
└── markdown-navigator.xml
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── .travis.yml
├── gradle.properties
├── README.md
├── gradlew.bat
├── gradlew
└── LICENSE.txt
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/library/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.html linguist-language=Java
2 |
3 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | include(":app")
2 | include(":library")
3 |
--------------------------------------------------------------------------------
/library/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/art/measure/links/page-1-active.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/art/measure/links/page-1-input.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/art/measure/links/page-1-main.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/art/screencast.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unixzii/android-SpringAnimator/HEAD/art/screencast.gif
--------------------------------------------------------------------------------
/art/spring_animator.sketch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unixzii/android-SpringAnimator/HEAD/art/spring_animator.sketch
--------------------------------------------------------------------------------
/.idea/copyright/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/app/src/main/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unixzii/android-SpringAnimator/HEAD/app/src/main/ic_launcher-web.png
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unixzii/android-SpringAnimator/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/art/measure/preview/page-1-input.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unixzii/android-SpringAnimator/HEAD/art/measure/preview/page-1-input.png
--------------------------------------------------------------------------------
/art/measure/preview/page-1-main.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unixzii/android-SpringAnimator/HEAD/art/measure/preview/page-1-main.png
--------------------------------------------------------------------------------
/art/measure/preview/page-1-active.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unixzii/android-SpringAnimator/HEAD/art/measure/preview/page-1-active.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_magic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unixzii/android-SpringAnimator/HEAD/app/src/main/res/drawable-hdpi/ic_magic.png
--------------------------------------------------------------------------------
/.idea/markdown-navigator/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_magic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unixzii/android-SpringAnimator/HEAD/app/src/main/res/drawable-xhdpi/ic_magic.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_magic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unixzii/android-SpringAnimator/HEAD/app/src/main/res/drawable-xxhdpi/ic_magic.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_magic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unixzii/android-SpringAnimator/HEAD/app/src/main/res/drawable-xxxhdpi/ic_magic.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unixzii/android-SpringAnimator/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unixzii/android-SpringAnimator/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unixzii/android-SpringAnimator/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unixzii/android-SpringAnimator/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-nodpi/illustration.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unixzii/android-SpringAnimator/HEAD/app/src/main/res/drawable-nodpi/illustration.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unixzii/android-SpringAnimator/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unixzii/android-SpringAnimator/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unixzii/android-SpringAnimator/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unixzii/android-SpringAnimator/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unixzii/android-SpringAnimator/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unixzii/android-SpringAnimator/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 | .externalNativeBuild
10 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Jul 18 09:36:29 CST 2017
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-7.4.2-bin.zip
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: android
2 | sudo: required
3 | dist: precise
4 |
5 | android:
6 | components:
7 | - tools
8 | - platform-tools
9 | - build-tools-26.0.0
10 | - android-25
11 | - extra-android-m2repository
12 |
13 | jdk:
14 | - oraclejdk8
15 |
16 | notifications:
17 | email: false
18 |
19 | script:
20 | - ./gradlew assemble check
21 |
--------------------------------------------------------------------------------
/.idea/statistic.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/test/java/me/cyandev/springanimator/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package me.cyandev.springanimator;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/.idea/copyright/Apache.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | android.enableJetifier=true
13 | android.useAndroidX=true
14 | org.gradle.jvmargs=-Xmx1536m
15 |
16 | # When configured, Gradle will run in incubating parallel mode.
17 | # This option should only be used with decoupled projects. More details, visit
18 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
19 | # org.gradle.parallel=true
20 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/me/cyandev/springanimator/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package me.cyandev.springanimator;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumentation test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("me.cyandev.springanimator", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 | #FF212121
20 | #FF000000
21 | #FF009688
22 |
23 | #FFBDBDBD
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 | SpringPlayground
19 | Adjustments
20 | You can open this activity in multi-window mode
21 | Drag me!
22 |
23 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/cyandev/Library/Android/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.kts.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/library/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/cyandev/Library/Android/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.kts.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_check_black_16dp.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
22 |
25 |
26 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_mode_edit_black_16dp.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
22 |
25 |
26 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
26 |
27 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/app/src/main/java/me/cyandev/springanimatordemo/util/SimpleAnimatorListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2017 Cyandev
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package me.cyandev.springanimatordemo.util;
18 |
19 | import android.animation.Animator;
20 |
21 | public abstract class SimpleAnimatorListener implements Animator.AnimatorListener {
22 |
23 | @Override
24 | public void onAnimationStart(Animator animation) {
25 | }
26 |
27 | @Override
28 | public void onAnimationEnd(Animator animation) {
29 | }
30 |
31 | @Override
32 | public void onAnimationCancel(Animator animation) {
33 | }
34 |
35 | @Override
36 | public void onAnimationRepeat(Animator animation) {
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_adjustment.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
26 |
27 |
28 |
29 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/layout_toolbar.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
20 |
23 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2017 Cyandev
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | plugins {
18 | id("com.android.application")
19 | }
20 |
21 | android {
22 | compileSdk = 33
23 | defaultConfig {
24 | applicationId = "me.cyandev.springanimatordemo"
25 | minSdk = 16
26 | targetSdk = 33
27 | versionCode = 1
28 | versionName = "1.0"
29 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
30 | }
31 |
32 | buildTypes {
33 | getByName("release") {
34 | isMinifyEnabled = true
35 | proguardFiles("proguard-android.txt", "proguard-rules.pro")
36 | }
37 | }
38 | }
39 |
40 | dependencies {
41 | implementation(project(mapOf("path" to ":library")))
42 | implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
43 | implementation("androidx.appcompat:appcompat:1.6.1")
44 | implementation("com.google.android.material:material:1.9.0")
45 | implementation("androidx.cardview:cardview:1.0.0")
46 | }
47 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
20 |
21 |
22 |
23 |
24 |
25 |
31 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/library/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import java.io.ByteArrayOutputStream
2 | plugins {
3 | id("com.android.library")
4 | id("maven-publish")
5 | }
6 |
7 | fun String.runCommand(currentWorkingDir: File = file("./")): String {
8 | val byteOut = ByteArrayOutputStream()
9 | project.exec {
10 | workingDir = currentWorkingDir
11 | commandLine = this@runCommand.split("\\s".toRegex())
12 | standardOutput = byteOut
13 | }
14 | return String(byteOut.toByteArray()).trim()
15 | }
16 |
17 | var gitVersionCode = 1
18 | try {
19 | gitVersionCode = Integer.parseInt("git rev-list HEAD --count".runCommand())
20 | } catch (ignored: NumberFormatException) {
21 | println("WARN: no git commits yet")
22 | }
23 |
24 | var gitVersionName = "git tag --list".runCommand().split('\n').first()
25 | if (gitVersionName.isEmpty()) {
26 | gitVersionName = "1.0.0"
27 | }
28 |
29 | android {
30 | compileSdk = 33
31 |
32 | defaultConfig {
33 | minSdk = 16
34 | targetSdk = 33
35 | }
36 | buildTypes {
37 | getByName("release") {
38 | isMinifyEnabled = false
39 | proguardFiles("proguard-android.txt", "proguard-rules.pro")
40 | }
41 | }
42 |
43 | publishing {
44 | singleVariant("release") {
45 | withSourcesJar()
46 | withJavadocJar()
47 | }
48 | }
49 | }
50 |
51 | dependencies {
52 | implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
53 | implementation("androidx.appcompat:appcompat:1.6.1")
54 | }
55 |
56 | publishing {
57 | publications {
58 | register("release") {
59 | groupId = "me.cyandev"
60 | artifactId = "springanimator"
61 | version = gitVersionName
62 |
63 | afterEvaluate {
64 | from(components["release"])
65 | }
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/app/src/main/java/me/cyandev/springanimatordemo/preview/BasePreviewFragment.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2017 Cyandev
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package me.cyandev.springanimatordemo.preview;
18 |
19 | import android.content.Context;
20 | import android.os.Bundle;
21 | import androidx.fragment.app.Fragment;
22 | import android.view.LayoutInflater;
23 | import android.view.View;
24 | import android.view.ViewGroup;
25 |
26 | import me.cyandev.springanimator.AbsSpringAnimator;
27 |
28 | public abstract class BasePreviewFragment extends Fragment {
29 |
30 | private SpringAnimatorProvider mProvider;
31 |
32 | public abstract void onResetView();
33 |
34 | public abstract void onStartAnimation();
35 |
36 | @Override
37 | public void onAttach(Context context) {
38 | super.onAttach(context);
39 |
40 | if (getActivity() instanceof SpringAnimatorProvider) {
41 | mProvider = (SpringAnimatorProvider) getActivity();
42 | }
43 | }
44 |
45 | @Override
46 | public abstract View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState);
47 |
48 | protected final AbsSpringAnimator createNewAnimator() {
49 | if (mProvider == null) {
50 | throw new IllegalStateException("Provider is not presented");
51 | }
52 |
53 | return mProvider.provideAnimator();
54 | }
55 |
56 | public interface SpringAnimatorProvider {
57 | AbsSpringAnimator provideAnimator();
58 | }
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
13 |
16 |
17 |
21 |
22 |
29 |
30 |
40 |
41 |
45 |
46 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SpringAnimator
2 | [](https://travis-ci.org/unixzii/android-SpringAnimator)
3 | A framer.js DHO and RK4 spring animation port for Android.
4 |
5 | ### Features
6 | * Extends from `Animator`, providing a familiar API to use
7 | * Provides **DHO** and **RK4** algorithm from Framer.js
8 | * Bundled playground app, fine-tuning made easy
9 |
10 | ## Demo
11 | Checkout the playground here: [playground.apk](https://github.com/unixzii/android-SpringAnimator/releases/download/0.1.0-alpha1/playground.apk)
12 |
13 |
14 |
15 | ## Requirements
16 | SpringAnimator requires API 16 or higher.
17 |
18 | ## Download
19 | Gradle:
20 | ```groovy
21 | repositories {
22 | jcenter()
23 | }
24 |
25 | dependencies {
26 | compile 'me.cyandev:springanimator:0.1.0-alpha1'
27 | }
28 | ```
29 |
30 | Find out more releases [here](https://github.com/unixzii/android-SpringAnimator/releases).
31 |
32 | ## Get Started
33 | ```java
34 | DhoSpringAnimator animator = new DhoSpringAnimator();
35 | animator.setStiffness(200);
36 | animator.setDamping(10);
37 | animator.setMass(1);
38 | animator.setVelocity(0);
39 | animator.addUpdateListener(new AbsSpringAnimator.AnimatorUpdateListener() {
40 | @Override
41 | public void onAnimationUpdate(AbsSpringAnimator animation) {
42 | // Do something cool here...
43 | }
44 | });
45 | animator.start();
46 | ```
47 |
48 | Parameters are fully matched with Frame.js.
49 |
50 | ## Acknowledgement
51 | Thanks [koenbok](https://github.com/koenbok)/[Framer](https://github.com/koenbok/Framer) for providing original algorithms.
52 |
53 | Thanks [MartinRGB](https://github.com/MartinRGB) for designing user interface of the playground app and providing graphical resources.
54 |
55 | ## License
56 | ```
57 | Copyright 2017 Cyandev
58 |
59 | Licensed under the Apache License, Version 2.0 (the "License");
60 | you may not use this file except in compliance with the License.
61 | You may obtain a copy of the License at
62 |
63 | http://www.apache.org/licenses/LICENSE-2.0
64 |
65 | Unless required by applicable law or agreed to in writing, software
66 | distributed under the License is distributed on an "AS IS" BASIS,
67 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
68 | See the License for the specific language governing permissions and
69 | limitations under the License.
70 | ```
71 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_simple.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
26 |
27 |
34 |
35 |
39 |
40 |
47 |
48 |
54 |
55 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/app/src/main/java/me/cyandev/springanimatordemo/adjustment/Rk4AdjustmentFragment.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2017 Cyandev
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package me.cyandev.springanimatordemo.adjustment;
18 |
19 | import android.os.Bundle;
20 |
21 | import java.util.ArrayList;
22 |
23 | import me.cyandev.springanimator.AbsSpringAnimator;
24 | import me.cyandev.springanimator.Rk4SpringAnimator;
25 | import me.cyandev.springanimatordemo.ConfigurationResolver;
26 |
27 | public class Rk4AdjustmentFragment extends BaseAdjustmentFragment {
28 |
29 | @Override
30 | protected String getTypeName() {
31 | return "RK4";
32 | }
33 |
34 | @Override
35 | protected ArrayList onCreateAdjustmentInfo() {
36 | int tension = 200;
37 | int friction = 10;
38 | int velocity = 0;
39 |
40 | Bundle args = getArguments();
41 | if (args != null) {
42 | AbsSpringAnimator animator = ConfigurationResolver.resolveConfiguration(args);
43 | if (animator != null && animator instanceof Rk4SpringAnimator) {
44 | Rk4SpringAnimator rk4SpringAnimator = (Rk4SpringAnimator) animator;
45 | tension = (int) rk4SpringAnimator.getTension();
46 | friction = (int) rk4SpringAnimator.getFriction();
47 | velocity = (int) rk4SpringAnimator.getVelocity();
48 | }
49 | }
50 |
51 | ArrayList result = new ArrayList<>();
52 |
53 | result.add(new AdjustmentInfo("Tension", tension, 0, 1000));
54 | result.add(new AdjustmentInfo("Friction", friction, 0, 100));
55 | result.add(new AdjustmentInfo("Velocity", velocity, 0, 100));
56 |
57 | return result;
58 | }
59 |
60 | @Override
61 | protected Bundle onCreateConfigurationBundle() {
62 | int tension = getAdjustmentValue(0);
63 | int friction = getAdjustmentValue(1);
64 | int velocity = getAdjustmentValue(2);
65 |
66 | return ConfigurationResolver.buildRk4Configuration(tension, friction, velocity);
67 | }
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/app/src/main/java/me/cyandev/springanimatordemo/adjustment/DhoAdjustmentFragment.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2017 Cyandev
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package me.cyandev.springanimatordemo.adjustment;
18 |
19 | import android.os.Bundle;
20 |
21 | import java.util.ArrayList;
22 |
23 | import me.cyandev.springanimator.AbsSpringAnimator;
24 | import me.cyandev.springanimator.DhoSpringAnimator;
25 | import me.cyandev.springanimatordemo.ConfigurationResolver;
26 |
27 | public class DhoAdjustmentFragment extends BaseAdjustmentFragment {
28 |
29 | @Override
30 | protected String getTypeName() {
31 | return "DHO";
32 | }
33 |
34 | @Override
35 | protected ArrayList onCreateAdjustmentInfo() {
36 | int stiffness = 200;
37 | int damping = 10;
38 | int mass = 1;
39 | int velocity = 0;
40 |
41 | Bundle args = getArguments();
42 | if (args != null) {
43 | AbsSpringAnimator animator = ConfigurationResolver.resolveConfiguration(args);
44 | if (animator != null && animator instanceof DhoSpringAnimator) {
45 | DhoSpringAnimator dhoSpringAnimator = (DhoSpringAnimator) animator;
46 | stiffness = (int) dhoSpringAnimator.getStiffness();
47 | damping = (int) dhoSpringAnimator.getDamping();
48 | mass = (int) dhoSpringAnimator.getMass();
49 | velocity = (int) dhoSpringAnimator.getVelocity();
50 | }
51 | }
52 |
53 | ArrayList result = new ArrayList<>();
54 |
55 | result.add(new AdjustmentInfo("Stiffness", stiffness, 0, 1000));
56 | result.add(new AdjustmentInfo("Damping", damping, 0, 100));
57 | result.add(new AdjustmentInfo("Mass", mass, 1, 20));
58 | result.add(new AdjustmentInfo("Velocity", velocity, 0, 100));
59 |
60 | return result;
61 | }
62 |
63 | @Override
64 | protected Bundle onCreateConfigurationBundle() {
65 | int stiffness = getAdjustmentValue(0);
66 | int damping = getAdjustmentValue(1);
67 | int mass = getAdjustmentValue(2);
68 | int velocity = getAdjustmentValue(3);
69 |
70 | return ConfigurationResolver.buildDhoConfiguration(stiffness, damping, mass, velocity);
71 | }
72 |
73 | }
74 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_square.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
24 |
25 |
30 |
31 |
35 |
36 |
39 |
40 |
45 |
46 |
50 |
51 |
61 |
62 |
63 |
64 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
--------------------------------------------------------------------------------
/app/src/main/java/me/cyandev/springanimatordemo/ConfigurationResolver.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2017 Cyandev
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package me.cyandev.springanimatordemo;
18 |
19 | import android.os.Bundle;
20 |
21 | import me.cyandev.springanimator.AbsSpringAnimator;
22 | import me.cyandev.springanimator.DhoSpringAnimator;
23 | import me.cyandev.springanimator.Rk4SpringAnimator;
24 |
25 | public final class ConfigurationResolver {
26 |
27 | private static final String KEY_TYPE = "type";
28 | private static final String KEY_STIFFNESS = "stiffness";
29 | private static final String KEY_DAMPING = "damping";
30 | private static final String KEY_MASS = "mass";
31 | private static final String KEY_VELOCITY = "velocity";
32 | private static final String KEY_TENSION = "tension";
33 | private static final String KEY_FRICTION = "friction";
34 |
35 | private static final String TYPE_DHO = "DHO";
36 | private static final String TYPE_RK4 = "RK4";
37 |
38 | public static Bundle buildDhoConfiguration(int stiffness, int damping, int mass, int velocity) {
39 | Bundle bundle = new Bundle();
40 | bundle.putString(KEY_TYPE, TYPE_DHO);
41 | bundle.putInt(KEY_STIFFNESS, stiffness);
42 | bundle.putInt(KEY_DAMPING, damping);
43 | bundle.putInt(KEY_MASS, mass);
44 | bundle.putInt(KEY_VELOCITY, velocity);
45 |
46 | return bundle;
47 | }
48 |
49 | public static Bundle buildRk4Configuration(int tension, int friction, int velocity) {
50 | Bundle bundle = new Bundle();
51 | bundle.putString(KEY_TYPE, TYPE_RK4);
52 | bundle.putInt(KEY_TENSION, tension);
53 | bundle.putInt(KEY_FRICTION, friction);
54 | bundle.putInt(KEY_VELOCITY, velocity);
55 |
56 | return bundle;
57 | }
58 |
59 | public static AbsSpringAnimator resolveConfiguration(Bundle confBundle) {
60 | String type = confBundle.getString(KEY_TYPE, "");
61 |
62 | if (TYPE_DHO.equals(type)) {
63 | DhoSpringAnimator animator = new DhoSpringAnimator();
64 | animator.setStiffness(confBundle.getInt(KEY_STIFFNESS));
65 | animator.setDamping(confBundle.getInt(KEY_DAMPING));
66 | animator.setMass(confBundle.getInt(KEY_MASS));
67 | animator.setVelocity(confBundle.getInt(KEY_VELOCITY));
68 |
69 | return animator;
70 | }
71 |
72 | if (TYPE_RK4.equals(type)) {
73 | Rk4SpringAnimator animator = new Rk4SpringAnimator();
74 | animator.setTension(confBundle.getInt(KEY_TENSION));
75 | animator.setFriction(confBundle.getInt(KEY_FRICTION));
76 | animator.setVelocity(confBundle.getInt(KEY_VELOCITY));
77 |
78 | return animator;
79 | }
80 |
81 | return null;
82 | }
83 |
84 | }
85 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/il_comments.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
23 |
24 |
31 |
38 |
41 |
48 |
55 |
56 |
59 |
66 |
73 |
80 |
81 |
82 |
--------------------------------------------------------------------------------
/library/src/main/java/me/cyandev/springanimator/DhoSpringAnimator.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2017 Cyandev
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package me.cyandev.springanimator;
18 |
19 | /**
20 | * A spring animator using Dho algorithm.
21 | *
22 | * Reference:
23 | * See
24 | * https://github.com/koenbok/Framer/blob/master/framer/Animators/SpringDHOAnimator.coffee
25 | */
26 | public class DhoSpringAnimator extends AbsSpringAnimator {
27 |
28 | private float mVelocity = 0;
29 | private float mTolerance = 1 / 10000.f;
30 | private float mStiffness = 120;
31 | private float mDamping = 5;
32 | private float mMass = 1;
33 |
34 | private boolean mIsFirstFrame = true;
35 | private float mValue = 0;
36 | private double mCurrentVelocity = 0;
37 |
38 | public float getVelocity() {
39 | return mVelocity;
40 | }
41 |
42 | public void setVelocity(float velocity) {
43 | mVelocity = velocity;
44 | }
45 |
46 | public float getTolerance() {
47 | return mTolerance;
48 | }
49 |
50 | public void setTolerance(float tolerance) {
51 | mTolerance = tolerance;
52 | }
53 |
54 | public float getStiffness() {
55 | return mStiffness;
56 | }
57 |
58 | public void setStiffness(float stiffness) {
59 | mStiffness = stiffness;
60 | }
61 |
62 | public float getDamping() {
63 | return mDamping;
64 | }
65 |
66 | public void setDamping(float damping) {
67 | mDamping = damping;
68 | }
69 |
70 | public float getMass() {
71 | return mMass;
72 | }
73 |
74 | public void setMass(float mass) {
75 | mMass = mass;
76 | }
77 |
78 | @Override
79 | protected long computeSettleDuration() {
80 | // TODO: Not implemented.
81 | return 0;
82 | }
83 |
84 | @Override
85 | protected void resetState() {
86 | mIsFirstFrame = true;
87 | mValue = 0;
88 | mCurrentVelocity = mVelocity;
89 | }
90 |
91 | @Override
92 | protected float enterFrame(long frameTime) {
93 | final float delta = Math.max(frameTime / 1000.f, 0.016f);
94 |
95 | // A trick to avoid jitter when frames dropped.
96 | // FIXME: Still encounter jitter sometimes...
97 | if (delta >= 0.024) {
98 | float last = 0;
99 | for (int i = 0, j = (int) Math.floor(delta / 0.016); i < j; i++) {
100 | last = enterFrame(16);
101 | }
102 | return last;
103 | }
104 |
105 | if (isFinished()) {
106 | return 1.f;
107 | }
108 |
109 | double k = 0 - mStiffness;
110 | double b = 0 - mDamping;
111 |
112 | double fSpring = k * (mValue - 1);
113 | double fDamper = b * mCurrentVelocity;
114 |
115 | mCurrentVelocity += ((fSpring + fDamper) / mMass) * delta;
116 | mValue += mCurrentVelocity * delta;
117 |
118 | mIsFirstFrame = false;
119 |
120 | return mValue;
121 | }
122 |
123 | protected boolean isFinished() {
124 | return (!mIsFirstFrame && Math.abs(mCurrentVelocity) < mTolerance);
125 | }
126 |
127 | }
128 |
--------------------------------------------------------------------------------
/.idea/markdown-navigator.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/combo_slider.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
26 |
27 |
34 |
35 |
42 |
43 |
51 |
52 |
61 |
62 |
63 |
64 |
69 |
70 |
71 |
72 |
81 |
82 |
87 |
88 |
89 |
90 |
101 |
102 |
103 |
--------------------------------------------------------------------------------
/app/src/main/java/me/cyandev/springanimatordemo/adjustment/BaseAdjustmentFragment.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2017 Cyandev
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package me.cyandev.springanimatordemo.adjustment;
18 |
19 | import android.content.Context;
20 | import android.os.Bundle;
21 | import androidx.annotation.Nullable;
22 | import androidx.fragment.app.Fragment;
23 | import android.view.LayoutInflater;
24 | import android.view.View;
25 | import android.view.ViewGroup;
26 | import android.widget.LinearLayout;
27 |
28 | import java.util.ArrayList;
29 |
30 | import me.cyandev.springanimatordemo.adjustment.view.ComboSliderView;
31 |
32 | public abstract class BaseAdjustmentFragment extends Fragment implements ComboSliderView.Interactor {
33 |
34 | private static final String TAG = "BaseAdjustmentFragment";
35 | private static final String KEY_VALUES = TAG + "_values";
36 |
37 | private ArrayList mAdjustmentViews = new ArrayList<>();
38 | private OnAdjustmentCommitListener mListener;
39 |
40 | @Override
41 | public void onAttach(Context context) {
42 | super.onAttach(context);
43 |
44 | if (getActivity() instanceof OnAdjustmentCommitListener) {
45 | mListener = (OnAdjustmentCommitListener) getActivity();
46 | }
47 | }
48 |
49 | @Override
50 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
51 | LinearLayout layout = new LinearLayout(container.getContext());
52 | layout.setOrientation(LinearLayout.VERTICAL);
53 |
54 | ArrayList adjustmentInfoList = onCreateAdjustmentInfo();
55 | int id = 0;
56 | for (AdjustmentInfo info : adjustmentInfoList) {
57 | ComboSliderView view = ComboSliderView.create(layout, false);
58 | view.bindAdjustmentInfo(info);
59 | view.setOnChangeListener(this);
60 | view.setId(id++);
61 |
62 | layout.addView(view, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
63 | ViewGroup.LayoutParams.WRAP_CONTENT));
64 | mAdjustmentViews.add(view);
65 | }
66 |
67 | return layout;
68 | }
69 |
70 | @Override
71 | public void onSaveInstanceState(Bundle outState) {
72 | super.onSaveInstanceState(outState);
73 |
74 | int[] values = new int[mAdjustmentViews.size()];
75 | for (int i = 0, l = values.length; i < l; i++) {
76 | values[i] = mAdjustmentViews.get(i).getValue();
77 | }
78 |
79 | outState.putIntArray(KEY_VALUES, values);
80 | }
81 |
82 | @Override
83 | public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
84 | super.onViewStateRestored(savedInstanceState);
85 |
86 | if (savedInstanceState != null) {
87 | int[] values = savedInstanceState.getIntArray(KEY_VALUES);
88 | if (values != null) {
89 | for (int i = 0, l = values.length; i < l; i++) {
90 | mAdjustmentViews.get(i).setValue(values[i]);
91 | }
92 | }
93 | }
94 | }
95 |
96 | @Override
97 | public void onValueChange(int value) {
98 | Bundle confBundle = onCreateConfigurationBundle();
99 |
100 | if (mListener != null) {
101 | mListener.onAdjustmentCommit(confBundle);
102 | }
103 | }
104 |
105 | @Override
106 | public void onFocusModeChange(View view, boolean focused) {
107 | for (ComboSliderView comboView : mAdjustmentViews) {
108 | if (focused) {
109 | if (view != comboView) {
110 | comboView.animate().alpha(0.4f).setDuration(250).start();
111 | }
112 | } else {
113 | comboView.animate().alpha(1).setDuration(300).start();
114 | }
115 | }
116 | }
117 |
118 | protected int getAdjustmentValue(int index) {
119 | return mAdjustmentViews.get(index).getValue();
120 | }
121 |
122 | protected abstract String getTypeName();
123 |
124 | protected abstract ArrayList onCreateAdjustmentInfo();
125 |
126 | protected abstract Bundle onCreateConfigurationBundle();
127 |
128 | public static class AdjustmentInfo {
129 | public String propertyName;
130 | public int defaultValue;
131 | public int minValue;
132 | public int maxValue;
133 |
134 | AdjustmentInfo(String propertyName, int defaultValue, int minValue, int maxValue) {
135 | this.propertyName = propertyName;
136 | this.defaultValue = defaultValue;
137 | this.minValue = minValue;
138 | this.maxValue = maxValue;
139 | }
140 | }
141 |
142 | public interface OnAdjustmentCommitListener {
143 | void onAdjustmentCommit(Bundle bundle);
144 | }
145 |
146 | }
147 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/app/src/main/java/me/cyandev/springanimatordemo/preview/RecyclerViewPreviewFragment.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2017 Cyandev
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package me.cyandev.springanimatordemo.preview;
18 |
19 | import android.animation.Animator;
20 | import android.os.Bundle;
21 | import androidx.recyclerview.widget.LinearLayoutManager;
22 | import androidx.recyclerview.widget.RecyclerView;
23 | import android.util.TypedValue;
24 | import android.view.LayoutInflater;
25 | import android.view.View;
26 | import android.view.ViewGroup;
27 |
28 | import java.util.ArrayList;
29 | import java.util.List;
30 |
31 | import me.cyandev.springanimator.AbsSpringAnimator;
32 | import me.cyandev.springanimatordemo.R;
33 | import me.cyandev.springanimatordemo.util.SimpleAnimatorListener;
34 |
35 | public class RecyclerViewPreviewFragment extends BasePreviewFragment {
36 |
37 | private RecyclerView mRecyclerView;
38 |
39 | private List mAnimators = new ArrayList<>();
40 | private int mLastScrollOffsetY = 0;
41 |
42 | @Override
43 | public void onResetView() {
44 | stopAnimators();
45 |
46 | for (int i = 0, count = mRecyclerView.getChildCount(); i < count; i++) {
47 | View child = mRecyclerView.getChildAt(i);
48 | child.setTranslationY(0);
49 | }
50 | }
51 |
52 | @Override
53 | public void onStartAnimation() {
54 | stopAnimators();
55 |
56 | for (int i = 0, count = mRecyclerView.getChildCount(); i < count; i++) {
57 | final View child = mRecyclerView.getChildAt(i);
58 | performAnimation(child, i);
59 | }
60 | }
61 |
62 | @Override
63 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
64 | mRecyclerView = new RecyclerView(getContext());
65 | mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
66 | mRecyclerView.setAdapter(new SimpleAdapter());
67 | mRecyclerView.setClipToPadding(false);
68 | mRecyclerView.setPadding(0, 0, 0,
69 | (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8, getResources().getDisplayMetrics()));
70 | mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
71 | @Override
72 | public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
73 | // track for determining animation direction.
74 | mLastScrollOffsetY = dy;
75 | }
76 | });
77 |
78 | return mRecyclerView;
79 | }
80 |
81 | private AbsSpringAnimator performAnimation(View v, long staggeringIndex) {
82 | return performAnimation(v, staggeringIndex, false);
83 | }
84 |
85 | private AbsSpringAnimator performAnimation(final View v, boolean slideDown) {
86 | return performAnimation(v, 0, slideDown);
87 | }
88 |
89 | private AbsSpringAnimator performAnimation(final View v, long staggeringIndex, boolean slideDown) {
90 | final float dy = (slideDown ? -1 : 1) * mRecyclerView.getHeight();
91 | v.setTranslationY(dy);
92 |
93 | AbsSpringAnimator animator = createNewAnimator();
94 | animator.setStartValue(dy);
95 | animator.setEndValue(0);
96 | animator.setStartDelay(50 * staggeringIndex);
97 | animator.addUpdateListener(new AbsSpringAnimator.AnimatorUpdateListener() {
98 | @Override
99 | public void onAnimationUpdate(AbsSpringAnimator animation) {
100 | v.setTranslationY(animation.getAnimatedValue());
101 | }
102 | });
103 | animator.addListener(new SimpleAnimatorListener() {
104 | @Override
105 | public void onAnimationEnd(Animator animation) {
106 | mAnimators.remove(animation);
107 | }
108 | });
109 | animator.start();
110 |
111 | mAnimators.add(animator);
112 |
113 | return animator;
114 | }
115 |
116 | private void stopAnimators() {
117 | ArrayList tmpAnimators = (ArrayList) ((ArrayList) mAnimators).clone();
118 | for (AbsSpringAnimator animator : tmpAnimators) {
119 | animator.cancel();
120 | }
121 |
122 | mAnimators.clear();
123 | }
124 |
125 | private class SimpleAdapter extends RecyclerView.Adapter {
126 |
127 | @Override
128 | public SimpleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
129 | View view = LayoutInflater.from(getContext()).inflate(R.layout.item_simple, parent, false);
130 | return new SimpleViewHolder(view);
131 | }
132 |
133 | @Override
134 | public void onBindViewHolder(SimpleViewHolder holder, int position) {
135 | // No-op
136 | }
137 |
138 | @Override
139 | public int getItemCount() {
140 | return 20;
141 | }
142 |
143 | @Override
144 | public void onViewAttachedToWindow(SimpleViewHolder holder) {
145 | if (holder.attachedAnimator != null && holder.attachedAnimator.isRunning()) {
146 | holder.attachedAnimator.cancel();
147 | }
148 | holder.attachedAnimator = performAnimation(holder.itemView, mLastScrollOffsetY < 0);
149 | }
150 |
151 | }
152 |
153 | private class SimpleViewHolder extends RecyclerView.ViewHolder {
154 |
155 | AbsSpringAnimator attachedAnimator;
156 |
157 | public SimpleViewHolder(View itemView) {
158 | super(itemView);
159 | itemView.setOnClickListener(new View.OnClickListener() {
160 | @Override
161 | public void onClick(View v) {
162 | onStartAnimation();
163 | }
164 | });
165 | }
166 |
167 | }
168 |
169 | }
170 |
--------------------------------------------------------------------------------
/library/src/main/java/me/cyandev/springanimator/Rk4SpringAnimator.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2017 Cyandev
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package me.cyandev.springanimator;
18 |
19 | import androidx.annotation.NonNull;
20 | import android.util.SparseArray;
21 |
22 | /**
23 | * A spring animator using Rk4 algorithm.
24 | *
25 | * Reference:
26 | * See
27 | * https://github.com/koenbok/Framer/blob/master/framer/Animators/SpringRK4Animator.coffee
28 | */
29 | public class Rk4SpringAnimator extends AbsSpringAnimator {
30 |
31 | private static SparseArray sStatePool;
32 |
33 | private float mTension = 250;
34 | private float mFriction = 25;
35 | private float mVelocity = 0;
36 | private float mTolerance = 1 / 10000.f;
37 |
38 | private float mValue = 0;
39 | private float mCurrentVelocity = 0;
40 | private boolean mStopSpring = false;
41 |
42 | private Integrator mIntegrator = new Integrator(new Integrator.AccelerationForStateEvaluator() {
43 | @Override
44 | public float evaluate(float[] state) {
45 | return - mTension * state[0] - mFriction * state[1];
46 | }
47 | });
48 |
49 | public float getTension() {
50 | return mTension;
51 | }
52 |
53 | public void setTension(float tension) {
54 | mTension = tension;
55 | }
56 |
57 | public float getFriction() {
58 | return mFriction;
59 | }
60 |
61 | public void setFriction(float friction) {
62 | mFriction = friction;
63 | }
64 |
65 | public float getVelocity() {
66 | return mVelocity;
67 | }
68 |
69 | public void setVelocity(float velocity) {
70 | mVelocity = velocity;
71 | }
72 |
73 | public float getTolerance() {
74 | return mTolerance;
75 | }
76 |
77 | public void setTolerance(float tolerance) {
78 | mTolerance = tolerance;
79 | }
80 |
81 | @Override
82 | protected long computeSettleDuration() {
83 | // TODO: Not implemented.
84 | return 0;
85 | }
86 |
87 | @Override
88 | protected void resetState() {
89 | mValue = 0;
90 | mCurrentVelocity = mVelocity;
91 | mStopSpring = false;
92 | }
93 |
94 | @Override
95 | protected float enterFrame(long frameTime) {
96 | final float delta = Math.max(frameTime / 1000.f, 0.016f);
97 |
98 | if (isFinished()) {
99 | return 1.f;
100 | }
101 |
102 | float[] stateBefore = getState(5);
103 | float[] stateAfter = getState(6);
104 |
105 | stateBefore[0] = mValue - 1;
106 | stateBefore[1] = mCurrentVelocity;
107 |
108 | mIntegrator.evaluateIntegrateState(stateBefore, delta);
109 | stateAfter[0] = stateBefore[0];
110 | stateAfter[1] = stateBefore[1];
111 |
112 | mValue = 1 + stateAfter[0];
113 |
114 | final float finalVelocity = stateAfter[1];
115 | final float netFloat = stateAfter[0];
116 | final float net1DVelocity = stateAfter[1];
117 |
118 | final boolean netValueIsLow = Math.abs(netFloat) < mTolerance;
119 | final boolean netVelocityIsLow = Math.abs(net1DVelocity) < mTolerance;
120 |
121 | mStopSpring = netValueIsLow && netVelocityIsLow;
122 | mCurrentVelocity = finalVelocity;
123 |
124 | return mValue;
125 | }
126 |
127 | @Override
128 | protected boolean isFinished() {
129 | return mStopSpring;
130 | }
131 |
132 | @NonNull
133 | private static float[] getState(int key) {
134 | if (sStatePool == null) {
135 | return new float[2];
136 | }
137 |
138 | float[] state = sStatePool.get(key);
139 | if (state == null) {
140 | state = new float[2];
141 | sStatePool.put(key, state);
142 | }
143 |
144 | return state;
145 | }
146 |
147 | private static class Integrator {
148 |
149 | private AccelerationForStateEvaluator mAccelerationForStateEvaluator;
150 |
151 | Integrator(AccelerationForStateEvaluator evaluator) {
152 | mAccelerationForStateEvaluator = evaluator;
153 |
154 | if (sStatePool == null) {
155 | sStatePool = new SparseArray<>();
156 | }
157 | }
158 |
159 | void evaluateIntegrateState(float[] state, float dt) {
160 | float[] a = getState(1);
161 | float[] b = getState(2);
162 | float[] c = getState(3);
163 | float[] d = getState(4);
164 |
165 | evaluateState(state, a);
166 | evaluateStateWithDerivative(state, dt * 0.5f, a, b);
167 | evaluateStateWithDerivative(state, dt * 0.5f, b, c);
168 | evaluateStateWithDerivative(state, dt, c, d);
169 |
170 | final float dxdt = 1.f / 6.f * (a[0] + 2.f * (b[0] + c[0]) + d[0]);
171 | final float dvdt = 1.f / 6.f * (a[1] + 2.f * (b[1] + c[1]) + d[1]);
172 |
173 | state[0] += dxdt * dt;
174 | state[1] += dvdt * dt;
175 | }
176 |
177 | private void evaluateState(float[] initialState, float[] output) {
178 | output[0] = initialState[1];
179 | output[1] = mAccelerationForStateEvaluator.evaluate(initialState);
180 | }
181 |
182 | private void evaluateStateWithDerivative(float[] initialState, float dt, float[] derivative, float[] output) {
183 | float[] state = getState(7);
184 | state[0] = initialState[0] + derivative[0] * dt;
185 | state[1] = initialState[1] + derivative[1] * dt;
186 |
187 | output[0] = state[1];
188 | output[1] = mAccelerationForStateEvaluator.evaluate(state);
189 | }
190 |
191 | interface AccelerationForStateEvaluator {
192 | float evaluate(float[] state);
193 | }
194 |
195 | }
196 |
197 | }
198 |
--------------------------------------------------------------------------------
/app/src/main/java/me/cyandev/springanimatordemo/preview/TranslationPreviewFragment.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2017 Cyandev
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package me.cyandev.springanimatordemo.preview;
18 |
19 | import android.os.Build;
20 | import android.os.Bundle;
21 | import androidx.core.view.ViewCompat;
22 | import androidx.customview.widget.ViewDragHelper;
23 | import androidx.cardview.widget.CardView;
24 | import android.util.TypedValue;
25 | import android.view.LayoutInflater;
26 | import android.view.MotionEvent;
27 | import android.view.View;
28 | import android.view.ViewGroup;
29 | import android.view.ViewTreeObserver;
30 |
31 | import me.cyandev.springanimator.AbsSpringAnimator;
32 | import me.cyandev.springanimatordemo.R;
33 |
34 | public class TranslationPreviewFragment extends BasePreviewFragment {
35 |
36 | private CardView mCardView;
37 |
38 | private AbsSpringAnimator mXAnimator;
39 | private AbsSpringAnimator mYAnimator;
40 |
41 | private boolean mOriLocationRecorded = false;
42 | private int mOriX = 0;
43 | private int mOriY = 0;
44 | private float mCardZ = 0;
45 |
46 | private ViewDragHelper.Callback mViewDragHelperCallback = new ViewDragHelper.Callback() {
47 |
48 | @Override
49 | public boolean tryCaptureView(View child, int pointerId) {
50 | return child == mCardView;
51 | }
52 |
53 | @Override
54 | public void onViewCaptured(View capturedChild, int activePointerId) {
55 | capturedChild.getParent().requestDisallowInterceptTouchEvent(true);
56 |
57 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
58 | capturedChild.animate().z(mCardZ * 5).setDuration(150).start();
59 | }
60 | stopAnimators();
61 | }
62 |
63 | @Override
64 | public int clampViewPositionHorizontal(View child, int left, int dx) {
65 | return left;
66 | }
67 |
68 | @Override
69 | public int clampViewPositionVertical(View child, int top, int dy) {
70 | return top;
71 | }
72 |
73 | @Override
74 | public void onViewReleased(View releasedChild, float xvel, float yvel) {
75 | releasedChild.getParent().requestDisallowInterceptTouchEvent(false);
76 |
77 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
78 | releasedChild.animate().z(mCardZ).start();
79 | }
80 | onStartAnimation();
81 | }
82 |
83 | };
84 |
85 | @Override
86 | public void onResetView() {
87 | stopAnimators();
88 |
89 | mCardView.getParent().requestLayout();
90 | recordOriginalLocation(mCardView);
91 | }
92 |
93 | @Override
94 | public void onStartAnimation() {
95 | stopAnimators();
96 |
97 | if (!mOriLocationRecorded) {
98 | onResetView();
99 | return;
100 | }
101 |
102 | AbsSpringAnimator animator = createNewAnimator();
103 | animator.setStartValue(mCardView.getLeft());
104 | animator.setEndValue(mOriX);
105 | animator.addUpdateListener(new AbsSpringAnimator.AnimatorUpdateListener() {
106 | @Override
107 | public void onAnimationUpdate(AbsSpringAnimator animation) {
108 | ViewCompat.offsetLeftAndRight(mCardView, -mCardView.getLeft() + (int) animation.getAnimatedValue());
109 | }
110 | });
111 | animator.start();
112 | mXAnimator = animator;
113 |
114 | animator = createNewAnimator();
115 | animator.setStartValue(mCardView.getTop());
116 | animator.setEndValue(mOriY);
117 | animator.addUpdateListener(new AbsSpringAnimator.AnimatorUpdateListener() {
118 | @Override
119 | public void onAnimationUpdate(AbsSpringAnimator animation) {
120 | ViewCompat.offsetTopAndBottom(mCardView, -mCardView.getTop() + (int) animation.getAnimatedValue());
121 | }
122 | });
123 | animator.start();
124 | mYAnimator = animator;
125 | }
126 |
127 | @Override
128 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
129 | ViewGroup view = (ViewGroup) inflater.inflate(R.layout.fragment_square, container, false);
130 | mCardView = (CardView) view.findViewById(R.id.card_view);
131 |
132 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
133 | mCardZ = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2.f, getResources().getDisplayMetrics());
134 | mCardView.setZ(mCardZ);
135 | }
136 |
137 | final ViewDragHelper viewDragHelper = ViewDragHelper.create(view, mViewDragHelperCallback);
138 | view.setOnTouchListener(new View.OnTouchListener() {
139 | @Override
140 | public boolean onTouch(View v, MotionEvent event) {
141 | viewDragHelper.processTouchEvent(event);
142 | return false;
143 | }
144 | });
145 |
146 | recordOriginalLocation(mCardView);
147 |
148 | return view;
149 | }
150 |
151 | private void stopAnimators() {
152 | if (mXAnimator != null) {
153 | mXAnimator.cancel();
154 | mXAnimator = null;
155 | }
156 |
157 | if (mYAnimator != null) {
158 | mYAnimator.cancel();
159 | mYAnimator = null;
160 | }
161 | }
162 |
163 | private void recordOriginalLocation(final View target) {
164 | mOriLocationRecorded = false;
165 | target.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
166 | @Override
167 | public void onGlobalLayout() {
168 | ViewTreeObserver observer = target.getViewTreeObserver();
169 | if (observer.isAlive()) {
170 | observer.removeOnGlobalLayoutListener(this);
171 | }
172 |
173 | mOriX = target.getLeft();
174 | mOriY = target.getTop();
175 | mOriLocationRecorded = true;
176 | }
177 | });
178 | }
179 |
180 | }
181 |
--------------------------------------------------------------------------------
/library/src/main/java/me/cyandev/springanimator/internal/AnimationHandler.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 The Android Open Source Project
3 | * Copyright (C) 2017 Cyandev
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package me.cyandev.springanimator.internal;
19 |
20 | import android.os.SystemClock;
21 | import android.view.Choreographer;
22 |
23 | import java.util.ArrayList;
24 | import java.util.HashMap;
25 |
26 | /**
27 | * A modified version of stock {@code AnimationHandler}, removing a bunch of usage of private API.
28 | *
29 | * This custom, static handler handles the timing pulse that is shared by all active
30 | * ValueAnimators. This approach ensures that the setting of animation values will happen on the
31 | * same thread that animations start on, and that all animations will share the same times for
32 | * calculating their values, which makes synchronizing animations possible.
33 | *
34 | * The handler uses the Choreographer by default for doing periodic callbacks. A custom
35 | * AnimationFrameCallbackProvider can be set on the handler to provide timing pulse that
36 | * may be independent of UI frame update. This could be useful in testing.
37 | */
38 | public class AnimationHandler {
39 | /**
40 | * Internal per-thread collections used to avoid set collisions as animations start and end
41 | * while being processed.
42 | */
43 | private final HashMap mDelayedCallbackStartTime =
44 | new HashMap<>();
45 | private final ArrayList mAnimationCallbacks =
46 | new ArrayList<>();
47 | private AnimationFrameCallbackProvider mProvider;
48 |
49 | private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
50 | @Override
51 | public void doFrame(long frameTimeNanos) {
52 | doAnimationFrame(getProvider().getFrameTime());
53 | if (mAnimationCallbacks.size() > 0) {
54 | getProvider().postFrameCallback(this);
55 | }
56 | }
57 | };
58 |
59 | private final static ThreadLocal sAnimatorHandler = new ThreadLocal<>();
60 | private boolean mListDirty = false;
61 |
62 | public static AnimationHandler getInstance() {
63 | if (sAnimatorHandler.get() == null) {
64 | sAnimatorHandler.set(new AnimationHandler());
65 | }
66 | return sAnimatorHandler.get();
67 | }
68 |
69 | /**
70 | * By default, the Choreographer is used to provide timing for frame callbacks. A custom
71 | * provider can be used here to provide different timing pulse.
72 | */
73 | public void setProvider(AnimationFrameCallbackProvider provider) {
74 | if (provider == null) {
75 | mProvider = new MyFrameCallbackProvider();
76 | } else {
77 | mProvider = provider;
78 | }
79 | }
80 |
81 | private AnimationFrameCallbackProvider getProvider() {
82 | if (mProvider == null) {
83 | mProvider = new MyFrameCallbackProvider();
84 | }
85 | return mProvider;
86 | }
87 |
88 | /**
89 | * Register to get a callback on the next frame after the delay.
90 | */
91 | public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
92 | if (mAnimationCallbacks.size() == 0) {
93 | getProvider().postFrameCallback(mFrameCallback);
94 | }
95 | if (!mAnimationCallbacks.contains(callback)) {
96 | mAnimationCallbacks.add(callback);
97 | }
98 |
99 | if (delay > 0) {
100 | mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay));
101 | }
102 | }
103 |
104 | /**
105 | * Removes the given callback from the list, so it will no longer be called for frame related
106 | * timing.
107 | */
108 | public void removeCallback(AnimationFrameCallback callback) {
109 | mDelayedCallbackStartTime.remove(callback);
110 | int id = mAnimationCallbacks.indexOf(callback);
111 | if (id >= 0) {
112 | mAnimationCallbacks.set(id, null);
113 | mListDirty = true;
114 | }
115 | }
116 |
117 | private void doAnimationFrame(long frameTime) {
118 | int size = mAnimationCallbacks.size();
119 | long currentTime = SystemClock.uptimeMillis();
120 | for (int i = 0; i < size; i++) {
121 | final AnimationFrameCallback callback = mAnimationCallbacks.get(i);
122 | if (callback == null) {
123 | continue;
124 | }
125 | if (isCallbackDue(callback, currentTime)) {
126 | callback.doAnimationFrame(frameTime);
127 | }
128 | }
129 | cleanUpList();
130 | }
131 |
132 | /**
133 | * Remove the callbacks from mDelayedCallbackStartTime once they have passed the initial delay
134 | * so that they can start getting frame callbacks.
135 | *
136 | * @return true if they have passed the initial delay or have no delay, false otherwise.
137 | */
138 | private boolean isCallbackDue(AnimationFrameCallback callback, long currentTime) {
139 | Long startTime = mDelayedCallbackStartTime.get(callback);
140 | if (startTime == null) {
141 | return true;
142 | }
143 | if (startTime < currentTime) {
144 | mDelayedCallbackStartTime.remove(callback);
145 | return true;
146 | }
147 | return false;
148 | }
149 |
150 | /**
151 | * Return the number of callbacks that have registered for frame callbacks.
152 | */
153 | public static int getAnimationCount() {
154 | AnimationHandler handler = sAnimatorHandler.get();
155 | if (handler == null) {
156 | return 0;
157 | }
158 | return handler.getCallbackSize();
159 | }
160 |
161 | private void cleanUpList() {
162 | if (mListDirty) {
163 | for (int i = mAnimationCallbacks.size() - 1; i >= 0; i--) {
164 | if (mAnimationCallbacks.get(i) == null) {
165 | mAnimationCallbacks.remove(i);
166 | }
167 | }
168 | mListDirty = false;
169 | }
170 | }
171 |
172 | private int getCallbackSize() {
173 | int count = 0;
174 | int size = mAnimationCallbacks.size();
175 | for (int i = size - 1; i >= 0; i--) {
176 | if (mAnimationCallbacks.get(i) != null) {
177 | count++;
178 | }
179 | }
180 | return count;
181 | }
182 |
183 | /**
184 | * Default provider of timing pulse that uses Choreographer for frame callbacks.
185 | */
186 | private class MyFrameCallbackProvider implements AnimationFrameCallbackProvider {
187 |
188 | final Choreographer mChoreographer = Choreographer.getInstance();
189 |
190 | @Override
191 | public void postFrameCallback(Choreographer.FrameCallback callback) {
192 | mChoreographer.postFrameCallback(callback);
193 | }
194 |
195 | @Override
196 | public long getFrameTime() {
197 | return System.nanoTime();
198 | }
199 |
200 | }
201 |
202 | /**
203 | * Callbacks that receives notifications for animation timing and frame commit timing.
204 | */
205 | public interface AnimationFrameCallback {
206 | /**
207 | * Run animation based on the frame time.
208 | * @param frameTime The frame start time, in the {@link SystemClock#uptimeMillis()} time
209 | * base.
210 | */
211 | void doAnimationFrame(long frameTime);
212 | }
213 |
214 | /**
215 | * The intention for having this interface is to increase the testability of ValueAnimator.
216 | * Specifically, we can have a custom implementation of the interface below and provide
217 | * timing pulse without using Choreographer. That way we could use any arbitrary interval for
218 | * our timing pulse in the tests.
219 | */
220 | public interface AnimationFrameCallbackProvider {
221 | void postFrameCallback(Choreographer.FrameCallback callback);
222 | long getFrameTime();
223 | }
224 | }
225 |
--------------------------------------------------------------------------------
/app/src/main/java/me/cyandev/springanimatordemo/adjustment/view/ComboSliderView.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2017 Cyandev
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package me.cyandev.springanimatordemo.adjustment.view;
18 |
19 | import android.animation.Animator;
20 | import android.content.Context;
21 | import android.content.res.ColorStateList;
22 | import android.content.res.TypedArray;
23 | import android.os.Build;
24 | import androidx.annotation.Nullable;
25 | import com.google.android.material.textfield.TextInputEditText;
26 | import com.google.android.material.textfield.TextInputLayout;
27 | import androidx.appcompat.widget.AppCompatImageButton;
28 | import androidx.appcompat.widget.AppCompatSeekBar;
29 | import android.util.AttributeSet;
30 | import android.view.KeyEvent;
31 | import android.view.LayoutInflater;
32 | import android.view.View;
33 | import android.view.ViewGroup;
34 | import android.view.inputmethod.InputMethodManager;
35 | import android.widget.FrameLayout;
36 | import android.widget.SeekBar;
37 | import android.widget.TextView;
38 |
39 | import me.cyandev.springanimatordemo.R;
40 | import me.cyandev.springanimatordemo.adjustment.BaseAdjustmentFragment;
41 | import me.cyandev.springanimatordemo.util.SimpleAnimatorListener;
42 |
43 | public class ComboSliderView extends FrameLayout {
44 |
45 | private ViewGroup mSliderLayout;
46 | private ViewGroup mInputViewLayout;
47 | private TextView mLabelTextView;
48 | private TextView mValueTextView;
49 | private AppCompatSeekBar mSeekBar;
50 | private TextInputEditText mEditText;
51 | private AppCompatImageButton mEditButton;
52 |
53 | private BaseAdjustmentFragment.AdjustmentInfo mAdjustmentInfo;
54 | private boolean mIsInputViewVisible = false;
55 |
56 | private InputMethodManager mImm;
57 |
58 | private Interactor mInteractor;
59 |
60 | public static ComboSliderView create(ViewGroup root, boolean attachToRoot) {
61 | return (ComboSliderView) LayoutInflater.from(root.getContext())
62 | .inflate(R.layout.combo_slider, root, attachToRoot);
63 | }
64 |
65 | public ComboSliderView(Context context) {
66 | this(context, null);
67 | }
68 |
69 | public ComboSliderView(Context context, @Nullable AttributeSet attrs) {
70 | this(context, attrs, 0);
71 | }
72 |
73 | public ComboSliderView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
74 | super(context, attrs, defStyleAttr);
75 |
76 | mImm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
77 | }
78 |
79 | public void setOnChangeListener(Interactor listener) {
80 | mInteractor = listener;
81 | }
82 |
83 | public void setValue(int value) {
84 | mSeekBar.setProgress(value - mAdjustmentInfo.minValue);
85 | mValueTextView.setText(String.valueOf(value));
86 | mEditText.setText(mValueTextView.getText());
87 | }
88 |
89 | public int getValue() {
90 | return mSeekBar.getProgress() + mAdjustmentInfo.minValue;
91 | }
92 |
93 | public void bindAdjustmentInfo(BaseAdjustmentFragment.AdjustmentInfo info) {
94 | mAdjustmentInfo = info;
95 |
96 | mLabelTextView.setText(info.propertyName);
97 | ((TextInputLayout) mInputViewLayout).setHint(info.propertyName);
98 | mSeekBar.setMax(info.maxValue - info.minValue);
99 | mSeekBar.setProgress(info.defaultValue - info.minValue);
100 | mValueTextView.setText(String.valueOf(info.defaultValue));
101 | mEditText.setText(mValueTextView.getText());
102 | }
103 |
104 | @Override
105 | protected void onFinishInflate() {
106 | super.onFinishInflate();
107 |
108 | mSliderLayout = (ViewGroup) findViewById(R.id.slider_layout);
109 | mInputViewLayout = (ViewGroup) findViewById(R.id.input_view_layout);
110 | mLabelTextView = (TextView) findViewById(R.id.text);
111 | mValueTextView = (TextView) findViewById(R.id.text_value);
112 | mSeekBar = (AppCompatSeekBar) findViewById(R.id.seek_bar);
113 | mEditText = (TextInputEditText) findViewById(R.id.edit);
114 | mEditButton = (AppCompatImageButton) findViewById(R.id.btn_edit);
115 |
116 | mInputViewLayout.setVisibility(INVISIBLE);
117 |
118 | mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
119 | @Override
120 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
121 | mValueTextView.setText(String.valueOf(progress + mAdjustmentInfo.minValue));
122 | }
123 |
124 | @Override
125 | public void onStartTrackingTouch(SeekBar seekBar) {
126 | notifyFocusChange(true);
127 | }
128 |
129 | @Override
130 | public void onStopTrackingTouch(SeekBar seekBar) {
131 | notifyFocusChange(false);
132 | commitValue(getValue());
133 | }
134 | });
135 |
136 | mEditText.setFocusable(true);
137 | mEditText.setFocusableInTouchMode(true);
138 | mEditText.setOnKeyListener(new OnKeyListener() {
139 | @Override
140 | public boolean onKey(View v, int keyCode, KeyEvent event) {
141 | if (keyCode == KeyEvent.KEYCODE_ENTER || keyCode == KeyEvent.KEYCODE_BACK) {
142 | try {
143 | commitValue(Integer.parseInt(mEditText.getText().toString()));
144 | } catch (NumberFormatException ignored) {}
145 |
146 | // If the input view is still visible, press back key to hide it.
147 | if (keyCode == KeyEvent.KEYCODE_BACK && mInputViewLayout.getVisibility() == VISIBLE) {
148 | return true;
149 | }
150 | }
151 | return false;
152 | }
153 | });
154 | mEditText.setOnFocusChangeListener(new OnFocusChangeListener() {
155 | @Override
156 | public void onFocusChange(View v, boolean hasFocus) {
157 | if (hasFocus) {
158 | return;
159 | }
160 |
161 | try {
162 | commitValue(Integer.parseInt(mEditText.getText().toString()));
163 | } catch (NumberFormatException ignored) {}
164 | }
165 | });
166 |
167 | mEditButton.setOnClickListener(new OnClickListener() {
168 | @Override
169 | public void onClick(View v) {
170 | if (mIsInputViewVisible) {
171 | commitValue(Integer.parseInt(mEditText.getText().toString()));
172 | } else {
173 | showInputView();
174 | }
175 | }
176 | });
177 | }
178 |
179 | private void commitValue(int value) {
180 | // Clamp the value.
181 | value = Math.min(Math.max(value, mAdjustmentInfo.minValue), mAdjustmentInfo.maxValue);
182 |
183 | mSeekBar.setProgress(value - mAdjustmentInfo.minValue);
184 | mValueTextView.setText(String.valueOf(value));
185 | mEditText.setText(mValueTextView.getText());
186 |
187 | if (mInteractor != null) {
188 | mInteractor.onValueChange(value);
189 | }
190 |
191 | if (mIsInputViewVisible) {
192 | hideInputView();
193 | }
194 | }
195 |
196 | private void notifyFocusChange(boolean focus) {
197 | if (mInteractor != null) {
198 | mInteractor.onFocusModeChange(this, focus);
199 | }
200 | }
201 |
202 | private void showInputView() {
203 | if (mIsInputViewVisible) {
204 | return;
205 | }
206 |
207 | mIsInputViewVisible = true;
208 |
209 | mEditButton.setImageResource(R.drawable.ic_check_black_16dp);
210 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
211 | TypedArray a = getContext().getTheme().obtainStyledAttributes(new int[] { R.attr.colorAccent });
212 | mEditButton.setImageTintList(ColorStateList.valueOf(a.getColor(0, 0)));
213 | a.recycle();
214 | }
215 |
216 | mSliderLayout
217 | .animate()
218 | .alpha(0)
219 | .setListener(new SimpleAnimatorListener() {
220 | @Override
221 | public void onAnimationEnd(Animator animation) {
222 | mSliderLayout.setVisibility(INVISIBLE);
223 | }
224 | })
225 | .setDuration(200)
226 | .start();
227 |
228 | mInputViewLayout.setVisibility(VISIBLE);
229 | mInputViewLayout.setAlpha(0);
230 | mInputViewLayout
231 | .animate()
232 | .alpha(1)
233 | .setListener(new SimpleAnimatorListener() {
234 | @Override
235 | public void onAnimationEnd(Animator animation) {
236 | mEditText.requestFocus();
237 | mEditText.setSelection(mEditText.getText().length());
238 | mImm.showSoftInput(mEditText, 0, null);
239 | notifyFocusChange(true);
240 | }
241 | })
242 | .setDuration(200)
243 | .start();
244 | }
245 |
246 | @SuppressWarnings("deprecation")
247 | private void hideInputView() {
248 | if (!mIsInputViewVisible) {
249 | return;
250 | }
251 |
252 | mIsInputViewVisible = false;
253 |
254 | mEditButton.setImageResource(R.drawable.ic_mode_edit_black_16dp);
255 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
256 | mEditButton.setImageTintList(ColorStateList.valueOf(getResources().getColor(R.color.colorSecondaryGray)));
257 | }
258 |
259 | mSliderLayout.setVisibility(VISIBLE);
260 | mSliderLayout
261 | .animate()
262 | .alpha(1)
263 | .setListener(null)
264 | .setDuration(200)
265 | .start();
266 |
267 | mInputViewLayout
268 | .animate()
269 | .alpha(0)
270 | .setListener(new SimpleAnimatorListener() {
271 | @Override
272 | public void onAnimationEnd(Animator animation) {
273 | mInputViewLayout.setVisibility(INVISIBLE);
274 | }
275 | })
276 | .setDuration(200)
277 | .start();
278 |
279 | mImm.hideSoftInputFromWindow(getWindowToken(), 0, null);
280 | notifyFocusChange(false);
281 | }
282 |
283 | public interface Interactor {
284 | void onValueChange(int value);
285 | void onFocusModeChange(View view, boolean focused);
286 | }
287 |
288 | }
289 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright 2017 Cyandev
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/library/src/main/java/me/cyandev/springanimator/AbsSpringAnimator.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2017 Cyandev
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package me.cyandev.springanimator;
18 |
19 | import android.animation.Animator;
20 | import android.animation.TimeInterpolator;
21 | import android.os.Build;
22 | import android.os.Looper;
23 | import androidx.annotation.RequiresApi;
24 | import android.util.AndroidRuntimeException;
25 | import android.util.Log;
26 |
27 | import java.util.ArrayList;
28 |
29 | import me.cyandev.springanimator.internal.AnimationHandler;
30 |
31 | /**
32 | * This abstract class provides the basic mechanism for each spring animator.
33 | */
34 | public abstract class AbsSpringAnimator extends Animator implements AnimationHandler.AnimationFrameCallback {
35 |
36 | private static final String TAG = "AbsSpringAnimator";
37 | private static final boolean DEBUG = false;
38 |
39 | private ArrayList mUpdateListeners = new ArrayList<>();
40 |
41 | private long mLastFrameTime = 0;
42 | private long mStartDelay = 0;
43 | private boolean mStarted = false;
44 | private boolean mRunning = false;
45 | private boolean mStartListenersCalled = false;
46 | private float mProgress = 0;
47 | private float mStartValue = 0;
48 | private float mEndValue = 0;
49 |
50 | /**
51 | * Sets the start value.
52 | */
53 | public void setStartValue(float startValue) {
54 | if (mRunning || mStarted) {
55 | throw new IllegalStateException("Animators that has been started cannot be changed");
56 | }
57 |
58 | mStartValue = startValue;
59 | }
60 |
61 | /**
62 | * Sets the end value.
63 | */
64 | public void setEndValue(float endValue) {
65 | if (mRunning || mStarted) {
66 | throw new IllegalStateException("Animators that has been started cannot be changed");
67 | }
68 |
69 | mEndValue = endValue;
70 | }
71 |
72 | /**
73 | * Gets current progress fraction of the animator.
74 | *
75 | * @return the progress
76 | */
77 | public float getProgress() {
78 | return mProgress;
79 | }
80 |
81 | /**
82 | * Gets current animated value of the animator.
83 | *
84 | * @return the value
85 | */
86 | public float getAnimatedValue() {
87 | return mStartValue + (mEndValue - mStartValue) * mProgress;
88 | }
89 |
90 | /**
91 | * Adds a listener to the set of listeners that are sent update events through the life of
92 | * an animation. This method is called on all listeners for every frame of the animation,
93 | * after the values for the animation have been calculated.
94 | *
95 | * @param listener the listener to be added to the current set of listeners for this animation
96 | */
97 | public void addUpdateListener(AnimatorUpdateListener listener) {
98 | if (!mUpdateListeners.contains(listener)) {
99 | mUpdateListeners.add(listener);
100 | }
101 | }
102 |
103 | /**
104 | * Removes a listener from the set listening to frame updates for this animation.
105 | *
106 | * @param listener the listener to be removed from the current set of update listeners
107 | * for this animation
108 | */
109 | public void removeUpdateListener(AnimatorUpdateListener listener) {
110 | mUpdateListeners.remove(listener);
111 | }
112 |
113 | /**
114 | * Removes all listeners from the set listening to frame updates for this animation.
115 | */
116 | public void removeAllUpdateListeners() {
117 | if (mUpdateListeners == null) {
118 | return;
119 | }
120 | mUpdateListeners.clear();
121 | }
122 |
123 | /** {@inheritDoc} */
124 | @Override
125 | public long getStartDelay() {
126 | return mStartDelay;
127 | }
128 |
129 | /** {@inheritDoc} */
130 | @Override
131 | public void setStartDelay(long startDelay) {
132 | // Clamp start delay to non-negative range.
133 | if (startDelay < 0) {
134 | Log.w(TAG, "Start delay should always be non-negative");
135 | startDelay = 0;
136 | }
137 | mStartDelay = startDelay;
138 | }
139 |
140 | @Override
141 | public Animator setDuration(long duration) {
142 | throw new UnsupportedOperationException("Duration should not be set manually");
143 | }
144 |
145 | /**
146 | * Gets a estimated duration based on current properties.
147 | *
148 | * @return the duration in milliseconds
149 | */
150 | @Override
151 | public long getDuration() {
152 | return computeSettleDuration();
153 | }
154 |
155 | @Override
156 | public void setInterpolator(TimeInterpolator value) {
157 | throw new UnsupportedOperationException("SpringAnimator does not support time interpolator");
158 | }
159 |
160 | /** {@inheritDoc} */
161 | @Override
162 | public boolean isRunning() {
163 | return mRunning;
164 | }
165 |
166 | /** {@inheritDoc} */
167 | @Override
168 | public void start() {
169 | if (Looper.myLooper() == null) {
170 | throw new AndroidRuntimeException("Animators may only be run on Looper threads");
171 | }
172 |
173 | mStarted = true;
174 | mRunning = false;
175 | mProgress = 0;
176 |
177 | mLastFrameTime = 0;
178 | AnimationHandler handler = AnimationHandler.getInstance();
179 | handler.addAnimationFrameCallback(this, mStartDelay);
180 |
181 | if (mStartDelay == 0) {
182 | startAnimation();
183 | }
184 | }
185 |
186 | /** {@inheritDoc} */
187 | @SuppressWarnings("unchecked")
188 | @Override
189 | public void cancel() {
190 | if (Looper.myLooper() == null) {
191 | throw new AndroidRuntimeException("Animators may only be run on Looper threads");
192 | }
193 |
194 | if ((mStarted || mRunning) && getListeners() != null) {
195 | if (!mRunning) {
196 | // If it's not yet running, then start listeners weren't called. Call them now.
197 | notifyStartListeners();
198 | }
199 | ArrayList tmpListeners =
200 | (ArrayList) getListeners().clone();
201 | for (AnimatorListener listener : tmpListeners) {
202 | listener.onAnimationCancel(this);
203 | }
204 | }
205 | endAnimation();
206 | }
207 |
208 | /** {@inheritDoc} */
209 | @Override
210 | public void end() {
211 | if (Looper.myLooper() == null) {
212 | throw new AndroidRuntimeException("Animators may only be run on Looper threads");
213 | }
214 | if (mProgress != 1.f) {
215 | mProgress = 1.f;
216 | notifyUpdateListeners();
217 | }
218 | endAnimation();
219 | }
220 |
221 | /** {@inheritDoc} */
222 | @RequiresApi(api = Build.VERSION_CODES.KITKAT)
223 | @Override
224 | public void resume() {
225 | if (Looper.myLooper() == null) {
226 | throw new AndroidRuntimeException("Animators may only be resumed from the same " +
227 | "thread that the animator was started on");
228 | }
229 | if (isPaused()) {
230 | if (!mRunning) {
231 | AnimationHandler handler = AnimationHandler.getInstance();
232 | handler.addAnimationFrameCallback(this, 0);
233 | }
234 | }
235 | super.resume();
236 | }
237 |
238 | /**
239 | * Override point for subclasses to compute an estimated settle duration.
240 | *
241 | * @return the duration
242 | */
243 | protected abstract long computeSettleDuration();
244 |
245 | /**
246 | * Override point for subclasses to reset internal state.
247 | */
248 | protected abstract void resetState();
249 |
250 | /**
251 | * Override point for subclasses to compute values for next frames.
252 | *
253 | * @param frameTime how long has been skipped since last call
254 | */
255 | protected abstract float enterFrame(long frameTime);
256 |
257 | /**
258 | * Override point for subclasses to report whether the animation is finished.
259 | *
260 | * @return whether the animation is finished
261 | */
262 | protected abstract boolean isFinished();
263 |
264 | private void startAnimation() {
265 | mRunning = true;
266 | resetState();
267 | notifyStartListeners();
268 | }
269 |
270 | @SuppressWarnings("unchecked")
271 | private void endAnimation() {
272 | AnimationHandler handler = AnimationHandler.getInstance();
273 | handler.removeCallback(this);
274 |
275 | if ((mStarted || mRunning) && getListeners() != null) {
276 | if (!mRunning) {
277 | // If it's not yet running, then start listeners weren't called. Call them now.
278 | notifyStartListeners();
279 | }
280 | ArrayList tmpListeners =
281 | (ArrayList) getListeners().clone();
282 | int numListeners = tmpListeners.size();
283 | for (int i = 0; i < numListeners; ++i) {
284 | tmpListeners.get(i).onAnimationEnd(this);
285 | }
286 | }
287 | mRunning = false;
288 | mStarted = false;
289 | mStartListenersCalled = false;
290 | mLastFrameTime = 0;
291 | }
292 |
293 | @SuppressWarnings("unchecked")
294 | private void notifyStartListeners() {
295 | if (getListeners() != null && !mStartListenersCalled) {
296 | ArrayList tmpListeners =
297 | (ArrayList) getListeners().clone();
298 | int numListeners = tmpListeners.size();
299 | for (int i = 0; i < numListeners; ++i) {
300 | tmpListeners.get(i).onAnimationStart(this);
301 | }
302 | }
303 | mStartListenersCalled = true;
304 | }
305 |
306 | @SuppressWarnings("unchecked")
307 | private void notifyUpdateListeners() {
308 | if (mUpdateListeners.size() > 0) {
309 | ArrayList tmpListeners =
310 | (ArrayList) mUpdateListeners.clone();
311 | int numListeners = tmpListeners.size();
312 | for (int i = 0; i < numListeners; ++i) {
313 | tmpListeners.get(i).onAnimationUpdate(this);
314 | }
315 | }
316 | }
317 |
318 | // ###################### AnimationFrameCallback ######################
319 |
320 | @Override
321 | public void doAnimationFrame(long frameTime) {
322 | AnimationHandler handler = AnimationHandler.getInstance();
323 | long skipped = 0;
324 | if (mLastFrameTime == 0) {
325 | if (getStartDelay() > 0) {
326 | startAnimation();
327 | }
328 | } else {
329 | skipped = frameTime - mLastFrameTime;
330 | }
331 | mLastFrameTime = frameTime;
332 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
333 | if (isPaused()) {
334 | handler.removeCallback(this);
335 | mLastFrameTime = 0;
336 | mRunning = false;
337 | }
338 | }
339 |
340 | mProgress = enterFrame(skipped / 1000000L);
341 | notifyUpdateListeners();
342 | if (isFinished()) {
343 | endAnimation();
344 | }
345 | }
346 |
347 | public interface AnimatorUpdateListener {
348 | /**
349 | * Notifies the occurrence of another frame of the animation.
350 | *
351 | * @param animation The animation which was repeated.
352 | */
353 | void onAnimationUpdate(AbsSpringAnimator animation);
354 |
355 | }
356 |
357 | }
358 |
359 |
360 |
--------------------------------------------------------------------------------
/app/src/main/java/me/cyandev/springanimatordemo/MainActivity.java:
--------------------------------------------------------------------------------
1 | package me.cyandev.springanimatordemo;
2 |
3 | import android.animation.Animator;
4 | import android.animation.ValueAnimator;
5 | import android.os.Build;
6 | import android.os.Bundle;
7 |
8 | import com.google.android.material.floatingactionbutton.FloatingActionButton;
9 | import androidx.fragment.app.Fragment;
10 | import androidx.fragment.app.FragmentManager;
11 | import androidx.fragment.app.FragmentPagerAdapter;
12 | import androidx.viewpager.widget.ViewPager;
13 | import androidx.interpolator.view.animation.FastOutLinearInInterpolator;
14 | import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
15 | import androidx.appcompat.app.AppCompatActivity;
16 | import androidx.appcompat.widget.AppCompatSpinner;
17 | import androidx.appcompat.widget.Toolbar;
18 | import android.transition.Slide;
19 | import android.util.SparseArray;
20 | import android.util.TypedValue;
21 | import android.view.Gravity;
22 | import android.view.Menu;
23 | import android.view.MenuItem;
24 | import android.view.MotionEvent;
25 | import android.view.View;
26 | import android.view.ViewGroup;
27 | import android.view.ViewTreeObserver;
28 | import android.widget.AdapterView;
29 | import android.widget.ArrayAdapter;
30 | import android.widget.FrameLayout;
31 |
32 | import java.util.ArrayList;
33 |
34 | import me.cyandev.springanimator.AbsSpringAnimator;
35 | import me.cyandev.springanimatordemo.adjustment.BaseAdjustmentFragment;
36 | import me.cyandev.springanimatordemo.adjustment.DhoAdjustmentFragment;
37 | import me.cyandev.springanimatordemo.adjustment.Rk4AdjustmentFragment;
38 | import me.cyandev.springanimatordemo.preview.BasePreviewFragment;
39 | import me.cyandev.springanimatordemo.preview.RecyclerViewPreviewFragment;
40 | import me.cyandev.springanimatordemo.preview.TranslationPreviewFragment;
41 | import me.cyandev.springanimatordemo.util.SimpleAnimatorListener;
42 |
43 | public class MainActivity extends AppCompatActivity
44 | implements BasePreviewFragment.SpringAnimatorProvider, BaseAdjustmentFragment.OnAdjustmentCommitListener {
45 |
46 | private static final String TAG = "MainActivity";
47 | private static final String KEY_CURRENT_CONFIGURATION = TAG + "_currentConfiguration";
48 | private static final String KEY_SELECTED_ADJUSTMENT = TAG + "_selectedAdjustment";
49 |
50 | private static final Class[] PREVIEW_FRAGMENT_CLASSES = {
51 | TranslationPreviewFragment.class,
52 | RecyclerViewPreviewFragment.class
53 | };
54 |
55 | private static final String[][] ADJUSTMENT_METADATA = {
56 | { "Spring - DHO", DhoAdjustmentFragment.class.getName() },
57 | { "Spring - RK4", Rk4AdjustmentFragment.class.getName() }
58 | };
59 |
60 | private FloatingActionButton mFab;
61 | private ViewPager mPager;
62 | private ViewGroup mPanelLayout;
63 | private AppCompatSpinner mAdjustmentTypeSpinner;
64 | private FrameLayout mPanelContainer;
65 |
66 | private Bundle mCurrentConfiguration;
67 |
68 | private boolean mPanelClosed = false;
69 | private int mSelectedAdjustment = 0;
70 |
71 | @SuppressWarnings("unchecked")
72 | @Override
73 | protected void onCreate(Bundle savedInstanceState) {
74 | super.onCreate(savedInstanceState);
75 | setContentView(R.layout.activity_main);
76 |
77 | setSupportActionBar((Toolbar) findViewById(R.id.toolbar));
78 |
79 | mFab = (FloatingActionButton) findViewById(R.id.fab);
80 | mPager = (ViewPager) findViewById(R.id.pager);
81 | mPanelLayout = (ViewGroup) findViewById(R.id.panel_layout);
82 | mAdjustmentTypeSpinner = (AppCompatSpinner) findViewById(R.id.spinner);
83 | mPanelContainer = (FrameLayout) findViewById(R.id.container2);
84 |
85 | mFab.setOnClickListener(new View.OnClickListener() {
86 | @Override
87 | public void onClick(View v) {
88 | openAdjustmentPanel();
89 | }
90 | });
91 |
92 | mPager.setAdapter(new PreviewPagerAdapter(getSupportFragmentManager()));
93 |
94 | mPanelLayout.setOnTouchListener(new View.OnTouchListener() {
95 | @Override
96 | public boolean onTouch(View v, MotionEvent event) {
97 | // Eat the event to prevent touching through.
98 | return true;
99 | }
100 | });
101 |
102 | setupSpinner();
103 |
104 | if (savedInstanceState == null) {
105 | mCurrentConfiguration = ConfigurationResolver.buildDhoConfiguration(200, 10, 1, 0);
106 | mPager.postDelayed(new Runnable() {
107 | @Override
108 | public void run() {
109 | peekPager();
110 | }
111 | }, 1500);
112 | }
113 | }
114 |
115 | @Override
116 | protected void onStart() {
117 | super.onStart();
118 |
119 | // Hide the panel for a while if it's not opened.
120 | if (!isAdjustmentPanelVisible()) {
121 | mPanelLayout.setVisibility(View.INVISIBLE);
122 | }
123 | }
124 |
125 | @Override
126 | public boolean onCreateOptionsMenu(Menu menu) {
127 | getMenuInflater().inflate(R.menu.activity_main, menu);
128 |
129 | return true;
130 | }
131 |
132 | @Override
133 | public boolean onOptionsItemSelected(MenuItem item) {
134 | switch (item.getItemId()) {
135 | case 0 /* placeholder */:
136 | break;
137 | default:
138 | return super.onOptionsItemSelected(item);
139 | }
140 |
141 | return true;
142 | }
143 |
144 | @Override
145 | protected void onSaveInstanceState(Bundle outState) {
146 | super.onSaveInstanceState(outState);
147 |
148 | outState.putBundle(KEY_CURRENT_CONFIGURATION, mCurrentConfiguration);
149 | outState.putInt(KEY_SELECTED_ADJUSTMENT, mSelectedAdjustment);
150 | }
151 |
152 | @Override
153 | protected void onRestoreInstanceState(Bundle savedInstanceState) {
154 | super.onRestoreInstanceState(savedInstanceState);
155 |
156 | mCurrentConfiguration = savedInstanceState.getBundle(KEY_CURRENT_CONFIGURATION);
157 | mSelectedAdjustment = savedInstanceState.getInt(KEY_SELECTED_ADJUSTMENT);
158 | }
159 |
160 | @Override
161 | public void onBackPressed() {
162 | if (closeAdjustmentPanel()) {
163 | return;
164 | }
165 |
166 | super.onBackPressed();
167 | }
168 |
169 | private void setupSpinner() {
170 | ArrayList types = new ArrayList<>();
171 | for (String[] metadata : ADJUSTMENT_METADATA) {
172 | types.add(metadata[0]);
173 | }
174 |
175 | mAdjustmentTypeSpinner.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, types));
176 | mAdjustmentTypeSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
177 | @Override
178 | public void onItemSelected(AdapterView> parent, View view, int position, long id) {
179 | // Workaround for duplicated selecting when restored.
180 | if (mSelectedAdjustment == position) {
181 | return;
182 | }
183 |
184 | mSelectedAdjustment = position;
185 | if (getSupportFragmentManager().findFragmentById(R.id.container2) != null) {
186 | mAdjustmentTypeSpinner.setEnabled(false);
187 | closeAdjustmentPanel(new Runnable() {
188 | @Override
189 | public void run() {
190 | mAdjustmentTypeSpinner.setEnabled(true);
191 | openAdjustmentPanel();
192 | }
193 | });
194 | }
195 | }
196 |
197 | @Override
198 | public void onNothingSelected(AdapterView> parent) {
199 |
200 | }
201 | });
202 | }
203 |
204 | private void peekPager() {
205 | final float peekDistance =
206 | TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 100, getResources().getDisplayMetrics());
207 | ValueAnimator peekAnimator = ValueAnimator.ofFloat(0, -peekDistance);
208 | peekAnimator.setDuration(450);
209 | peekAnimator.setInterpolator(new FastOutSlowInInterpolator());
210 | peekAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
211 | float mLastValue = 0;
212 |
213 | @Override
214 | public void onAnimationUpdate(ValueAnimator animation) {
215 | final float currentValue = (float) animation.getAnimatedValue();
216 | mPager.fakeDragBy(currentValue - mLastValue);
217 | mLastValue = currentValue;
218 | }
219 | });
220 | peekAnimator.addListener(new SimpleAnimatorListener() {
221 | @Override
222 | public void onAnimationStart(Animator animation) {
223 | mPager.beginFakeDrag();
224 | }
225 |
226 | @Override
227 | public void onAnimationEnd(Animator animation) {
228 | mPager.postDelayed(new Runnable() {
229 | @Override
230 | public void run() {
231 | mPager.endFakeDrag();
232 | }
233 | }, 300);
234 | }
235 | });
236 | peekAnimator.start();
237 | }
238 |
239 | private BasePreviewFragment getCurrentPreviewFragment() {
240 | Fragment f = ((PreviewPagerAdapter) mPager.getAdapter()).getItem(mPager.getCurrentItem());
241 |
242 | if (f instanceof BasePreviewFragment) {
243 | return (BasePreviewFragment) f;
244 | }
245 |
246 | return null;
247 | }
248 |
249 | private void openAdjustmentPanel() {
250 | final Fragment f;
251 |
252 | String adjustmentFragmentClassname = ADJUSTMENT_METADATA[mSelectedAdjustment][1];
253 | try {
254 | Class adjustmentFragmentClass = Class.forName(adjustmentFragmentClassname);
255 | f = (Fragment) adjustmentFragmentClass.newInstance();
256 | } catch (IllegalAccessException | InstantiationException | ClassNotFoundException e) {
257 | e.printStackTrace();
258 | return;
259 | }
260 |
261 | f.setArguments(mCurrentConfiguration);
262 |
263 | // Perform a slide up fashion on L or higher versions.
264 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
265 | f.setEnterTransition(new Slide(Gravity.BOTTOM).setDuration(500));
266 | f.postponeEnterTransition();
267 | }
268 |
269 | FragmentManager fm = getSupportFragmentManager();
270 | fm.beginTransaction()
271 | .replace(R.id.container2, f)
272 | .commitNow();
273 |
274 | mPanelContainer.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
275 | @Override
276 | public boolean onPreDraw() {
277 | if (mPanelContainer.getChildCount() == 0) {
278 | // Fragment view is not installed, ignore the callback one time.
279 | return true;
280 | }
281 |
282 | ViewTreeObserver observer = mPanelLayout.getViewTreeObserver();
283 | if (observer.isAlive()) {
284 | observer.removeOnPreDrawListener(this);
285 | }
286 |
287 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
288 | f.startPostponedEnterTransition();
289 | }
290 |
291 | mPanelLayout.setVisibility(View.VISIBLE);
292 | mPanelLayout.setTranslationY(mPanelLayout.getHeight());
293 | mPanelLayout
294 | .animate()
295 | .translationY(0)
296 | .setDuration(400)
297 | .setListener(null)
298 | .start();
299 |
300 | return true;
301 | }
302 | });
303 |
304 | mPanelClosed = false;
305 | mFab.hide();
306 | }
307 |
308 | private boolean closeAdjustmentPanel() {
309 | return closeAdjustmentPanel(null);
310 | }
311 |
312 | private boolean closeAdjustmentPanel(final Runnable callback) {
313 | final FragmentManager fm = getSupportFragmentManager();
314 | final Fragment f = fm.findFragmentById(R.id.container2);
315 | if (f != null && !mPanelClosed) {
316 | mPanelClosed = true;
317 | mPanelLayout
318 | .animate()
319 | .translationY(mPanelLayout.getHeight())
320 | .setDuration(300)
321 | .setListener(new SimpleAnimatorListener() {
322 | @Override
323 | public void onAnimationEnd(Animator animation) {
324 | mPanelClosed = false;
325 | mFab.show();
326 |
327 | fm.beginTransaction()
328 | .remove(f)
329 | .commit();
330 |
331 | if (callback != null) {
332 | callback.run();
333 | }
334 | }
335 | });
336 | } else {
337 | return false;
338 | }
339 |
340 | return true;
341 | }
342 |
343 | private boolean isAdjustmentPanelVisible() {
344 | return getSupportFragmentManager().findFragmentById(R.id.container2) != null;
345 | }
346 |
347 | @Override
348 | public AbsSpringAnimator provideAnimator() {
349 | return ConfigurationResolver.resolveConfiguration(mCurrentConfiguration);
350 | }
351 |
352 | @Override
353 | public void onAdjustmentCommit(Bundle bundle) {
354 | mCurrentConfiguration = bundle;
355 | BasePreviewFragment f = getCurrentPreviewFragment();
356 | if (f != null) {
357 | f.onStartAnimation();
358 | }
359 | }
360 |
361 | private class PreviewPagerAdapter extends FragmentPagerAdapter {
362 |
363 | private SparseArray mCache = new SparseArray<>();
364 |
365 | public PreviewPagerAdapter(FragmentManager fm) {
366 | super(fm);
367 | }
368 |
369 | @Override
370 | public Fragment getItem(int position) {
371 | Fragment f = mCache.get(position);
372 | if (f == null) {
373 | try {
374 | f = (Fragment) PREVIEW_FRAGMENT_CLASSES[position].newInstance();
375 | mCache.put(position, f);
376 | } catch (InstantiationException | IllegalAccessException e) {
377 | e.printStackTrace();
378 | }
379 | }
380 | return f;
381 | }
382 |
383 | @Override
384 | public int getCount() {
385 | return PREVIEW_FRAGMENT_CLASSES.length;
386 | }
387 |
388 | }
389 |
390 | }
391 |
--------------------------------------------------------------------------------