├── .gitignore ├── LICENSE ├── README.md ├── additive_animations ├── .gitignore ├── aar-release.gradle ├── build.gradle ├── gradle.properties └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── at │ └── wirecube │ └── additiveanimations │ ├── additive_animator │ ├── AccumulatedAnimationValue.java │ ├── AccumulatedAnimationValueManager.java │ ├── AdditiveAnimation.java │ ├── AdditiveAnimationAccumulator.java │ ├── AdditiveAnimator.java │ ├── AdditiveAnimatorGroup.java │ ├── AdditiveObjectAnimator.java │ ├── AnimationEndListener.java │ ├── BaseAdditiveAnimator.java │ ├── RunningAnimationsManager.java │ ├── SubclassableAdditiveViewAnimator.java │ ├── ViewAnimationApplier.java │ ├── animation_set │ │ ├── AnimationAction.java │ │ ├── AnimationState.java │ │ ├── SingleAnimationAction.java │ │ └── view │ │ │ ├── ViewAnimation.java │ │ │ └── ViewAnimationState.java │ ├── sequence │ │ ├── AnimationSequence.java │ │ ├── PlaySequentiallyAnimationSequence.java │ │ ├── PlayTogetherAnimationSequence.java │ │ └── PlayWithStaggerAnimationSequence.java │ └── view_visibility │ │ ├── ViewVisibilityAnimation.java │ │ └── ViewVisibilityBuilder.java │ └── helper │ ├── AnimationUtils.java │ ├── EaseInOutPathInterpolator.java │ ├── FloatProperty.java │ ├── SpringInterpolator.java │ ├── evaluators │ ├── ColorEvaluator.java │ └── PathEvaluator.java │ └── propertywrappers │ ├── ColorProperties.java │ ├── ElevationProperties.java │ ├── MarginProperties.java │ ├── PaddingProperties.java │ ├── ScrollProperties.java │ └── SizeProperties.java ├── build.gradle ├── demo ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── at │ │ └── wirecube │ │ └── additiveanimations │ │ └── additiveanimationsdemo │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── additive_animations │ │ │ ├── AAApplication.java │ │ │ ├── AdditiveAnimationsShowcaseActivity.java │ │ │ ├── fragments │ │ │ ├── AnimationChainingDemoFragment.java │ │ │ ├── CustomAnimationsWithoutSubclassDemoFragment.java │ │ │ ├── MarginsDemoFragment.java │ │ │ ├── MoveAlongPathDemoFragment.java │ │ │ ├── MultipleViewsAnimationDemoFragment.java │ │ │ ├── RepeatingChainedAnimationsDemoFragment.java │ │ │ ├── TapToChangeColorDemoFragment.java │ │ │ ├── TapToMoveDemoFragment.java │ │ │ ├── custom_drawing │ │ │ │ ├── AdditiveRectAnimator.java │ │ │ │ ├── CustomDrawingFragment.java │ │ │ │ ├── DemoView.java │ │ │ │ └── Rect.java │ │ │ └── states │ │ │ │ ├── CustomViewStateAnimation.java │ │ │ │ └── StateDemoFragment.java │ │ │ ├── helper │ │ │ └── DpConverter.java │ │ │ └── subclass │ │ │ └── AdditiveAnimatorSubclassDemo.java │ └── res │ │ ├── drawable │ │ ├── round_25dp_radius.xml │ │ ├── round_blue_12_point_5_dp_radius.xml │ │ └── side_nav_bar.xml │ │ ├── layout │ │ ├── activity_additive_animations_showcase.xml │ │ ├── app_bar_additive_animations_showcase.xml │ │ ├── fragment_custom_drawing.xml │ │ ├── fragment_margins_demo.xml │ │ ├── fragment_move_along_path_demo.xml │ │ ├── fragment_multiple_views_demo.xml │ │ ├── fragment_state_demo.xml │ │ ├── fragment_tap_to_change_color_demo.xml │ │ ├── fragment_tap_to_move_demo.xml │ │ ├── fragment_text_view_color_demo.xml │ │ └── nav_header_additive_animations_showcase.xml │ │ ├── menu │ │ ├── activity_additive_animations_showcase_drawer.xml │ │ └── additive_animations_showcase.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-v21 │ │ └── styles.xml │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── at │ └── wirecube │ └── additiveanimations │ └── additiveanimationsdemo │ └── ExampleUnitTest.java ├── gif ├── chain_then.gif ├── chain_then_after_end.gif ├── chain_then_before_end.gif ├── chain_then_with_delay.gif ├── multiple_views.gif ├── multiple_views.mp4 └── single_view.gif ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /local.properties 3 | /.idea 4 | .DS_Store 5 | /build 6 | /captures 7 | /app/build 8 | *.iml 9 | /additive_animations/local.properties -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | https://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 | Copyright 2017 David Ganster 179 | 180 | Licensed under the Apache License, Version 2.0 (the "License"); 181 | you may not use this file except in compliance with the License. 182 | You may obtain a copy of the License at 183 | 184 | https://www.apache.org/licenses/LICENSE-2.0 185 | 186 | Unless required by applicable law or agreed to in writing, software 187 | distributed under the License is distributed on an "AS IS" BASIS, 188 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 189 | See the License for the specific language governing permissions and 190 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Android Additive Animations 2 | 3 | Additive animations for Android! 4 | An easy way to additively animate any property of any object, with convenient builder methods for `View`s. 5 | 6 | Get a good overview of this library here: https://medium.com/@david.gansterd/bringing-smooth-animation-transitions-to-android-88786347e512 7 | 8 | 9 | # Integration 10 | To use `AdditiveAnimator` in your project, add the following lines to your `build.gradle`: 11 | ``` 12 | dependencies { 13 | compile 'at.wirecube:additive_animations:1.9.3' 14 | } 15 | ``` 16 | 17 | # Quick start 18 | Here is a sample of what additive animations can do for the user experience (note: there seem to be a few dropped frames in the gif which aren't present when running on a device): 19 | 20 | 21 | ![Additive animations demo](https://github.com/davidganster/android_additive_animations/blob/master/gif/single_view.gif?raw=true) 22 | 23 | 24 | The amount code required to produce this animation is trivial: 25 | 26 | ```java 27 | public boolean onTouch(View v, MotionEvent event) { 28 | AdditiveAnimator.animate(animatedView, 1000).x(event.getX()).y(event.getY()).start(); 29 | return true; 30 | } 31 | ``` 32 | 33 | Additionally, `AdditiveAnimator` supports animating multiple targets simultaneously without any boilerplate: 34 | 35 | ```java 36 | new AdditiveAnimator().setDuration(1000) 37 | .target(myView1).x(100).y(100) 38 | .target(myView2).xBy(20).yBy(20) 39 | .start(); 40 | ``` 41 | 42 | **New in 1.6:** 43 | 44 | 1.6 added a some convenience features, such as the ability to switch duration midway to building an animation, providing a `SpringInterpolator` class, and being able to switch back to the default interpolator using the `switchToDefaultInterpolator()` method. 45 | 46 | Then main attraction of 1.6 though: 47 | 48 | You can now animate the same property for multiple views without looping. 49 | 50 | ```java 51 | new AdditiveAnimator().targets(myView1, myView2).alpha(0).start(); 52 | ``` 53 | 54 | To achieve a delay between the start of the animation of each target, you can optionally add the 'stagger' parameter to add a delay between each of the animations. 55 | ```java 56 | long staggerBetweenAnimations = 50L; 57 | new AdditiveAnimator().targets(Arrays.asList(myView1, myView2), staggerBetweenAnimations).alpha(0).start(); 58 | ``` 59 | In this example, `myView1` is faded out 50 milliseconds before `myView2`. 60 | 61 | Starting with 1.6.1, the delay between the animation of the views is preserved when using `then()` chaining: 62 | ```java 63 | long staggerBetweenAnimations = 50L; 64 | AdditiveAnimator.animate(Arrays.asList(myView1, myView2), staggerBetweenAnimations).translationYBy(50).thenWithDelay(20).translationYBy(-50).start(); 65 | ``` 66 | 67 | The timeline of this animation looks like this: 68 | `myView1` is translated by 50 pixels at delay 0. 69 | `myView2` is translated by 50 pixels at delay 50. 70 | `myView1` is translated by -50 pixles at delay 20. 71 | `myView2` is translated by -50 pixles at delay 70. 72 | 73 | Check out `MultipleViewsAnimationDemoFragment` in the demo app for an example of this! 74 | 75 | 76 | # Visibility animations 77 | **New in 1.7.2** 78 | 79 | View visibility can now be properly animated without adding an animation end block and checking if the visibility should be updated based on some other state variable: 80 | 81 | ```java 82 | AdditiveAnimator.animate(view) 83 | .fadeVisibility(View.GONE) // fades out the view, then sets visibility to GONE 84 | .start(); 85 | ``` 86 | 87 | Since fading the visibiliy is probably the most common usecase, there's a default builder method for it. 88 | A few more default animations are provided as well: 89 | 90 | ```java 91 | AdditiveAnimator.animate(view) 92 | // the first param decides whether the view should be GONE or INVISIBLE, 93 | // the second one decides how much to move the view as it fades out 94 | .visibility(ViewVisibilityAnimation.fadeOutAndTranslateX(true, 100f)) // only move x 95 | .visibility(ViewVisibilityAnimation.fadeOutAndTranslateY(true, 100f)) // only move y 96 | .visibility(ViewVisibilityAnimation.fadeOutAndTranslate(true, 100f, 100f)) // move x and y 97 | .start(); 98 | ``` 99 | 100 | The new `ViewVisibilityAnimation` class provides a convenient constructor to make your own view state animations - an example can be found in the new demo (`StateDemoFragment`). 101 | 102 | **New in 1.9.2** 103 | The API for building new AnimationStates and view visibility animations has been improved. 104 | You can now access the `AnimationState.Builder` class to more easily use one-off states. 105 | There are also more specializations for `View`-specific classes, like the `ViewAnimation`, `ViewAnimationState` and `ViewStateBuilder`. 106 | 107 | # Animation States 108 | 109 | `AdditiveAnimator` now supports the concept of _animation states_. 110 | A __State__ encapsulates a set of animations to perform when an object changes its... state. 111 | 112 | What's special about this is that `AdditiveAnimator` can now automatically decide whether or not to run animation start and end blocks - if the view is no longer in the appropriate state for the block, it won't run. 113 | 114 | This is how the view visibility feature is implemented, and it can easily be extended to work with all kinds of custom states via the new `state()` builder method. 115 | 116 | For example, we might want to switch the states of some views between __highlighted__ and __normal__ in a `then()`-chained block like this: 117 | 118 | ```java 119 | new AdditiveAnimator() 120 | .targets(normalViews) 121 | .scale(1f) // normal 122 | .then() 123 | .target(highlightedView) 124 | .scale(1.2f) // highlighted 125 | .start(); 126 | ``` 127 | 128 | There's a race condition in this piece of code: The `then()`-chained animation is executed whether or not the `highlightedView` is actually still highlighted by the time the previous animation finishes. 129 | 130 | Animation states fix this problem entirely: 131 | 132 | ```java 133 | new AdditiveAnimator() 134 | .targets(normalViews) 135 | .state(MyViewState.NORMAL) 136 | .then() 137 | .target(highlightedView) 138 | .state(MyViewState.HIGHLIGHTED) 139 | .start(); 140 | ``` 141 | 142 | With this code, the animations associated with the `NORMAL` and `HIGHLIGHTED` states are only allowed to run if the state of the enqueued animation still matches the current view state. 143 | Even when rapidly switching which view is highlighted, this will produce the desired outcome. 144 | 145 | # Animating all kinds of objects and properties 146 | In addition to the builder methods for views, there are multiple options for animating custom properties of any object. 147 | The first - *highly recommended* - option is to simply provide a `Property` for the object you want to animate, plus (if needed) a way to trigger a redraw of your custom object: 148 | 149 | ```java 150 | // Declaring an animatable property: 151 | FloatProperty mPaintColorProperty = 152 | FloatProperty.create("PaintColor", paint -> (float) paint.getColor(), (paint, color) -> paint.setColor((int) color)); 153 | ... 154 | 155 | // Using the property to animate the color of a paint: 156 | AdditiveObjectAnimator.animate(myPaint) 157 | .property(targetColor, // target value 158 | new ColorEvaluator(), // custom evaluator for colors 159 | mPaintColorProperty) // how to get/set the property value 160 | .setAnimationApplier(new ViewAnimationApplier(myView)) // tells the generic AdditiveObjectAnimator how to apply the changed values 161 | .start(); 162 | ``` 163 | 164 | The second option is not recommended unless you need very specific control over how properties are applied (for example, only applying x/y-scroll changes together instead of one at a time when animating 2-dimensional scrolling). 165 | In works by subclassing `BaseAdditiveAnimator` and providing your own builder methods (which are usually one-liners) such as this: 166 | 167 | ```java 168 | class PaintAdditiveAnimator extends BaseAdditiveAnimator { 169 | private static final String COLOR_ANIMATION_KEY = "ANIMATION_KEY"; 170 | 171 | // Support animation chaining by providing a construction method: 172 | @Override protected PaintAdditiveAnimator newInstance() { return new PaintAdditiveAnimator(); } 173 | 174 | // Custom builder method for animating the color of a Paint: 175 | public PaintAdditiveAnimator color(int color) { 176 | return animate(new AdditiveAnimation<>( 177 | mCurrentTarget, // animated object (usually this is the current target) 178 | COLOR_ANIMATION_KEY, // key to identify the animation 179 | mCurrentTarget.getColor(), // start value 180 | color)); // target value 181 | } 182 | 183 | // Applying the changed properties when they don't have a Property wrapper: 184 | @Override protected void applyCustomProperties(Map tempProperties, Paint target) { 185 | if(tempProperties.containsKey(COLOR_ANIMATION_KEY)) { 186 | target.setColor(tempProperties.get(COLOR_ANIMATION_KEY).intValue()); 187 | } 188 | } 189 | 190 | // For animations without a property, your subclass is responsible for providing the current property value. 191 | // This is easy to forget when adding new animatable properties, which is one of the reasons this method is discouraged. 192 | @Override public Float getCurrentPropertyValue(String propertyName) { 193 | switch(propertyName) { 194 | case ANIMATION_KEY: 195 | return mCurrentTarget.getColor(); 196 | } 197 | return null; 198 | } 199 | } 200 | ``` 201 | 202 | A more complete example of both of these approaches can be found in the sample app in `CustomDrawingFragment.java`. 203 | 204 | 205 | Of course you can combine both approaches - custom builder methods which animate properties. This is the **recommended approach** and is how everything provided by `AdditiveAnimator` was built. 206 | 207 | Both versions only require very little code, and the few lines you have to write are almost always trivial - mostly getters and setters. 208 | 209 | ### Note: 210 | There is a breaking change when migrating from a version <1.5.0 to a version >= 1.5.0: 211 | Instead of subclassing `AdditiveAnimator`, you now have to subclass `SubclassableAdditiveViewAnimator` instead. 212 | Sorry for the change, it was necessary due to Java constraints (nesting of generics across subclasses) and improves interop with Kotlin (no more generic arguments required!). 213 | 214 | ### Note 215 | There is another breaking change when migrating from <1.6.0 to >= 1.6.0: 216 | You have to implement a new abstract method (`getCurrentPropertyValue()`) when subclassing `BaseAdditiveAnimator`. 217 | This method is only called when using tag-based animations, instead of property-based ones. If your subclass does not use tag-based animations, you can simply `return null;`. 218 | 219 | # License 220 | `AdditiveAnimator` is licensed under the Apache v2 license: 221 | 222 | ``` 223 | Copyright 2021 David Ganster 224 | 225 | Licensed under the Apache License, Version 2.0 (the "License"); 226 | you may not use this file except in compliance with the License. 227 | You may obtain a copy of the License at 228 | 229 | http://www.apache.org/licenses/LICENSE-2.0 230 | 231 | Unless required by applicable law or agreed to in writing, software 232 | distributed under the License is distributed on an "AS IS" BASIS, 233 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 234 | See the License for the specific language governing permissions and 235 | limitations under the License. 236 | ``` 237 | -------------------------------------------------------------------------------- /additive_animations/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /additive_animations/aar-release.gradle: -------------------------------------------------------------------------------- 1 | // credit https://raw.githubusercontent.com/blundell/release-android-library/master/android-release-aar.gradle 2 | // run ./gradlew clean build generateRelease 3 | 4 | apply plugin: 'maven' 5 | 6 | def groupId = 'at.wirecube' 7 | def artifactId = 'additive_animations' 8 | def version = android.defaultConfig.versionName 9 | 10 | def localReleaseDest = "${buildDir}/release/${version}" 11 | 12 | task androidJavadocs(type: Javadoc) { 13 | source = android.sourceSets.main.java.srcDirs 14 | ext.androidJar = "${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar" 15 | classpath += files(ext.androidJar) 16 | } 17 | 18 | task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) { 19 | classifier = 'javadoc' 20 | from androidJavadocs.destinationDir 21 | } 22 | 23 | task androidSourcesJar(type: Jar) { 24 | classifier = 'sources' 25 | from android.sourceSets.main.java.srcDirs 26 | } 27 | 28 | uploadArchives { 29 | repositories.mavenDeployer { 30 | pom.groupId = groupId 31 | pom.artifactId = artifactId 32 | pom.version = version 33 | // Add other pom properties here if you want (developer details / licenses) 34 | repository(url: "file://${localReleaseDest}") 35 | } 36 | } 37 | 38 | task zipRelease(type: Zip) { 39 | from localReleaseDest 40 | destinationDir buildDir 41 | archiveName "release-${version}.zip" 42 | } 43 | 44 | task generateRelease { 45 | doLast { 46 | println "Release ${version} can be found at ${localReleaseDest}/" 47 | println "Release ${version} zipped can be found ${buildDir}/release-${version}.zip" 48 | } 49 | } 50 | 51 | generateRelease.dependsOn(uploadArchives) 52 | generateRelease.dependsOn(zipRelease) 53 | 54 | artifacts { 55 | archives androidSourcesJar 56 | //archives androidJavadocsJar 57 | } -------------------------------------------------------------------------------- /additive_animations/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 David Ganster 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | apply plugin: 'com.android.library' 18 | apply plugin: "com.vanniktech.maven.publish" 19 | 20 | android { 21 | compileSdkVersion 30 22 | buildToolsVersion '30.0.3' 23 | 24 | defaultConfig { 25 | minSdkVersion 14 26 | targetSdkVersion 30 27 | versionCode 28 28 | versionName "1.9.3" 29 | } 30 | buildTypes { 31 | release { 32 | minifyEnabled false 33 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 34 | } 35 | } 36 | compileOptions { 37 | sourceCompatibility JavaVersion.VERSION_1_8 38 | targetCompatibility JavaVersion.VERSION_1_8 39 | } 40 | } 41 | 42 | dependencies { 43 | implementation fileTree(include: ['*.jar'], dir: 'libs') 44 | testImplementation 'junit:junit:4.13' 45 | implementation 'androidx.appcompat:appcompat:1.3.1' 46 | implementation 'com.google.android.material:material:1.4.0' 47 | } 48 | -------------------------------------------------------------------------------- /additive_animations/gradle.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2017 David Ganster 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | POM_NAME=Android Additive Animations 18 | POM_ARTIFACT_ID=additive_animations 19 | POM_PACKAGING=aar -------------------------------------------------------------------------------- /additive_animations/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /additive_animations/src/main/java/at/wirecube/additiveanimations/additive_animator/AccumulatedAnimationValue.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 David Ganster 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package at.wirecube.additiveanimations.additive_animator; 18 | 19 | class AccumulatedAnimationValue { 20 | final AdditiveAnimation animation; 21 | float tempValue; 22 | 23 | AccumulatedAnimationValue(AdditiveAnimation animation) { 24 | this.animation = animation; 25 | } 26 | 27 | void addDelta(float delta) { 28 | tempValue += delta; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /additive_animations/src/main/java/at/wirecube/additiveanimations/additive_animator/AccumulatedAnimationValueManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 David Ganster 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package at.wirecube.additiveanimations.additive_animator; 18 | 19 | import java.util.HashMap; 20 | 21 | /** 22 | * Helper class for accumulating the changes made by all of the additive animators. 23 | */ 24 | class AccumulatedAnimationValueManager { 25 | 26 | private HashMap accumulatedAnimationValues = new HashMap<>(); 27 | 28 | /** 29 | * Returns an accumulator to use for this animation. All animations with the same tag and target share the same accumulator. 30 | * The lookup is rather slow, so try to store the returned object somewhere you can access it directly. 31 | */ 32 | public AccumulatedAnimationValue getAccumulatedAnimationValue(AdditiveAnimation animation) { 33 | // TODO: is there any way to make this `get()` faster? 34 | AccumulatedAnimationValue accumulatedAnimationValue = accumulatedAnimationValues.get(animation); 35 | if(accumulatedAnimationValue != null) { 36 | return accumulatedAnimationValue; 37 | } 38 | accumulatedAnimationValue = new AccumulatedAnimationValue(animation); 39 | accumulatedAnimationValues.put(animation, accumulatedAnimationValue); 40 | return accumulatedAnimationValue; 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /additive_animations/src/main/java/at/wirecube/additiveanimations/additive_animator/AdditiveAnimation.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 David Ganster 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package at.wirecube.additiveanimations.additive_animator; 18 | 19 | import android.animation.TimeInterpolator; 20 | import android.animation.TypeEvaluator; 21 | import android.graphics.Path; 22 | import android.util.Property; 23 | 24 | import at.wirecube.additiveanimations.additive_animator.animation_set.AnimationState; 25 | import at.wirecube.additiveanimations.helper.evaluators.PathEvaluator; 26 | 27 | /** 28 | * This class is public for subclasses of AdditiveAnimator only, and should not be used outside of that. 29 | */ 30 | public class AdditiveAnimation { 31 | 32 | private String mTag; 33 | private float mStartValue; 34 | private float mTargetValue; 35 | private Property mProperty; 36 | private Path mPath; 37 | private PathEvaluator.PathMode mPathMode; 38 | private PathEvaluator mSharedPathEvaluator; 39 | private TypeEvaluator mCustomTypeEvaluator; 40 | private T mTarget; 41 | private int mHashCode; 42 | private TimeInterpolator mCustomInterpolator; // each animation can have its own interpolator 43 | private AccumulatedAnimationValue mAccumulatedValue; 44 | private AnimationState mAssociatedAnimationState; 45 | 46 | /** 47 | * Determines if the `targetValue` is a 'by' value. If it is, the actual target value will be computed when the animation starts 48 | * (as opposed to computing just the start value when it is enqueued). 49 | */ 50 | private boolean mBy = false; 51 | private float mByValue; // used for storing the by-value when the animation is a "by"-animation so that the animation can be copied correctly. 52 | 53 | /** 54 | * The preferred constructor to use when animating properties. If you use this constructor, you 55 | * don't need to worry about the logic to apply the changes. This is taken care of by using the 56 | * Setter provided by `property`. 57 | */ 58 | public AdditiveAnimation(T target, Property property, float startValue, float targetValue) { 59 | mTarget = target; 60 | mProperty = property; 61 | mTargetValue = targetValue; 62 | mStartValue = startValue; 63 | setTag(property.getName()); 64 | } 65 | 66 | /** 67 | * Use this constructor for custom properties that have no simple getter or setter. 68 | * 69 | * @param tag Name of the animated property. Must be unique. 70 | * @param startValue Start value of the animated property. 71 | * @param targetValue Target value of the animated property. 72 | */ 73 | public AdditiveAnimation(T target, String tag, float startValue, float targetValue) { 74 | mTarget = target; 75 | mStartValue = startValue; 76 | mTargetValue = targetValue; 77 | setTag(tag); 78 | } 79 | 80 | public AdditiveAnimation(T target, String tag, float startValue, Path path, PathEvaluator.PathMode pathMode, PathEvaluator sharedEvaluator) { 81 | mTarget = target; 82 | mStartValue = startValue; 83 | mPath = path; 84 | mSharedPathEvaluator = sharedEvaluator; 85 | mPathMode = pathMode; 86 | mTargetValue = evaluateAt(1f); 87 | setTag(tag); 88 | } 89 | 90 | public AdditiveAnimation(T target, Property property, float startValue, Path path, PathEvaluator.PathMode pathMode, PathEvaluator sharedEvaluator) { 91 | mTarget = target; 92 | mProperty = property; 93 | mStartValue = startValue; 94 | mPath = path; 95 | mSharedPathEvaluator = sharedEvaluator; 96 | mPathMode = pathMode; 97 | mTargetValue = evaluateAt(1f); 98 | setTag(property.getName()); 99 | } 100 | 101 | public void setAccumulatedValue(AccumulatedAnimationValue av) { 102 | mAccumulatedValue = av; 103 | } 104 | 105 | private void setTag(String tag) { 106 | mTag = tag; 107 | // TODO: find a good hash code that doesn't collide often 108 | mHashCode = mTag.hashCode() * ((2 << 17) - 1) + mTarget.hashCode(); 109 | } 110 | 111 | public String getTag() { 112 | return mTag; 113 | } 114 | 115 | public float getStartValue() { 116 | return mStartValue; 117 | } 118 | 119 | public float getTargetValue() { 120 | return mTargetValue; 121 | } 122 | 123 | public void setStartValue(float startValue) { 124 | this.mStartValue = startValue; 125 | } 126 | 127 | public void setTargetValue(float targetValue) { 128 | mTargetValue = targetValue; 129 | } 130 | 131 | public void setCustomTypeEvaluator(TypeEvaluator evaluator) { 132 | mCustomTypeEvaluator = evaluator; 133 | } 134 | 135 | public TypeEvaluator getCustomTypeEvaluator() { 136 | return mCustomTypeEvaluator; 137 | } 138 | 139 | public T getTarget() { 140 | return mTarget; 141 | } 142 | 143 | /** 144 | * Set this immediately after creating the animation. Failure to do so will result in incorrect target values. 145 | */ 146 | public void setBy(boolean by) { 147 | mBy = by; 148 | if (by) { 149 | mByValue = mTargetValue; 150 | } 151 | } 152 | 153 | public boolean isBy() { 154 | return mBy; 155 | } 156 | 157 | public float getByValue() { 158 | return mByValue; 159 | } 160 | 161 | public Property getProperty() { 162 | return mProperty; 163 | } 164 | 165 | public Path getPath() { 166 | return mPath; 167 | } 168 | 169 | public void setCustomInterpolator(TimeInterpolator customInterpolator) { 170 | mCustomInterpolator = customInterpolator; 171 | } 172 | 173 | public float evaluateAt(float progress) { 174 | if (mCustomInterpolator != null) { 175 | progress = mCustomInterpolator.getInterpolation(progress); 176 | } 177 | if (mPath != null) { 178 | return mSharedPathEvaluator.evaluate(progress, mPathMode, mPath); 179 | } else { 180 | if (mCustomTypeEvaluator != null) { 181 | return mCustomTypeEvaluator.evaluate(progress, mStartValue, mTargetValue); 182 | } else { 183 | return mStartValue + (mTargetValue - mStartValue) * progress; 184 | } 185 | } 186 | } 187 | 188 | public AccumulatedAnimationValue getAccumulatedValue() { 189 | return mAccumulatedValue; 190 | } 191 | 192 | public AdditiveAnimation cloneWithTarget(T target, Float startValue) { 193 | final AdditiveAnimation animation; 194 | if (this.getProperty() != null) { 195 | if (this.getPath() != null) { 196 | animation = new AdditiveAnimation<>(target, mProperty, startValue, getPath(), mPathMode, mSharedPathEvaluator); 197 | } else { 198 | animation = new AdditiveAnimation<>(target, mProperty, startValue, mTargetValue); 199 | } 200 | } else { 201 | if (this.getPath() != null) { 202 | animation = new AdditiveAnimation<>(target, mTag, startValue, getPath(), mPathMode, mSharedPathEvaluator); 203 | } else { 204 | animation = new AdditiveAnimation<>(target, mTag, startValue, mTargetValue); 205 | } 206 | } 207 | if (mBy) { 208 | animation.mBy = mBy; 209 | animation.mByValue = mByValue; 210 | animation.mTargetValue = startValue + animation.mByValue; 211 | } 212 | if (mCustomInterpolator != null) { 213 | animation.setCustomInterpolator(mCustomInterpolator); 214 | } 215 | if (mCustomTypeEvaluator != null) { 216 | animation.setCustomTypeEvaluator(mCustomTypeEvaluator); 217 | } 218 | if (mAssociatedAnimationState != null) { 219 | animation.setAssociatedAnimationState(mAssociatedAnimationState); 220 | } 221 | return animation; 222 | } 223 | 224 | public void setAssociatedAnimationState(AnimationState associatedAnimationStateId) { 225 | this.mAssociatedAnimationState = associatedAnimationStateId; 226 | } 227 | 228 | public AnimationState getAssociatedAnimationState() { 229 | return mAssociatedAnimationState; 230 | } 231 | 232 | @Override 233 | public int hashCode() { 234 | return mHashCode; 235 | } 236 | 237 | @Override 238 | public boolean equals(Object o) { 239 | if (this == o) { 240 | return true; 241 | } 242 | if (!(o instanceof AdditiveAnimation)) { 243 | return false; 244 | } 245 | AdditiveAnimation other = (AdditiveAnimation) o; 246 | return other.mTag.hashCode() == mTag.hashCode() && other.mTarget == mTarget; 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /additive_animations/src/main/java/at/wirecube/additiveanimations/additive_animator/AdditiveAnimationAccumulator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 David Ganster 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package at.wirecube.additiveanimations.additive_animator; 18 | 19 | import android.animation.Animator; 20 | import android.animation.AnimatorListenerAdapter; 21 | import android.animation.ValueAnimator; 22 | 23 | import java.util.ArrayList; 24 | import java.util.Collection; 25 | import java.util.HashMap; 26 | import java.util.HashSet; 27 | import java.util.List; 28 | import java.util.Map; 29 | import java.util.Set; 30 | 31 | /** 32 | * This is the class that actually runs the ValueAnimator and adds the delta of each animation to its accumulator. 33 | */ 34 | class AdditiveAnimationAccumulator { 35 | 36 | // Exists only for performance reasons to avoid map lookups 37 | private static final class AdditiveAnimationWrapper { 38 | private final AdditiveAnimation animation; 39 | private float previousValue; 40 | 41 | AdditiveAnimationWrapper(AdditiveAnimation animation) { 42 | this.animation = animation; 43 | this.previousValue = 0f; 44 | } 45 | 46 | @Override 47 | public int hashCode() { 48 | return animation.hashCode(); 49 | } 50 | 51 | @Override 52 | public boolean equals(Object obj) { 53 | if (this == obj) { 54 | return true; 55 | } 56 | return animation.equals(((AdditiveAnimationWrapper) obj).animation); 57 | } 58 | } 59 | 60 | private List mAnimationWrappers = new ArrayList<>(); 61 | private Map> mAnimationsPerObject = new HashMap<>(); 62 | private ValueAnimator mAnimator = null; 63 | private boolean mHasInformedStateManagerAboutAnimationStart = false; 64 | private BaseAdditiveAnimator mAdditiveAnimator; 65 | 66 | AdditiveAnimationAccumulator(BaseAdditiveAnimator additiveAnimator) { 67 | mAnimator = ValueAnimator.ofFloat(0f, 1f); 68 | mAdditiveAnimator = additiveAnimator; 69 | // it's better not to allocate once every frame, so we just create the list once and then clear() it. 70 | final List accumulatedAnimationValues = new ArrayList<>(); 71 | mAnimator.addUpdateListener(valueAnimator -> { 72 | if (!mHasInformedStateManagerAboutAnimationStart) { 73 | notifyStateManagerAboutAnimationStartIfNeeded(); 74 | } 75 | for (AdditiveAnimationWrapper animationWrapper : mAnimationWrappers) { 76 | AdditiveAnimation animation = animationWrapper.animation; 77 | AccumulatedAnimationValue tempProperties = animation.getAccumulatedValue(); 78 | tempProperties.addDelta(getDelta(animationWrapper, valueAnimator.getAnimatedFraction())); 79 | accumulatedAnimationValues.add(tempProperties); 80 | } 81 | 82 | /* 83 | * TODO: is there some way to figure out whether or not to apply the changes? 84 | * Only the very last running accumulator would need to apply the changes, since the 85 | * accumulation happens inside the accumulatedAnimationValues. 86 | * This would bring a big performance gain in that the property setters would not need to 87 | * be called for every running animation, but only for the final accumulated value. 88 | * Unfortunately, it's really hard to gather a list of all accumulated values that need 89 | * to be set in each frame and to figure out when the last animator is done. 90 | */ 91 | mAdditiveAnimator.applyChanges(accumulatedAnimationValues); 92 | 93 | // clear() does not resize the underlying elementData memory, so each subsequent frame will be able to reuse the previously allocated slots. 94 | accumulatedAnimationValues.clear(); 95 | }); 96 | 97 | mAnimator.addListener(new AnimatorListenerAdapter() { 98 | boolean animationDidCancel = false; 99 | 100 | @Override 101 | public void onAnimationCancel(Animator animation) { 102 | animationDidCancel = true; 103 | } 104 | 105 | @Override 106 | public void onAnimationEnd(Animator animation) { 107 | for (Object v : mAnimationsPerObject.keySet()) { 108 | RunningAnimationsManager.from(v).onAnimationAccumulatorEnd(AdditiveAnimationAccumulator.this, animationDidCancel); 109 | } 110 | } 111 | 112 | @Override 113 | public void onAnimationStart(Animator animation) { 114 | notifyStateManagerAboutAnimationStartIfNeeded(); 115 | } 116 | }); 117 | } 118 | 119 | private void notifyStateManagerAboutAnimationStartIfNeeded() { 120 | if (mHasInformedStateManagerAboutAnimationStart) { 121 | return; 122 | } 123 | mHasInformedStateManagerAboutAnimationStart = true; 124 | Collection animationTargets = new ArrayList<>(mAnimationsPerObject.keySet()); 125 | for (Object v : animationTargets) { 126 | RunningAnimationsManager manager = RunningAnimationsManager.from(v); 127 | manager.onAnimationAccumulatorStart(AdditiveAnimationAccumulator.this); 128 | for (AdditiveAnimationWrapper wrapper : getAnimationWrappers(v)) { 129 | manager.prepareAnimationStart(wrapper.animation); 130 | wrapper.previousValue = wrapper.animation.getStartValue(); 131 | } 132 | } 133 | } 134 | 135 | void addAnimation(AdditiveAnimation animation) { 136 | // the correct value will be set when the animation actually starts instead of when we add the animation. 137 | AdditiveAnimationWrapper wrapper = new AdditiveAnimationWrapper(animation); 138 | mAnimationWrappers.add(wrapper); 139 | 140 | // TODO: speed this up 141 | addToAnimationMap(wrapper); 142 | } 143 | 144 | /* 145 | * Returns true if this removed all animations from the object, false if there are still more animations running. 146 | */ 147 | boolean removeAnimation(String animatedPropertyName, Object v) { 148 | removeAnimationFromTarget(v, animatedPropertyName); 149 | Collection c = mAnimationsPerObject.get(v); 150 | return c == null || c.size() == 0; 151 | } 152 | 153 | private void removeTarget(Object v) { 154 | Set animatedValues = collectAnimatedProperties(v); 155 | if (animatedValues == null) { 156 | return; 157 | } 158 | if (animatedValues.size() == mAnimationWrappers.size()) { 159 | cancel(); 160 | } else { 161 | for (String animatedValue : animatedValues) { 162 | removeAnimationFromTarget(v, animatedValue); 163 | } 164 | } 165 | } 166 | 167 | private Set collectAnimatedProperties(Object v) { 168 | Collection wrappers = mAnimationsPerObject.get(v); 169 | if (wrappers == null) { 170 | return new HashSet<>(); 171 | } 172 | Set properties = new HashSet<>(2); 173 | for (AdditiveAnimationWrapper wrapper : wrappers) { 174 | properties.add(wrapper.animation.getTag()); 175 | } 176 | return properties; 177 | } 178 | 179 | /** 180 | * Removes the animation with the given name from the given object. 181 | */ 182 | private void removeAnimationFromTarget(Object v, String additiveAnimationName) { 183 | AdditiveAnimationWrapper animationToRemove = null; 184 | for (AdditiveAnimationWrapper anim : getAnimationWrappers(v)) { 185 | if (anim.animation.getTag().equals(additiveAnimationName)) { 186 | animationToRemove = anim; 187 | break; 188 | } 189 | } 190 | if (animationToRemove != null) { 191 | mAnimationWrappers.remove(animationToRemove); 192 | removeFromAnimationMap(animationToRemove); 193 | } 194 | } 195 | 196 | private void addToAnimationMap(AdditiveAnimationWrapper wrapper) { 197 | Set animations = mAnimationsPerObject.get(wrapper.animation.getTarget()); 198 | if (animations == null) { 199 | animations = new HashSet<>(1); 200 | mAnimationsPerObject.put(wrapper.animation.getTarget(), animations); 201 | } 202 | animations.add(wrapper); 203 | } 204 | 205 | private void removeFromAnimationMap(AdditiveAnimationWrapper wrapper) { 206 | Set animations = mAnimationsPerObject.get(wrapper.animation.getTarget()); 207 | if (animations == null) { 208 | return; 209 | } 210 | animations.remove(wrapper); 211 | if (animations.size() == 0) { 212 | mAnimationsPerObject.remove(wrapper.animation.getTarget()); 213 | } 214 | } 215 | 216 | Collection getAnimations(Object v) { 217 | Collection wrappers = getAnimationWrappers(v); 218 | List animations = new ArrayList<>(wrappers.size()); 219 | for (AdditiveAnimationWrapper wrapper : wrappers) { 220 | animations.add(wrapper.animation); 221 | } 222 | return animations; 223 | } 224 | 225 | private Collection getAnimationWrappers(Object v) { 226 | Set wrappers = mAnimationsPerObject.get(v); 227 | if (wrappers == null) { 228 | return new HashSet<>(); 229 | } 230 | return wrappers; 231 | } 232 | 233 | ValueAnimator getAnimator() { 234 | return mAnimator; 235 | } 236 | 237 | Collection getAnimations() { 238 | Set allAnimations = new HashSet<>(mAnimationWrappers.size()); 239 | for (AdditiveAnimationWrapper wrapper : mAnimationWrappers) { 240 | allAnimations.add(wrapper.animation); 241 | } 242 | return allAnimations; 243 | } 244 | 245 | final float getDelta(AdditiveAnimationWrapper wrapper, float progress) { 246 | float lastVal = wrapper.previousValue; 247 | AdditiveAnimation animation = wrapper.animation; 248 | float newVal = animation.evaluateAt(progress); 249 | float delta = newVal - lastVal; 250 | wrapper.previousValue = newVal; 251 | return delta; 252 | } 253 | 254 | /** 255 | * Remove all properties belonging to `v`. 256 | */ 257 | final void cancel(Object v) { 258 | removeTarget(v); 259 | } 260 | 261 | final void cancel() { 262 | mAnimator.cancel(); 263 | } 264 | 265 | @Override 266 | public int hashCode() { 267 | return mAnimator.hashCode(); 268 | } 269 | 270 | @Override 271 | public boolean equals(Object obj) { 272 | if (obj == this) { 273 | return true; 274 | } 275 | AdditiveAnimationAccumulator other = (AdditiveAnimationAccumulator) obj; 276 | return other.mAnimator == mAnimator; 277 | } 278 | } 279 | -------------------------------------------------------------------------------- /additive_animations/src/main/java/at/wirecube/additiveanimations/additive_animator/AdditiveAnimator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 David Ganster 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package at.wirecube.additiveanimations.additive_animator; 18 | 19 | import android.view.View; 20 | 21 | import java.util.List; 22 | 23 | /** 24 | * Use this class to additively animate a lot of properties of Views. 25 | * If you want to extend the animatable properties, you should extend `SubclassableAdditiveViewAnimator`. 26 | * Additive animations are nicely explained here: http://ronnqvi.st/multiple-animations/ 27 | */ 28 | public final class AdditiveAnimator extends SubclassableAdditiveViewAnimator { 29 | 30 | /** 31 | * This is just a convenience method when you need to animate a single view. 32 | * No state is kept in individual AdditiveAnimator instances, so you don't need to keep a reference to it. 33 | * @param view The view to animate. 34 | * @return A new instance of AdditiveAnimator with `target` set to `view`. 35 | */ 36 | public static AdditiveAnimator animate(View view) { 37 | return new AdditiveAnimator(view); 38 | } 39 | 40 | public static AdditiveAnimator animate(View... views) { 41 | return new AdditiveAnimator().targets(views); 42 | } 43 | 44 | public static AdditiveAnimator animate(List views, long stagger) { 45 | return new AdditiveAnimator().targets(views, stagger); 46 | } 47 | 48 | public static AdditiveAnimator animate(List views, long duration, long stagger) { 49 | return new AdditiveAnimator(duration).targets(views, stagger); 50 | } 51 | 52 | /** 53 | * This is just a convenience method when you need to animate a single view. 54 | * No state is kept in individual AdditiveAnimator instances, so you don't need to keep a reference to it. 55 | * @param view The view to animate. 56 | * @param duration The animation duration. 57 | * @return A new instance of AdditiveAnimator with the animation target set to `view` and the animationDuration set to `duration`. 58 | */ 59 | public static AdditiveAnimator animate(View view, long duration) { 60 | return new AdditiveAnimator(view).setDuration(duration); 61 | } 62 | 63 | @Override 64 | protected AdditiveAnimator newInstance() { 65 | return new AdditiveAnimator(); 66 | } 67 | 68 | protected AdditiveAnimator(View view) { 69 | target(view); 70 | } 71 | 72 | /** 73 | * Creates a new AdditiveAnimator instance without a target view. 74 | * You **must** call `target(View v)` before calling one of the animation methods. 75 | */ 76 | public AdditiveAnimator() {} 77 | 78 | /** 79 | * Creates a new AdditiveAnimator instance with the specified animation duration for more convenience. 80 | * You **must** call `target(View v)` before calling one of the animation methods. 81 | */ 82 | public AdditiveAnimator(long duration) { 83 | setDuration(duration); 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /additive_animations/src/main/java/at/wirecube/additiveanimations/additive_animator/AdditiveAnimatorGroup.java: -------------------------------------------------------------------------------- 1 | package at.wirecube.additiveanimations.additive_animator; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | /** 7 | * Helper class to make then-chaining possible when targeting multiple views with a stagger parameter. 8 | * 9 | * Rationale: 10 | * Creating an animation by calling animate(targets, stagger) creates n animators, one for each target. 11 | * Calling then() on the result has to create n animator again, each targeting the same child as before but with a different delay. 12 | * The animation group is used to figure out which animators have been then-chained and which ones have been staggered. 13 | */ 14 | class AdditiveAnimatorGroup { 15 | 16 | interface StartDelayProvider { 17 | long getStartDelay(BaseAdditiveAnimator parent); 18 | } 19 | 20 | /** 21 | * The list of grouped animators. The first element is the innermost parent of the group ([1] is child of [0]). 22 | */ 23 | List mAnimators = new ArrayList<>(); 24 | 25 | public void add(BaseAdditiveAnimator animator) { 26 | animator.setAnimationGroup(this); 27 | mAnimators.add(animator); 28 | } 29 | 30 | public BaseAdditiveAnimator outermostChildAnimator() { 31 | return mAnimators.get(mAnimators.size() - 1); 32 | } 33 | 34 | /** 35 | * Creates a new animation group with all animators in the group and links them together such that the new group 36 | * targets the same views in the same order. 37 | * The first animator in the returned group is the child of the first animator in the current group. 38 | */ 39 | public AdditiveAnimatorGroup copyAndChain(StartDelayProvider delayProvider) { 40 | AdditiveAnimatorGroup newGroup = new AdditiveAnimatorGroup(); 41 | BaseAdditiveAnimator parent = outermostChildAnimator(); 42 | BaseAdditiveAnimator newestChild; 43 | for(BaseAdditiveAnimator animator : mAnimators) { 44 | newestChild = animator.newInstance(); 45 | // we want to copy the properties from the parent: 46 | newestChild.setParent(parent); 47 | // but keep the same target as the current animator in the chain: 48 | newestChild.target(animator.getCurrentTarget()); 49 | // we also need to make sure the animation timing is correct: 50 | newestChild.setStartDelay(delayProvider.getStartDelay(animator)); 51 | 52 | newGroup.add(newestChild); 53 | parent = newestChild; 54 | } 55 | return newGroup; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /additive_animations/src/main/java/at/wirecube/additiveanimations/additive_animator/AdditiveObjectAnimator.java: -------------------------------------------------------------------------------- 1 | package at.wirecube.additiveanimations.additive_animator; 2 | 3 | import android.animation.TypeEvaluator; 4 | 5 | import java.util.List; 6 | 7 | import at.wirecube.additiveanimations.helper.FloatProperty; 8 | 9 | /** 10 | * This is a small utility class which can animate any kind of object using the 11 | * {@link #property(float, FloatProperty)} and {@link #property(float, TypeEvaluator, FloatProperty)} methods. 12 | * If you'd like to provide your own builder methods for creating animations, subclass {@link BaseAdditiveAnimator}. 13 | */ 14 | public class AdditiveObjectAnimator extends BaseAdditiveAnimator, V> { 15 | 16 | private Runnable mAnimationApplier = null; 17 | 18 | @Override 19 | protected AdditiveObjectAnimator newInstance() { 20 | return new AdditiveObjectAnimator<>(); 21 | } 22 | 23 | public static AdditiveObjectAnimator animate(V target) { 24 | return new AdditiveObjectAnimator().target(target); 25 | } 26 | 27 | public static AdditiveObjectAnimator animate(V target, long duration) { 28 | return animate(target).setDuration(duration); 29 | } 30 | 31 | public static AdditiveObjectAnimator create() { 32 | return new AdditiveObjectAnimator(); 33 | } 34 | 35 | public static AdditiveObjectAnimator create(long duration) { 36 | return new AdditiveObjectAnimator().setDuration(duration); 37 | } 38 | 39 | @Override 40 | protected AdditiveObjectAnimator setParent(AdditiveObjectAnimator other) { 41 | AdditiveObjectAnimator child = super.setParent(other); 42 | child.setAnimationApplier(mAnimationApplier); 43 | return child; 44 | } 45 | 46 | public AdditiveObjectAnimator setAnimationApplier(Runnable animationApplier) { 47 | mAnimationApplier = animationApplier; 48 | return this; 49 | } 50 | 51 | @Override 52 | public Float getCurrentPropertyValue(String propertyName) { 53 | // AdditiveObjectAnimator only works with property-backed animations, so we don't need to implement this method 54 | return null; 55 | } 56 | 57 | @Override 58 | public void onApplyChanges() { 59 | if(mAnimationApplier != null) { 60 | mAnimationApplier.run(); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /additive_animations/src/main/java/at/wirecube/additiveanimations/additive_animator/AnimationEndListener.java: -------------------------------------------------------------------------------- 1 | package at.wirecube.additiveanimations.additive_animator; 2 | 3 | public interface AnimationEndListener { 4 | void onAnimationEnd(boolean wasCancelled); 5 | } 6 | -------------------------------------------------------------------------------- /additive_animations/src/main/java/at/wirecube/additiveanimations/additive_animator/ViewAnimationApplier.java: -------------------------------------------------------------------------------- 1 | package at.wirecube.additiveanimations.additive_animator; 2 | 3 | import android.view.View; 4 | 5 | public class ViewAnimationApplier implements Runnable { 6 | 7 | private final View mTarget; 8 | private final boolean mRequestLayout; 9 | 10 | public ViewAnimationApplier(View target) { 11 | mTarget = target; 12 | mRequestLayout = false; 13 | } 14 | 15 | public ViewAnimationApplier(View target, boolean requestLayout) { 16 | mTarget = target; 17 | mRequestLayout = requestLayout; 18 | } 19 | 20 | @Override 21 | public void run() { 22 | if(mRequestLayout) { 23 | mTarget.requestLayout(); 24 | } else { 25 | mTarget.invalidate(); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /additive_animations/src/main/java/at/wirecube/additiveanimations/additive_animator/animation_set/AnimationAction.java: -------------------------------------------------------------------------------- 1 | package at.wirecube.additiveanimations.additive_animator.animation_set; 2 | 3 | import android.animation.TypeEvaluator; 4 | import android.util.Property; 5 | 6 | import java.util.List; 7 | 8 | public interface AnimationAction { 9 | class Animation { 10 | private final Property mProperty; 11 | private final float mTargetValue; 12 | private TypeEvaluator mTypeEvaluator = null; 13 | 14 | public Animation(Property property, float targetValue) { 15 | this.mProperty = property; 16 | this.mTargetValue = targetValue; 17 | } 18 | 19 | public Animation(Property property, float targetValue, TypeEvaluator evaluator) { 20 | this.mProperty = property; 21 | this.mTargetValue = targetValue; 22 | this.mTypeEvaluator = evaluator; 23 | } 24 | 25 | public Property getProperty() { 26 | return mProperty; 27 | } 28 | 29 | public float getTargetValue() { 30 | return mTargetValue; 31 | } 32 | 33 | public TypeEvaluator getTypeEvaluator() { 34 | return mTypeEvaluator; 35 | } 36 | } 37 | 38 | List> getAnimations(); 39 | 40 | } -------------------------------------------------------------------------------- /additive_animations/src/main/java/at/wirecube/additiveanimations/additive_animator/animation_set/AnimationState.java: -------------------------------------------------------------------------------- 1 | package at.wirecube.additiveanimations.additive_animator.animation_set; 2 | 3 | import androidx.annotation.NonNull; 4 | import androidx.annotation.Nullable; 5 | 6 | import java.util.ArrayList; 7 | import java.util.Arrays; 8 | import java.util.List; 9 | 10 | public abstract class AnimationState implements AnimationAction { 11 | 12 | public static class Builder, T> { 13 | 14 | @NonNull 15 | protected final List> animations = new ArrayList<>(); 16 | 17 | @Nullable 18 | protected AnimationEndAction endAction; 19 | 20 | @Nullable 21 | protected AnimationStartAction startAction; 22 | 23 | public Builder() {} 24 | 25 | @NonNull 26 | public BuilderSubclass addAnimation(@NonNull AnimationAction.Animation animation) { 27 | animations.add(animation); 28 | return (BuilderSubclass) this; 29 | } 30 | 31 | @NonNull 32 | public BuilderSubclass addAnimations(@NonNull List> animations) { 33 | this.animations.addAll(animations); 34 | return (BuilderSubclass) this; 35 | } 36 | 37 | @SafeVarargs 38 | @NonNull 39 | public final BuilderSubclass addAnimations(@NonNull AnimationAction.Animation... animations) { 40 | this.animations.addAll(Arrays.asList(animations)); 41 | return (BuilderSubclass) this; 42 | } 43 | 44 | @NonNull 45 | public BuilderSubclass withEndAction(@Nullable AnimationEndAction endAction) { 46 | this.endAction = endAction; 47 | return (BuilderSubclass) this; 48 | } 49 | 50 | @NonNull 51 | public BuilderSubclass withStartAction(@Nullable AnimationStartAction startAction) { 52 | this.startAction = startAction; 53 | return (BuilderSubclass) this; 54 | } 55 | 56 | public AnimationState build() { 57 | return new AnimationState() { 58 | @Override 59 | public List> getAnimations() { 60 | return animations; 61 | } 62 | 63 | @Override 64 | public AnimationEndAction getAnimationEndAction() { 65 | return endAction; 66 | } 67 | 68 | @Override 69 | public AnimationStartAction getAnimationStartAction() { 70 | return startAction; 71 | } 72 | }; 73 | } 74 | } 75 | 76 | public interface AnimationStartAction { 77 | void onStart(T target); 78 | } 79 | 80 | public interface AnimationEndAction { 81 | void onEnd(T target, boolean wasCancelled); 82 | } 83 | 84 | /** 85 | * The animations are only allowed to run if the current state of the animated object matches 86 | * this state. 87 | */ 88 | public final boolean shouldRun(AnimationState currentState) { 89 | return currentState == null || currentState == this; 90 | } 91 | 92 | /** 93 | * The animationEndListener is only allowed to run if the current state of the animated object matches 94 | * this state. 95 | */ 96 | public final boolean shouldRunEndListener(AnimationState currentState) { 97 | return currentState == null || currentState == this; 98 | } 99 | 100 | public AnimationEndAction getAnimationEndAction() { 101 | return null; 102 | } 103 | 104 | public AnimationStartAction getAnimationStartAction() { 105 | return null; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /additive_animations/src/main/java/at/wirecube/additiveanimations/additive_animator/animation_set/SingleAnimationAction.java: -------------------------------------------------------------------------------- 1 | package at.wirecube.additiveanimations.additive_animator.animation_set; 2 | 3 | import android.animation.TypeEvaluator; 4 | import android.util.Property; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | public class SingleAnimationAction implements AnimationAction { 10 | private List> mAnimations = new ArrayList<>(); 11 | 12 | @Override 13 | public List> getAnimations() { 14 | return mAnimations; 15 | } 16 | 17 | public SingleAnimationAction(Property property, float target) { 18 | mAnimations.add(new AnimationAction.Animation<>(property, target)); 19 | } 20 | 21 | public SingleAnimationAction(Property property, float target, TypeEvaluator evaluator) { 22 | mAnimations.add(new AnimationAction.Animation(property, target, evaluator)); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /additive_animations/src/main/java/at/wirecube/additiveanimations/additive_animator/animation_set/view/ViewAnimation.java: -------------------------------------------------------------------------------- 1 | package at.wirecube.additiveanimations.additive_animator.animation_set.view; 2 | 3 | import android.animation.TypeEvaluator; 4 | import android.util.Property; 5 | import android.view.View; 6 | 7 | import at.wirecube.additiveanimations.additive_animator.animation_set.AnimationAction; 8 | 9 | public class ViewAnimation extends AnimationAction.Animation { 10 | public ViewAnimation(Property property, float targetValue) { 11 | super(property, targetValue); 12 | } 13 | 14 | public ViewAnimation(Property property, float targetValue, TypeEvaluator evaluator) { 15 | super(property, targetValue, evaluator); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /additive_animations/src/main/java/at/wirecube/additiveanimations/additive_animator/animation_set/view/ViewAnimationState.java: -------------------------------------------------------------------------------- 1 | package at.wirecube.additiveanimations.additive_animator.animation_set.view; 2 | 3 | import android.view.View; 4 | 5 | import androidx.annotation.NonNull; 6 | import androidx.annotation.Nullable; 7 | 8 | import java.util.ArrayList; 9 | import java.util.Arrays; 10 | import java.util.List; 11 | 12 | import at.wirecube.additiveanimations.additive_animator.animation_set.AnimationAction; 13 | import at.wirecube.additiveanimations.additive_animator.animation_set.AnimationState; 14 | 15 | public class ViewAnimationState extends AnimationState { 16 | 17 | @NonNull 18 | private final List> mAnimations = new ArrayList<>(); 19 | @Nullable 20 | private final AnimationStartAction mStartAction; 21 | @Nullable 22 | private final AnimationEndAction mEndAction; 23 | 24 | public ViewAnimationState(@NonNull List animations) { 25 | this(animations, null, null); 26 | } 27 | 28 | public ViewAnimationState(@NonNull ViewAnimation... animations) { 29 | this(Arrays.asList(animations), null, null); 30 | } 31 | 32 | public ViewAnimationState(@NonNull List animations, @Nullable AnimationStartAction startAction) { 33 | this(animations, startAction, null); 34 | } 35 | 36 | public ViewAnimationState(ViewAnimation animation, @Nullable AnimationStartAction startAction) { 37 | this(Arrays.asList(animation), startAction, null); 38 | } 39 | 40 | public ViewAnimationState(@Nullable AnimationStartAction startAction, @NonNull ViewAnimation... animations) { 41 | this(Arrays.asList(animations), startAction, null); 42 | } 43 | 44 | public ViewAnimationState(@NonNull ViewAnimation animation, @Nullable AnimationEndAction endAction) { 45 | this(Arrays.asList(animation), null, endAction); 46 | } 47 | 48 | public ViewAnimationState(@NonNull List animations, @Nullable AnimationEndAction endAction) { 49 | this(animations, null, endAction); 50 | } 51 | 52 | public ViewAnimationState(@Nullable AnimationEndAction endAction, @NonNull ViewAnimation... animations) { 53 | this(Arrays.asList(animations), null, endAction); 54 | } 55 | 56 | public ViewAnimationState(@NonNull ViewAnimation animation, @Nullable AnimationStartAction startAction, @Nullable AnimationEndAction endAction) { 57 | this(Arrays.asList(animation), startAction, endAction); 58 | } 59 | 60 | public ViewAnimationState(@Nullable AnimationStartAction startAction, @Nullable AnimationEndAction endAction, @NonNull ViewAnimation... animations) { 61 | this(Arrays.asList(animations), startAction, endAction); 62 | } 63 | 64 | public ViewAnimationState(@NonNull List animations, @Nullable AnimationStartAction startAction, @Nullable AnimationEndAction endAction) { 65 | mAnimations.addAll(animations); 66 | mStartAction = startAction; 67 | mEndAction = endAction; 68 | } 69 | 70 | @Override 71 | public List> getAnimations() { 72 | return mAnimations; 73 | } 74 | 75 | @Override 76 | public AnimationStartAction getAnimationStartAction() { 77 | return mStartAction; 78 | } 79 | 80 | @Override 81 | public AnimationEndAction getAnimationEndAction() { 82 | return mEndAction; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /additive_animations/src/main/java/at/wirecube/additiveanimations/additive_animator/sequence/AnimationSequence.java: -------------------------------------------------------------------------------- 1 | package at.wirecube.additiveanimations.additive_animator.sequence; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | 6 | public abstract class AnimationSequence { 7 | 8 | public abstract void start(); 9 | public abstract void setDelayInSequence(long delay); 10 | public abstract long getTotalDurationInSequence(); 11 | 12 | public static AnimationSequence playTogether(AnimationSequence... animations) { 13 | return new PlayTogetherAnimationSequence(Arrays.asList(animations)); 14 | } 15 | 16 | public static AnimationSequence playTogether(List animations) { 17 | return new PlayTogetherAnimationSequence(animations); 18 | } 19 | 20 | public static AnimationSequence playSequentially(AnimationSequence... animations) { 21 | return new PlaySequentiallyAnimationSequence(Arrays.asList(animations)); 22 | } 23 | 24 | public static AnimationSequence playSequentially(List animations) { 25 | return new PlaySequentiallyAnimationSequence(animations); 26 | } 27 | 28 | public static AnimationSequence playWithDelayBetweenAnimations(long stagger, AnimationSequence... animations) { 29 | return new PlayWithStaggerAnimationSequence(stagger, animations); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /additive_animations/src/main/java/at/wirecube/additiveanimations/additive_animator/sequence/PlaySequentiallyAnimationSequence.java: -------------------------------------------------------------------------------- 1 | package at.wirecube.additiveanimations.additive_animator.sequence; 2 | 3 | import java.util.List; 4 | 5 | class PlaySequentiallyAnimationSequence extends AnimationSequence { 6 | private final List animations; 7 | private long delay = 0; 8 | 9 | public PlaySequentiallyAnimationSequence(List animations) { 10 | this.animations = animations; 11 | } 12 | 13 | @Override 14 | public void start() { 15 | long totalDelay = 0; 16 | for(AnimationSequence sequence : animations) { 17 | sequence.setDelayInSequence(totalDelay + this.delay); 18 | totalDelay += sequence.getTotalDurationInSequence(); 19 | sequence.start(); 20 | } 21 | } 22 | 23 | @Override 24 | public void setDelayInSequence(long delay) { 25 | this.delay = delay; 26 | } 27 | 28 | @Override 29 | public long getTotalDurationInSequence() { 30 | long totalDelay = delay; 31 | for(AnimationSequence sequence : animations) { 32 | totalDelay += sequence.getTotalDurationInSequence(); 33 | } 34 | return totalDelay + this.delay; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /additive_animations/src/main/java/at/wirecube/additiveanimations/additive_animator/sequence/PlayTogetherAnimationSequence.java: -------------------------------------------------------------------------------- 1 | package at.wirecube.additiveanimations.additive_animator.sequence; 2 | 3 | import java.util.List; 4 | 5 | public class PlayTogetherAnimationSequence extends AnimationSequence { 6 | 7 | private final List animations; 8 | private long delayInSequence; 9 | 10 | PlayTogetherAnimationSequence(List animations) { 11 | this.animations = animations; 12 | } 13 | 14 | @Override 15 | public void start() { 16 | for (AnimationSequence sequence : animations) { 17 | sequence.setDelayInSequence(delayInSequence); 18 | sequence.start(); 19 | } 20 | } 21 | 22 | @Override 23 | public void setDelayInSequence(long delay) { 24 | this.delayInSequence = delay; 25 | } 26 | 27 | @Override 28 | public long getTotalDurationInSequence() { 29 | long longestDuration = 0; 30 | for (AnimationSequence sequence : animations) { 31 | if(sequence.getTotalDurationInSequence() > longestDuration) { 32 | longestDuration = sequence.getTotalDurationInSequence(); 33 | } 34 | } 35 | return longestDuration + delayInSequence; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /additive_animations/src/main/java/at/wirecube/additiveanimations/additive_animator/sequence/PlayWithStaggerAnimationSequence.java: -------------------------------------------------------------------------------- 1 | package at.wirecube.additiveanimations.additive_animator.sequence; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | 6 | public class PlayWithStaggerAnimationSequence extends AnimationSequence { 7 | 8 | private final List animations; 9 | private final long stagger; 10 | private long delayInSequence; 11 | 12 | public PlayWithStaggerAnimationSequence(long stagger, AnimationSequence... animations) { 13 | this.stagger = stagger; 14 | this.animations = Arrays.asList(animations); 15 | } 16 | 17 | 18 | @Override 19 | public void start() { 20 | long totalDelay = 0; 21 | for(AnimationSequence sequence : animations) { 22 | sequence.setDelayInSequence(totalDelay + this.delayInSequence); 23 | totalDelay += this.stagger; 24 | sequence.start(); 25 | } 26 | } 27 | 28 | @Override 29 | public void setDelayInSequence(long delay) { 30 | this.delayInSequence = delay; 31 | } 32 | 33 | @Override 34 | public long getTotalDurationInSequence() { 35 | long longestDuration = 0; 36 | long currentStagger = 0; 37 | for (AnimationSequence sequence : animations) { 38 | long duration = sequence.getTotalDurationInSequence() + currentStagger; 39 | if(duration > longestDuration) { 40 | longestDuration = duration; 41 | } 42 | currentStagger += stagger; 43 | } 44 | return longestDuration + delayInSequence; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /additive_animations/src/main/java/at/wirecube/additiveanimations/additive_animator/view_visibility/ViewVisibilityAnimation.java: -------------------------------------------------------------------------------- 1 | package at.wirecube.additiveanimations.additive_animator.view_visibility; 2 | 3 | import android.view.View; 4 | 5 | import java.util.List; 6 | 7 | import at.wirecube.additiveanimations.additive_animator.animation_set.AnimationAction; 8 | import at.wirecube.additiveanimations.additive_animator.animation_set.AnimationState; 9 | 10 | public class ViewVisibilityAnimation extends AnimationState { 11 | 12 | public static ViewVisibilityBuilder builder(int visibility) { 13 | return new ViewVisibilityBuilder(visibility); 14 | } 15 | 16 | public static ViewVisibilityBuilder gone() { 17 | return new ViewVisibilityBuilder(View.GONE); 18 | } 19 | 20 | public static ViewVisibilityBuilder visible() { 21 | return new ViewVisibilityBuilder(View.VISIBLE); 22 | } 23 | 24 | public static ViewVisibilityBuilder invisible() { 25 | return new ViewVisibilityBuilder(View.INVISIBLE); 26 | } 27 | 28 | /** 29 | * Sets the visibility of the view to View.VISIBLE and fades it in. 30 | */ 31 | public static AnimationState fadeIn() { 32 | return visible() 33 | .addAnimation(new AnimationAction.Animation<>(View.ALPHA, 1f)) 34 | .build(); 35 | } 36 | 37 | /** 38 | * Sets the visibility of the view to View.VISIBLE, fades it in and also set its translationX and translationY back to 0. 39 | */ 40 | public static AnimationState fadeInAndTranslateBack() { 41 | return visible() 42 | .addAnimations( 43 | new AnimationAction.Animation<>(View.ALPHA, 1f), 44 | new AnimationAction.Animation<>(View.TRANSLATION_X, 0f), 45 | new AnimationAction.Animation<>(View.TRANSLATION_Y, 0f)) 46 | .build(); 47 | } 48 | 49 | /** 50 | * Fades out the target and then sets its visibility to either View.INVISIBLE or GONE, depending on the gone parameter. 51 | */ 52 | public static AnimationState fadeOut(boolean gone) { 53 | return new ViewVisibilityBuilder(gone ? View.GONE : View.INVISIBLE) 54 | .addAnimation(new AnimationAction.Animation<>(View.ALPHA, 0f)) 55 | .build(); 56 | } 57 | 58 | /** 59 | * Fades out the target and then sets its visibility to either View.INVISIBLE or GONE, depending on the gone parameter. 60 | * Also moves the view by xTranslation and yTranslation. 61 | */ 62 | public static AnimationState fadeOutAndTranslate(boolean gone, float xTranslation, float yTranslation) { 63 | return new ViewVisibilityBuilder(gone ? View.GONE : View.INVISIBLE) 64 | .addAnimations(new AnimationAction.Animation<>(View.ALPHA, 0f), 65 | new AnimationAction.Animation<>(View.TRANSLATION_X, xTranslation), 66 | new AnimationAction.Animation<>(View.TRANSLATION_Y, yTranslation)) 67 | .build(); 68 | } 69 | 70 | /** 71 | * Fades out the target and then sets its visibility to either View.INVISIBLE or GONE, depending on the gone parameter. 72 | * Also moves the view horizontally by xTranslation. 73 | */ 74 | public static AnimationState fadeOutAndTranslateX(boolean gone, float xTranslation) { 75 | return new ViewVisibilityBuilder(gone ? View.GONE : View.INVISIBLE) 76 | .addAnimations( 77 | new AnimationAction.Animation<>(View.ALPHA, 0f), 78 | new AnimationAction.Animation<>(View.TRANSLATION_X, xTranslation) 79 | ) 80 | .build(); 81 | } 82 | 83 | /** 84 | * Fades out the target and then sets its visibility to either View.INVISIBLE or GONE, depending on the gone parameter. 85 | * Also moves the view vertically by yTranslation. 86 | */ 87 | public static AnimationState fadeOutAndTranslateY(boolean gone, float yTranslation) { 88 | return new ViewVisibilityBuilder(gone ? View.GONE : View.INVISIBLE) 89 | .addAnimations( 90 | new AnimationAction.Animation<>(View.ALPHA, 0f), 91 | new AnimationAction.Animation<>(View.TRANSLATION_Y, yTranslation)) 92 | .build(); 93 | } 94 | 95 | private List> mAnimations; 96 | private AnimationState.AnimationEndAction mEndAction; 97 | private AnimationState.AnimationStartAction mStartAction; 98 | 99 | public ViewVisibilityAnimation(int visibility, List> animations) { 100 | switch (visibility) { 101 | case View.VISIBLE: 102 | mStartAction = view -> view.setVisibility(View.VISIBLE); 103 | break; 104 | case View.INVISIBLE: 105 | mEndAction = (view, wasCancelled) -> view.setVisibility(View.INVISIBLE); 106 | break; 107 | case View.GONE: 108 | mEndAction = (view, wasCancelled) -> view.setVisibility(View.GONE); 109 | break; 110 | } 111 | mAnimations = animations; 112 | } 113 | 114 | @Override 115 | public List> getAnimations() { 116 | return mAnimations; 117 | } 118 | 119 | @Override 120 | public AnimationEndAction getAnimationEndAction() { 121 | return mEndAction; 122 | } 123 | 124 | @Override 125 | public AnimationStartAction getAnimationStartAction() { 126 | return mStartAction; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /additive_animations/src/main/java/at/wirecube/additiveanimations/additive_animator/view_visibility/ViewVisibilityBuilder.java: -------------------------------------------------------------------------------- 1 | package at.wirecube.additiveanimations.additive_animator.view_visibility; 2 | 3 | import android.view.View; 4 | 5 | import androidx.annotation.NonNull; 6 | import androidx.annotation.Nullable; 7 | 8 | import at.wirecube.additiveanimations.additive_animator.animation_set.AnimationState; 9 | 10 | public class ViewVisibilityBuilder extends AnimationState.Builder { 11 | 12 | private final AnimationState.AnimationStartAction visibilityStartAction; 13 | private final AnimationState.AnimationEndAction visibilityEndAction; 14 | 15 | public ViewVisibilityBuilder(int visibility) { 16 | switch (visibility) { 17 | case View.VISIBLE: 18 | visibilityStartAction = view -> view.setVisibility(View.VISIBLE); 19 | visibilityEndAction = null; 20 | break; 21 | case View.INVISIBLE: 22 | visibilityStartAction = null; 23 | visibilityEndAction = (view, wasCancelled) -> view.setVisibility(View.INVISIBLE); 24 | break; 25 | case View.GONE: 26 | visibilityStartAction = null; 27 | visibilityEndAction = (view, wasCancelled) -> view.setVisibility(View.GONE); 28 | break; 29 | default: 30 | throw new IllegalArgumentException("Cannot instantiate a ViewVisibilityAnimation.Builder without a valid visibility (given: " + visibility + ")."); 31 | } 32 | startAction = getWrappedStartAction(null); 33 | endAction = getWrappedEndAction(null); 34 | } 35 | 36 | private AnimationState.AnimationStartAction getWrappedStartAction(@Nullable AnimationState.AnimationStartAction startAction) { 37 | return view -> { 38 | if (visibilityStartAction != null) { 39 | visibilityStartAction.onStart(view); 40 | } 41 | if (startAction != null) { 42 | startAction.onStart(view); 43 | } 44 | }; 45 | } 46 | 47 | private AnimationState.AnimationEndAction getWrappedEndAction(@Nullable AnimationState.AnimationEndAction endAction) { 48 | return (view, wasCancelled) -> { 49 | if (visibilityEndAction != null) { 50 | visibilityEndAction.onEnd(view, wasCancelled); 51 | } 52 | if (endAction != null) { 53 | endAction.onEnd(view, wasCancelled); 54 | } 55 | }; 56 | } 57 | 58 | @Override 59 | @NonNull 60 | public ViewVisibilityBuilder withStartAction(@Nullable AnimationState.AnimationStartAction startAction) { 61 | return super.withStartAction(getWrappedStartAction(startAction)); 62 | } 63 | 64 | @Override 65 | @NonNull 66 | public ViewVisibilityBuilder withEndAction(@Nullable AnimationState.AnimationEndAction endAction) { 67 | return super.withEndAction(getWrappedEndAction(endAction)); 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /additive_animations/src/main/java/at/wirecube/additiveanimations/helper/AnimationUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 David Ganster 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package at.wirecube.additiveanimations.helper; 18 | 19 | public class AnimationUtils { 20 | 21 | public static float clamp(float from, float to, float value) { 22 | return Math.max(from, Math.min(to, value)); 23 | } 24 | 25 | public static float lerp(float from, float to, float progress) { 26 | return from + (to - from) * clamp(0, 1, progress); 27 | } 28 | 29 | public static float shortestAngleBetween(float start, float dest) { 30 | float diff = dest - start; 31 | if (Math.abs(diff) > 180) { 32 | if (diff > 0) { 33 | diff = diff - 360; 34 | } else { 35 | diff = 360 + diff; 36 | } 37 | } 38 | while (diff > 360) { 39 | diff -= 360; 40 | } 41 | while (diff < -360) { 42 | diff += 360; 43 | } 44 | if (diff > 180) { 45 | diff -= 360; 46 | } 47 | if (diff < -180) { 48 | diff += 360; 49 | } 50 | return diff; 51 | } 52 | 53 | } 54 | 55 | -------------------------------------------------------------------------------- /additive_animations/src/main/java/at/wirecube/additiveanimations/helper/EaseInOutPathInterpolator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 David Ganster 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package at.wirecube.additiveanimations.helper; 18 | 19 | import android.view.animation.Interpolator; 20 | 21 | import androidx.core.view.animation.PathInterpolatorCompat; 22 | 23 | public class EaseInOutPathInterpolator { 24 | public static Interpolator create() { 25 | return PathInterpolatorCompat.create(0.25f, 0.1f, 0.25f, 1.f); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /additive_animations/src/main/java/at/wirecube/additiveanimations/helper/FloatProperty.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 David Ganster 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package at.wirecube.additiveanimations.helper; 18 | 19 | import android.util.Property; 20 | import android.view.View; 21 | 22 | public abstract class FloatProperty extends Property { 23 | 24 | public interface Get { 25 | float get(T object); 26 | } 27 | 28 | public interface Set { 29 | void set(T object, float value); 30 | } 31 | 32 | public static FloatProperty create(Property baseProperty) { 33 | return new FloatProperty(baseProperty.getName()) { 34 | @Override 35 | public void set(T object, Float value) { 36 | baseProperty.set(object, value); 37 | } 38 | 39 | @Override 40 | public Float get(T object) { 41 | return baseProperty.get(object); 42 | } 43 | }; 44 | } 45 | 46 | public static FloatProperty create(String name, Get getter, Set setter) { 47 | return new FloatProperty(name) { 48 | @Override 49 | public void set(T object, Float value) { 50 | setter.set(object, value); 51 | } 52 | 53 | @Override 54 | public Float get(T object) { 55 | return getter.get(object); 56 | } 57 | }; 58 | } 59 | 60 | public FloatProperty(String name) { 61 | super(Float.class, name); 62 | } 63 | 64 | @Override 65 | public abstract void set(T object, Float value); 66 | } 67 | -------------------------------------------------------------------------------- /additive_animations/src/main/java/at/wirecube/additiveanimations/helper/SpringInterpolator.java: -------------------------------------------------------------------------------- 1 | package at.wirecube.additiveanimations.helper; 2 | 3 | import android.view.animation.Interpolator; 4 | 5 | public class SpringInterpolator implements Interpolator { 6 | // curve parameters generated with https://www.desmos.com/calculator/6gbvrm5i0s 7 | private static final double BOUNCY_AMPLITUDE = 0.13; 8 | private static final double BOUNCY_FREQUENCY = 13.5; 9 | 10 | private static final double SOFT_AMPLITUDE = 0.2; 11 | private static final double SOFT_FREQUENCY = 6.8; 12 | 13 | double mAmplitude = BOUNCY_AMPLITUDE; 14 | double mFrequency = BOUNCY_FREQUENCY; 15 | 16 | public SpringInterpolator() {} 17 | 18 | public SpringInterpolator(double amplitude, double frequency) { 19 | mAmplitude = amplitude; 20 | mFrequency = frequency; 21 | } 22 | 23 | public static SpringInterpolator bouncySpring() { 24 | return new SpringInterpolator(BOUNCY_AMPLITUDE, BOUNCY_FREQUENCY); 25 | } 26 | 27 | public static SpringInterpolator softSpring() { 28 | return new SpringInterpolator(SOFT_AMPLITUDE, SOFT_FREQUENCY); 29 | } 30 | 31 | public float getInterpolation(float time) { 32 | return (float) (-1 * Math.pow(Math.E, -time/ mAmplitude) * Math.cos(mFrequency * time) + 1); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /additive_animations/src/main/java/at/wirecube/additiveanimations/helper/evaluators/ColorEvaluator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 David Ganster 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package at.wirecube.additiveanimations.helper.evaluators; 18 | 19 | import android.animation.TypeEvaluator; 20 | 21 | public class ColorEvaluator implements TypeEvaluator{ 22 | 23 | @Override 24 | public Float evaluate(float fraction, Float startValue, Float endValue) { 25 | int startInt = startValue.intValue(); 26 | int startA = (startInt >> 24) & 0xff; 27 | int startR = (startInt >> 16) & 0xff; 28 | int startG = (startInt >> 8) & 0xff; 29 | int startB = startInt & 0xff; 30 | 31 | int endInt = endValue.intValue(); 32 | int endA = (endInt >> 24) & 0xff; 33 | int endR = (endInt >> 16) & 0xff; 34 | int endG = (endInt >> 8) & 0xff; 35 | int endB = endInt & 0xff; 36 | 37 | return (float)(((startA + (int)(fraction * (endA - startA))) << 24) | 38 | ((startR + (int)(fraction * (endR - startR))) << 16) | 39 | ((startG + (int)(fraction * (endG - startG))) << 8) | 40 | ((startB + (int)(fraction * (endB - startB))))); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /additive_animations/src/main/java/at/wirecube/additiveanimations/helper/evaluators/PathEvaluator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 David Ganster 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package at.wirecube.additiveanimations.helper.evaluators; 18 | 19 | import android.graphics.Path; 20 | import android.graphics.PathMeasure; 21 | 22 | 23 | /** 24 | * A custom evaluator only to be used by {@link at.wirecube.additiveanimations.additive_animator.AdditiveAnimation}. 25 | * Use this class if you subclass {@link at.wirecube.additiveanimations.additive_animator.AdditiveAnimator} and want to 26 | * implement animating custom properties along paths. 27 | * It is NOT safe to share objects of this type among different animators since it holds state. 28 | */ 29 | public class PathEvaluator { 30 | 31 | public enum PathMode { 32 | X, Y, ROTATION; 33 | public static PathMode from(int mode) { 34 | switch (mode) { 35 | case 1: return Y; 36 | case 2: return ROTATION; 37 | default: return X; 38 | } 39 | } 40 | } 41 | 42 | private float lastEvaluatedFraction = -1; 43 | private float[] lastPoint = new float[2]; 44 | private float lastAngle = 0; 45 | 46 | private float getResult(PathMode pathMode) { 47 | switch (pathMode) { 48 | case X: 49 | return lastPoint[0]; 50 | case Y: 51 | return lastPoint[1]; 52 | case ROTATION: 53 | return lastAngle; 54 | } 55 | return 0; 56 | } 57 | 58 | public float evaluate(float fraction, PathMode pathMode, Path path) { 59 | if(fraction == lastEvaluatedFraction) { 60 | return getResult(pathMode); 61 | } 62 | float tan[] = new float[2]; 63 | PathMeasure pathMeasure = new PathMeasure(path, true); 64 | pathMeasure.getPosTan(pathMeasure.getLength() * fraction, lastPoint, tan); 65 | lastAngle = (float)(Math.atan2(tan[1], tan[0])*180.0/Math.PI); 66 | lastEvaluatedFraction = fraction; 67 | return getResult(pathMode); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /additive_animations/src/main/java/at/wirecube/additiveanimations/helper/propertywrappers/ColorProperties.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 David Ganster 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package at.wirecube.additiveanimations.helper.propertywrappers; 18 | 19 | import android.graphics.drawable.ColorDrawable; 20 | import android.util.Property; 21 | import android.view.View; 22 | import android.view.ViewGroup; 23 | 24 | import at.wirecube.additiveanimations.helper.FloatProperty; 25 | 26 | public class ColorProperties { 27 | public static Property BACKGROUND_COLOR = new FloatProperty("BACKGROUND_COLOR") { 28 | @Override 29 | public Float get(View object) { 30 | try { 31 | return (float) ((ColorDrawable) object.getBackground()).getColor(); 32 | } catch (ClassCastException ex) { 33 | ex.printStackTrace(); 34 | } catch (NullPointerException ex) { 35 | ex.printStackTrace(); 36 | } 37 | return null; 38 | } 39 | 40 | @Override 41 | public void set(View object, Float value) { 42 | object.setBackgroundColor(value.intValue()); 43 | } 44 | }; 45 | 46 | } 47 | -------------------------------------------------------------------------------- /additive_animations/src/main/java/at/wirecube/additiveanimations/helper/propertywrappers/ElevationProperties.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 David Ganster 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package at.wirecube.additiveanimations.helper.propertywrappers; 18 | 19 | import android.util.Property; 20 | import android.view.View; 21 | 22 | import at.wirecube.additiveanimations.helper.FloatProperty; 23 | 24 | public class ElevationProperties { 25 | 26 | @SuppressWarnings("NewApi") 27 | public static Property ELEVATION = new FloatProperty("ELEVATION") { 28 | @Override 29 | public Float get(View object) { 30 | return object.getElevation(); 31 | } 32 | 33 | @Override 34 | public void set(View object, Float value) { 35 | object.setElevation(value); 36 | } 37 | }; 38 | 39 | } 40 | -------------------------------------------------------------------------------- /additive_animations/src/main/java/at/wirecube/additiveanimations/helper/propertywrappers/MarginProperties.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 David Ganster 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package at.wirecube.additiveanimations.helper.propertywrappers; 18 | 19 | import android.util.Property; 20 | import android.view.View; 21 | import android.view.ViewGroup; 22 | 23 | import at.wirecube.additiveanimations.helper.FloatProperty; 24 | 25 | public class MarginProperties { 26 | 27 | public static Property MARGIN_LEFT = new FloatProperty("MARGIN_LEFT") { 28 | @Override 29 | public Float get(View object) { 30 | return (float) ((ViewGroup.MarginLayoutParams) object.getLayoutParams()).leftMargin; 31 | } 32 | 33 | @Override 34 | public void set(View object, Float value) { 35 | ((ViewGroup.MarginLayoutParams) object.getLayoutParams()).leftMargin = value.intValue(); 36 | } 37 | }; 38 | 39 | public static Property MARGIN_RIGHT = new FloatProperty("MARGIN_RIGHT") { 40 | @Override 41 | public Float get(View object) { 42 | return (float) ((ViewGroup.MarginLayoutParams) object.getLayoutParams()).rightMargin; 43 | } 44 | 45 | @Override 46 | public void set(View object, Float value) { 47 | ((ViewGroup.MarginLayoutParams) object.getLayoutParams()).rightMargin = value.intValue(); 48 | } 49 | }; 50 | 51 | public static Property MARGIN_TOP = new FloatProperty("MARGIN_TOP") { 52 | @Override 53 | public Float get(View object) { 54 | return (float) ((ViewGroup.MarginLayoutParams) object.getLayoutParams()).topMargin; 55 | } 56 | 57 | @Override 58 | public void set(View object, Float value) { 59 | ((ViewGroup.MarginLayoutParams) object.getLayoutParams()).topMargin = value.intValue(); 60 | } 61 | }; 62 | 63 | public static Property MARGIN_BOTTOM = new FloatProperty("MARGIN_BOTTOM") { 64 | @Override 65 | public Float get(View object) { 66 | return (float) ((ViewGroup.MarginLayoutParams) object.getLayoutParams()).bottomMargin; 67 | } 68 | 69 | @Override 70 | public void set(View object, Float value) { 71 | ((ViewGroup.MarginLayoutParams) object.getLayoutParams()).bottomMargin = value.intValue(); 72 | } 73 | }; 74 | } 75 | -------------------------------------------------------------------------------- /additive_animations/src/main/java/at/wirecube/additiveanimations/helper/propertywrappers/PaddingProperties.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 David Ganster 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package at.wirecube.additiveanimations.helper.propertywrappers; 18 | 19 | import android.util.Property; 20 | import android.view.View; 21 | import android.view.ViewGroup; 22 | 23 | import at.wirecube.additiveanimations.helper.FloatProperty; 24 | 25 | public class PaddingProperties { 26 | 27 | public static Property PADDING_LEFT = new FloatProperty("PADDING_LEFT") { 28 | @Override 29 | public Float get(View object) { 30 | return (float) object.getPaddingLeft(); 31 | } 32 | 33 | @Override 34 | public void set(View object, Float value) { 35 | object.setPadding(value.intValue(), object.getPaddingTop(), object.getPaddingRight(), object.getPaddingBottom()); 36 | } 37 | }; 38 | 39 | public static Property PADDING_RIGHT = new FloatProperty("PADDING_RIGHT") { 40 | @Override 41 | public Float get(View object) { 42 | return (float) object.getPaddingRight(); 43 | } 44 | 45 | @Override 46 | public void set(View object, Float value) { 47 | object.setPadding(object.getPaddingLeft(), object.getPaddingTop(), value.intValue(), object.getPaddingBottom()); 48 | } 49 | }; 50 | 51 | public static Property PADDING_TOP = new FloatProperty("PADDING_TOP") { 52 | @Override 53 | public Float get(View object) { 54 | return (float) object.getPaddingTop(); 55 | } 56 | 57 | @Override 58 | public void set(View object, Float value) { 59 | object.setPadding(object.getPaddingLeft(), value.intValue(), object.getPaddingRight(), object.getPaddingBottom()); 60 | } 61 | }; 62 | 63 | public static Property PADDING_BOTTOM = new FloatProperty("PADDING_BOTTOM") { 64 | @Override 65 | public Float get(View object) { 66 | return (float) object.getPaddingBottom(); 67 | } 68 | 69 | @Override 70 | public void set(View object, Float value) { 71 | object.setPadding(object.getPaddingLeft(), object.getPaddingTop(), object.getPaddingRight(), value.intValue()); 72 | } 73 | }; 74 | } 75 | -------------------------------------------------------------------------------- /additive_animations/src/main/java/at/wirecube/additiveanimations/helper/propertywrappers/ScrollProperties.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 David Ganster 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package at.wirecube.additiveanimations.helper.propertywrappers; 18 | 19 | import android.util.Property; 20 | import android.view.View; 21 | 22 | import at.wirecube.additiveanimations.helper.FloatProperty; 23 | 24 | // TODO: this works, but fires the onScrollChanged() event too many times (once for scrollX, once for scrollY). 25 | public class ScrollProperties { 26 | 27 | public static Property SCROLL_X = new FloatProperty("SCROLL_X") { 28 | @Override 29 | public Float get(View object) { 30 | return (float) object.getScrollX(); 31 | } 32 | 33 | @Override 34 | public void set(View object, Float value) { 35 | object.setScrollX(value.intValue()); 36 | } 37 | }; 38 | 39 | public static Property SCROLL_Y = new FloatProperty("SCROLL_Y") { 40 | @Override 41 | public Float get(View object) { 42 | return (float) object.getScrollY(); 43 | } 44 | 45 | @Override 46 | public void set(View object, Float value) { 47 | object.setScrollY(value.intValue()); 48 | } 49 | }; 50 | 51 | } 52 | -------------------------------------------------------------------------------- /additive_animations/src/main/java/at/wirecube/additiveanimations/helper/propertywrappers/SizeProperties.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 David Ganster 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package at.wirecube.additiveanimations.helper.propertywrappers; 18 | 19 | import android.util.Property; 20 | import android.view.View; 21 | 22 | import at.wirecube.additiveanimations.helper.FloatProperty; 23 | 24 | public class SizeProperties { 25 | public static Property WIDTH = new FloatProperty("VIEW_WIDTH") { 26 | @Override 27 | public Float get(View object) { 28 | return (float) (object.getLayoutParams()).width; 29 | } 30 | 31 | @Override 32 | public void set(View object, Float value) { 33 | object.getLayoutParams().width = value.intValue(); 34 | } 35 | }; 36 | 37 | public static Property HEIGHT = new FloatProperty("VIEW_HEIGHT") { 38 | @Override 39 | public Float get(View object) { 40 | return (float) (object.getLayoutParams()).height; 41 | } 42 | 43 | @Override 44 | public void set(View object, Float value) { 45 | object.getLayoutParams().height = value.intValue(); 46 | } 47 | }; 48 | 49 | } 50 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | 4 | buildscript { 5 | repositories { 6 | mavenCentral() 7 | maven { 8 | url "https://maven.google.com" 9 | } 10 | google() 11 | } 12 | configurations { 13 | // javadocDeps 14 | } 15 | dependencies { 16 | classpath 'com.android.tools.build:gradle:4.1.3' 17 | classpath 'com.github.dcendents:android-maven-gradle-plugin:1.4.1' 18 | 19 | // publishing to mavenCentral: 20 | classpath 'com.vanniktech:gradle-maven-publish-plugin:0.15.0' 21 | } 22 | } 23 | 24 | allprojects { 25 | repositories { 26 | mavenCentral() 27 | maven { 28 | url "https://maven.google.com" 29 | } 30 | google() 31 | } 32 | } 33 | 34 | task clean(type: Delete) { 35 | delete rootProject.buildDir 36 | } 37 | 38 | task javadoc(type: Javadoc) { 39 | failOnError false 40 | // classpath += configurations.javadocDeps 41 | } -------------------------------------------------------------------------------- /demo/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /demo/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 30 5 | buildToolsVersion '28.0.3' 6 | 7 | defaultConfig { 8 | applicationId "additive_animations.demo" 9 | minSdkVersion 14 10 | targetSdkVersion 30 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | compileOptions { 21 | sourceCompatibility JavaVersion.VERSION_1_8 22 | targetCompatibility JavaVersion.VERSION_1_8 23 | } 24 | } 25 | 26 | dependencies { 27 | implementation fileTree(include: ['*.jar'], dir: 'libs') 28 | testImplementation 'junit:junit:4.12' 29 | implementation 'androidx.appcompat:appcompat:1.0.0' 30 | implementation 'com.google.android.material:material:1.0.0' 31 | implementation project(':additive_animations') 32 | implementation 'com.bartoszlipinski:viewpropertyobjectanimator:1.4.5' 33 | implementation 'com.google.code.gson:gson:2.8.6' 34 | 35 | debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.4' 36 | } 37 | -------------------------------------------------------------------------------- /demo/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/davidganster/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /demo/src/androidTest/java/at/wirecube/additiveanimations/additiveanimationsdemo/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package at.wirecube.additiveanimations.additiveanimationsdemo; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /demo/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /demo/src/main/java/additive_animations/AAApplication.java: -------------------------------------------------------------------------------- 1 | package additive_animations; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | 6 | public class AAApplication extends Application { 7 | 8 | private static Context sContext; 9 | 10 | @Override 11 | public void onCreate() { 12 | super.onCreate(); 13 | sContext = getApplicationContext(); 14 | } 15 | 16 | public static Context getContext() { 17 | return sContext; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /demo/src/main/java/additive_animations/AdditiveAnimationsShowcaseActivity.java: -------------------------------------------------------------------------------- 1 | package additive_animations; 2 | 3 | import android.os.Bundle; 4 | import com.google.android.material.navigation.NavigationView; 5 | import androidx.core.view.GravityCompat; 6 | import androidx.drawerlayout.widget.DrawerLayout; 7 | import androidx.appcompat.app.ActionBarDrawerToggle; 8 | import androidx.appcompat.app.AppCompatActivity; 9 | import androidx.appcompat.widget.Toolbar; 10 | import android.view.Menu; 11 | import android.view.MenuItem; 12 | import android.view.View; 13 | import android.widget.Switch; 14 | 15 | import additive_animations.fragments.AnimationChainingDemoFragment; 16 | import additive_animations.fragments.CustomAnimationsWithoutSubclassDemoFragment; 17 | import additive_animations.fragments.states.StateDemoFragment; 18 | import additive_animations.fragments.custom_drawing.CustomDrawingFragment; 19 | import additive_animations.fragments.MarginsDemoFragment; 20 | import additive_animations.fragments.MoveAlongPathDemoFragment; 21 | import additive_animations.fragments.MultipleViewsAnimationDemoFragment; 22 | import additive_animations.fragments.RepeatingChainedAnimationsDemoFragment; 23 | import additive_animations.fragments.TapToChangeColorDemoFragment; 24 | import additive_animations.fragments.TapToMoveDemoFragment; 25 | import at.wirecube.additiveanimations.additive_animator.AdditiveAnimator; 26 | import at.wirecube.additiveanimations.additiveanimationsdemo.R; 27 | 28 | public class AdditiveAnimationsShowcaseActivity extends AppCompatActivity 29 | implements NavigationView.OnNavigationItemSelectedListener { 30 | 31 | public static boolean ADDITIVE_ANIMATIONS_ENABLED = true; 32 | 33 | @Override 34 | protected void onCreate(Bundle savedInstanceState) { 35 | super.onCreate(savedInstanceState); 36 | setContentView(R.layout.activity_additive_animations_showcase); 37 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 38 | setSupportActionBar(toolbar); 39 | 40 | DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); 41 | ActionBarDrawerToggle toggle = new ActionBarDrawerToggle( 42 | this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close); 43 | drawer.setDrawerListener(toggle); 44 | toggle.syncState(); 45 | 46 | NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view); 47 | navigationView.setNavigationItemSelectedListener(this); 48 | 49 | // load default fragment = tap to move 50 | getSupportFragmentManager().beginTransaction().add(R.id.fragment_container, new TapToMoveDemoFragment()).commit(); 51 | Switch additiveEnabledSwitch = (Switch) findViewById(R.id.additive_animations_enabled_switch); 52 | additiveEnabledSwitch.setOnClickListener(new View.OnClickListener() { 53 | @Override 54 | public void onClick(View v) { 55 | ADDITIVE_ANIMATIONS_ENABLED = ((Switch)v).isChecked(); 56 | } 57 | }); 58 | 59 | // set the default duration for all animations: 60 | AdditiveAnimator.setDefaultDuration(1000); 61 | } 62 | 63 | @Override 64 | public void onBackPressed() { 65 | DrawerLayout drawer = findViewById(R.id.drawer_layout); 66 | if (drawer.isDrawerOpen(GravityCompat.START)) { 67 | drawer.closeDrawer(GravityCompat.START); 68 | } else { 69 | super.onBackPressed(); 70 | } 71 | } 72 | 73 | @Override 74 | public boolean onCreateOptionsMenu(Menu menu) { 75 | // Inflate the menu; this adds items to the action bar if it is present. 76 | getMenuInflater().inflate(R.menu.additive_animations_showcase, menu); 77 | return true; 78 | } 79 | 80 | @Override 81 | public boolean onOptionsItemSelected(MenuItem item) { 82 | // Handle action bar item clicks here. The action bar will 83 | // automatically handle clicks on the Home/Up button, so long 84 | // as you specify a parent activity in AndroidManifest.xml. 85 | int id = item.getItemId(); 86 | 87 | if (id == R.id.action_settings) { 88 | return true; 89 | } 90 | 91 | return super.onOptionsItemSelected(item); 92 | } 93 | 94 | @Override 95 | public boolean onNavigationItemSelected(MenuItem item) { 96 | int id = item.getItemId(); 97 | 98 | if (id == R.id.nav_tap_to_move) { 99 | getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, new TapToMoveDemoFragment()).commit(); 100 | } else if(id == R.id.nav_multiple_views) { 101 | getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, new MultipleViewsAnimationDemoFragment()).commit(); 102 | } else if(id == R.id.nav_margins) { 103 | getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, new MarginsDemoFragment()).commit(); 104 | } else if(id == R.id.nav_color) { 105 | getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, new TapToChangeColorDemoFragment()).commit(); 106 | } else if(id == R.id.nav_path) { 107 | getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, new MoveAlongPathDemoFragment()).commit(); 108 | } else if(id == R.id.nav_chaining) { 109 | getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, new AnimationChainingDemoFragment()).commit(); 110 | } else if(id == R.id.nav_chaining_repeated) { 111 | getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, new RepeatingChainedAnimationsDemoFragment()).commit(); 112 | } else if(id == R.id.nav_change_text_color) { 113 | getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, new CustomAnimationsWithoutSubclassDemoFragment()).commit(); 114 | } else if(id == R.id.nav_custom_drawing) { 115 | getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, new CustomDrawingFragment()).commit(); 116 | } else if (id == R.id.nav_state) { 117 | getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, new StateDemoFragment()).commit(); 118 | } 119 | 120 | DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); 121 | drawer.closeDrawer(GravityCompat.START); 122 | return true; 123 | } 124 | } -------------------------------------------------------------------------------- /demo/src/main/java/additive_animations/fragments/AnimationChainingDemoFragment.java: -------------------------------------------------------------------------------- 1 | package additive_animations.fragments; 2 | 3 | import android.animation.AnimatorSet; 4 | import android.animation.ObjectAnimator; 5 | import android.animation.PropertyValuesHolder; 6 | import android.os.Bundle; 7 | import androidx.annotation.Nullable; 8 | import androidx.fragment.app.Fragment; 9 | import android.view.LayoutInflater; 10 | import android.view.MotionEvent; 11 | import android.view.View; 12 | import android.view.ViewGroup; 13 | import android.widget.FrameLayout; 14 | 15 | import additive_animations.AdditiveAnimationsShowcaseActivity; 16 | import additive_animations.helper.DpConverter; 17 | import at.wirecube.additiveanimations.additive_animator.AdditiveAnimator; 18 | import at.wirecube.additiveanimations.additiveanimationsdemo.R; 19 | import at.wirecube.additiveanimations.helper.EaseInOutPathInterpolator; 20 | 21 | public class AnimationChainingDemoFragment extends Fragment { 22 | FrameLayout rootView; 23 | View animatedView; 24 | 25 | @Nullable 26 | @Override 27 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 28 | rootView = (FrameLayout) inflater.inflate(R.layout.fragment_tap_to_move_demo, container, false); 29 | animatedView = rootView.findViewById(R.id.animated_view); 30 | 31 | rootView.setOnTouchListener(new View.OnTouchListener() { 32 | @Override 33 | public boolean onTouch(View v, final MotionEvent event) { 34 | if (event.getAction() == MotionEvent.ACTION_MOVE || event.getAction() == MotionEvent.ACTION_DOWN) { 35 | int offset = DpConverter.converDpToPx(150); 36 | if(AdditiveAnimationsShowcaseActivity.ADDITIVE_ANIMATIONS_ENABLED) { 37 | 38 | AdditiveAnimator.animate(animatedView) 39 | .centerX(event.getX()).centerY(event.getY()) 40 | .then() // execute the following animations after the previous ones have finished 41 | .centerX(event.getX() - offset).centerY(event.getY() - offset) 42 | .start(); 43 | 44 | } else { 45 | 46 | PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat(View.TRANSLATION_X, event.getX() - animatedView.getWidth() / 2); 47 | PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, event.getY() - animatedView.getHeight() / 2); 48 | ObjectAnimator animator1 = ObjectAnimator.ofPropertyValuesHolder(animatedView, pvhX, pvhY); 49 | 50 | PropertyValuesHolder pvhX2 = PropertyValuesHolder.ofFloat(View.TRANSLATION_X, (event.getX() - animatedView.getWidth() / 2) - offset); 51 | PropertyValuesHolder pvhY2 = PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, (event.getY() - animatedView.getHeight() / 2) - offset); 52 | ObjectAnimator animator2 = ObjectAnimator.ofPropertyValuesHolder(animatedView, pvhX2, pvhY2); 53 | 54 | AnimatorSet animators = new AnimatorSet(); 55 | animators.playSequentially(animator1, animator2); 56 | animators.setDuration(2000); 57 | animators.setInterpolator(EaseInOutPathInterpolator.create()); 58 | animators.start(); 59 | } 60 | } 61 | return true; 62 | } 63 | }); 64 | return rootView; 65 | } 66 | 67 | @Override 68 | public void onDestroyView() { 69 | super.onDestroyView(); 70 | AdditiveAnimator.cancelAnimationsForObject(animatedView); 71 | } 72 | } -------------------------------------------------------------------------------- /demo/src/main/java/additive_animations/fragments/CustomAnimationsWithoutSubclassDemoFragment.java: -------------------------------------------------------------------------------- 1 | package additive_animations.fragments; 2 | 3 | import android.os.Bundle; 4 | import androidx.annotation.Nullable; 5 | import androidx.fragment.app.Fragment; 6 | import androidx.interpolator.view.animation.LinearOutSlowInInterpolator; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.Button; 11 | import android.widget.TextView; 12 | 13 | import at.wirecube.additiveanimations.additive_animator.AdditiveAnimator; 14 | import at.wirecube.additiveanimations.additiveanimationsdemo.R; 15 | import at.wirecube.additiveanimations.helper.FloatProperty; 16 | import at.wirecube.additiveanimations.helper.evaluators.ColorEvaluator; 17 | 18 | public class CustomAnimationsWithoutSubclassDemoFragment extends Fragment { 19 | ViewGroup rootView; 20 | 21 | @Nullable 22 | @Override 23 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 24 | rootView = (ViewGroup) inflater.inflate(R.layout.fragment_text_view_color_demo, container, false); 25 | final TextView animatedView = (TextView) rootView.findViewById(R.id.animated_text_view); 26 | Button button = (Button) rootView.findViewById(R.id.change_color_button); 27 | 28 | button.setOnClickListener(new View.OnClickListener() { 29 | int currentColor = 0; 30 | final int colors[] = new int[] { 31 | getResources().getColor(R.color.niceBlue), 32 | getResources().getColor(R.color.niceGreen), 33 | getResources().getColor(R.color.nicePink), 34 | getResources().getColor(R.color.niceOrange) 35 | }; 36 | @Override 37 | public void onClick(View v) { 38 | AdditiveAnimator.animate(animatedView).setInterpolator(new LinearOutSlowInInterpolator()) 39 | .property(colors[currentColor++ % 4], new ColorEvaluator(), new FloatProperty("TextColorAnimationTag") { 40 | @Override 41 | public Float get(View object) { 42 | return Float.valueOf(animatedView.getCurrentTextColor()); 43 | } 44 | 45 | @Override 46 | public void set(View object, Float value) { 47 | animatedView.setTextColor(value.intValue()); 48 | } 49 | }).start(); 50 | } 51 | }); 52 | return rootView; 53 | } 54 | } -------------------------------------------------------------------------------- /demo/src/main/java/additive_animations/fragments/MarginsDemoFragment.java: -------------------------------------------------------------------------------- 1 | package additive_animations.fragments; 2 | 3 | import android.os.Bundle; 4 | import androidx.annotation.Nullable; 5 | import androidx.fragment.app.Fragment; 6 | import android.view.LayoutInflater; 7 | import android.view.MotionEvent; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | 11 | import com.bartoszlipinski.viewpropertyobjectanimator.ViewPropertyObjectAnimator; 12 | 13 | import additive_animations.AdditiveAnimationsShowcaseActivity; 14 | import at.wirecube.additiveanimations.additive_animator.AdditiveAnimator; 15 | import at.wirecube.additiveanimations.additiveanimationsdemo.R; 16 | import at.wirecube.additiveanimations.helper.EaseInOutPathInterpolator; 17 | 18 | public class MarginsDemoFragment extends Fragment { 19 | ViewGroup rootView; 20 | View animatedView; 21 | @Nullable 22 | @Override 23 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 24 | rootView = (ViewGroup) inflater.inflate(R.layout.fragment_margins_demo, container, false); 25 | animatedView = rootView.findViewById(R.id.animated_view_with_margins); 26 | 27 | rootView.setOnTouchListener((v, event) -> { 28 | if (event.getAction() == MotionEvent.ACTION_MOVE || event.getAction() == MotionEvent.ACTION_DOWN) { 29 | if(AdditiveAnimationsShowcaseActivity.ADDITIVE_ANIMATIONS_ENABLED) { 30 | AdditiveAnimator.animate(animatedView).leftMargin((int) event.getX()).topMargin((int) event.getY()).start(); 31 | } else { 32 | ViewPropertyObjectAnimator.animate(animatedView).leftMargin((int) event.getX()).topMargin((int) event.getY()).setInterpolator(EaseInOutPathInterpolator.create()).setDuration(1000).start(); 33 | } 34 | } 35 | return true; 36 | }); 37 | return rootView; 38 | } 39 | } -------------------------------------------------------------------------------- /demo/src/main/java/additive_animations/fragments/MoveAlongPathDemoFragment.java: -------------------------------------------------------------------------------- 1 | package additive_animations.fragments; 2 | 3 | import android.animation.ValueAnimator; 4 | import android.graphics.Path; 5 | import android.os.Bundle; 6 | 7 | import androidx.annotation.Nullable; 8 | import androidx.fragment.app.Fragment; 9 | import android.view.LayoutInflater; 10 | import android.view.MotionEvent; 11 | import android.view.View; 12 | import android.view.ViewGroup; 13 | import android.view.animation.LinearInterpolator; 14 | import android.widget.FrameLayout; 15 | 16 | import additive_animations.AdditiveAnimationsShowcaseActivity; 17 | import additive_animations.helper.DpConverter; 18 | import at.wirecube.additiveanimations.additive_animator.AdditiveAnimator; 19 | import at.wirecube.additiveanimations.additiveanimationsdemo.R; 20 | 21 | public class MoveAlongPathDemoFragment extends Fragment { 22 | FrameLayout rootView; 23 | View animatedView; 24 | 25 | int circleRadius = DpConverter.converDpToPx(50); 26 | 27 | @Nullable 28 | @Override 29 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 30 | rootView = (FrameLayout) inflater.inflate(R.layout.fragment_move_along_path_demo, container, false); 31 | animatedView = rootView.findViewById(R.id.animated_view); 32 | 33 | rootView.setOnTouchListener((v, event) -> { 34 | if (event.getAction() == MotionEvent.ACTION_MOVE || event.getAction() == MotionEvent.ACTION_DOWN) { 35 | if(AdditiveAnimationsShowcaseActivity.ADDITIVE_ANIMATIONS_ENABLED) { 36 | AdditiveAnimator.animate(animatedView) 37 | .x(event.getX()) 38 | .y(event.getY()) 39 | .start(); 40 | } 41 | } 42 | return true; 43 | }); 44 | 45 | // wait for rootView to layout itself so we can get its center 46 | rootView.post(() -> { 47 | if (AdditiveAnimationsShowcaseActivity.ADDITIVE_ANIMATIONS_ENABLED) { 48 | // small circle 49 | final Path path1 = new Path(); 50 | path1.addCircle(rootView.getWidth() / 2, rootView.getHeight() / 2, circleRadius, Path.Direction.CW); 51 | AdditiveAnimator.animate(animatedView).setInterpolator(new LinearInterpolator()) 52 | .xyAlongPath(path1) 53 | .setRepeatCount(ValueAnimator.INFINITE) 54 | .start(); 55 | 56 | // another circle which also updates rotation to better show where on the path we are 57 | final Path path2 = new Path(); 58 | path2.addCircle(rootView.getWidth() / 2, rootView.getHeight() / 2, rootView.getWidth() / 3, Path.Direction.CW); 59 | AdditiveAnimator.animate(animatedView).setDuration(3200).setInterpolator(new LinearInterpolator()) 60 | .xyRotationAlongPath(path2) 61 | .setRepeatCount(ValueAnimator.INFINITE) 62 | .start(); 63 | } else { 64 | // TODO 65 | } 66 | }); 67 | return rootView; 68 | } 69 | 70 | @Override 71 | public void onDestroyView() { 72 | super.onDestroyView(); 73 | AdditiveAnimator.cancelAnimationsForObject(animatedView); 74 | } 75 | } -------------------------------------------------------------------------------- /demo/src/main/java/additive_animations/fragments/MultipleViewsAnimationDemoFragment.java: -------------------------------------------------------------------------------- 1 | package additive_animations.fragments; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.os.Bundle; 5 | import androidx.annotation.Nullable; 6 | import androidx.fragment.app.Fragment; 7 | import android.view.LayoutInflater; 8 | import android.view.MotionEvent; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | import android.widget.FrameLayout; 12 | 13 | import com.bartoszlipinski.viewpropertyobjectanimator.ViewPropertyObjectAnimator; 14 | 15 | import java.util.ArrayList; 16 | import java.util.Arrays; 17 | import java.util.Date; 18 | import java.util.List; 19 | 20 | import additive_animations.AdditiveAnimationsShowcaseActivity; 21 | import at.wirecube.additiveanimations.additive_animator.AdditiveAnimator; 22 | import at.wirecube.additiveanimations.additiveanimationsdemo.R; 23 | 24 | public class MultipleViewsAnimationDemoFragment extends Fragment { 25 | FrameLayout rootView; 26 | int rotation = 0; 27 | 28 | List views = new ArrayList<>(); 29 | 30 | @SuppressLint("ClickableViewAccessibility") 31 | @Nullable 32 | @Override 33 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 34 | rootView = (FrameLayout) inflater.inflate(R.layout.fragment_multiple_views_demo, container, false); 35 | views = Arrays.asList( 36 | rootView.findViewById(R.id.animated_view3), rootView.findViewById(R.id.animated_view5), 37 | rootView.findViewById(R.id.animated_view6), rootView.findViewById(R.id.animated_view7), 38 | rootView.findViewById(R.id.animated_view8), rootView.findViewById(R.id.animated_view9), 39 | rootView.findViewById(R.id.animated_view10), rootView.findViewById(R.id.animated_view11), 40 | rootView.findViewById(R.id.animated_view12), rootView.findViewById(R.id.animated_view13), 41 | rootView.findViewById(R.id.animated_view14), rootView.findViewById(R.id.animated_view15), 42 | rootView.findViewById(R.id.animated_view16), rootView.findViewById(R.id.animated_view17), 43 | rootView.findViewById(R.id.animated_view18), rootView.findViewById(R.id.animated_view19), 44 | rootView.findViewById(R.id.animated_view20), rootView.findViewById(R.id.animated_view21), 45 | rootView.findViewById(R.id.animated_view22), rootView.findViewById(R.id.animated_view23), 46 | rootView.findViewById(R.id.animated_view24), rootView.findViewById(R.id.animated_view25)); 47 | 48 | for(View v : views) { 49 | v.setAlpha(0.2f); 50 | } 51 | 52 | final int blue = getResources().getColor(R.color.niceBlue); 53 | final int pink = getResources().getColor(R.color.nicePink); 54 | 55 | rootView.setOnTouchListener(new View.OnTouchListener() { 56 | Date lastTouchEvent = new Date(); 57 | @Override 58 | public boolean onTouch(View v, MotionEvent event) { 59 | if (event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_MOVE || event.getAction() == MotionEvent.ACTION_UP) { 60 | if((new Date().getTime() - lastTouchEvent.getTime()) < 200 && event.getAction() != MotionEvent.ACTION_UP) { 61 | // Throttle a little - a single call of this function enqueues about 100 animators, 62 | // which means we have about 6000 animators after one second of dragging around. 63 | // Unfortunately, that's too much for the current animation system to handle, 64 | // because creating ValueAnimators is a very expensive operation. 65 | // There is some work on using a single shared value animator for all AdditiveAnimator instances, 66 | // but it's extremely difficult to handle repeat modes correctly, and performance is not really an issue at the moment anyway. 67 | return true; 68 | } 69 | lastTouchEvent = new Date(); 70 | float x = event.getX(); 71 | float y = event.getY(); 72 | 73 | if(event.getAction() == MotionEvent.ACTION_UP && AdditiveAnimationsShowcaseActivity.ADDITIVE_ANIMATIONS_ENABLED) { 74 | // snap to 360° only when using additive animations - you won't ever see the views rotate without additive animations otherwise. 75 | rotation = 0; 76 | } else if(x < rootView.getWidth()/2.0) { 77 | rotation -= 30; 78 | } else { 79 | rotation += 30; 80 | } 81 | 82 | long animationStagger = 50; 83 | if(AdditiveAnimationsShowcaseActivity.ADDITIVE_ANIMATIONS_ENABLED) { 84 | // what this line does: 85 | // 1. Create a new additive animator that targets all View objects in the views array. 86 | // The animationStagger describes how much delay should occur between the animations of each animated View. 87 | // 2. Adding some animations to the view: x(), y(), rotation(). 88 | // These animations apply to all views that were previously targeted. 89 | // 3. thenWithDelay() enqueues new animations after the specified delay. 90 | // All targets that we previously set are still valid, and the stagger between the views is preserved. 91 | // Example: views[0] started with 0ms delay, views[1] started with 50ms delay. 92 | // By calling thenWithDelay(200), the next animations for views[0] will run at 200ms, the ones for view[1] will run at 250ms. 93 | // 4. start() starts the entire block on animations. 94 | AdditiveAnimator.animate(views, animationStagger) 95 | .x(x).y(y).rotation(rotation) 96 | .thenWithDelay(200).scale(1.5f).backgroundColor(blue) 97 | .thenWithDelay(200).scale(1.f).backgroundColor(pink) 98 | .start(); 99 | } else { 100 | // This approximates the animation code from above, but is much more verbose and doesn't even really work: 101 | // ValueAnimator can't handle startDelays very gracefully, so you'll see a lot of jumping when dragging your finger across the screen. 102 | // We also can't do background color, and the two scale animations will overwrite one another. 103 | for(int i = 0; i < views.size(); i++) { 104 | ViewPropertyObjectAnimator.animate(views.get(i)) 105 | .setStartDelay(animationStagger * i) 106 | .setDuration(1000) 107 | .x(x).y(y).rotation(rotation) 108 | .start(); 109 | 110 | ViewPropertyObjectAnimator.animate(views.get(i)) 111 | .setStartDelay(animationStagger * i + 200) 112 | .setDuration(1000) 113 | .scales(1.5f) 114 | // no support for background color :/ 115 | .start(); 116 | 117 | ViewPropertyObjectAnimator.animate(views.get(i)) 118 | .setStartDelay(animationStagger * i + 200 + 200) 119 | .setDuration(1000) 120 | .scales(1.f) 121 | // no support for background color :/ 122 | .start(); 123 | } 124 | } 125 | } 126 | return true; 127 | } 128 | }); 129 | return rootView; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /demo/src/main/java/additive_animations/fragments/RepeatingChainedAnimationsDemoFragment.java: -------------------------------------------------------------------------------- 1 | package additive_animations.fragments; 2 | 3 | import android.os.Bundle; 4 | import androidx.annotation.Nullable; 5 | import androidx.fragment.app.Fragment; 6 | import android.view.LayoutInflater; 7 | import android.view.MotionEvent; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | 11 | import additive_animations.helper.DpConverter; 12 | import at.wirecube.additiveanimations.additive_animator.AdditiveAnimator; 13 | import additive_animations.subclass.AdditiveAnimatorSubclassDemo; 14 | import at.wirecube.additiveanimations.additiveanimationsdemo.R; 15 | 16 | public class RepeatingChainedAnimationsDemoFragment extends Fragment { 17 | ViewGroup rootView; 18 | View animatedView; 19 | 20 | @Nullable 21 | @Override 22 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 23 | rootView = (ViewGroup) inflater.inflate(R.layout.fragment_tap_to_move_demo, container, false); 24 | animatedView = rootView.findViewById(R.id.animated_view); 25 | 26 | rootView.setOnTouchListener((v, event) -> { 27 | if(event.getAction() == MotionEvent.ACTION_DOWN) { 28 | AdditiveAnimator.cancelAnimation(animatedView, View.ROTATION); 29 | } 30 | return true; 31 | }); 32 | 33 | animate(); 34 | return rootView; 35 | } 36 | 37 | private void animate() { 38 | int[] colors = new int[] { 39 | getResources().getColor(R.color.niceOrange), 40 | getResources().getColor(R.color.niceBlue), 41 | getResources().getColor(R.color.niceGreen), 42 | getResources().getColor(R.color.nicePink) 43 | }; 44 | // TODO: don't use hardcoded px values 45 | AdditiveAnimatorSubclassDemo.animate(animatedView) 46 | .x(px(50)).y(px(100)).backgroundColor(colors[1]).rotation(0) 47 | .thenBounceBeforeEnd(800, 300) 48 | .thenBeforeEnd(400).x(px(250)).backgroundColor(colors[2]).rotationBy(45).setDuration(1000) 49 | .thenBounceBeforeEnd(800, 300) 50 | .thenBeforeEnd(400).y(px(500)).backgroundColor(colors[3]).rotationBy(45).setDuration(1000) 51 | .thenBounceBeforeEnd(800, 300) 52 | .thenBeforeEnd(400).x(px(50)).backgroundColor(colors[0]).rotationBy(90).setDuration(1000) 53 | .thenBounceBeforeEnd(800, 300) 54 | .addEndAction(wasCancelled -> { 55 | if (getActivity() != null) { 56 | animate(); 57 | } 58 | }) 59 | .start(); 60 | } 61 | 62 | private int px(int dp) { 63 | return DpConverter.converDpToPx(dp); 64 | } 65 | 66 | @Override 67 | public void onDestroyView() { 68 | super.onDestroyView(); 69 | AdditiveAnimator.cancelAnimationsForObject(animatedView); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /demo/src/main/java/additive_animations/fragments/TapToChangeColorDemoFragment.java: -------------------------------------------------------------------------------- 1 | package additive_animations.fragments; 2 | 3 | import android.animation.ArgbEvaluator; 4 | import android.animation.ObjectAnimator; 5 | import android.graphics.drawable.ColorDrawable; 6 | import android.os.Bundle; 7 | import androidx.annotation.Nullable; 8 | import androidx.fragment.app.Fragment; 9 | import android.view.LayoutInflater; 10 | import android.view.View; 11 | import android.view.ViewGroup; 12 | 13 | import additive_animations.AdditiveAnimationsShowcaseActivity; 14 | import additive_animations.subclass.AdditiveAnimatorSubclassDemo; 15 | import at.wirecube.additiveanimations.additiveanimationsdemo.R; 16 | import at.wirecube.additiveanimations.helper.EaseInOutPathInterpolator; 17 | 18 | public class TapToChangeColorDemoFragment extends Fragment { 19 | ViewGroup rootView; 20 | View animatedView; 21 | 22 | int index = 0; 23 | 24 | @Nullable 25 | @Override 26 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 27 | rootView = (ViewGroup) inflater.inflate(R.layout.fragment_tap_to_change_color_demo, container, false); 28 | animatedView = rootView.findViewById(R.id.animated_view); 29 | return rootView; 30 | } 31 | 32 | @Override 33 | public void onActivityCreated(@Nullable Bundle savedInstanceState) { 34 | super.onActivityCreated(savedInstanceState); 35 | 36 | // AdditiveAnimatorSubclassDemo.animate(animatedView).startPulsing(); 37 | 38 | final int colors[] = new int[] { 39 | getResources().getColor(R.color.niceOrange), 40 | getResources().getColor(R.color.niceBlue), 41 | getResources().getColor(R.color.niceGreen), 42 | getResources().getColor(R.color.nicePink) 43 | }; 44 | animatedView.setOnClickListener(new View.OnClickListener() { 45 | @Override 46 | public void onClick(View v) { 47 | if(AdditiveAnimationsShowcaseActivity.ADDITIVE_ANIMATIONS_ENABLED) { 48 | AdditiveAnimatorSubclassDemo.animate(v).backgroundColor(colors[++index % 4]).start(); 49 | } else { 50 | final ObjectAnimator backgroundColorAnimator = ObjectAnimator.ofObject(animatedView, 51 | "backgroundColor", 52 | new ArgbEvaluator(), 53 | ((ColorDrawable)v.getBackground()).getColor(), 54 | colors[++index % 4]); 55 | backgroundColorAnimator.setDuration(1000); 56 | backgroundColorAnimator.setInterpolator(EaseInOutPathInterpolator.create()); 57 | backgroundColorAnimator.start(); 58 | } 59 | } 60 | }); 61 | } 62 | 63 | @Override 64 | public void onDestroy() { 65 | // AdditiveAnimatorSubclassDemo.animate(animatedView).stopPulsing(); 66 | super.onDestroy(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /demo/src/main/java/additive_animations/fragments/TapToMoveDemoFragment.java: -------------------------------------------------------------------------------- 1 | package additive_animations.fragments; 2 | 3 | import android.os.Bundle; 4 | import androidx.annotation.Nullable; 5 | import androidx.fragment.app.Fragment; 6 | import android.view.LayoutInflater; 7 | import android.view.MotionEvent; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.FrameLayout; 11 | 12 | import additive_animations.AdditiveAnimationsShowcaseActivity; 13 | import at.wirecube.additiveanimations.additive_animator.AdditiveAnimator; 14 | import at.wirecube.additiveanimations.additiveanimationsdemo.R; 15 | import at.wirecube.additiveanimations.helper.EaseInOutPathInterpolator; 16 | 17 | public class TapToMoveDemoFragment extends Fragment { 18 | private FrameLayout rootView; 19 | private View animatedView; 20 | private View mTouchView; 21 | 22 | @Nullable 23 | @Override 24 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 25 | rootView = (FrameLayout) inflater.inflate(R.layout.fragment_tap_to_move_demo, container, false); 26 | mTouchView = rootView.findViewById(R.id.touch_view); 27 | animatedView = rootView.findViewById(R.id.animated_view); 28 | 29 | rootView.setOnTouchListener((v, event) -> { 30 | if (event.getAction() == MotionEvent.ACTION_MOVE || event.getAction() == MotionEvent.ACTION_DOWN) { 31 | AdditiveAnimator.cancelAnimationsForObject(mTouchView); 32 | mTouchView.setAlpha(1); 33 | mTouchView.setX(event.getX() - mTouchView.getWidth()/2); 34 | mTouchView.setY(event.getY() - mTouchView.getHeight()/2); 35 | if(AdditiveAnimationsShowcaseActivity.ADDITIVE_ANIMATIONS_ENABLED) { 36 | AdditiveAnimator.animate(animatedView) 37 | .centerX(event.getX()) 38 | // uncomment the next line to see how you can use a different interpolator for each property! 39 | // .switchInterpolator(new BounceInterpolator()) 40 | .centerY(event.getY()) 41 | .start(); 42 | 43 | } else { 44 | animatedView.animate().setInterpolator(EaseInOutPathInterpolator.create()).setDuration(1000) 45 | .x(event.getX() - animatedView.getWidth() / 2) 46 | .y(event.getY() - animatedView.getHeight() / 2) 47 | .start(); 48 | } 49 | } 50 | if(event.getAction() == MotionEvent.ACTION_UP) { 51 | AdditiveAnimator.animate(mTouchView, 100).alpha(0).start(); 52 | } 53 | return true; 54 | }); 55 | return rootView; 56 | } 57 | } -------------------------------------------------------------------------------- /demo/src/main/java/additive_animations/fragments/custom_drawing/AdditiveRectAnimator.java: -------------------------------------------------------------------------------- 1 | package additive_animations.fragments.custom_drawing; 2 | 3 | import at.wirecube.additiveanimations.additive_animator.BaseAdditiveAnimator; 4 | import at.wirecube.additiveanimations.helper.FloatProperty; 5 | 6 | /** 7 | * Example of an animator subclass that works with a class which doesn't derive from `View`. 8 | * This example shows off both property- and tag-based animations. 9 | * Tag-based animations use only a String tag to identify them, but their value must be manually manipulated by the AdditiveAnimator subclass. 10 | * Property-based animations are easier to use, but they require a getter and setter for each property. 11 | * In principle, property-based animations should be favored because they make the rest of the implementation simpler and 12 | * more reusable - but it comes at the cost of more verbosity when declaring the properties. 13 | */ 14 | public class AdditiveRectAnimator extends BaseAdditiveAnimator { 15 | 16 | @Override 17 | protected AdditiveRectAnimator newInstance() { 18 | return new AdditiveRectAnimator(); 19 | } 20 | 21 | public static AdditiveRectAnimator animate(Rect rect) { 22 | return new AdditiveRectAnimator().target(rect); 23 | } 24 | 25 | public AdditiveRectAnimator size(float size) { 26 | // AdditiveAnimation objects can (preferably) be created using a FloatProperty object. 27 | // In this case, you don't need to do anything else to make your property animatable! 28 | return property(size, FloatProperty.create("RectSize", rect -> rect.mSize, (rect, s) -> rect.mSize = s)); 29 | } 30 | 31 | public AdditiveRectAnimator cornerRadius(float cornerRadius) { 32 | return property(cornerRadius, FloatProperty.create("RectCornerRadius", rect -> rect.mCornerRadius, (rect, cr) -> rect.mCornerRadius = cr)); 33 | } 34 | 35 | public AdditiveRectAnimator rotation(float rotation) { 36 | return property(rotation, FloatProperty.create("RectRotation", rect -> rect.mRotation, (rect, r) -> rect.mRotation = r)); 37 | } 38 | 39 | public AdditiveRectAnimator x(float x) { 40 | return property(x, FloatProperty.create("RectX", rect -> rect.mX, (rect, xVal) -> rect.mX = xVal)); 41 | } 42 | 43 | public AdditiveRectAnimator y(float y) { 44 | return property(y, FloatProperty.create("RectY", rect -> rect.mY, (rect, yVal) -> rect.mY = yVal)); 45 | } 46 | 47 | // This method is called after the current frame has been calculated. 48 | // You have to make sure to invalidate the your view/custom object here! 49 | @Override 50 | public void onApplyChanges() { 51 | // force redraw of the parent view: 52 | if (getCurrentTarget().getView() != null) { 53 | getCurrentTarget().getView().invalidate(); 54 | } 55 | } 56 | 57 | @Override 58 | public Float getCurrentPropertyValue(String propertyName) { 59 | // only necessary when implementing non-property-based (tag-based) animations, which is highly discouraged. 60 | return null; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /demo/src/main/java/additive_animations/fragments/custom_drawing/CustomDrawingFragment.java: -------------------------------------------------------------------------------- 1 | package additive_animations.fragments.custom_drawing; 2 | 3 | import android.os.Bundle; 4 | import androidx.annotation.Nullable; 5 | import androidx.fragment.app.Fragment; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | 10 | import at.wirecube.additiveanimations.additiveanimationsdemo.R; 11 | 12 | 13 | public class CustomDrawingFragment extends Fragment { 14 | 15 | @Nullable 16 | @Override 17 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 18 | ViewGroup root = (ViewGroup) inflater.inflate(R.layout.fragment_custom_drawing, container, false); 19 | View v = new DemoView(root.getContext()); 20 | root.addView(v); 21 | return root; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /demo/src/main/java/additive_animations/fragments/custom_drawing/DemoView.java: -------------------------------------------------------------------------------- 1 | package additive_animations.fragments.custom_drawing; 2 | 3 | import android.animation.ValueAnimator; 4 | import android.content.Context; 5 | import android.graphics.Canvas; 6 | import android.graphics.Paint; 7 | import android.os.Build; 8 | import android.view.MotionEvent; 9 | import android.view.View; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | import additive_animations.helper.DpConverter; 15 | import at.wirecube.additiveanimations.additive_animator.AdditiveObjectAnimator; 16 | import at.wirecube.additiveanimations.additive_animator.ViewAnimationApplier; 17 | import at.wirecube.additiveanimations.additiveanimationsdemo.R; 18 | import at.wirecube.additiveanimations.helper.FloatProperty; 19 | import at.wirecube.additiveanimations.helper.evaluators.ColorEvaluator; 20 | 21 | public class DemoView extends View { 22 | 23 | final List mRects = new ArrayList<>(); 24 | final List mPaints = new ArrayList<>(); 25 | 26 | public DemoView(Context context) { 27 | super(context); 28 | for (int i = 0; i < 5; i++) { 29 | Rect rect = new Rect(this); 30 | mRects.add(rect); 31 | mPaints.add(rect.mPaint); 32 | } 33 | 34 | // Helper to tell AdditiveObjectAnimator how to make our changes visible. 35 | // In our case, we just want to invalidate ourselves to trigger a redraw of the canvas. 36 | Runnable animationApplier = new ViewAnimationApplier(this); 37 | 38 | long delayBetweenAnimations = 100; 39 | 40 | // Use the custom subclass to animate size and corner radius of all rects 41 | new AdditiveRectAnimator().setDuration(1000).setRepeatCount(ValueAnimator.INFINITE).setRepeatMode(ValueAnimator.REVERSE) 42 | .targets(mRects, delayBetweenAnimations) 43 | .size(DpConverter.converDpToPx(80)) 44 | .cornerRadius(DpConverter.converDpToPx(50)) 45 | .start(); 46 | 47 | // Default object animator to animate all the paints: 48 | new AdditiveObjectAnimator() 49 | .setDuration(1000) 50 | .setRepeatCount(ValueAnimator.INFINITE) 51 | .setRepeatMode(ValueAnimator.REVERSE) 52 | .setAnimationApplier(animationApplier) 53 | .targets(mPaints, delayBetweenAnimations) 54 | .property(context.getResources().getColor(R.color.niceGreen), new ColorEvaluator(), 55 | // creating an inline property to use for the animation - very convenient when you don't want to create a subclass just for a single custom animation: 56 | FloatProperty.create("PaintColor", paint -> (float) paint.getColor(), (paint, color) -> paint.setColor((int) color))) 57 | .start(); 58 | 59 | setOnTouchListener(new OnTouchListener() { 60 | float rotationTarget = 0; 61 | 62 | @Override 63 | public boolean onTouch(View view, MotionEvent event) { 64 | if (event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_MOVE) { 65 | if (event.getX() >= getRootView().getWidth() / 2) { 66 | rotationTarget += 10; 67 | } else { 68 | rotationTarget -= 10; 69 | } 70 | // moving the custom-drawn view additively around the canvas just like a normal view: 71 | new AdditiveRectAnimator().targets(mRects, 50).x(event.getX()).y(event.getY()).rotation(rotationTarget).start(); 72 | } 73 | return true; 74 | } 75 | }); 76 | } 77 | 78 | @Override 79 | protected void onDetachedFromWindow() { 80 | super.onDetachedFromWindow(); 81 | AdditiveRectAnimator.cancelAnimationsInCollection(mRects); 82 | AdditiveRectAnimator.cancelAnimationsInCollection(mPaints); 83 | 84 | for(Rect rect : mRects) { 85 | rect.clearView(); 86 | } 87 | } 88 | 89 | @Override 90 | protected void onDraw(Canvas canvas) { 91 | super.onDraw(canvas); 92 | 93 | for (Rect rect : mRects) { 94 | canvas.save(); 95 | 96 | // make sure we rotate around the center 97 | canvas.translate(rect.mX, rect.mY); 98 | canvas.rotate(rect.mRotation); 99 | 100 | float hs = rect.mSize / 2; 101 | 102 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 103 | // the canvas center is now at mRect.mX/mRect.mY, so we need to draw ourselves from -size/2 to size/2. 104 | canvas.drawRoundRect(-hs, -hs, hs, hs, rect.mCornerRadius, rect.mCornerRadius, rect.mPaint); 105 | } else { 106 | // No rounded corners for API <= 21 :( 107 | canvas.drawRect(-hs, -hs, hs, hs, rect.mPaint); 108 | } 109 | 110 | canvas.restore(); 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /demo/src/main/java/additive_animations/fragments/custom_drawing/Rect.java: -------------------------------------------------------------------------------- 1 | package additive_animations.fragments.custom_drawing; 2 | 3 | import android.graphics.Paint; 4 | import android.view.View; 5 | 6 | import additive_animations.helper.DpConverter; 7 | import at.wirecube.additiveanimations.additiveanimationsdemo.R; 8 | 9 | public class Rect { 10 | private View mView; 11 | final Paint mPaint; 12 | float mRotation = 0; 13 | float mX = DpConverter.converDpToPx(60); 14 | float mY = DpConverter.converDpToPx(120); 15 | float mSize = DpConverter.converDpToPx(100); 16 | float mCornerRadius = 0; 17 | 18 | public Rect(View parent) { 19 | mView = parent; 20 | mPaint = new Paint(); 21 | mPaint.setStyle(Paint.Style.FILL); 22 | mPaint.setColor(parent.getContext().getResources().getColor(R.color.niceBlue)); 23 | } 24 | 25 | public View getView() { 26 | return mView; 27 | } 28 | 29 | public void clearView() { 30 | mView = null; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /demo/src/main/java/additive_animations/fragments/states/CustomViewStateAnimation.java: -------------------------------------------------------------------------------- 1 | package additive_animations.fragments.states; 2 | 3 | import android.view.View; 4 | 5 | import at.wirecube.additiveanimations.additive_animator.animation_set.AnimationAction; 6 | import at.wirecube.additiveanimations.additive_animator.animation_set.AnimationState; 7 | import at.wirecube.additiveanimations.additive_animator.view_visibility.ViewVisibilityAnimation; 8 | 9 | public class CustomViewStateAnimation { 10 | 11 | public static AnimationState getCustomGoneAnimation() { 12 | return ViewVisibilityAnimation.gone() 13 | .addAnimations( 14 | new AnimationAction.Animation<>(View.ALPHA, 0f), 15 | new AnimationAction.Animation<>(View.ROTATION, 90), 16 | new AnimationAction.Animation<>(View.SCALE_X, 0.1f), 17 | new AnimationAction.Animation<>(View.SCALE_Y, 0.1f) 18 | ) 19 | // this shows how to attach a custom end action to any state AnimationState builder: 20 | // .withEndAction((view, wasCancelled) -> { 21 | // Toast.makeText(view.getContext(), "EndAction is called", Toast.LENGTH_SHORT).show(); 22 | // }) 23 | .build(); 24 | } 25 | 26 | public static AnimationState getCustomVisibleAnimation() { 27 | return ViewVisibilityAnimation.visible() 28 | .addAnimations( 29 | new AnimationAction.Animation<>(View.ALPHA, 1f), 30 | new AnimationAction.Animation<>(View.ROTATION, 0f), 31 | new AnimationAction.Animation<>(View.SCALE_X, 1f), 32 | new AnimationAction.Animation<>(View.SCALE_Y, 1f), 33 | new AnimationAction.Animation<>(View.TRANSLATION_X, 0f) 34 | ) 35 | // this shows how to attach a custom start action to any state AnimationState builder: 36 | // .withStartAction(view -> { 37 | // Toast.makeText(view.getContext(), "StartAction is called", Toast.LENGTH_SHORT).show(); 38 | // }) 39 | .build(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /demo/src/main/java/additive_animations/fragments/states/StateDemoFragment.java: -------------------------------------------------------------------------------- 1 | package additive_animations.fragments.states; 2 | 3 | import android.os.Bundle; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | 8 | import androidx.annotation.Nullable; 9 | import androidx.appcompat.widget.SwitchCompat; 10 | import androidx.fragment.app.Fragment; 11 | 12 | import at.wirecube.additiveanimations.additive_animator.AdditiveAnimator; 13 | import at.wirecube.additiveanimations.additive_animator.animation_set.AnimationState; 14 | import at.wirecube.additiveanimations.additive_animator.view_visibility.ViewVisibilityAnimation; 15 | import at.wirecube.additiveanimations.additiveanimationsdemo.R; 16 | 17 | public class StateDemoFragment extends Fragment { 18 | 19 | View rootView; 20 | View view1; 21 | View view2; 22 | View view3; 23 | SwitchCompat mUseCustomTransitionSwitch; 24 | 25 | private boolean mFirstClick = false; 26 | private boolean mUseCustomTransition = false; 27 | 28 | private AnimationState getGoneAnim() { 29 | return mUseCustomTransition ? CustomViewStateAnimation.getCustomGoneAnimation() : ViewVisibilityAnimation.fadeOutAndTranslateX(true, 100f); 30 | } 31 | 32 | private AnimationState getVisibleAnim() { 33 | return CustomViewStateAnimation.getCustomVisibleAnimation(); 34 | } 35 | 36 | @Nullable 37 | @Override 38 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 39 | rootView = inflater.inflate(R.layout.fragment_state_demo, container, false); 40 | view1 = rootView.findViewById(R.id.animated_view); 41 | view2 = rootView.findViewById(R.id.animated_view2); 42 | view3 = rootView.findViewById(R.id.animated_view3); 43 | 44 | mUseCustomTransitionSwitch = rootView.findViewById(R.id.sdf_custom_transition_switch); 45 | 46 | mUseCustomTransitionSwitch.setOnCheckedChangeListener((compoundButton, b) -> mUseCustomTransition = b); 47 | 48 | // A state can be set without any animation by using the `apply()` method - this will 49 | // cause the animation start/end actions to be called immediately. 50 | AdditiveAnimator.apply(getVisibleAnim(), view1, view2); 51 | AdditiveAnimator.apply(getGoneAnim(), view3); 52 | 53 | rootView.setOnClickListener(view -> { 54 | mFirstClick = !mFirstClick; 55 | if (mFirstClick) { 56 | AdditiveAnimator.animate(view1, view2) 57 | .setDuration(300) 58 | .visibility(getGoneAnim()) 59 | .thenWithDelay(50).target(view3) 60 | .visibility(getVisibleAnim()) 61 | .start(); 62 | } else { 63 | AdditiveAnimator.animate(view3) 64 | .setDuration(300) 65 | .visibility(getGoneAnim()) 66 | .thenWithDelay(50).targets(view1, view2) 67 | .visibility(getVisibleAnim()) 68 | .start(); 69 | } 70 | }); 71 | return rootView; 72 | } 73 | } -------------------------------------------------------------------------------- /demo/src/main/java/additive_animations/helper/DpConverter.java: -------------------------------------------------------------------------------- 1 | package additive_animations.helper; 2 | 3 | import android.util.TypedValue; 4 | 5 | import additive_animations.AAApplication; 6 | 7 | public class DpConverter { 8 | 9 | public static int converDpToPx(float dp) { 10 | float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, AAApplication.getContext().getResources().getDisplayMetrics()); 11 | return Math.round(px); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /demo/src/main/java/additive_animations/subclass/AdditiveAnimatorSubclassDemo.java: -------------------------------------------------------------------------------- 1 | package additive_animations.subclass; 2 | 3 | import android.view.View; 4 | 5 | import at.wirecube.additiveanimations.additive_animator.AnimationEndListener; 6 | import at.wirecube.additiveanimations.additive_animator.SubclassableAdditiveViewAnimator; 7 | 8 | public class AdditiveAnimatorSubclassDemo extends SubclassableAdditiveViewAnimator { 9 | 10 | public static AdditiveAnimatorSubclassDemo animate(View v) { 11 | return new AdditiveAnimatorSubclassDemo().target(v); 12 | } 13 | 14 | public AdditiveAnimatorSubclassDemo() { super(); } 15 | 16 | @Override 17 | protected AdditiveAnimatorSubclassDemo newInstance() { 18 | return new AdditiveAnimatorSubclassDemo(); 19 | } 20 | 21 | static boolean isPulsing = false; 22 | 23 | public void startPulsing() { 24 | setDuration(3000); 25 | getCurrentTarget().setScaleX(0); 26 | getCurrentTarget().setScaleY(0); 27 | getCurrentTarget().setAlpha(1f); 28 | addEndAction(new AnimationEndListener() { 29 | @Override 30 | public void onAnimationEnd(boolean wasCancelled) { 31 | if (isPulsing) { 32 | AdditiveAnimatorSubclassDemo.animate(getCurrentTarget()).startPulsing(); 33 | } 34 | } 35 | }); 36 | alpha(0); 37 | scale(1); 38 | start(); 39 | isPulsing = true; 40 | } 41 | 42 | public void stopPulsing() { 43 | isPulsing = false; 44 | } 45 | 46 | 47 | public AdditiveAnimatorSubclassDemo thenBounceBeforeEnd(int millis, long duration) { 48 | return thenBeforeEnd(millis).scale(1.2f).setDuration(duration) 49 | .thenBeforeEnd(100).scale(0.8f) 50 | .thenBeforeEnd(100).scale(1f); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /demo/src/main/res/drawable/round_25dp_radius.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /demo/src/main/res/drawable/round_blue_12_point_5_dp_radius.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /demo/src/main/res/drawable/side_nav_bar.xml: -------------------------------------------------------------------------------- 1 | 3 | 9 | -------------------------------------------------------------------------------- /demo/src/main/res/layout/activity_additive_animations_showcase.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 10 | 11 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /demo/src/main/res/layout/app_bar_additive_animations_showcase.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 11 | 12 | 15 | 16 | 17 | 18 | 22 | 23 | 24 | 29 | 37 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /demo/src/main/res/layout/fragment_custom_drawing.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /demo/src/main/res/layout/fragment_margins_demo.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 18 | 19 | 20 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /demo/src/main/res/layout/fragment_move_along_path_demo.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /demo/src/main/res/layout/fragment_multiple_views_demo.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 19 | 20 | 21 | 29 | 30 | 38 | 39 | 47 | 48 | 56 | 57 | 65 | 66 | 74 | 75 | 83 | 84 | 92 | 93 | 101 | 102 | 110 | 111 | 119 | 120 | 128 | 129 | 137 | 138 | 146 | 147 | 155 | 156 | 164 | 165 | 173 | 174 | 182 | 183 | 191 | 192 | 200 | 201 | 209 | 210 | 211 | 212 | -------------------------------------------------------------------------------- /demo/src/main/res/layout/fragment_state_demo.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 18 | 19 | 26 | 27 | 34 | 35 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /demo/src/main/res/layout/fragment_tap_to_change_color_demo.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /demo/src/main/res/layout/fragment_tap_to_move_demo.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 17 | 18 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /demo/src/main/res/layout/fragment_text_view_color_demo.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 21 | 22 | 23 |