├── .buildkite └── pipeline.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── backboard-example ├── .gitignore ├── build.gradle ├── proguard-rules.txt └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── tumblr │ │ └── backboard │ │ └── example │ │ ├── AppearFragment.java │ │ ├── BackboardActivity.java │ │ ├── BloomFragment.java │ │ ├── ConstrainedFragment.java │ │ ├── ExplosionFragment.java │ │ ├── FlowerFragment.java │ │ ├── FollowFragment.java │ │ ├── MoveFragment.java │ │ ├── PressFragment.java │ │ ├── ScaleFragment.java │ │ ├── SnapFragment.java │ │ └── ZoomFragment.java │ └── res │ ├── drawable │ ├── circle_blue.xml │ ├── circle_gray.xml │ ├── circle_green.xml │ ├── circle_orange.xml │ ├── circle_purple.xml │ ├── circle_red.xml │ └── ring_navy.xml │ ├── layout │ ├── activity_backboard.xml │ ├── fragment_appear.xml │ ├── fragment_bloom.xml │ ├── fragment_constrain.xml │ ├── fragment_flower.xml │ ├── fragment_follow.xml │ ├── fragment_move.xml │ ├── fragment_press.xml │ ├── fragment_scale.xml │ └── fragment_snap.xml │ ├── menu │ └── backboard.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── mipmap-xxxhdpi │ └── ic_launcher.png │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── backboard ├── .gitignore ├── build.gradle ├── checkstyle-suppressions.xml ├── checkstyle.xml ├── proguard-rules.txt └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── tumblr │ └── backboard │ ├── Actor.java │ ├── MotionProperty.java │ ├── imitator │ ├── ConstrainedMotionImitator.java │ ├── EventImitator.java │ ├── Imitator.java │ ├── InertialImitator.java │ ├── MotionImitator.java │ ├── SpringImitator.java │ └── ToggleImitator.java │ └── performer │ ├── MapPerformer.java │ └── Performer.java ├── build.gradle ├── circle.yml ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.buildkite/pipeline.yml: -------------------------------------------------------------------------------- 1 | common-params: 2 | &docker-container 3 | docker#v3.8.0: 4 | image: "public.ecr.aws/automattic/android-build-image:v1.1.0" 5 | propagate-environment: true 6 | environment: 7 | # DO NOT MANUALLY SET THESE VALUES! 8 | # They are passed from the Buildkite agent to the Docker container 9 | - "AWS_ACCESS_KEY" 10 | - "AWS_SECRET_KEY" 11 | 12 | steps: 13 | - label: "Lint & Checkstyle" 14 | key: "lint_and_checkstyle" 15 | plugins: 16 | - *docker-container 17 | command: | 18 | ./gradlew lintRelease checkstyle 19 | artifact_paths: 20 | - "**/build/reports/lint-results**.*" 21 | - "**/build/reports/checkstyle/checkstyle.*" 22 | 23 | - label: "Publish to S3 Maven" 24 | depends_on: 25 | - "lint_and_checkstyle" 26 | plugins: 27 | - *docker-container 28 | command: | 29 | ./gradlew \ 30 | :backboard:prepareToPublishToS3 $(prepare_to_publish_to_s3_params) \ 31 | :backboard:publish 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | .idea 15 | *.iml 16 | 17 | # Gradle files 18 | .gradle/ 19 | build/ 20 | /*/build/ 21 | 22 | # Local configuration file (sdk path, etc) 23 | local.properties 24 | 25 | # Proguard folder generated by Eclipse 26 | proguard/ 27 | 28 | # Log Files 29 | *.log 30 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | We want to make contributing to Backboard as easy and transparent as possible. If you run into problems, please open an issue. We also actively welcome pull requests. 4 | 5 | ## Pull Requests 6 | 7 | 1. Fork the repo and create your branch from `master`. 8 | 2. If you've added code that should be tested, add tests. 9 | 3. If you've changed APIs, update the documentation. 10 | 4. Ensure the test suite passes. 11 | 5. If you haven't already, complete the Contributor License Agreement ("CLA"). 12 | 13 | ## Contributor License Agreement ("CLA") 14 | 15 | In order to accept your pull request, we need you to submit a CLA. 16 | 17 | Complete your CLA [here](http://static.tumblr.com/zyubucd/GaTngbrpr/tumblr_corporate_contributor_license_agreement_v1__10-7-14.pdf) (a more integrated web form is coming soon). 18 | 19 | ## License 20 | 21 | By contributing to Backboard you agree that your contributions will be licensed under its Apache 2.0 license. 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2015-2016 Tumblr, Inc. 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![the backboard icon](backboard-example/src/main/res/mipmap-xxhdpi/ic_launcher.png?raw=true) 2 | 3 | # Backboard 4 | 5 | A motion-driven animation framework for Android. 6 | 7 | `backboard` is a framework on top of [rebound](http://facebook.github.io/rebound/) that makes it easier to use by coupling it to views and motions. 8 | 9 | `backboard-example` is an Android app with a few demos of animations made possible by Backboard. 10 | 11 | [Javadoc](http://tumblr.github.io/Backboard/javadoc/) 12 | 13 | ![follow animation](../screenshots/screenshots/follow.gif?raw=true) 14 | ![bloom animation](../screenshots/screenshots/bloom.gif?raw=true) 15 | ![scale animation](../screenshots/screenshots/scale.gif?raw=true) 16 | 17 | ## Table of Contents 18 | 19 | * [Usage](#usage) 20 | * [Publishing a new version](#publishing-a-new-version) 21 | * [Getting Started](#getting-started) 22 | * [Performers](#performers) 23 | * [Imitators](#imitators) 24 | * [Actors](#actors) 25 | * [Dependencies](#dependencies) 26 | * [Contact](#contact) 27 | * [License](#license) 28 | 29 | ## Usage 30 | 31 | Update your `build.gradle` with 32 | 33 | ```groovy 34 | repositories { 35 | exclusiveContent { 36 | forRepository { 37 | maven { 38 | url "https://a8c-libs.s3.amazonaws.com/android" 39 | } 40 | } 41 | filter { 42 | includeModule "com.tumblr", "backboard" 43 | } 44 | } 45 | } 46 | 47 | dependencies { 48 | implementation 'com.facebook.rebound:rebound:0.3.8' 49 | implementation 'com.tumblr:backboard:0.2.0' 50 | } 51 | ``` 52 | 53 | ## Publishing a new version 54 | 55 | In the following cases, the CI will publish a new version with the following format to our S3 Maven repo: 56 | 57 | * For each commit in an open PR: `-` 58 | * Each time a PR is merged to `master`: `master-` 59 | * Each time a new tag is created: `{tag-name}` 60 | 61 | ## Getting Started 62 | 63 | Backboard is a framework on top of [rebound](http://facebook.github.io/rebound/) that manages how `Springs` are used and simplifies the 64 | most common use cases: 65 | 66 | - Actions, such as `MotionEvents`, are mapped to `Springs` via `Imitators`. 67 | - `Springs` are mapped to `Views` and view properties via `Performers`. 68 | 69 | In addition, an `Actor` wraps the above objects and provides a simple interface for mapping touch motion to a view's position - dragging. 70 | 71 | ### Performers 72 | 73 | A `Performer` takes the current value of a `Spring` and sets it as the value of a view property. 74 | ```Java 75 | Spring bounce = SpringSystem.create().createSpring(); 76 | Performer xMotion = new Performer(view, View.TRANSLATION_X); 77 | bounce.addListener(xMotion); 78 | ``` 79 | for those saving screen space, a [fluent interface](http://en.wikipedia.org/wiki/Fluent_interface) is available: 80 | ```Java 81 | Spring bounce = SpringSystem.create().createSpring().addListener(new Performer(view, View.TRANSLATION_X)); 82 | ``` 83 | 84 | ### Imitators 85 | 86 | An `Imitator` constantly perturbs the `Spring` it is attached to. This perturbation can originate a variety of sources: 87 | 88 | 1. A `MotionEvent`, where the `Spring` can change based on the action (`ACTION_DOWN`, `ACTION_UP`), or imitate a property (`x`, `y`, etc.). These are called `EventImitators`. 89 | 2. Another `Spring`, which leads to results similar to springs being chained together. These are called `SpringImitators`. 90 | 91 | 92 | #### Imitating Touch 93 | 94 | An `EventImitator` primarily operates with `OnTouchListeners`. The simplest example is a `ToggleImitator`, which toggles between two different values depending on the touch state: 95 | ```Java 96 | view.setOnTouchListener(new ToggleImitator(spring, 0, 1)); 97 | ``` 98 | when the user touches the view, a value of `1` is set on the spring, and when the user releases, a value of `0` is set. 99 | 100 | #### Imitating Motion 101 | 102 | A `MotionImitator` is a special type of `EventImitator` that maps _x_ and _y_ movement to a spring. This is done with `MotionProperty` enums, which specifies which methods to call in a `MotionEvent` object. For example, `MotionProperty.X.getValue(MotionEvent)` calls `event.getX()`. It also specifies the view property to animate - `MotionEvent.X.getViewProperty()` corresponds to `View.TRANSLATION_X`. This is useful for the `Actor` builder later on. In addition, tracking and following strategies allow for customization of how the event value is mapped to the spring. 103 | 104 | ##### Tracking Strategies 105 | 106 | Two tracking strategies are available to configure how an imitator tracks its imitatee. 107 | 108 | * `TRACK_ABSOLUTE` maps the imitatee value directly to the spring. 109 | * `TRACK_DELTA` maps the change in the imitatee value (relative to the initial touch) to the spring. 110 | 111 | ##### Follow Strategies 112 | 113 | Two follow strategies are available to configure how the spring is updated. 114 | 115 | * `FOLLOW_EXACT` maps the imitatee value directly to the current and end value of the spring. 116 | * `FOLLOW_SPRING` maps the imitatee value to the end value of the spring (which allows the spring 117 | to overshoot the current position) 118 | 119 | #### Imitating Springs 120 | 121 | A `SpringImitator` is also a `SpringListener`. When the `Spring` it is imitating updates, it updates the end value of the `Spring` it is controlling. Usage is simple: 122 | ```Java 123 | SpringSystem springSystem = SpringSystem.create(); 124 | 125 | Spring leader = springSystem.createSpring(); 126 | Spring follower = springSystem.createSpring(); 127 | 128 | SpringImitator follow = new SpringImitator(follower); 129 | leader.addListener(follow); 130 | ``` 131 | 132 | ### Actors 133 | 134 | Even though backboard reduces a significant amount of boilerplate code, the `Actor` class further simplifes view motion by connecting each component together. It also manages a `View.onTouchListener()` (a `MotionListener`), which it attaches to the `View` automatically (this can be disabled). Here is how to create one: 135 | ```Java 136 | Actor actor = new Actor.Builder(SpringSystem.create(), view) 137 | .addTranslateMotion(MotionProperty.X) 138 | .build(); 139 | ``` 140 | in two dimensions: 141 | ```Java 142 | Actor actor = new Actor.Builder(SpringSystem.create(), view) 143 | .addTranslateMotion(MotionProperty.X) 144 | .addTranslateMotion(MotionProperty.Y) 145 | .build(); 146 | ``` 147 | Two calls to `addTranslateMotion(MotionProperty)` are needed because each axis is independent of the other. The builder will create an `OnTouchListener` and attach it to the `View`, as well as a `Spring` with the default settings. A `Performer` is also created and attached to the `Spring`. When there is a touch event, it is passed to the `MotionImitator`, which perturbs the spring, which moves the view. 148 | 149 | It is also possible to supply your own `SpringSystem`, `Spring`, `MotionImitator` and `Performer`, and the builder will properly connect them. The first example above can also be expressed as: 150 | ```Java 151 | SpringSystem springSystem = SpringSystem.create(); 152 | Spring spring = springSystem.createSpring(); 153 | 154 | Actor verbose = new Actor.Builder(springSystem, view) 155 | .addMotion(spring, new MotionImitator(spring, MotionProperty.X), 156 | new Performer(view, View.TRANSLATION_X) 157 | .build(); 158 | ``` 159 | 160 | The `View` can be also left out of the constructor of the `Performer` and the `Spring` out of the `MotionImitator` (using the default `SpringConfig`), since the builder will connect them. 161 | ```Java 162 | Actor walk = new Actor.Builder(SpringSystem.create(), walker) 163 | .addMotion( 164 | new MotionImitator(MotionProperty.X), 165 | new Performer(View.TRANSLATION_X)) 166 | .build(); 167 | ``` 168 | which can be further simplified to 169 | ```Java 170 | Actor run = new Actor.Builder(SpringSystem.create(), runner).addMotion(MotionProperty.X, View.TRANSLATION_X).build(); 171 | ``` 172 | and for more sugar, the previous case: 173 | ```Java 174 | Actor bolt = new Actor.Builder(SpringSystem.create(), bolter).addTranslateMotion(MotionProperty.X).build(); 175 | ``` 176 | 177 | #### Actor Options 178 | 179 | - `requestDisallowTouchEvent()` causes the `Actor` to call `ViewParent.requestDisallowTouchEvent(true)` which is helpful when the view is inside a `ListView` or another view that captures touch events. 180 | - `dontAttachMotionListener()` tells the builder to not attach the `MotionListener` to the `View`, which is useful when you want to attach your own `OnTouchListener` to the view. 181 | 182 | ## Dependencies 183 | 184 | * [rebound](http://facebook.github.io/rebound/) 185 | 186 | ## Contact 187 | 188 | * [Eric Leong](mailto:ericleong@tumblr.com) 189 | 190 | ## License 191 | 192 | Copyright 2015-2016 Tumblr, Inc. 193 | 194 | Licensed under the Apache License, Version 2.0 (the “License”); you may not use 195 | this file except in compliance with the License. You may obtain a copy of the 196 | License at [apache.org/licenses/LICENSE-2.0](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, WITHOUT 200 | > WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 201 | > License for the specific language governing permissions and limitations under 202 | > the License. 203 | -------------------------------------------------------------------------------- /backboard-example/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /backboard-example/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "com.android.application" 3 | } 4 | 5 | android { 6 | compileSdkVersion rootProject.ext.compileSdkVersion 7 | 8 | defaultConfig { 9 | minSdkVersion rootProject.ext.minSdkVersion 10 | targetSdkVersion rootProject.ext.targetSdkVersion 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | 15 | lintOptions { 16 | disable 'AllowBackup', 'GoogleAppIndexingWarning' 17 | } 18 | 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' 23 | } 24 | } 25 | } 26 | 27 | dependencies { 28 | implementation rootProject.ext.facebookRebound 29 | implementation project(':backboard') 30 | implementation fileTree(dir: 'libs', include: ['*.jar']) 31 | } 32 | -------------------------------------------------------------------------------- /backboard-example/proguard-rules.txt: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Applications/Android Studio.app/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the ProGuard 5 | # include property in project.properties. 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 | #} -------------------------------------------------------------------------------- /backboard-example/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /backboard-example/src/main/java/com/tumblr/backboard/example/AppearFragment.java: -------------------------------------------------------------------------------- 1 | package com.tumblr.backboard.example; 2 | 3 | import android.app.Fragment; 4 | import android.os.Bundle; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import com.facebook.rebound.Spring; 9 | import com.facebook.rebound.SpringSystem; 10 | import com.tumblr.backboard.Actor; 11 | import com.tumblr.backboard.MotionProperty; 12 | import com.tumblr.backboard.imitator.EventImitator; 13 | import com.tumblr.backboard.imitator.MotionImitator; 14 | import com.tumblr.backboard.performer.Performer; 15 | 16 | /** 17 | * A dialog-like animation. 18 | */ 19 | public class AppearFragment extends Fragment { 20 | 21 | private static final String TAG = AppearFragment.class.getSimpleName(); 22 | 23 | private View mRootView; 24 | private View[] mCircles; 25 | 26 | @Override 27 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 28 | Bundle savedInstanceState) { 29 | mRootView = inflater.inflate(R.layout.fragment_appear, container, false); 30 | 31 | // grab the circles 32 | mCircles = new View[6]; 33 | mCircles[0] = mRootView.findViewById(R.id.circle0); 34 | mCircles[1] = mRootView.findViewById(R.id.circle1); 35 | mCircles[2] = mRootView.findViewById(R.id.circle2); 36 | mCircles[3] = mRootView.findViewById(R.id.circle3); 37 | mCircles[4] = mRootView.findViewById(R.id.circle4); 38 | mCircles[5] = mRootView.findViewById(R.id.circle5); 39 | 40 | final SpringSystem springSystem = SpringSystem.create(); 41 | 42 | final Spring[] springs = new Spring[6]; 43 | final Actor[] actors = new Actor[6]; 44 | 45 | // the selected view should move to heaven and the unselected ones should go to hell 46 | View.OnClickListener select = new View.OnClickListener() { 47 | @Override 48 | public void onClick(View v) { 49 | Spring spring = (Spring) v.getTag(); 50 | 51 | // get root location so we can compensate for it 52 | int[] rootLocation = new int[2]; 53 | mRootView.getLocationInWindow(rootLocation); 54 | 55 | int[] location = new int[2]; 56 | 57 | for (int i = 0; i < mCircles.length; i++) { 58 | actors[i].setTouchEnabled(false); 59 | 60 | for (Actor.Motion motion : actors[i].getMotions()) { 61 | for (EventImitator imitator : motion.getImitators()) { 62 | if (imitator instanceof MotionImitator) { 63 | final MotionImitator motionImitator = (MotionImitator) imitator; 64 | if (motionImitator.getProperty() == MotionProperty.Y) { 65 | // TODO: disable the y-motion because it is about to be animated 66 | // imitator.getSpring().deregister(); 67 | } else { 68 | imitator.release(null); 69 | } 70 | } 71 | } 72 | } 73 | 74 | mCircles[i].getLocationInWindow(location); 75 | 76 | if (springs[i] == spring) { 77 | // goes to the top 78 | springs[i].setEndValue(-location[1] + rootLocation[1] 79 | - v.getMeasuredHeight()); 80 | } else { 81 | // go back to the bottom 82 | springs[i].setEndValue(mRootView.getMeasuredHeight() - location[1] + 83 | rootLocation[1] + 84 | 2 * Math.random() * mCircles[i].getMeasuredHeight()); 85 | } 86 | } 87 | } 88 | }; 89 | 90 | // attach listeners 91 | for (int i = 0; i < mCircles.length; i++) { 92 | springs[i] = springSystem.createSpring(); 93 | 94 | springs[i].addListener(new Performer(mCircles[i], View.TRANSLATION_Y)); 95 | 96 | mCircles[i].setTag(springs[i]); 97 | 98 | mCircles[i].setOnClickListener(select); 99 | 100 | actors[i] = new Actor.Builder(springSystem, mCircles[i]) 101 | .addTranslateMotion(MotionProperty.X) 102 | .addTranslateMotion(MotionProperty.Y).build(); 103 | } 104 | 105 | mRootView.setOnClickListener(new View.OnClickListener() { 106 | @Override 107 | public void onClick(View v) { 108 | 109 | // grab location of root view so we can compensate for it 110 | int[] rootLocation = new int[2]; 111 | v.getLocationInWindow(rootLocation); 112 | 113 | int[] location = new int[2]; 114 | 115 | for (int i = 0; i < mCircles.length; i++) { 116 | 117 | if (springs[i].getEndValue() == 0) { // hide 118 | mCircles[i].getLocationInWindow(location); 119 | 120 | // if the end values are different, they will move at different speeds 121 | springs[i].setEndValue(mRootView.getMeasuredHeight() - location[1] + 122 | rootLocation[1] + 123 | 2 * Math.random() * mCircles[i].getMeasuredHeight()); 124 | } else { 125 | actors[i].setTouchEnabled(true); 126 | 127 | for (Actor.Motion motion : actors[i].getMotions()) { 128 | for (EventImitator imitator : motion.getImitators()) { 129 | if (imitator instanceof MotionImitator) { 130 | final MotionImitator motionImitator = (MotionImitator) imitator; 131 | imitator.getSpring().setCurrentValue(0); 132 | 133 | // TODO: re-enable the y motion. 134 | // if (imitator.getProperty() == MotionProperty.Y && 135 | // !imitator.getSpring().isRegistered()) { 136 | // imitator.getSpring().register(); 137 | // } 138 | } 139 | } 140 | } 141 | 142 | springs[i].setEndValue(0); // appear 143 | } 144 | } 145 | } 146 | }); 147 | 148 | return mRootView; 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /backboard-example/src/main/java/com/tumblr/backboard/example/BackboardActivity.java: -------------------------------------------------------------------------------- 1 | package com.tumblr.backboard.example; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.view.Menu; 6 | import android.view.MenuItem; 7 | 8 | /** 9 | * Container activity. 10 | */ 11 | public class BackboardActivity extends Activity { 12 | 13 | protected void onCreate(Bundle savedInstanceState) { 14 | super.onCreate(savedInstanceState); 15 | setContentView(R.layout.activity_backboard); 16 | 17 | if (savedInstanceState == null) { 18 | getFragmentManager().beginTransaction() 19 | .add(R.id.container, new MoveFragment()) 20 | .commit(); 21 | 22 | setTitle(R.string.action_move); 23 | } 24 | } 25 | 26 | @Override 27 | public boolean onCreateOptionsMenu(Menu menu) { 28 | // Inflate the menu; this adds items to the action bar if it is present. 29 | getMenuInflater().inflate(R.menu.backboard, menu); 30 | return true; 31 | } 32 | 33 | @Override 34 | public boolean onOptionsItemSelected(MenuItem item) { 35 | // Handle action bar item clicks here. The action bar will 36 | // automatically handle clicks on the Home/Up button, so long 37 | // as you specify a parent activity in AndroidManifest.xml. 38 | int id = item.getItemId(); 39 | 40 | switch (id) { 41 | case R.id.action_move: 42 | getFragmentManager().beginTransaction() 43 | .replace(R.id.container, new MoveFragment()) 44 | .commit(); 45 | setTitle(R.string.action_move); 46 | return true; 47 | 48 | case R.id.action_snap: 49 | getFragmentManager().beginTransaction() 50 | .replace(R.id.container, new SnapFragment()) 51 | .commit(); 52 | setTitle(R.string.action_snap); 53 | return true; 54 | 55 | case R.id.action_scale: 56 | getFragmentManager().beginTransaction() 57 | .replace(R.id.container, new ScaleFragment()) 58 | .commit(); 59 | setTitle(R.string.action_scale); 60 | return true; 61 | 62 | case R.id.action_bloom: 63 | getFragmentManager().beginTransaction() 64 | .replace(R.id.container, new BloomFragment()) 65 | .commit(); 66 | setTitle(R.string.action_bloom); 67 | return true; 68 | 69 | case R.id.action_flower: 70 | getFragmentManager().beginTransaction() 71 | .replace(R.id.container, new FlowerFragment()) 72 | .commit(); 73 | setTitle(R.string.action_flower); 74 | return true; 75 | 76 | case R.id.action_appear: 77 | getFragmentManager().beginTransaction() 78 | .replace(R.id.container, new AppearFragment()) 79 | .commit(); 80 | setTitle(R.string.action_appear); 81 | return true; 82 | 83 | case R.id.action_explosion: 84 | getFragmentManager().beginTransaction() 85 | .replace(R.id.container, new ExplosionFragment()) 86 | .commit(); 87 | setTitle(R.string.action_explosion); 88 | return true; 89 | 90 | case R.id.action_follow: 91 | getFragmentManager().beginTransaction() 92 | .replace(R.id.container, new FollowFragment()) 93 | .commit(); 94 | setTitle(R.string.action_follow); 95 | return true; 96 | 97 | case R.id.action_zoom: 98 | getFragmentManager().beginTransaction() 99 | .replace(R.id.container, new ZoomFragment()) 100 | .commit(); 101 | setTitle(R.string.action_zoom); 102 | return true; 103 | 104 | case R.id.action_constrained: 105 | getFragmentManager().beginTransaction() 106 | .replace(R.id.container, new ConstrainedFragment()) 107 | .commit(); 108 | setTitle(R.string.action_constrained); 109 | return true; 110 | 111 | case R.id.action_press: 112 | getFragmentManager().beginTransaction() 113 | .replace(R.id.container, new PressFragment()) 114 | .commit(); 115 | setTitle(R.string.action_press); 116 | return true; 117 | } 118 | 119 | return super.onOptionsItemSelected(item); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /backboard-example/src/main/java/com/tumblr/backboard/example/BloomFragment.java: -------------------------------------------------------------------------------- 1 | package com.tumblr.backboard.example; 2 | 3 | import android.app.Fragment; 4 | import android.content.res.TypedArray; 5 | import android.os.Bundle; 6 | import android.util.TypedValue; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.RelativeLayout; 11 | import com.facebook.rebound.Spring; 12 | import com.facebook.rebound.SpringSystem; 13 | import com.tumblr.backboard.imitator.ToggleImitator; 14 | import com.tumblr.backboard.performer.MapPerformer; 15 | 16 | /** 17 | * A ring of views that bloom and then contract. 18 | *

19 | * Created by ericleong on 5/7/14. 20 | */ 21 | public class BloomFragment extends Fragment { 22 | 23 | private static final int DIAMETER = 80; 24 | private static final int RING_DIAMETER = 5 * DIAMETER; 25 | 26 | private static final int OPEN = 1; 27 | private static final int CLOSED = 0; 28 | 29 | private RelativeLayout mRootView; 30 | private View[] mCircles; 31 | 32 | @Override 33 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 34 | Bundle savedInstanceState) { 35 | mRootView = (RelativeLayout) inflater.inflate(R.layout.fragment_bloom, container, false); 36 | 37 | mCircles = new View[6]; 38 | 39 | float diameter = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DIAMETER, 40 | getResources().getDisplayMetrics()); 41 | 42 | final TypedArray circles = getResources().obtainTypedArray(R.array.circles); 43 | 44 | // layout params 45 | RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams((int) diameter, 46 | (int) diameter); 47 | params.addRule(RelativeLayout.CENTER_IN_PARENT); 48 | 49 | // create the circle views 50 | int colorIndex = 0; 51 | for (int i = 0; i < mCircles.length; i++) { 52 | mCircles[i] = new View(getActivity()); 53 | 54 | mCircles[i].setLayoutParams(params); 55 | 56 | mCircles[i].setBackgroundDrawable(getResources().getDrawable(circles 57 | .getResourceId(colorIndex, -1))); 58 | 59 | colorIndex++; 60 | if (colorIndex >= circles.length()) { 61 | colorIndex = 0; 62 | } 63 | 64 | mRootView.addView(mCircles[i]); 65 | } 66 | 67 | circles.recycle(); 68 | 69 | /* Animations! */ 70 | 71 | final SpringSystem springSystem = SpringSystem.create(); 72 | 73 | // create spring 74 | final Spring spring = springSystem.createSpring(); 75 | 76 | // add listeners along arc 77 | double arc = 2 * Math.PI / mCircles.length; 78 | 79 | for (int i = 0; i < mCircles.length; i++) { 80 | View view = mCircles[i]; 81 | 82 | // map spring to a line segment from the center to the edge of the ring 83 | spring.addListener(new MapPerformer(view, View.TRANSLATION_X, 0, 1, 84 | 0, (float) (RING_DIAMETER * Math.cos(i * arc)))); 85 | 86 | spring.addListener(new MapPerformer(view, View.TRANSLATION_Y, 0, 1, 87 | 0, (float) (RING_DIAMETER * Math.sin(i * arc)))); 88 | 89 | spring.setEndValue(CLOSED); 90 | } 91 | 92 | mRootView.setOnTouchListener(new ToggleImitator(spring, CLOSED, OPEN)); 93 | 94 | return mRootView; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /backboard-example/src/main/java/com/tumblr/backboard/example/ConstrainedFragment.java: -------------------------------------------------------------------------------- 1 | package com.tumblr.backboard.example; 2 | 3 | import android.app.Fragment; 4 | import android.os.Build; 5 | import android.os.Bundle; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.view.ViewTreeObserver; 10 | import com.facebook.rebound.SpringSystem; 11 | import com.tumblr.backboard.Actor; 12 | import com.tumblr.backboard.MotionProperty; 13 | import com.tumblr.backboard.imitator.Imitator; 14 | import com.tumblr.backboard.imitator.InertialImitator; 15 | 16 | /** 17 | * Snap a view to either the lower left or lower right corner. 18 | *

19 | * Created by ericleong on 5/7/14. 20 | */ 21 | public class ConstrainedFragment extends Fragment { 22 | 23 | @Override 24 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 25 | Bundle savedInstanceState) { 26 | 27 | final View rootView = inflater.inflate(R.layout.fragment_constrain, container, false); 28 | 29 | final View constraintView = rootView.findViewById(R.id.constraint); 30 | 31 | final View circle = rootView.findViewById(R.id.circle); 32 | 33 | final InertialImitator motionImitatorX = 34 | new InertialImitator(MotionProperty.X, Imitator.TRACK_DELTA, 35 | Imitator.FOLLOW_SPRING, 0, 0); 36 | 37 | final InertialImitator motionImitatorY = 38 | new InertialImitator(MotionProperty.Y, Imitator.TRACK_DELTA, 39 | Imitator.FOLLOW_SPRING, 0, 0); 40 | 41 | new Actor.Builder(SpringSystem.create(), circle) 42 | .addMotion(motionImitatorX, View.TRANSLATION_X) 43 | .addMotion(motionImitatorY, View.TRANSLATION_Y) 44 | .build(); 45 | 46 | rootView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { 47 | @Override 48 | public void onGlobalLayout() { 49 | motionImitatorX.setMinValue( 50 | -constraintView.getMeasuredWidth() / 2 + circle.getMeasuredWidth() / 2); 51 | motionImitatorX.setMaxValue( 52 | constraintView.getMeasuredWidth() / 2 - circle.getMeasuredWidth() / 2); 53 | motionImitatorY.setMinValue( 54 | -constraintView.getMeasuredHeight() / 2 + circle.getMeasuredWidth() / 2); 55 | motionImitatorY.setMaxValue( 56 | constraintView.getMeasuredHeight() / 2 - circle.getMeasuredWidth() / 2); 57 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 58 | rootView.getViewTreeObserver().removeOnGlobalLayoutListener(this); 59 | } else { 60 | rootView.getViewTreeObserver().removeGlobalOnLayoutListener(this); 61 | } 62 | } 63 | }); 64 | 65 | return rootView; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /backboard-example/src/main/java/com/tumblr/backboard/example/ExplosionFragment.java: -------------------------------------------------------------------------------- 1 | package com.tumblr.backboard.example; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.app.Fragment; 5 | import android.content.Context; 6 | import android.content.res.TypedArray; 7 | import android.graphics.drawable.Drawable; 8 | import android.os.Bundle; 9 | import android.os.Handler; 10 | import android.util.TypedValue; 11 | import android.view.LayoutInflater; 12 | import android.view.MotionEvent; 13 | import android.view.View; 14 | import android.view.ViewGroup; 15 | import android.widget.RelativeLayout; 16 | import com.facebook.rebound.Spring; 17 | import com.facebook.rebound.SpringConfig; 18 | import com.facebook.rebound.SpringListener; 19 | import com.facebook.rebound.SpringSystem; 20 | import com.tumblr.backboard.performer.Performer; 21 | 22 | /** 23 | * Demonstrates creating and removing {@link android.view.View}s and 24 | * {@link com.facebook.rebound.Spring}s. 25 | *

26 | * Created by ericleong on 5/7/14. 27 | */ 28 | public class ExplosionFragment extends Fragment { 29 | 30 | private static final int DIAMETER = 80; 31 | 32 | private RelativeLayout mRootView; 33 | 34 | private int colorIndex; 35 | private TypedArray mCircles; 36 | 37 | private Handler mHandler; 38 | private Runnable mRunnable; 39 | private boolean mTouching; 40 | 41 | private SpringSystem mSpringSystem; 42 | 43 | private SpringConfig mCoasting; 44 | private SpringConfig mGravity; 45 | 46 | /** 47 | * Destroys the attached {@link com.facebook.rebound.Spring}. 48 | */ 49 | private static class Destroyer implements SpringListener { 50 | 51 | public int mMin, mMax; 52 | 53 | protected ViewGroup mViewGroup; 54 | protected View mViewToRemove; 55 | 56 | private Destroyer(ViewGroup viewGroup, View viewToRemove, int min, 57 | int max) { 58 | mViewGroup = viewGroup; 59 | mViewToRemove = viewToRemove; 60 | 61 | mMin = min; 62 | mMax = max; 63 | } 64 | 65 | public boolean shouldClean(Spring spring) { 66 | // these are arbitrary values to keep the view from disappearing before it is 67 | // fully off the screen 68 | return spring.getCurrentValue() < mMin || spring.getCurrentValue() > mMax; 69 | } 70 | 71 | public void clean(Spring spring) { 72 | if (mViewGroup != null && mViewToRemove != null) { 73 | mViewGroup.removeView(mViewToRemove); 74 | } 75 | if (spring != null) { 76 | spring.destroy(); 77 | } 78 | } 79 | 80 | @Override 81 | public void onSpringUpdate(Spring spring) { 82 | if (shouldClean(spring)) { 83 | clean(spring); 84 | } 85 | } 86 | 87 | @Override 88 | public void onSpringAtRest(Spring spring) { 89 | 90 | } 91 | 92 | @Override 93 | public void onSpringActivate(Spring spring) { 94 | 95 | } 96 | 97 | @Override 98 | public void onSpringEndStateChange(Spring spring) { 99 | 100 | } 101 | } 102 | 103 | private class CircleSpawn implements Runnable { 104 | 105 | @Override 106 | public void run() { 107 | if (mTouching) { 108 | 109 | colorIndex++; 110 | if (colorIndex >= mCircles.length()) { 111 | colorIndex = 0; 112 | } 113 | 114 | float diameter = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DIAMETER, 115 | getResources().getDisplayMetrics()); 116 | 117 | Drawable drawable = getResources().getDrawable(mCircles.getResourceId 118 | (colorIndex, -1)); 119 | 120 | createCircle(getActivity(), mRootView, mSpringSystem, mCoasting, mGravity, 121 | (int) diameter, drawable); 122 | 123 | mHandler.postDelayed(this, 100); 124 | } 125 | } 126 | } 127 | 128 | private static void createCircle(Context context, ViewGroup rootView, 129 | SpringSystem springSystem, 130 | SpringConfig coasting, 131 | SpringConfig gravity, 132 | int diameter, 133 | Drawable backgroundDrawable) { 134 | 135 | final Spring xSpring = springSystem.createSpring().setSpringConfig(coasting); 136 | final Spring ySpring = springSystem.createSpring().setSpringConfig(gravity); 137 | 138 | // create view 139 | View view = new View(context); 140 | 141 | RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(diameter, diameter); 142 | params.addRule(RelativeLayout.CENTER_IN_PARENT); 143 | view.setLayoutParams(params); 144 | view.setBackgroundDrawable(backgroundDrawable); 145 | 146 | rootView.addView(view); 147 | 148 | // generate random direction and magnitude 149 | double magnitude = Math.random() * 1000 + 3000; 150 | double angle = Math.random() * Math.PI / 2 + Math.PI / 4; 151 | 152 | xSpring.setVelocity(magnitude * Math.cos(angle)); 153 | ySpring.setVelocity(-magnitude * Math.sin(angle)); 154 | 155 | int maxX = rootView.getMeasuredWidth() / 2 + diameter; 156 | xSpring.addListener(new Destroyer(rootView, view, -maxX, maxX)); 157 | 158 | int maxY = rootView.getMeasuredHeight() / 2 + diameter; 159 | ySpring.addListener(new Destroyer(rootView, view, -maxY, maxY)); 160 | 161 | xSpring.addListener(new Performer(view, View.TRANSLATION_X)); 162 | ySpring.addListener(new Performer(view, View.TRANSLATION_Y)); 163 | 164 | // set a different end value to cause the animation to play 165 | xSpring.setEndValue(2); 166 | ySpring.setEndValue(9001); 167 | } 168 | 169 | @Override 170 | public void onCreate(Bundle savedInstanceState) { 171 | super.onCreate(savedInstanceState); 172 | 173 | mHandler = new Handler(); 174 | mSpringSystem = SpringSystem.create(); 175 | 176 | mCoasting = SpringConfig.fromOrigamiTensionAndFriction(0, 0); 177 | mCoasting.tension = 0; 178 | 179 | // this is very much a hack, since the end value is set to 9001 to simulate constant 180 | // acceleration. 181 | mGravity = SpringConfig.fromOrigamiTensionAndFriction(0, 0); 182 | mGravity.tension = 1; 183 | } 184 | 185 | @Override 186 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 187 | Bundle savedInstanceState) { 188 | mRootView = (RelativeLayout) inflater.inflate(R.layout.fragment_bloom, container, false); 189 | 190 | mCircles = getResources().obtainTypedArray(R.array.circles); 191 | mRunnable = new CircleSpawn(); 192 | 193 | mRootView.setOnTouchListener(new View.OnTouchListener() { 194 | @Override 195 | @SuppressLint("ClickableViewAccessibility") 196 | public boolean onTouch(View v, MotionEvent event) { 197 | switch (event.getAction()) { 198 | case MotionEvent.ACTION_DOWN: 199 | 200 | // create circles as long as the user is holding down 201 | mTouching = true; 202 | mHandler.post(mRunnable); 203 | 204 | break; 205 | case MotionEvent.ACTION_UP: 206 | mTouching = false; 207 | 208 | break; 209 | } 210 | 211 | return true; 212 | } 213 | }); 214 | 215 | return mRootView; 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /backboard-example/src/main/java/com/tumblr/backboard/example/FlowerFragment.java: -------------------------------------------------------------------------------- 1 | package com.tumblr.backboard.example; 2 | 3 | import android.app.Fragment; 4 | import android.content.res.TypedArray; 5 | import android.os.Bundle; 6 | import android.util.TypedValue; 7 | import android.view.LayoutInflater; 8 | import android.view.MotionEvent; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | import android.widget.RelativeLayout; 12 | 13 | import com.facebook.rebound.Spring; 14 | import com.facebook.rebound.SpringSystem; 15 | import com.tumblr.backboard.Actor; 16 | import com.tumblr.backboard.MotionProperty; 17 | import com.tumblr.backboard.imitator.Imitator; 18 | import com.tumblr.backboard.imitator.MotionImitator; 19 | import com.tumblr.backboard.imitator.ToggleImitator; 20 | import com.tumblr.backboard.performer.MapPerformer; 21 | 22 | /** 23 | * A ring of views that bloom and then contract, with a selector that follows the finger. 24 | *

25 | * Created by ericleong on 5/7/14. 26 | */ 27 | public class FlowerFragment extends Fragment { 28 | 29 | private static final int DIAMETER = 50; 30 | private static final int RING_DIAMETER = 7 * DIAMETER; 31 | 32 | private RelativeLayout mRootView; 33 | private View mCircle; 34 | private View[] mCircles; 35 | 36 | private static final int OPEN = 1; 37 | private static final int CLOSED = 0; 38 | 39 | private static double distSq(double x1, double y1, double x2, double y2) { 40 | return Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2); 41 | } 42 | 43 | private static View nearest(float x, float y, View[] views) { 44 | double minDistSq = Double.MAX_VALUE; 45 | View minView = null; 46 | 47 | for (View view : views) { 48 | double distSq = distSq(x, y, view.getX() + view.getMeasuredWidth() / 2, 49 | view.getY() + view.getMeasuredHeight() / 2); 50 | 51 | if (distSq < Math.pow(1.5f * view.getMeasuredWidth(), 2) && distSq < minDistSq) { 52 | minDistSq = distSq; 53 | minView = view; 54 | } 55 | } 56 | 57 | return minView; 58 | } 59 | 60 | /** 61 | * Snaps to the nearest circle. 62 | */ 63 | private class SnapImitator extends MotionImitator { 64 | 65 | public SnapImitator(MotionProperty property) { 66 | super(property, 0, Imitator.TRACK_ABSOLUTE, Imitator.FOLLOW_SPRING); 67 | } 68 | 69 | @Override 70 | public void mime(float offset, float value, float delta, float dt, MotionEvent event) { 71 | // find the nearest view 72 | final View nearest = nearest( 73 | event.getX() + mCircle.getX(), 74 | event.getY() + mCircle.getY(), mCircles); 75 | 76 | if (nearest != null) { 77 | // snap to it - remember to compensate for translation 78 | switch (mProperty) { 79 | case X: 80 | getSpring().setEndValue(nearest.getX() + nearest.getWidth() / 2 81 | - mCircle.getLeft() - mCircle.getWidth() / 2); 82 | break; 83 | case Y: 84 | getSpring().setEndValue(nearest.getY() + nearest.getHeight() / 2 85 | - mCircle.getTop() - mCircle.getHeight() / 2); 86 | break; 87 | } 88 | } else { 89 | // follow finger 90 | super.mime(offset, value, delta, dt, event); 91 | } 92 | } 93 | } 94 | 95 | @Override 96 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 97 | Bundle savedInstanceState) { 98 | 99 | mRootView = (RelativeLayout) inflater.inflate(R.layout.fragment_flower, container, false); 100 | 101 | mCircles = new View[6]; 102 | mCircle = mRootView.findViewById(R.id.circle); 103 | 104 | final float diameter = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DIAMETER, 105 | getResources().getDisplayMetrics()); 106 | 107 | final TypedArray circles = getResources().obtainTypedArray(R.array.circles); 108 | 109 | // layout params 110 | final RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams((int) diameter, 111 | (int) diameter); 112 | params.addRule(RelativeLayout.CENTER_IN_PARENT); 113 | 114 | // create the circle views 115 | int colorIndex = 0; 116 | for (int i = 0; i < mCircles.length; i++) { 117 | mCircles[i] = new View(getActivity()); 118 | 119 | mCircles[i].setLayoutParams(params); 120 | 121 | mCircles[i].setBackgroundDrawable(getResources().getDrawable( 122 | circles.getResourceId(colorIndex, -1))); 123 | 124 | colorIndex++; 125 | if (colorIndex >= circles.length()) { 126 | colorIndex = 0; 127 | } 128 | 129 | mRootView.addView(mCircles[i], 0); 130 | } 131 | 132 | circles.recycle(); 133 | 134 | /* Animations! */ 135 | 136 | final SpringSystem springSystem = SpringSystem.create(); 137 | 138 | // create spring 139 | final Spring spring = springSystem.createSpring(); 140 | 141 | // add listeners along arc 142 | final double arc = 2 * Math.PI / mCircles.length; 143 | 144 | for (int i = 0; i < mCircles.length; i++) { 145 | View view = mCircles[i]; 146 | 147 | // map spring to a line segment from the center to the edge of the ring 148 | spring.addListener(new MapPerformer(view, View.TRANSLATION_X, 0, 1, 149 | 0, (float) (RING_DIAMETER * Math.cos(i * arc)))); 150 | 151 | spring.addListener(new MapPerformer(view, View.TRANSLATION_Y, 0, 1, 152 | 0, (float) (RING_DIAMETER * Math.sin(i * arc)))); 153 | 154 | spring.setEndValue(CLOSED); 155 | } 156 | 157 | final ToggleImitator imitator = new ToggleImitator(spring, CLOSED, OPEN); 158 | 159 | // move circle using finger, snap when near another circle, and bloom when touched 160 | new Actor.Builder(SpringSystem.create(), mCircle) 161 | .addMotion(new SnapImitator(MotionProperty.X), View.TRANSLATION_X) 162 | .addMotion(new SnapImitator(MotionProperty.Y), View.TRANSLATION_Y) 163 | .onTouchListener(imitator) 164 | .build(); 165 | 166 | return mRootView; 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /backboard-example/src/main/java/com/tumblr/backboard/example/FollowFragment.java: -------------------------------------------------------------------------------- 1 | package com.tumblr.backboard.example; 2 | 3 | import android.app.Fragment; 4 | import android.content.res.TypedArray; 5 | import android.os.Bundle; 6 | import android.util.TypedValue; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.FrameLayout; 11 | import com.facebook.rebound.Spring; 12 | import com.facebook.rebound.SpringSystem; 13 | import com.tumblr.backboard.Actor; 14 | import com.tumblr.backboard.MotionProperty; 15 | import com.tumblr.backboard.imitator.SpringImitator; 16 | import com.tumblr.backboard.performer.Performer; 17 | 18 | /** 19 | * Demonstrates a draggable view that bounces back when released. 20 | *

21 | * Created by ericleong on 5/7/14. 22 | */ 23 | public class FollowFragment extends Fragment { 24 | 25 | private static final String TAG = FollowFragment.class.getSimpleName(); 26 | 27 | private static final int DIAMETER = 80; 28 | 29 | private ViewGroup mRootView; 30 | private View mCircle; 31 | private View[] mFollowers; 32 | 33 | @Override 34 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 35 | Bundle savedInstanceState) { 36 | mRootView = (ViewGroup) inflater.inflate(R.layout.fragment_follow, container, false); 37 | 38 | mCircle = mRootView.findViewById(R.id.circle); 39 | 40 | FrameLayout.LayoutParams leaderParams = (FrameLayout.LayoutParams) mCircle 41 | .getLayoutParams(); 42 | 43 | mFollowers = new View[4]; 44 | 45 | float diameter = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DIAMETER, 46 | getResources().getDisplayMetrics()); 47 | 48 | TypedArray circles = getResources().obtainTypedArray(R.array.circles); 49 | 50 | // create the circle views 51 | int colorIndex = 1; 52 | for (int i = 0; i < mFollowers.length; i++) { 53 | mFollowers[i] = new View(getActivity()); 54 | 55 | FrameLayout.LayoutParams params = 56 | new FrameLayout.LayoutParams((int) diameter, (int) diameter); 57 | params.gravity = leaderParams.gravity; 58 | mFollowers[i].setLayoutParams(params); 59 | 60 | mFollowers[i].setBackgroundDrawable(getResources().getDrawable( 61 | circles.getResourceId(colorIndex, -1))); 62 | 63 | colorIndex++; 64 | if (colorIndex >= circles.length()) { 65 | colorIndex = 0; 66 | } 67 | 68 | mRootView.addView(mFollowers[i]); 69 | } 70 | 71 | circles.recycle(); 72 | 73 | /* Animation code */ 74 | 75 | final SpringSystem springSystem = SpringSystem.create(); 76 | 77 | // create the springs that control movement 78 | final Spring springX = springSystem.createSpring(); 79 | final Spring springY = springSystem.createSpring(); 80 | 81 | // bind circle movement to events 82 | new Actor.Builder(springSystem, mCircle).addMotion(springX, MotionProperty.X) 83 | .addMotion(springY, MotionProperty.Y).build(); 84 | 85 | // add springs to connect between the views 86 | final Spring[] followsX = new Spring[mFollowers.length]; 87 | final Spring[] followsY = new Spring[mFollowers.length]; 88 | 89 | for (int i = 0; i < mFollowers.length; i++) { 90 | 91 | // create spring to bind views 92 | followsX[i] = springSystem.createSpring(); 93 | followsY[i] = springSystem.createSpring(); 94 | followsX[i].addListener(new Performer(mFollowers[i], View.TRANSLATION_X)); 95 | followsY[i].addListener(new Performer(mFollowers[i], View.TRANSLATION_Y)); 96 | 97 | // imitates another character 98 | final SpringImitator followX = new SpringImitator(followsX[i]); 99 | final SpringImitator followY = new SpringImitator(followsY[i]); 100 | 101 | // imitate the previous character 102 | if (i == 0) { 103 | springX.addListener(followX); 104 | springY.addListener(followY); 105 | } else { 106 | followsX[i - 1].addListener(followX); 107 | followsY[i - 1].addListener(followY); 108 | } 109 | } 110 | 111 | return mRootView; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /backboard-example/src/main/java/com/tumblr/backboard/example/MoveFragment.java: -------------------------------------------------------------------------------- 1 | package com.tumblr.backboard.example; 2 | 3 | import android.app.Fragment; 4 | import android.os.Bundle; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import com.facebook.rebound.SpringSystem; 9 | import com.tumblr.backboard.Actor; 10 | import com.tumblr.backboard.MotionProperty; 11 | import com.tumblr.backboard.imitator.Imitator; 12 | 13 | /** 14 | * Demonstrates a draggable view that bounces back when released. 15 | *

16 | * Created by ericleong on 5/7/14. 17 | */ 18 | public class MoveFragment extends Fragment { 19 | 20 | @Override 21 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 22 | Bundle savedInstanceState) { 23 | 24 | View rootView = inflater.inflate(R.layout.fragment_move, container, false); 25 | 26 | new Actor.Builder(SpringSystem.create(), rootView.findViewById(R.id.circle)) 27 | .addTranslateMotion(Imitator.TRACK_DELTA, Imitator.FOLLOW_EXACT, MotionProperty.X) 28 | .addTranslateMotion(Imitator.TRACK_DELTA, Imitator.FOLLOW_EXACT, MotionProperty.Y) 29 | .build(); 30 | 31 | return rootView; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /backboard-example/src/main/java/com/tumblr/backboard/example/PressFragment.java: -------------------------------------------------------------------------------- 1 | package com.tumblr.backboard.example; 2 | 3 | import android.app.Fragment; 4 | import android.os.Bundle; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | 9 | import com.facebook.rebound.SpringSystem; 10 | import com.tumblr.backboard.Actor; 11 | import com.tumblr.backboard.imitator.ToggleImitator; 12 | 13 | /** 14 | * Demonstrates a view that shrinks when touched and bounces back when released. 15 | *

16 | * Created by ericleong on 11/28/15. 17 | */ 18 | public class PressFragment extends Fragment { 19 | 20 | @Override 21 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 22 | Bundle savedInstanceState) { 23 | 24 | final View rootView = inflater.inflate(R.layout.fragment_press, container, false); 25 | 26 | new Actor.Builder(SpringSystem.create(), rootView.findViewById(R.id.circle)) 27 | .addMotion(new ToggleImitator(null, 1.0, 0.5), View.SCALE_X, View.SCALE_Y) 28 | .build(); 29 | 30 | return rootView; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /backboard-example/src/main/java/com/tumblr/backboard/example/ScaleFragment.java: -------------------------------------------------------------------------------- 1 | package com.tumblr.backboard.example; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.app.Fragment; 5 | import android.os.Bundle; 6 | import android.view.LayoutInflater; 7 | import android.view.MotionEvent; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import com.facebook.rebound.Spring; 11 | import com.facebook.rebound.SpringSystem; 12 | import com.tumblr.backboard.performer.Performer; 13 | 14 | /** 15 | * Scale a view with a finger. 16 | *

17 | * Created by ericleong on 5/7/14. 18 | */ 19 | public class ScaleFragment extends Fragment { 20 | 21 | @Override 22 | public void onCreate(Bundle savedInstanceState) { 23 | super.onCreate(savedInstanceState); 24 | } 25 | 26 | @Override 27 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 28 | Bundle savedInstanceState) { 29 | final View rootView = inflater.inflate(R.layout.fragment_scale, container, false); 30 | 31 | final View rect = rootView.findViewById(R.id.rect); 32 | 33 | final SpringSystem springSystem = SpringSystem.create(); 34 | 35 | final Spring spring = springSystem.createSpring(); 36 | 37 | spring.addListener(new Performer(rect, View.SCALE_X)); 38 | spring.addListener(new Performer(rect, View.SCALE_Y)); 39 | 40 | rootView.setOnTouchListener(new View.OnTouchListener() { 41 | @Override 42 | @SuppressLint("ClickableViewAccessibility") 43 | public boolean onTouch(View v, MotionEvent event) { 44 | switch (event.getAction()) { 45 | case MotionEvent.ACTION_DOWN: 46 | spring.setVelocity(0); 47 | 48 | case MotionEvent.ACTION_MOVE: 49 | 50 | // can't use Imitation here because there is no nice mapping from 51 | // an event property to a Spring 52 | float scaleX, scaleY; 53 | 54 | float delta = event.getX() - (rect.getX() + rect.getMeasuredWidth() / 2); 55 | scaleX = Math.abs(delta) / (rect.getMeasuredWidth() / 2); 56 | 57 | delta = event.getY() - (rect.getY() + rect.getMeasuredHeight() / 2); 58 | scaleY = Math.abs(delta) / (rect.getMeasuredHeight() / 2); 59 | 60 | float scale = Math.max(scaleX, scaleY); 61 | 62 | spring.setEndValue(scale); 63 | 64 | break; 65 | case MotionEvent.ACTION_UP: 66 | spring.setEndValue(1f); 67 | 68 | break; 69 | } 70 | 71 | return true; 72 | } 73 | }); 74 | 75 | return rootView; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /backboard-example/src/main/java/com/tumblr/backboard/example/SnapFragment.java: -------------------------------------------------------------------------------- 1 | package com.tumblr.backboard.example; 2 | 3 | import android.app.Fragment; 4 | import android.os.Bundle; 5 | import android.view.LayoutInflater; 6 | import android.view.MotionEvent; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import com.facebook.rebound.SpringSystem; 10 | import com.tumblr.backboard.Actor; 11 | import com.tumblr.backboard.MotionProperty; 12 | import com.tumblr.backboard.imitator.MotionImitator; 13 | 14 | /** 15 | * Snap a view to either the lower left or lower right corner. 16 | *

17 | * Created by ericleong on 5/7/14. 18 | */ 19 | public class SnapFragment extends Fragment { 20 | 21 | @Override 22 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 23 | Bundle savedInstanceState) { 24 | 25 | final View rootView = inflater.inflate(R.layout.fragment_snap, container, false); 26 | 27 | final View circle = rootView.findViewById(R.id.circle); 28 | 29 | new Actor.Builder(SpringSystem.create(), circle) 30 | .addTranslateMotion(MotionProperty.Y) 31 | .addMotion( 32 | new MotionImitator(MotionProperty.X) { 33 | @Override 34 | public void release(MotionEvent event) { 35 | 36 | // snap to left or right depending on current location 37 | if (mSpring.getCurrentValue() > 38 | rootView.getMeasuredWidth() / 2 - 39 | circle.getMeasuredWidth() / 2) { 40 | 41 | mSpring.setEndValue(rootView.getMeasuredWidth() - 42 | circle.getMeasuredWidth()); 43 | } else { 44 | 45 | mSpring.setEndValue(0); 46 | } 47 | } 48 | }, 49 | View.TRANSLATION_X 50 | ) 51 | .build(); 52 | 53 | return rootView; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /backboard-example/src/main/java/com/tumblr/backboard/example/ZoomFragment.java: -------------------------------------------------------------------------------- 1 | package com.tumblr.backboard.example; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.app.Fragment; 5 | import android.os.Bundle; 6 | import android.view.*; 7 | import com.facebook.rebound.Spring; 8 | import com.facebook.rebound.SpringSystem; 9 | import com.tumblr.backboard.performer.Performer; 10 | 11 | /** 12 | * Scale a view with pinch zoom. 13 | *

14 | * Created by ericleong on 5/7/14. 15 | */ 16 | public class ZoomFragment extends Fragment { 17 | 18 | @Override 19 | public void onCreate(Bundle savedInstanceState) { 20 | super.onCreate(savedInstanceState); 21 | } 22 | 23 | @Override 24 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 25 | Bundle savedInstanceState) { 26 | final View rootView = inflater.inflate(R.layout.fragment_scale, container, false); 27 | 28 | final View rect = rootView.findViewById(R.id.rect); 29 | 30 | final SpringSystem springSystem = SpringSystem.create(); 31 | final Spring spring = springSystem.createSpring(); 32 | 33 | spring.addListener(new Performer(rect, View.SCALE_X)); 34 | spring.addListener(new Performer(rect, View.SCALE_Y)); 35 | 36 | spring.setCurrentValue(1.0f, true); 37 | 38 | final ScaleGestureDetector scaleGestureDetector = new ScaleGestureDetector(getActivity(), 39 | new ScaleGestureDetector.SimpleOnScaleGestureListener() { 40 | @Override 41 | public boolean onScale(ScaleGestureDetector detector) { 42 | spring.setCurrentValue( 43 | spring.getCurrentValue() * detector.getScaleFactor(), true); 44 | 45 | return true; 46 | } 47 | 48 | @Override 49 | public void onScaleEnd(ScaleGestureDetector detector) { 50 | spring.setEndValue(1.0f); 51 | } 52 | }); 53 | 54 | rootView.setOnTouchListener(new View.OnTouchListener() { 55 | @Override 56 | @SuppressLint("ClickableViewAccessibility") 57 | public boolean onTouch(View v, MotionEvent event) { 58 | return scaleGestureDetector.onTouchEvent(event); 59 | } 60 | }); 61 | 62 | return rootView; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /backboard-example/src/main/res/drawable/circle_blue.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /backboard-example/src/main/res/drawable/circle_gray.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /backboard-example/src/main/res/drawable/circle_green.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /backboard-example/src/main/res/drawable/circle_orange.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /backboard-example/src/main/res/drawable/circle_purple.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /backboard-example/src/main/res/drawable/circle_red.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /backboard-example/src/main/res/drawable/ring_navy.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /backboard-example/src/main/res/layout/activity_backboard.xml: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /backboard-example/src/main/res/layout/fragment_appear.xml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 16 | 17 | 21 | 22 | 27 | 28 | 32 | 33 | 38 | 39 | 43 | 44 | 49 | 50 | 54 | 55 | 56 | 57 | 64 | 65 | 69 | 70 | 75 | 76 | 80 | 81 | 86 | 87 | 91 | 92 | 97 | 98 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /backboard-example/src/main/res/layout/fragment_bloom.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /backboard-example/src/main/res/layout/fragment_constrain.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 17 | 18 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /backboard-example/src/main/res/layout/fragment_flower.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /backboard-example/src/main/res/layout/fragment_follow.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /backboard-example/src/main/res/layout/fragment_move.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /backboard-example/src/main/res/layout/fragment_press.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /backboard-example/src/main/res/layout/fragment_scale.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /backboard-example/src/main/res/layout/fragment_snap.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /backboard-example/src/main/res/menu/backboard.xml: -------------------------------------------------------------------------------- 1 |

4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | 29 | 30 | 34 | 35 | 39 | 40 | 44 | 45 | 49 | 50 | 54 | 55 | 59 | 60 | -------------------------------------------------------------------------------- /backboard-example/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tumblr/Backboard/8fec03fb8f52d69b98e60f0058b0edbccf6a5c4e/backboard-example/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /backboard-example/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tumblr/Backboard/8fec03fb8f52d69b98e60f0058b0edbccf6a5c4e/backboard-example/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /backboard-example/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tumblr/Backboard/8fec03fb8f52d69b98e60f0058b0edbccf6a5c4e/backboard-example/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /backboard-example/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tumblr/Backboard/8fec03fb8f52d69b98e60f0058b0edbccf6a5c4e/backboard-example/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /backboard-example/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tumblr/Backboard/8fec03fb8f52d69b98e60f0058b0edbccf6a5c4e/backboard-example/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /backboard-example/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #99cc00 5 | #ff4444 6 | #aa66cc 7 | #ffbb33 8 | #00ddff 9 | #888888 10 | #32506d 11 | 12 | #ffffff 13 | 14 | 15 | @drawable/circle_green 16 | @drawable/circle_red 17 | @drawable/circle_purple 18 | @drawable/circle_orange 19 | @drawable/circle_blue 20 | @drawable/circle_gray 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /backboard-example/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /backboard-example/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Backboard 5 | Move 6 | Snap 7 | Scale 8 | Bloom 9 | Flower 10 | Appear 11 | Explosion 12 | Follow 13 | Zoom 14 | Constrain 15 | Press 16 | 17 | 18 | -------------------------------------------------------------------------------- /backboard-example/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /backboard/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /backboard/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "com.android.library" 3 | id "checkstyle" 4 | id "com.automattic.android.publish-to-s3" 5 | } 6 | 7 | android { 8 | compileSdkVersion rootProject.ext.compileSdkVersion 9 | 10 | defaultConfig { 11 | minSdkVersion rootProject.ext.minSdkVersion 12 | targetSdkVersion rootProject.ext.targetSdkVersion 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' 18 | } 19 | } 20 | 21 | libraryVariants.all { variant -> 22 | task("javadoc${variant.name}", type: Javadoc) { 23 | source android.sourceSets.main.java.srcDirs 24 | classpath = variant.javaCompileProvider.get().classpath + files(project.android.getBootClasspath()) + files("$buildDir/intermediates/classes/${variant.name}") 25 | options.links("http://docs.oracle.com/javase/7/docs/api/") 26 | exclude '**/BuildConfig.java' 27 | exclude '**/R.java' 28 | } 29 | } 30 | } 31 | 32 | checkstyle { 33 | toolVersion = "6.7" 34 | } 35 | 36 | task checkstyle(type: Checkstyle) { 37 | configFile = rootProject.file('backboard/checkstyle.xml') 38 | 39 | source 'src/main/java' 40 | 41 | include '**/*.java' 42 | 43 | exclude '**/gen/**' 44 | 45 | def configProps = ['proj.module.dir': projectDir.absolutePath] 46 | configProperties configProps 47 | 48 | // empty classpath 49 | classpath = files() 50 | } 51 | 52 | check.dependsOn 'checkstyle' 53 | 54 | // disable insane, build-breaking doclint tool in Java 8 55 | if (JavaVersion.current().isJava8Compatible()) { 56 | tasks.withType(Javadoc) { 57 | //noinspection SpellCheckingInspection 58 | options.addStringOption('Xdoclint:none', '-quiet') 59 | } 60 | } 61 | 62 | dependencies { 63 | implementation rootProject.ext.facebookRebound 64 | implementation rootProject.ext.supportAnnotation 65 | 66 | implementation fileTree(dir: 'libs', include: ['*.jar']) 67 | } 68 | 69 | project.afterEvaluate { 70 | publishing { 71 | publications { 72 | BackboardPublication(MavenPublication) { 73 | from components.release 74 | 75 | groupId "com.tumblr" 76 | artifactId "backboard" 77 | // version is set by 'publish-to-s3' plugin 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /backboard/checkstyle-suppressions.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /backboard/checkstyle.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 23 | 25 | 26 | 27 | 28 | 30 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 40 | 41 | 42 | 44 | 46 | 47 | 48 | 50 | 51 | 52 | 54 | 55 | 56 | 57 | 58 | 60 | 62 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 147 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | -------------------------------------------------------------------------------- /backboard/proguard-rules.txt: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Applications/Android Studio.app/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the ProGuard 5 | # include property in project.properties. 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 | #} -------------------------------------------------------------------------------- /backboard/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /backboard/src/main/java/com/tumblr/backboard/Actor.java: -------------------------------------------------------------------------------- 1 | package com.tumblr.backboard; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.util.Property; 5 | import android.view.MotionEvent; 6 | import android.view.View; 7 | import android.view.ViewConfiguration; 8 | 9 | import androidx.annotation.NonNull; 10 | import androidx.annotation.Nullable; 11 | 12 | import com.facebook.rebound.Spring; 13 | import com.facebook.rebound.SpringListener; 14 | import com.facebook.rebound.SpringSystem; 15 | import com.tumblr.backboard.imitator.EventImitator; 16 | import com.tumblr.backboard.imitator.Imitator; 17 | import com.tumblr.backboard.imitator.MotionImitator; 18 | import com.tumblr.backboard.performer.Performer; 19 | 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | 23 | /** 24 | * Coordinates the relationship between {@link com.tumblr.backboard.imitator.MotionImitator}s, 25 | * {@link com.facebook.rebound.Spring}s, and {@link com.tumblr.backboard.performer.Performer}s on a 26 | * single {@link android.view.View}. 27 | *

28 | * This primarily exists to manage the {@link android.view.View.OnTouchListener} on the 29 | * {@link android.view.View}. 30 | *

31 | * Created by ericleong on 5/20/14. 32 | */ 33 | public final class Actor { 34 | 35 | /** 36 | * Distance in pixels that can be moved before a touch is no longer considered a "click". 37 | */ 38 | public static final int MAX_CLICK_DISTANCE = 10; 39 | 40 | /** 41 | * Contains the imitators and listeners coupled to a single spring. 42 | */ 43 | public static final class Motion { 44 | @NonNull 45 | private final Spring spring; 46 | @NonNull 47 | private final EventImitator[] imitators; 48 | @NonNull 49 | private final Performer[] performers; 50 | @Nullable 51 | private final SpringListener[] springListeners; 52 | 53 | private Motion(@NonNull final Spring spring, @NonNull final EventImitator imitator, @NonNull final Performer[] performers, 54 | @Nullable final SpringListener[] springListeners) { 55 | this(spring, new EventImitator[] { imitator }, performers, springListeners); 56 | } 57 | 58 | private Motion(@NonNull final Spring spring, @NonNull final Performer[] performers, 59 | @Nullable final SpringListener[] springListeners) { 60 | this.imitators = new MotionImitator[0]; 61 | this.performers = performers; 62 | this.spring = spring; 63 | this.springListeners = springListeners; 64 | } 65 | 66 | private Motion(@NonNull final Spring spring, @NonNull final EventImitator[] imitators, @NonNull final Performer[] performers, 67 | @Nullable final SpringListener[] springListeners) { 68 | this.imitators = imitators; 69 | this.performers = performers; 70 | this.spring = spring; 71 | this.springListeners = springListeners; 72 | } 73 | 74 | @NonNull 75 | public Spring getSpring() { 76 | return spring; 77 | } 78 | 79 | @NonNull 80 | public EventImitator[] getImitators() { 81 | return imitators; 82 | } 83 | } 84 | 85 | @NonNull 86 | private final View mView; 87 | @NonNull 88 | private final List mMotions; 89 | @NonNull 90 | private final MotionListener mMotionListener; 91 | @Nullable 92 | private final View.OnTouchListener mOnTouchListener; 93 | /** 94 | * Allows the user to disable the motion listener. 95 | */ 96 | private boolean mMotionListenerEnabled; 97 | /** 98 | * Prevent parent from intercepting touch events (useful when in lists). 99 | */ 100 | private boolean mRequestDisallowTouchEvent; 101 | 102 | private Actor(@NonNull final View view, @NonNull final List motions, 103 | @Nullable final View.OnTouchListener onTouchListener, 104 | final boolean motionListenerEnabled, final boolean attachTouchListener, 105 | final boolean requestDisallowTouchEvent) { 106 | mView = view; 107 | mMotions = motions; 108 | mOnTouchListener = onTouchListener; 109 | 110 | mMotionListener = new MotionListener(); 111 | mMotionListenerEnabled = motionListenerEnabled; 112 | 113 | mRequestDisallowTouchEvent = requestDisallowTouchEvent; 114 | 115 | if (attachTouchListener) { 116 | view.setOnTouchListener(mMotionListener); 117 | } 118 | } 119 | 120 | @Nullable 121 | public View.OnTouchListener getOnTouchListener() { 122 | return mOnTouchListener; 123 | } 124 | 125 | @NonNull 126 | public View.OnTouchListener getMotionListener() { 127 | return mMotionListener; 128 | } 129 | 130 | @NonNull 131 | public View getView() { 132 | return mView; 133 | } 134 | 135 | @NonNull 136 | public List getMotions() { 137 | return mMotions; 138 | } 139 | 140 | public boolean isTouchEnabled() { 141 | return mMotionListenerEnabled; 142 | } 143 | 144 | public void setTouchEnabled(final boolean enabled) { 145 | this.mMotionListenerEnabled = enabled; 146 | } 147 | 148 | /** 149 | * Removes all spring listeners controlled by this {@link Actor}. 150 | */ 151 | public void removeAllListeners() { 152 | for (Motion motion : mMotions) { 153 | for (Performer performer : motion.performers) { 154 | motion.spring.removeListener(performer); 155 | } 156 | 157 | if (motion.springListeners != null) { 158 | for (SpringListener listener : motion.springListeners) { 159 | motion.spring.removeListener(listener); 160 | } 161 | } 162 | } 163 | } 164 | 165 | /** 166 | * Adds all spring listeners back. 167 | */ 168 | public void addAllListeners() { 169 | for (Motion motion : mMotions) { 170 | for (Performer performer : motion.performers) { 171 | motion.spring.addListener(performer); 172 | } 173 | 174 | if (motion.springListeners != null) { 175 | for (SpringListener listener : motion.springListeners) { 176 | motion.spring.addListener(listener); 177 | } 178 | } 179 | } 180 | } 181 | 182 | /** 183 | * Implements the builder pattern for {@link Actor}. 184 | */ 185 | public static class Builder { 186 | 187 | @NonNull 188 | private final View mView; 189 | @NonNull 190 | private final List mMotions = new ArrayList(); 191 | @Nullable 192 | private View.OnTouchListener mOnTouchListener; 193 | @NonNull 194 | private final SpringSystem mSpringSystem; 195 | private boolean mMotionListenerEnabled = true; 196 | private boolean mAttachMotionListener = true; 197 | private boolean mRequestDisallowTouchEvent; 198 | private boolean mAttachSpringListeners = true; 199 | 200 | /** 201 | * Animates the given view with the default {@link com.facebook.rebound.SpringConfig} and 202 | * automatically creates a {@link com.facebook.rebound.SpringSystem}. 203 | * 204 | * @param springSystem 205 | * the spring system to use 206 | * @param view 207 | * the view to animate 208 | */ 209 | public Builder(@NonNull final SpringSystem springSystem, @NonNull final View view) { 210 | mView = view; 211 | mSpringSystem = springSystem; 212 | } 213 | 214 | /** 215 | * @param onTouchListener 216 | * a touch listener to pass touch events to 217 | * @return this builder for chaining 218 | */ 219 | @NonNull 220 | public Builder onTouchListener(final View.OnTouchListener onTouchListener) { 221 | mOnTouchListener = onTouchListener; 222 | return this; 223 | } 224 | 225 | /** 226 | * Uses the default {@link com.facebook.rebound.SpringConfig} to animate the view. 227 | * 228 | * @param properties 229 | * the event fields to imitate and the view properties to animate. 230 | * @return this builder for chaining 231 | */ 232 | @NonNull 233 | public Builder addTranslateMotion(final MotionProperty... properties) { 234 | return addMotion(mSpringSystem.createSpring(), properties); 235 | } 236 | 237 | /** 238 | * Uses the default {@link com.facebook.rebound.SpringConfig} to animate the view. 239 | * 240 | * @param property 241 | * the event field to imitate and the view property to animate. 242 | * @param listener 243 | * a listener to call 244 | * @return this builder for chaining 245 | */ 246 | @NonNull 247 | public Builder addTranslateMotion(final MotionProperty property, final SpringListener listener) { 248 | return addMotion(mSpringSystem.createSpring(), Imitator.TRACK_ABSOLUTE, 249 | Imitator.FOLLOW_EXACT, new MotionProperty[] { property }, 250 | new SpringListener[] { listener }); 251 | } 252 | 253 | /** 254 | * Uses the default {@link com.facebook.rebound.SpringConfig} to animate the view. 255 | * 256 | * @param trackStrategy 257 | * the tracking behavior 258 | * @param followStrategy 259 | * the follow behavior 260 | * @param properties 261 | * the event fields to imitate and the view properties to animate. 262 | * @return this builder for chaining 263 | */ 264 | @NonNull 265 | public Builder addTranslateMotion(final int trackStrategy, final int followStrategy, 266 | final MotionProperty... properties) { 267 | return addMotion(mSpringSystem.createSpring(), trackStrategy, followStrategy, 268 | properties); 269 | } 270 | 271 | /** 272 | * Uses the default {@link com.facebook.rebound.SpringConfig} to animate the view. 273 | * 274 | * @param trackStrategy 275 | * the tracking behavior 276 | * @param followStrategy 277 | * the follow behavior 278 | * @param restValue 279 | * the rest value of the spring 280 | * @param properties 281 | * the event fields to imitate and the view properties to animate. 282 | * @return this builder for chaining 283 | */ 284 | @NonNull 285 | public Builder addTranslateMotion(final int trackStrategy, final int followStrategy, 286 | final int restValue, 287 | final MotionProperty... properties) { 288 | return addMotion(mSpringSystem.createSpring(), trackStrategy, followStrategy, 289 | restValue, properties); 290 | } 291 | 292 | /** 293 | * @param spring 294 | * the underlying {@link com.facebook.rebound.Spring}. 295 | * @param properties 296 | * the event fields to imitate and the view properties to animate. 297 | * @return this builder for chaining 298 | */ 299 | @NonNull 300 | public Builder addMotion(@NonNull final Spring spring, final MotionProperty... properties) { 301 | return addMotion(spring, Imitator.TRACK_ABSOLUTE, Imitator.FOLLOW_EXACT, properties); 302 | } 303 | 304 | /** 305 | * @param spring 306 | * the underlying {@link com.facebook.rebound.Spring}. 307 | * @param trackStrategy 308 | * the tracking behavior 309 | * @param followStrategy 310 | * the follow behavior 311 | * @param properties 312 | * the event fields to imitate and the view properties to animate. 313 | * @return this builder for chaining 314 | */ 315 | @NonNull 316 | public Builder addMotion(@NonNull final Spring spring, final int trackStrategy, final int followStrategy, 317 | @NonNull final MotionProperty... properties) { 318 | 319 | mMotions.add(createMotionFromProperties(spring, properties, null, trackStrategy, followStrategy, 0)); 320 | 321 | return this; 322 | } 323 | 324 | /** 325 | * @param spring 326 | * the underlying {@link com.facebook.rebound.Spring}. 327 | * @param trackStrategy 328 | * the tracking behavior 329 | * @param followStrategy 330 | * the follow behavior 331 | * @param restValue 332 | * the rest value 333 | * @param properties 334 | * the event fields to imitate and the view properties to animate. 335 | * @return this builder for chaining 336 | */ 337 | @NonNull 338 | public Builder addMotion(@NonNull final Spring spring, final int trackStrategy, final int followStrategy, 339 | final int restValue, @NonNull final MotionProperty... properties) { 340 | 341 | mMotions.add( 342 | createMotionFromProperties(spring, properties, null, trackStrategy, followStrategy, restValue)); 343 | 344 | return this; 345 | } 346 | 347 | /** 348 | * @param spring 349 | * the underlying {@link com.facebook.rebound.Spring}. 350 | * @param trackStrategy 351 | * the tracking behavior 352 | * @param followStrategy 353 | * the follow behavior 354 | * @param restValue 355 | * the rest value 356 | * @param property 357 | * the event fields to imitate and the view property to animate. 358 | * @param springListener 359 | * a spring listener to attach to the spring 360 | * @return this builder for chaining 361 | */ 362 | @NonNull 363 | public Builder addMotion(@NonNull final Spring spring, final int trackStrategy, final int followStrategy, 364 | final int restValue, final MotionProperty property, @Nullable final SpringListener springListener) { 365 | 366 | mMotions.add( 367 | createMotionFromProperties(spring, new MotionProperty[] { property }, 368 | new SpringListener[] { springListener }, trackStrategy, followStrategy, restValue)); 369 | 370 | return this; 371 | } 372 | 373 | /** 374 | * @param spring 375 | * the underlying {@link com.facebook.rebound.Spring}. 376 | * @param trackStrategy 377 | * the tracking behavior 378 | * @param followStrategy 379 | * the follow behavior 380 | * @param properties 381 | * the event fields to imitate and the view properties to animate. 382 | * @param springListeners 383 | * an array of spring listeners to attach to the spring 384 | * @return this builder for chaining 385 | */ 386 | @NonNull 387 | public Builder addMotion(@NonNull final Spring spring, final int trackStrategy, final int followStrategy, 388 | @NonNull final MotionProperty[] properties, final SpringListener[] springListeners) { 389 | 390 | mMotions.add( 391 | createMotionFromProperties(spring, properties, springListeners, trackStrategy, followStrategy, 0)); 392 | 393 | return this; 394 | } 395 | 396 | /** 397 | * Uses a default {@link com.facebook.rebound.SpringConfig}. 398 | * 399 | * @param eventImitator 400 | * maps an event to a {@link com.facebook.rebound.Spring} 401 | * @param viewProperties 402 | * the {@link android.view.View} property to animate 403 | * @return the builder for chaining 404 | */ 405 | @NonNull 406 | public Builder addMotion(@NonNull final EventImitator eventImitator, 407 | @NonNull final Property... viewProperties) { 408 | final Performer[] performers = new Performer[viewProperties.length]; 409 | 410 | for (int i = 0; i < viewProperties.length; i++) { 411 | performers[i] = new Performer(viewProperties[i]); 412 | } 413 | 414 | return addMotion(mSpringSystem.createSpring(), eventImitator, performers); 415 | } 416 | 417 | /** 418 | * Uses a default {@link com.facebook.rebound.SpringConfig}. 419 | * 420 | * @param eventImitator 421 | * maps an event to a {@link com.facebook.rebound.Spring} 422 | * @param performers 423 | * map the {@link com.facebook.rebound.Spring} to a 424 | * {@link android.view.View} 425 | * @return the builder for chaining 426 | */ 427 | @NonNull 428 | public Builder addMotion(@NonNull final EventImitator eventImitator, final Performer... performers) { 429 | return addMotion(mSpringSystem.createSpring(), eventImitator, performers); 430 | } 431 | 432 | /** 433 | * @param spring 434 | * the underlying {@link com.facebook.rebound.Spring}. 435 | * @param eventImitator 436 | * maps an event to a {@link com.facebook.rebound.Spring} 437 | * @param performers 438 | * map the {@link com.facebook.rebound.Spring} to a 439 | * {@link android.view.View} 440 | * @return the builder for chaining 441 | */ 442 | @NonNull 443 | public Builder addMotion(@NonNull final Spring spring, @NonNull final EventImitator eventImitator, 444 | @NonNull final Performer... performers) { 445 | 446 | final Motion motion = new Motion(spring, eventImitator, performers, null); 447 | 448 | // connect actors 449 | motion.imitators[0].setSpring(motion.spring); 450 | 451 | for (Performer performer : motion.performers) { 452 | performer.setTarget(mView); 453 | } 454 | 455 | mMotions.add(motion); 456 | 457 | return this; 458 | } 459 | 460 | /** 461 | * @param spring 462 | * the underlying {@link com.facebook.rebound.Spring}. 463 | * @param eventImitator 464 | * maps an event to a {@link com.facebook.rebound.Spring} 465 | * @param performers 466 | * map the {@link com.facebook.rebound.Spring} to a 467 | * {@link android.view.View} 468 | * @param springListeners 469 | * additional listeners to attach 470 | * @return the builder for chaining 471 | */ 472 | @NonNull 473 | public Builder addMotion(@NonNull final Spring spring, @NonNull final EventImitator eventImitator, 474 | @NonNull final Performer[] performers, final SpringListener[] springListeners) { 475 | 476 | // create struct 477 | final Motion motion = new Motion(spring, eventImitator, performers, springListeners); 478 | 479 | // connect actors 480 | motion.imitators[0].setSpring(motion.spring); 481 | 482 | for (Performer performer : motion.performers) { 483 | performer.setTarget(mView); 484 | } 485 | 486 | mMotions.add(motion); 487 | 488 | return this; 489 | } 490 | 491 | /** 492 | * @param motionImitator 493 | * maps an event to a {@link com.facebook.rebound.Spring} 494 | * @param viewProperty 495 | * the {@link android.view.View} property to animate 496 | * @param springListener 497 | * additional listener to attach 498 | * @return the builder for chaining 499 | */ 500 | @NonNull 501 | public Builder addMotion(@NonNull final MotionImitator motionImitator, 502 | @NonNull final Property viewProperty, 503 | final SpringListener springListener) { 504 | 505 | return addMotion(mSpringSystem.createSpring(), motionImitator, 506 | new Performer[] { new Performer(viewProperty) }, 507 | new SpringListener[] { springListener }); 508 | } 509 | 510 | /** 511 | * @return flag to tell the attached {@link android.view.View.OnTouchListener} to call 512 | * {@link android.view.ViewParent#requestDisallowInterceptTouchEvent(boolean)} with 513 | * true. 514 | */ 515 | @NonNull 516 | public Builder requestDisallowTouchEvent() { 517 | mRequestDisallowTouchEvent = true; 518 | return this; 519 | } 520 | 521 | /** 522 | * A flag to tell this {@link Actor} not to attach the touch listener to the view. 523 | * 524 | * @return the builder for chaining 525 | */ 526 | @NonNull 527 | public Builder dontAttachMotionListener() { 528 | mAttachMotionListener = false; 529 | return this; 530 | } 531 | 532 | /** 533 | * A flag to tell this builder not to attach the spring listeners to the spring. 534 | * They can be added with {@link Actor#addAllListeners()}. 535 | * 536 | * @return the builder for chaining 537 | */ 538 | @NonNull 539 | public Builder dontAttachSpringListeners() { 540 | mAttachSpringListeners = false; 541 | return this; 542 | } 543 | 544 | /** 545 | * Creations a new motion object. 546 | * 547 | * @param spring 548 | * the spring to use 549 | * @param motionProperties 550 | * the properties of the event to track 551 | * @param springListeners 552 | * additional spring listeners to add 553 | * @param trackStrategy 554 | * the tracking strategy 555 | * @param followStrategy 556 | * the follow strategy 557 | * @param restValue 558 | * the spring rest value 559 | * @return a motion object 560 | */ 561 | @Nullable 562 | private Motion createMotionFromProperties(@NonNull final Spring spring, 563 | @NonNull final MotionProperty[] motionProperties, 564 | @Nullable final SpringListener[] springListeners, 565 | final int trackStrategy, final int followStrategy, 566 | final int restValue) { 567 | 568 | final MotionImitator[] motionImitators = new MotionImitator[motionProperties.length]; 569 | final Performer[] performers = new Performer[motionProperties.length]; 570 | 571 | for (int i = 0; i < motionProperties.length; i++) { 572 | 573 | final MotionProperty property = motionProperties[i]; 574 | 575 | motionImitators[i] = new MotionImitator(spring, property, restValue, trackStrategy, followStrategy); 576 | performers[i] = new Performer(mView, property.getViewProperty()); 577 | } 578 | 579 | return new Motion(spring, motionImitators, performers, springListeners); 580 | } 581 | 582 | /** 583 | * @return Builds the {@link Actor}. 584 | */ 585 | @NonNull 586 | public Actor build() { 587 | // make connections 588 | 589 | final Actor actor = new Actor(mView, mMotions, mOnTouchListener, mMotionListenerEnabled, mAttachMotionListener, 590 | mRequestDisallowTouchEvent); 591 | 592 | if (mAttachSpringListeners) { 593 | actor.addAllListeners(); 594 | } 595 | 596 | return actor; 597 | } 598 | } 599 | 600 | private class MotionListener implements View.OnTouchListener { 601 | @Override 602 | @SuppressLint("ClickableViewAccessibility") 603 | public boolean onTouch(@NonNull final View v, @NonNull final MotionEvent event) { 604 | 605 | final boolean retVal; 606 | 607 | if (!mMotionListenerEnabled || mMotions.isEmpty()) { 608 | 609 | if (mOnTouchListener != null) { 610 | retVal = mOnTouchListener.onTouch(v, event); 611 | } else { 612 | retVal = false; 613 | } 614 | 615 | return retVal; 616 | } 617 | 618 | for (Motion motion : mMotions) { 619 | for (EventImitator imitator : motion.imitators) { 620 | imitator.imitate(v, event); 621 | } 622 | } 623 | 624 | if (mOnTouchListener != null) { 625 | retVal = mOnTouchListener.onTouch(v, event); 626 | } else { 627 | retVal = true; 628 | } 629 | 630 | if (mRequestDisallowTouchEvent) { 631 | // prevents parent from scrolling or otherwise stealing touch events 632 | v.getParent().requestDisallowInterceptTouchEvent(true); 633 | } 634 | 635 | if (v.isClickable()) { 636 | if (event.getEventTime() - event.getDownTime() 637 | > ViewConfiguration.getLongPressTimeout()) { 638 | v.setPressed(false); 639 | 640 | return true; 641 | } 642 | 643 | if (event.getHistorySize() > 0) { 644 | final float deltaX = event.getHistoricalX(event.getHistorySize() - 1) - event.getX(); 645 | final float deltaY = event.getHistoricalY(event.getHistorySize() - 1) - event.getY(); 646 | 647 | // if user has moved too far, it is no longer a click 648 | final boolean removeClickState = Math.pow(deltaX, 2) + Math.pow(deltaY, 2) 649 | > Math.pow(MAX_CLICK_DISTANCE, 2); 650 | 651 | v.setPressed(!removeClickState); 652 | 653 | return removeClickState; 654 | } else { 655 | return false; 656 | } 657 | } 658 | 659 | return retVal; 660 | } 661 | } 662 | } 663 | -------------------------------------------------------------------------------- /backboard/src/main/java/com/tumblr/backboard/MotionProperty.java: -------------------------------------------------------------------------------- 1 | package com.tumblr.backboard; 2 | 3 | import android.util.Property; 4 | import android.view.MotionEvent; 5 | import android.view.View; 6 | 7 | import androidx.annotation.NonNull; 8 | import androidx.annotation.Nullable; 9 | 10 | /** 11 | * Used by {@link com.tumblr.backboard.Actor} and 12 | * {@link com.tumblr.backboard.imitator.MotionImitator} to determine which axis to use when mapping 13 | * from a {@link android.view.MotionEvent}. 14 | *

15 | * Created by ericleong on 5/29/14. 16 | */ 17 | public enum MotionProperty { 18 | /** 19 | * X direction, corresponds to {@link MotionEvent#getX()} and maps to {@link View#TRANSLATION_X}. 20 | */ 21 | X(View.TRANSLATION_X), 22 | /** 23 | * Y direction, corresponds to {@link MotionEvent#getY()} and maps to {@link View#TRANSLATION_Y}. 24 | */ 25 | Y(View.TRANSLATION_Y); 26 | 27 | @NonNull 28 | private final Property mViewProperty; 29 | 30 | private MotionProperty(@NonNull final Property viewProperty) { 31 | mViewProperty = viewProperty; 32 | } 33 | 34 | /** 35 | * @return the view property that this {@link com.tumblr.backboard.MotionProperty} 36 | * corresponds to 37 | */ 38 | @NonNull 39 | public Property getViewProperty() { 40 | return mViewProperty; 41 | } 42 | 43 | /** 44 | * @param view 45 | * the view to inspect 46 | * @return the current value that this property represents on the given View. 47 | */ 48 | public float getValue(@Nullable final View view) { 49 | if (view != null) { 50 | return mViewProperty.get(view); 51 | } 52 | 53 | return 0; 54 | } 55 | 56 | /** 57 | * @param event 58 | * the event to inspect 59 | * @return the current value that this property represents on the given 60 | * MotionEvent. 61 | */ 62 | public float getValue(@Nullable final MotionEvent event) { 63 | if (event != null) { 64 | switch (this) { 65 | case X: 66 | return event.getX(0); 67 | case Y: 68 | return event.getY(0); 69 | default: 70 | return event.getX(0); 71 | } 72 | } 73 | 74 | return 0; 75 | } 76 | 77 | /** 78 | * Note that this method does not check the validity of index. 79 | * 80 | * @param event 81 | * the event to inspect 82 | * @param index 83 | * the historical index (in {@link MotionEvent#getHistoricalX(int)} or {@link MotionEvent#getHistoricalY(int)}) 84 | * @return the historical value that this property represents on the given 85 | * MotionEvent. 86 | */ 87 | public float getHistoricalValue(@Nullable final MotionEvent event, final int index) { 88 | if (event != null) { 89 | switch (this) { 90 | case X: 91 | return event.getHistoricalX(index); 92 | case Y: 93 | return event.getHistoricalY(index); 94 | default: 95 | return 0; 96 | } 97 | } 98 | 99 | return 0; 100 | } 101 | 102 | /** 103 | * Note that this method does not check the validity of index. 104 | * 105 | * @param event 106 | * the event to inspect 107 | * @return the oldest historical value that this property represents on the given 108 | * MotionEvent. 109 | */ 110 | public float getOldestValue(final MotionEvent event) { 111 | return getHistoricalValue(event, 0); 112 | } 113 | 114 | /** 115 | * @param view 116 | * the view to inspect 117 | * @return the offset from the center that this property represents on the given 118 | * View, in pixels. 119 | */ 120 | public float getOffset(@Nullable final View view) { 121 | if (view != null) { 122 | switch (this) { 123 | case X: 124 | return -view.getWidth() / 2; 125 | case Y: 126 | return -view.getHeight() / 2; 127 | default: 128 | return -view.getWidth() / 2; 129 | } 130 | } 131 | 132 | return 0; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /backboard/src/main/java/com/tumblr/backboard/imitator/ConstrainedMotionImitator.java: -------------------------------------------------------------------------------- 1 | package com.tumblr.backboard.imitator; 2 | 3 | import android.view.MotionEvent; 4 | 5 | import androidx.annotation.NonNull; 6 | 7 | import com.tumblr.backboard.MotionProperty; 8 | 9 | /** 10 | * Constrains the motion between the minimum and maximum values. 11 | *

12 | * Created by ericleong on 10/9/14. 13 | */ 14 | public class ConstrainedMotionImitator extends MotionImitator { 15 | 16 | /** 17 | * Desired minimum spring value (overshoot may still occur). 18 | */ 19 | protected double mMinValue; 20 | /** 21 | * Desired maximum spring value (overshoot may still occur). 22 | */ 23 | protected double mMaxValue; 24 | 25 | /** 26 | * Constructor. Uses {@link #TRACK_ABSOLUTE} and {@link #FOLLOW_EXACT}. 27 | * 28 | * @param property 29 | * the property to track. 30 | * @param minValue 31 | * the desired minimum spring value. 32 | * @param maxValue 33 | * the desired maximum spring value. 34 | */ 35 | public ConstrainedMotionImitator(@NonNull final MotionProperty property, final double minValue, final double maxValue) { 36 | this(property, TRACK_ABSOLUTE, FOLLOW_EXACT, minValue, maxValue); 37 | } 38 | 39 | /** 40 | * Constructor. 41 | * 42 | * @param property 43 | * the property to track. 44 | * @param trackStrategy 45 | * the tracking strategy. 46 | * @param followStrategy 47 | * the follow strategy. 48 | * @param minValue 49 | * the desired minimum spring value. 50 | * @param maxValue 51 | * the desired maximum spring value. 52 | */ 53 | public ConstrainedMotionImitator(@NonNull final MotionProperty property, final int trackStrategy, 54 | final int followStrategy, final double minValue, final double maxValue) { 55 | super(property, trackStrategy, followStrategy); 56 | 57 | mMinValue = minValue; 58 | mMaxValue = maxValue; 59 | } 60 | 61 | @Override 62 | public void release(final MotionEvent event) { 63 | if (mSpring != null) { 64 | // snap to left or right depending on current location 65 | if (mSpring.getCurrentValue() > mMaxValue) { 66 | mSpring.setEndValue(mMaxValue); 67 | } else if (mSpring.getCurrentValue() < mMinValue) { 68 | mSpring.setEndValue(mMinValue); 69 | } 70 | } 71 | } 72 | 73 | public void setMinValue(final double minValue) { 74 | this.mMinValue = minValue; 75 | } 76 | 77 | public void setMaxValue(final double maxValue) { 78 | this.mMaxValue = maxValue; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /backboard/src/main/java/com/tumblr/backboard/imitator/EventImitator.java: -------------------------------------------------------------------------------- 1 | package com.tumblr.backboard.imitator; 2 | 3 | import android.view.MotionEvent; 4 | import android.view.View; 5 | 6 | import androidx.annotation.NonNull; 7 | import androidx.annotation.Nullable; 8 | 9 | import com.facebook.rebound.Spring; 10 | 11 | /** 12 | * Maps a {@link android.view.MotionEvent} to a {@link com.facebook.rebound.Spring}, 13 | * although it does not pick a property to map. 14 | *

15 | * Created by ericleong on 5/30/14. 16 | */ 17 | public abstract class EventImitator extends Imitator { 18 | 19 | /** 20 | * Constructor. 21 | * 22 | * @param spring 23 | * the spring to use. 24 | * @param restValue 25 | * the rest value for the spring. 26 | * @param trackStrategy 27 | * the tracking strategy. 28 | * @param followStrategy 29 | * the follow strategy. 30 | */ 31 | public EventImitator(@NonNull final Spring spring, final double restValue, final int trackStrategy, final int followStrategy) { 32 | super(spring, restValue, trackStrategy, followStrategy); 33 | } 34 | 35 | /** 36 | * Constructor. Note that the spring must be set with {@link #setSpring(Spring)}. 37 | * 38 | * @param restValue 39 | * the rest value for the spring. 40 | * @param trackStrategy 41 | * the tracking strategy. 42 | * @param followStrategy 43 | * the follow strategy. 44 | */ 45 | protected EventImitator(final double restValue, final int trackStrategy, final int followStrategy) { 46 | super(restValue, trackStrategy, followStrategy); 47 | } 48 | 49 | /** 50 | * Called when the user touches ({@link android.view.MotionEvent#ACTION_DOWN}). 51 | * 52 | * @param event 53 | * the motion event 54 | */ 55 | public void constrain(final MotionEvent event) { 56 | if (mSpring != null && mFollowStrategy == FOLLOW_EXACT) { 57 | mSpring.setVelocity(0); 58 | } 59 | } 60 | 61 | /** 62 | * Called when the user moves their finger ({@link android.view.MotionEvent#ACTION_MOVE}). 63 | * 64 | * @param offset 65 | * the value offset 66 | * @param value 67 | * the current value 68 | * @param delta 69 | * the change in the value 70 | * @param dt 71 | * the change in time 72 | * @param event 73 | * the motion event 74 | */ 75 | public void mime(final float offset, final float value, final float delta, final float dt, final MotionEvent event) { 76 | if (mSpring != null) { 77 | mSpring.setEndValue(mapToSpring(offset + value)); 78 | 79 | if (mFollowStrategy == FOLLOW_EXACT) { 80 | mSpring.setCurrentValue(mSpring.getEndValue()); 81 | 82 | if (dt > 0) { 83 | mSpring.setVelocity(delta / dt); 84 | } 85 | } 86 | } 87 | } 88 | 89 | /** 90 | * Called when the user releases their finger ({@link android.view.MotionEvent#ACTION_UP}). 91 | * 92 | * @param event 93 | * the motion event 94 | */ 95 | public void release(final MotionEvent event) { 96 | if (mSpring != null) { 97 | mSpring.setEndValue(mRestValue); 98 | } 99 | } 100 | 101 | /** 102 | * Called by a {@link com.tumblr.backboard.imitator.MotionImitator} (or another {@link 103 | * android.view.View.OnTouchListener}) when a {@link android.view.MotionEvent} occurs. 104 | * 105 | * @param offset 106 | * the value offset 107 | * @param value 108 | * the current value 109 | * @param delta 110 | * the change in the value 111 | * @param event 112 | * the motion event 113 | */ 114 | protected void imitate(final float offset, final float value, final float delta, @Nullable final MotionEvent event) { 115 | if (event != null) { 116 | switch (event.getAction()) { 117 | case MotionEvent.ACTION_DOWN: 118 | constrain(event); 119 | 120 | case MotionEvent.ACTION_MOVE: 121 | if (event.getHistorySize() > 0) { 122 | mime(offset, value, delta, 123 | event.getEventTime() - event.getHistoricalEventTime(0), event); 124 | } else { 125 | mime(offset, value, delta, 0, event); 126 | } 127 | 128 | break; 129 | default: 130 | case MotionEvent.ACTION_UP: 131 | release(event); 132 | 133 | break; 134 | } 135 | } 136 | } 137 | 138 | /** 139 | * Maps a user's motion to {@link android.view.View} via a {@link com.facebook.rebound.Spring}. 140 | * 141 | * @param view 142 | * the view to perturb. 143 | * @param event 144 | * the motion to imitate. 145 | */ 146 | public abstract void imitate(final View view, @NonNull final MotionEvent event); 147 | } 148 | -------------------------------------------------------------------------------- /backboard/src/main/java/com/tumblr/backboard/imitator/Imitator.java: -------------------------------------------------------------------------------- 1 | package com.tumblr.backboard.imitator; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import com.facebook.rebound.Spring; 6 | 7 | /** 8 | * Perturbs a {@link com.facebook.rebound.Spring} based on some other source. 9 | * {@link #mapToSpring(float)} controls how the source value maps to the spring. Track and follow 10 | * strategies are hints to subclasses as to the desired behavior of the mapping. 11 | *

12 | * Created by ericleong on 5/16/14. 13 | */ 14 | public abstract class Imitator { 15 | 16 | /** 17 | * Directly map the imitatee to the spring. 18 | */ 19 | public static final int TRACK_ABSOLUTE = 1; 20 | /** 21 | * Whether or not the spring will simply connect the object to the property we are imitating. 22 | */ 23 | protected int mTrackStrategy = TRACK_ABSOLUTE; 24 | /** 25 | * Map the change in the imitatee to the spring. 26 | */ 27 | public static final int TRACK_DELTA = 2; 28 | /** 29 | * Map the imitatee to the current value of the spring. 30 | */ 31 | public static final int FOLLOW_EXACT = 1; 32 | /** 33 | * Whether or not the spring will simply connect the object to the property we are imitating. 34 | */ 35 | protected int mFollowStrategy = FOLLOW_EXACT; 36 | /** 37 | * Map the imitatee to the end value of the spring. 38 | */ 39 | public static final int FOLLOW_SPRING = 2; 40 | 41 | /** 42 | * The spring to perturb. 43 | */ 44 | @NonNull 45 | protected Spring mSpring; 46 | /** 47 | * The desired rest value of the spring when not being perturbed. 48 | */ 49 | protected double mRestValue; 50 | 51 | /** 52 | * Intended to be used as part of a builder. The spring must be set with {@link #setSpring(Spring)}. 53 | * 54 | * @param restValue 55 | * the rest value for the spring. 56 | * @param trackStrategy 57 | * the tracking strategy. 58 | * @param followStrategy 59 | * the follow strategy. 60 | */ 61 | protected Imitator(final double restValue, final int trackStrategy, final int followStrategy) { 62 | this(null, restValue, trackStrategy, followStrategy); 63 | } 64 | 65 | /** 66 | * Constructor. Uses {@link #TRACK_ABSOLUTE} and {@link #FOLLOW_EXACT}. 67 | * 68 | * @param spring 69 | * the spring to use. 70 | */ 71 | protected Imitator(@NonNull final Spring spring) { 72 | this(spring, TRACK_ABSOLUTE, FOLLOW_EXACT); 73 | } 74 | 75 | /** 76 | * Constructor. 77 | * 78 | * @param spring 79 | * the spring to use. 80 | * @param trackStrategy 81 | * the tracking strategy. 82 | * @param followStrategy 83 | * the follow strategy. 84 | */ 85 | protected Imitator(@NonNull final Spring spring, final int trackStrategy, final int followStrategy) { 86 | this(spring, spring.getEndValue(), trackStrategy, followStrategy); 87 | } 88 | 89 | /** 90 | * Constructor. 91 | * 92 | * @param spring 93 | * the spring to use. 94 | * @param restValue 95 | * the rest value for the spring. 96 | * @param trackStrategy 97 | * the tracking strategy. 98 | * @param followStrategy 99 | * the follow strategy. 100 | */ 101 | protected Imitator(@NonNull final Spring spring, final double restValue, final int trackStrategy, final int followStrategy) { 102 | mSpring = spring; 103 | mRestValue = restValue; 104 | mTrackStrategy = trackStrategy; 105 | mFollowStrategy = followStrategy; 106 | } 107 | 108 | /** 109 | * @param motionValue 110 | * Maps the value we are tracking to the value of the spring. 111 | * @return the new end value of the spring. If set to {@link #FOLLOW_EXACT}, it is also the current value of the 112 | * spring. 113 | */ 114 | protected abstract double mapToSpring(final float motionValue); 115 | 116 | public int getTrackStrategy() { 117 | return mTrackStrategy; 118 | } 119 | 120 | /** 121 | * @param trackStrategy 122 | * the desired tracking strategy 123 | * @return this object for chaining 124 | */ 125 | @NonNull 126 | public Imitator setTrackStrategy(final int trackStrategy) { 127 | mTrackStrategy = trackStrategy; 128 | return this; 129 | } 130 | 131 | public int getFollowStrategy() { 132 | return mFollowStrategy; 133 | } 134 | 135 | /** 136 | * @param followStrategy 137 | * the desired follow strategy 138 | * @return this object for chaining 139 | */ 140 | @NonNull 141 | public Imitator setFollowStrategy(final int followStrategy) { 142 | mFollowStrategy = followStrategy; 143 | return this; 144 | } 145 | 146 | @NonNull 147 | public Spring getSpring() { 148 | return mSpring; 149 | } 150 | 151 | /** 152 | * @param spring 153 | * the spring to set (sets spring's current to the rest value) 154 | */ 155 | public void setSpring(@NonNull final Spring spring) { 156 | mSpring = spring; 157 | 158 | if (mSpring != null) { 159 | // Start spring at rest. 160 | mSpring.setCurrentValue(mRestValue, true); 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /backboard/src/main/java/com/tumblr/backboard/imitator/InertialImitator.java: -------------------------------------------------------------------------------- 1 | package com.tumblr.backboard.imitator; 2 | 3 | import android.view.MotionEvent; 4 | 5 | import androidx.annotation.NonNull; 6 | 7 | import com.facebook.rebound.Spring; 8 | import com.facebook.rebound.SpringConfig; 9 | import com.facebook.rebound.SpringListener; 10 | import com.tumblr.backboard.MotionProperty; 11 | 12 | /** 13 | * A {@link com.tumblr.backboard.imitator.ConstrainedMotionImitator} that moves freely when the 14 | * user is not dragging it. It copies the {@link com.facebook.rebound.SpringConfig} in 15 | * {@link #setSpring(com.facebook.rebound.Spring)} to use when the user is dragging. 16 | *

17 | * Created by ericleong on 11/6/14. 18 | */ 19 | public class InertialImitator extends ConstrainedMotionImitator implements SpringListener { 20 | 21 | /** 22 | * The friction (in {@link com.facebook.rebound.SpringConfig}) to use when moving freely. 23 | */ 24 | public static final float DEFAULT_FRICTION = 1.0f; 25 | 26 | /** 27 | * The {@link com.facebook.rebound.SpringConfig} to use when moving freely. 28 | */ 29 | public static final SpringConfig SPRING_CONFIG_FRICTION = new SpringConfig(0, DEFAULT_FRICTION); 30 | 31 | /** 32 | * Used to convert {@link com.facebook.rebound.Spring#getVelocity()} to the units needed by 33 | * {@link #calculateRestPosition()}. 34 | */ 35 | public static final int VELOCITY_RATIO = 24; 36 | 37 | /** 38 | * The {@link SpringConfig} to use when being dragged. 39 | */ 40 | protected SpringConfig mOriginalConfig; 41 | 42 | /** 43 | * Constructor. Uses {@link #TRACK_ABSOLUTE} and {@link #FOLLOW_EXACT}. 44 | * 45 | * @param property 46 | * the desired property to imitate 47 | * @param minValue 48 | * the minimum value 49 | * @param maxValue 50 | * the maximum value 51 | */ 52 | public InertialImitator(@NonNull final MotionProperty property, final double minValue, final double maxValue) { 53 | super(property, minValue, maxValue); 54 | } 55 | 56 | /** 57 | * Constructor. 58 | * 59 | * @param property 60 | * the desired property to imitate 61 | * @param trackStrategy 62 | * the tracking strategy. 63 | * @param followStrategy 64 | * the follow strategy. 65 | * @param minValue 66 | * the desired minimum spring value. 67 | * @param maxValue 68 | * the desired maximum spring value. 69 | */ 70 | public InertialImitator(@NonNull final MotionProperty property, final int trackStrategy, final int followStrategy, 71 | final double minValue, final double maxValue) { 72 | super(property, trackStrategy, followStrategy, minValue, maxValue); 73 | } 74 | 75 | /** 76 | * Sets the {@link com.facebook.rebound.Spring} that this imitator should use. This class 77 | * attaches itself as a {@link com.facebook.rebound.SpringListener} and stores the 78 | * {@link com.facebook.rebound.SpringConfig} to use when the user is dragging. 79 | * 80 | * @param spring 81 | * the spring to use 82 | */ 83 | @Override 84 | public void setSpring(@NonNull final Spring spring) { 85 | super.setSpring(spring); 86 | spring.addListener(this); 87 | 88 | mOriginalConfig = spring.getSpringConfig(); 89 | } 90 | 91 | @Override 92 | public void constrain(final MotionEvent event) { 93 | super.constrain(event); 94 | 95 | mSpring.setSpringConfig(mOriginalConfig); 96 | } 97 | 98 | @Override 99 | public void release(final MotionEvent event) { 100 | // snap to left or right depending on current location 101 | final double restPosition = calculateRestPosition(); 102 | if (mSpring.getCurrentValue() > mMaxValue && restPosition > mMaxValue) { 103 | mSpring.setEndValue(mMaxValue); 104 | } else if (mSpring.getCurrentValue() < mMinValue && restPosition < mMinValue) { 105 | mSpring.setEndValue(mMinValue); 106 | } else { 107 | mSpring.setSpringConfig(SPRING_CONFIG_FRICTION); 108 | mSpring.setEndValue(Double.MAX_VALUE); 109 | } 110 | } 111 | 112 | /** 113 | * @return the spring position when it comes to rest (given infinite time). 114 | */ 115 | private double calculateRestPosition() { 116 | // http://prettygoodphysics.wikispaces.com/file/view/DifferentialEquations.pdf 117 | return mSpring.getCurrentValue() 118 | + VELOCITY_RATIO * mSpring.getVelocity() / (mSpring.getSpringConfig().friction); 119 | } 120 | 121 | public void setMinValue(final double minValue) { 122 | this.mMinValue = minValue; 123 | } 124 | 125 | public void setMaxValue(final double maxValue) { 126 | this.mMaxValue = maxValue; 127 | } 128 | 129 | @Override 130 | public void onSpringUpdate(final Spring spring) { 131 | if (mSpring != null) { 132 | final double restPosition = calculateRestPosition(); 133 | if (mSpring.getSpringConfig().equals(SPRING_CONFIG_FRICTION)) { 134 | if (mSpring.getCurrentValue() > mMaxValue && restPosition > mMaxValue) { 135 | mSpring.setSpringConfig(mOriginalConfig); 136 | mSpring.setEndValue(mMaxValue); 137 | } else if (mSpring.getCurrentValue() < mMinValue && restPosition < mMinValue) { 138 | mSpring.setSpringConfig(mOriginalConfig); 139 | mSpring.setEndValue(mMinValue); 140 | } 141 | } 142 | } 143 | } 144 | 145 | @Override 146 | public void onSpringAtRest(final Spring spring) { 147 | // pass 148 | } 149 | 150 | @Override 151 | public void onSpringActivate(final Spring spring) { 152 | // pass 153 | } 154 | 155 | @Override 156 | public void onSpringEndStateChange(final Spring spring) { 157 | // pass 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /backboard/src/main/java/com/tumblr/backboard/imitator/MotionImitator.java: -------------------------------------------------------------------------------- 1 | package com.tumblr.backboard.imitator; 2 | 3 | import android.view.MotionEvent; 4 | import android.view.View; 5 | 6 | import androidx.annotation.NonNull; 7 | 8 | import com.facebook.rebound.Spring; 9 | import com.tumblr.backboard.MotionProperty; 10 | 11 | /** 12 | * Maps a user's motion to a {@link android.view.View} via a {@link com.facebook.rebound.Spring}. 13 | *

14 | * Created by ericleong on 5/13/14. 15 | */ 16 | public class MotionImitator extends EventImitator { 17 | 18 | private static final String TAG = MotionImitator.class.getSimpleName(); 19 | 20 | /** 21 | * The motion property to imitate. 22 | */ 23 | @NonNull 24 | protected MotionProperty mProperty; 25 | 26 | /** 27 | * Used internally to keep track of the initial down position. 28 | */ 29 | protected float mDownPosition; 30 | 31 | /** 32 | * The offset between the view left/right location and the desired "center" of the view. 33 | */ 34 | protected float mOffset; 35 | 36 | /** 37 | * Constructor. Uses {@link #TRACK_ABSOLUTE} and {@link #FOLLOW_EXACT}. 38 | * 39 | * @param property 40 | * the property to track. 41 | */ 42 | public MotionImitator(@NonNull final MotionProperty property) { 43 | this(null, property, 0, TRACK_ABSOLUTE, FOLLOW_EXACT); 44 | } 45 | 46 | /** 47 | * Constructor. It is necessary to call {@link #setSpring(Spring)} to set the spring. 48 | * 49 | * @param property 50 | * the property to track. 51 | * @param trackStrategy 52 | * the tracking strategy. 53 | * @param followStrategy 54 | * the follow strategy. 55 | */ 56 | public MotionImitator(@NonNull final MotionProperty property, final int trackStrategy, final int followStrategy) { 57 | this(null, property, 0, trackStrategy, followStrategy); 58 | } 59 | 60 | /** 61 | * Constructor. Uses {@link #TRACK_ABSOLUTE} and {@link #FOLLOW_EXACT}. 62 | * 63 | * @param spring 64 | * the spring to use. 65 | * @param property 66 | * the property to track. 67 | */ 68 | public MotionImitator(@NonNull final Spring spring, @NonNull final MotionProperty property) { 69 | this(spring, property, spring.getEndValue(), TRACK_ABSOLUTE, FOLLOW_EXACT); 70 | } 71 | 72 | /** 73 | * Constructor. Uses {@link #TRACK_ABSOLUTE} and {@link #FOLLOW_EXACT}. 74 | * 75 | * @param spring 76 | * the spring to use. 77 | * @param property 78 | * the property to track. 79 | * @param restValue 80 | * the rest value for the spring. 81 | */ 82 | public MotionImitator(@NonNull final Spring spring, @NonNull final MotionProperty property, final double restValue) { 83 | this(spring, property, restValue, TRACK_ABSOLUTE, FOLLOW_EXACT); 84 | } 85 | 86 | /** 87 | * Constructor. 88 | * 89 | * @param spring 90 | * the spring to use. 91 | * @param property 92 | * the property to track. 93 | * @param restValue 94 | * the rest value for the spring. 95 | * @param trackStrategy 96 | * the tracking strategy. 97 | * @param followStrategy 98 | * the follow strategy. 99 | */ 100 | public MotionImitator(@NonNull final Spring spring, @NonNull final MotionProperty property, final double restValue, 101 | final int trackStrategy, final int followStrategy) { 102 | super(spring, restValue, trackStrategy, followStrategy); 103 | mProperty = property; 104 | } 105 | 106 | /** 107 | * Constructor. 108 | * 109 | * @param property 110 | * the property to track. 111 | * @param restValue 112 | * the rest value for the spring. 113 | * @param trackStrategy 114 | * the tracking strategy. 115 | * @param followStrategy 116 | * the follow strategy. 117 | */ 118 | public MotionImitator(@NonNull final MotionProperty property, final double restValue, final int trackStrategy, 119 | final int followStrategy) { 120 | super(restValue, trackStrategy, followStrategy); 121 | mProperty = property; 122 | } 123 | 124 | public void setRestValue(final double restValue) { 125 | this.mRestValue = restValue; 126 | } 127 | 128 | /** 129 | * Puts the spring to rest. 130 | * 131 | * @return this object for chaining. 132 | */ 133 | @NonNull 134 | public MotionImitator rest() { 135 | if (mSpring != null) { 136 | mSpring.setEndValue(mRestValue); 137 | } 138 | 139 | return this; 140 | } 141 | 142 | @Override 143 | public void constrain(final MotionEvent event) { 144 | super.constrain(event); 145 | 146 | mDownPosition = mProperty.getValue(event) + mOffset; 147 | } 148 | 149 | @Override 150 | public void imitate(final View view, @NonNull final MotionEvent event) { 151 | 152 | final float viewValue = mProperty.getValue(view); 153 | final float eventValue = mProperty.getValue(event); 154 | mOffset = mProperty.getOffset(view); 155 | 156 | if (event.getHistorySize() > 0) { 157 | final float historicalValue = mProperty.getOldestValue(event); 158 | 159 | imitate(viewValue + mOffset, eventValue, eventValue - historicalValue, event); 160 | } else { 161 | imitate(viewValue + mOffset, eventValue, 0, event); 162 | } 163 | } 164 | 165 | @Override 166 | public void mime(final float offset, final float value, final float delta, final float dt, final MotionEvent event) { 167 | if (mTrackStrategy == TRACK_DELTA) { 168 | super.mime(offset - mDownPosition, value, delta, dt, event); 169 | } else { 170 | super.mime(offset, value, delta, dt, event); 171 | } 172 | } 173 | 174 | @Override 175 | protected double mapToSpring(final float motionValue) { 176 | return motionValue; 177 | } 178 | 179 | @NonNull 180 | public MotionProperty getProperty() { 181 | return mProperty; 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /backboard/src/main/java/com/tumblr/backboard/imitator/SpringImitator.java: -------------------------------------------------------------------------------- 1 | package com.tumblr.backboard.imitator; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import com.facebook.rebound.Spring; 6 | import com.facebook.rebound.SpringListener; 7 | 8 | /** 9 | * Allows a {@link com.facebook.rebound.Spring} to imitate another {@link com.facebook.rebound.Spring}. 10 | *

11 | * The default implementation sets the end value of the attached Spring to be the same as the 12 | * current value of the couple it is imitating. 13 | *

14 | * Created by ericleong on 5/16/14. 15 | */ 16 | public class SpringImitator extends Imitator implements SpringListener { 17 | 18 | /** 19 | * @param spring 20 | * the spring to imitate 21 | */ 22 | public SpringImitator(@NonNull final Spring spring) { 23 | super(spring); 24 | } 25 | 26 | /** 27 | * Constructor. 28 | * 29 | * @param spring 30 | * the spring to imitate. 31 | * @param trackStrategy 32 | * the tracking strategy. 33 | * @param followStrategy 34 | * the follow strategy. 35 | */ 36 | protected SpringImitator(@NonNull final Spring spring, final int trackStrategy, final int followStrategy) { 37 | super(spring, trackStrategy, followStrategy); 38 | } 39 | 40 | /** 41 | * Constructor. 42 | * 43 | * @param spring 44 | * the spring to imitate. 45 | * @param restValue 46 | * the spring value when not being perturbed. 47 | * @param trackStrategy 48 | * the tracking strategy. 49 | * @param followStrategy 50 | * the follow strategy. 51 | */ 52 | protected SpringImitator(@NonNull final Spring spring, final double restValue, final int trackStrategy, final int followStrategy) { 53 | super(spring, restValue, trackStrategy, followStrategy); 54 | } 55 | 56 | @Override 57 | protected double mapToSpring(final float motionValue) { 58 | return motionValue; 59 | } 60 | 61 | @Override 62 | public void onSpringUpdate(@NonNull final Spring spring) { 63 | mSpring.setEndValue(spring.getCurrentValue()); 64 | } 65 | 66 | @Override 67 | public void onSpringAtRest(final Spring spring) { 68 | 69 | } 70 | 71 | @Override 72 | public void onSpringActivate(final Spring spring) { 73 | 74 | } 75 | 76 | @Override 77 | public void onSpringEndStateChange(final Spring spring) { 78 | 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /backboard/src/main/java/com/tumblr/backboard/imitator/ToggleImitator.java: -------------------------------------------------------------------------------- 1 | package com.tumblr.backboard.imitator; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.view.MotionEvent; 5 | import android.view.View; 6 | 7 | import androidx.annotation.NonNull; 8 | 9 | import com.facebook.rebound.Spring; 10 | 11 | /** 12 | * Toggle between two {@link com.facebook.rebound.Spring} states depending on whether or not the user is touching the 13 | * screen. When the user presses, {@link #constrain(android.view.MotionEvent)} is called and the active value is set. 14 | * When the user releases, {@link #release(android.view.MotionEvent)} is called and the rest value is set. 15 | *

16 | * Created by ericleong on 5/21/14. 17 | */ 18 | public class ToggleImitator extends EventImitator implements View.OnTouchListener { 19 | 20 | private double mActiveValue; 21 | 22 | /** 23 | * Constructor. It is necessary to call {@link #setSpring(Spring)} to set the spring. 24 | * 25 | * @param spring 26 | * the spring to use. 27 | * @param restValue 28 | * the value when off. 29 | * @param activeValue 30 | * the value when on. 31 | */ 32 | public ToggleImitator(@NonNull final Spring spring, final double restValue, final double activeValue) { 33 | super(spring, restValue, TRACK_ABSOLUTE, FOLLOW_EXACT); 34 | mActiveValue = activeValue; 35 | } 36 | 37 | @Override 38 | public void constrain(final MotionEvent event) { 39 | mSpring.setEndValue(mActiveValue); 40 | } 41 | 42 | @Override 43 | protected double mapToSpring(final float motionValue) { 44 | // not used 45 | return mActiveValue; 46 | } 47 | 48 | @Override 49 | public boolean onTouch(final View v, @NonNull final MotionEvent event) { 50 | imitate(v, event); 51 | 52 | return true; 53 | } 54 | 55 | @Override 56 | @SuppressLint("ClickableViewAccessibility") 57 | public void imitate(final View view, @NonNull final MotionEvent event) { 58 | switch (event.getAction()) { 59 | case MotionEvent.ACTION_DOWN: 60 | constrain(event); 61 | break; 62 | 63 | case MotionEvent.ACTION_CANCEL: 64 | case MotionEvent.ACTION_UP: 65 | release(event); 66 | break; 67 | 68 | default: 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /backboard/src/main/java/com/tumblr/backboard/performer/MapPerformer.java: -------------------------------------------------------------------------------- 1 | package com.tumblr.backboard.performer; 2 | 3 | import android.util.Property; 4 | import android.view.View; 5 | 6 | import androidx.annotation.NonNull; 7 | 8 | import com.facebook.rebound.Spring; 9 | import com.facebook.rebound.SpringUtil; 10 | 11 | /** 12 | * Maps the motion of a {@link com.facebook.rebound.Spring} to a 13 | * {@link android.util.Property} on a {@link android.view.View}. 14 | *

15 | * Created by ericleong on 5/6/14. 16 | */ 17 | public class MapPerformer extends Performer { 18 | 19 | private static final String TAG = MapPerformer.class.getSimpleName(); 20 | 21 | private float initialStart, initialEnd, start, end; 22 | 23 | /** 24 | * Constructor. Maps the spring domain [0, 1] to the view property range [0, 1]. 25 | * 26 | * @param target 27 | * the view to modify. 28 | * @param property 29 | * the view property to modify. 30 | */ 31 | public MapPerformer(@NonNull final View target, @NonNull final Property property) { 32 | this(target, property, 0, 1, 0, 1); 33 | } 34 | 35 | /** 36 | * Constructor. Maps the spring domain [0, 1] to the specified view property range. 37 | * 38 | * @param target 39 | * the view to modify. 40 | * @param property 41 | * the view property to modify. 42 | * @param start 43 | * the minimum value for the view property range. 44 | * @param end 45 | * the maximum value for the view property range. 46 | */ 47 | public MapPerformer(@NonNull final View target, @NonNull final Property property, final float start, final float end) { 48 | this(target, property, 0, 1, start, end); 49 | } 50 | 51 | /** 52 | * Constructor. Maps the spring domain [0, 1] to the specified view property range. 53 | * 54 | * @param target 55 | * the view to modify. 56 | * @param property 57 | * the view property to modify. 58 | * @param initialStart 59 | * the minimum value for the spring domain. 60 | * @param initialEnd 61 | * the maximum value for the spring domain. 62 | * @param start 63 | * the minimum value for the view property range. 64 | * @param end 65 | * the maximum value for the view property range. 66 | */ 67 | public MapPerformer(@NonNull final View target, @NonNull final Property property, final float initialStart, 68 | final float initialEnd, final float start, final float end) { 69 | super(target, property); 70 | 71 | this.initialStart = initialStart; 72 | this.initialEnd = initialEnd; 73 | this.start = start; 74 | this.end = end; 75 | } 76 | 77 | @Override 78 | public void onSpringUpdate(@NonNull final Spring spring) { 79 | 80 | mProperty.set(mTarget, 81 | (float) SpringUtil.mapValueFromRangeToRange(spring.getCurrentValue(), 82 | initialStart, initialEnd, start, end) 83 | ); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /backboard/src/main/java/com/tumblr/backboard/performer/Performer.java: -------------------------------------------------------------------------------- 1 | package com.tumblr.backboard.performer; 2 | 3 | import android.util.Property; 4 | import android.view.View; 5 | 6 | import androidx.annotation.NonNull; 7 | import androidx.annotation.Nullable; 8 | 9 | import com.facebook.rebound.Spring; 10 | import com.facebook.rebound.SpringListener; 11 | 12 | /** 13 | * Directly maps the motion of a {@link com.facebook.rebound.Spring} to a 14 | * {@link android.util.Property} on a {@link android.view.View}. 15 | *

16 | * Created by ericleong on 5/19/14. 17 | */ 18 | public class Performer implements SpringListener { 19 | 20 | /** 21 | * The view to modify. 22 | */ 23 | @Nullable 24 | protected View mTarget; 25 | /** 26 | * The property of the view to modify. 27 | */ 28 | @NonNull 29 | protected Property mProperty; 30 | 31 | /** 32 | * Constructor. Note that a {@link View} must be specified by {@link #setTarget(View)}. 33 | * 34 | * @param property 35 | * the view property to modify. 36 | */ 37 | public Performer(@NonNull final Property property) { 38 | this(null, property); 39 | } 40 | 41 | /** 42 | * Constructor. 43 | * 44 | * @param target 45 | * the view to modify. 46 | * @param property 47 | * the view property to modify. 48 | */ 49 | public Performer(@Nullable final View target, @NonNull final Property property) { 50 | this.mTarget = target; 51 | this.mProperty = property; 52 | } 53 | 54 | @Nullable 55 | public View getTarget() { 56 | return mTarget; 57 | } 58 | 59 | public void setTarget(@Nullable final View target) { 60 | this.mTarget = target; 61 | } 62 | 63 | @NonNull 64 | public Property getProperty() { 65 | return mProperty; 66 | } 67 | 68 | public void setProperty(@NonNull final Property property) { 69 | this.mProperty = property; 70 | } 71 | 72 | @Override 73 | public void onSpringUpdate(@NonNull final Spring spring) { 74 | if (mProperty != null && mTarget != null) { 75 | mProperty.set(mTarget, (float) spring.getCurrentValue()); 76 | } 77 | } 78 | 79 | @Override 80 | public void onSpringAtRest(final Spring spring) { 81 | 82 | } 83 | 84 | @Override 85 | public void onSpringActivate(final Spring spring) { 86 | 87 | } 88 | 89 | @Override 90 | public void onSpringEndStateChange(final Spring spring) { 91 | 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | allprojects { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | } 6 | } 7 | 8 | ext { 9 | compileSdkVersion = 31 10 | minSdkVersion = 14 11 | targetSdkVersion = 31 12 | supportAnnotation = 'androidx.annotation:annotation:1.0.0' 13 | facebookRebound = "com.facebook.rebound:rebound:0.3.8" 14 | } 15 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | machine: 2 | java: 3 | version: oraclejdk8 4 | 5 | test: 6 | override: 7 | - ./gradlew --info check 8 | 9 | general: 10 | branches: 11 | ignore: 12 | - gh-pages -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Settings specified in this file will override any Gradle settings 5 | # configured through the IDE. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | android.useAndroidX = true 20 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tumblr/Backboard/8fec03fb8f52d69b98e60f0058b0edbccf6a5c4e/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | # Determine the Java command to use to start the JVM. 86 | if [ -n "$JAVA_HOME" ] ; then 87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 88 | # IBM's JDK on AIX uses strange locations for the executables 89 | JAVACMD="$JAVA_HOME/jre/sh/java" 90 | else 91 | JAVACMD="$JAVA_HOME/bin/java" 92 | fi 93 | if [ ! -x "$JAVACMD" ] ; then 94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 95 | 96 | Please set the JAVA_HOME variable in your environment to match the 97 | location of your Java installation." 98 | fi 99 | else 100 | JAVACMD="java" 101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 102 | 103 | Please set the JAVA_HOME variable in your environment to match the 104 | location of your Java installation." 105 | fi 106 | 107 | # Increase the maximum file descriptors if we can. 108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 109 | MAX_FD_LIMIT=`ulimit -H -n` 110 | if [ $? -eq 0 ] ; then 111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 112 | MAX_FD="$MAX_FD_LIMIT" 113 | fi 114 | ulimit -n $MAX_FD 115 | if [ $? -ne 0 ] ; then 116 | warn "Could not set maximum file descriptor limit: $MAX_FD" 117 | fi 118 | else 119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 120 | fi 121 | fi 122 | 123 | # For Darwin, add options to specify how the application appears in the dock 124 | if $darwin; then 125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 126 | fi 127 | 128 | # For Cygwin or MSYS, switch paths to Windows format before running java 129 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 132 | JAVACMD=`cygpath --unix "$JAVACMD"` 133 | 134 | # We build the pattern for arguments to be converted via cygpath 135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 136 | SEP="" 137 | for dir in $ROOTDIRSRAW ; do 138 | ROOTDIRS="$ROOTDIRS$SEP$dir" 139 | SEP="|" 140 | done 141 | OURCYGPATTERN="(^($ROOTDIRS))" 142 | # Add a user-defined pattern to the cygpath arguments 143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 145 | fi 146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 147 | i=0 148 | for arg in "$@" ; do 149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 151 | 152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 154 | else 155 | eval `echo args$i`="\"$arg\"" 156 | fi 157 | i=`expr $i + 1` 158 | done 159 | case $i in 160 | 0) set -- ;; 161 | 1) set -- "$args0" ;; 162 | 2) set -- "$args0" "$args1" ;; 163 | 3) set -- "$args0" "$args1" "$args2" ;; 164 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 165 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 166 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 167 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 168 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 169 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 170 | esac 171 | fi 172 | 173 | # Escape application args 174 | save () { 175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 176 | echo " " 177 | } 178 | APP_ARGS=`save "$@"` 179 | 180 | # Collect all arguments for the java command, following the shell quoting and substitution rules 181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 182 | 183 | exec "$JAVACMD" "$@" 184 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 34 | 35 | @rem Find java.exe 36 | if defined JAVA_HOME goto findJavaFromJavaHome 37 | 38 | set JAVA_EXE=java.exe 39 | %JAVA_EXE% -version >NUL 2>&1 40 | if "%ERRORLEVEL%" == "0" goto init 41 | 42 | echo. 43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 44 | echo. 45 | echo Please set the JAVA_HOME variable in your environment to match the 46 | echo location of your Java installation. 47 | 48 | goto fail 49 | 50 | :findJavaFromJavaHome 51 | set JAVA_HOME=%JAVA_HOME:"=% 52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 53 | 54 | if exist "%JAVA_EXE%" goto init 55 | 56 | echo. 57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 58 | echo. 59 | echo Please set the JAVA_HOME variable in your environment to match the 60 | echo location of your Java installation. 61 | 62 | goto fail 63 | 64 | :init 65 | @rem Get command-line arguments, handling Windows variants 66 | 67 | if not "%OS%" == "Windows_NT" goto win9xME_args 68 | 69 | :win9xME_args 70 | @rem Slurp the command line arguments. 71 | set CMD_LINE_ARGS= 72 | set _SKIP=2 73 | 74 | :win9xME_args_slurp 75 | if "x%~1" == "x" goto execute 76 | 77 | set CMD_LINE_ARGS=%* 78 | 79 | :execute 80 | @rem Setup the command line 81 | 82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 83 | 84 | @rem Execute Gradle 85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 86 | 87 | :end 88 | @rem End local scope for the variables with windows NT shell 89 | if "%ERRORLEVEL%"=="0" goto mainEnd 90 | 91 | :fail 92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 93 | rem the _cmd.exe /c_ return code! 94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 95 | exit /b 1 96 | 97 | :mainEnd 98 | if "%OS%"=="Windows_NT" endlocal 99 | 100 | :omega 101 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | gradle.ext.agpVersion = '7.1.1' 3 | gradle.ext.automatticPublishToS3Version = '0.9.0' 4 | 5 | plugins { 6 | id "com.android.application" version gradle.ext.agpVersion 7 | id "com.android.library" version gradle.ext.agpVersion 8 | id "com.automattic.android.publish-to-s3" version gradle.ext.automatticPublishToS3Version 9 | } 10 | repositories { 11 | maven { 12 | url 'https://a8c-libs.s3.amazonaws.com/android' 13 | content { 14 | includeGroup "com.automattic.android" 15 | includeGroup "com.automattic.android.publish-to-s3" 16 | } 17 | } 18 | google() 19 | mavenCentral() 20 | } 21 | } 22 | 23 | include ':backboard-example', ':backboard' 24 | --------------------------------------------------------------------------------