├── .circleci └── config.yml ├── .github └── FUNDING.yml ├── .gitignore ├── README.md ├── animator ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── richpathanimator │ │ ├── AnimationBuilder.kt │ │ ├── AnimationListener.kt │ │ ├── AnimationUpdateListener.kt │ │ ├── PathEvaluator.kt │ │ ├── RepeatMode.kt │ │ └── RichPathAnimator.kt │ └── res │ └── values │ └── strings.xml ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── richpathanimator │ │ └── sample │ │ ├── AnimationSamplesActivity.java │ │ ├── CompoundViewSamplesActivity.java │ │ └── MainActivity.java │ └── res │ ├── drawable │ ├── animal.xml │ ├── color_picker.xml │ ├── face_love.xml │ ├── ic_android.xml │ ├── ic_arrow_search.xml │ ├── ic_command.xml │ ├── ic_notifications.xml │ └── ic_playlist_add_check.xml │ ├── layout │ ├── activity_animation_samples.xml │ ├── activity_compound_view_samples.xml │ └── activity_main.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml │ └── xml │ └── square.xml ├── appkt ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── richpathanimator │ │ └── sample │ │ ├── AnimationSamplesActivity.kt │ │ ├── CompoundViewSamplesActivity.kt │ │ └── MainActivity.kt │ └── res │ ├── drawable │ ├── animal.xml │ ├── color_picker.xml │ ├── face_love.xml │ ├── ic_android.xml │ ├── ic_arrow_search.xml │ ├── ic_command.xml │ ├── ic_notifications.xml │ └── ic_playlist_add_check.xml │ ├── layout │ ├── activity_animation_samples.xml │ ├── activity_compound_view_samples.xml │ └── activity_main.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ └── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── buildSrc ├── build.gradle └── src │ └── main │ └── kotlin │ └── com │ └── richbuild │ └── KoshryTask.kt ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── richpath ├── .gitignore ├── build.gradle ├── consumer-proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── richpath │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── richpath │ │ │ ├── RichPath.kt │ │ │ ├── RichPathDrawable.kt │ │ │ ├── RichPathView.kt │ │ │ ├── listener │ │ │ └── OnRichPathUpdatedListener.kt │ │ │ ├── model │ │ │ ├── Group.kt │ │ │ └── Vector.kt │ │ │ ├── pathparser │ │ │ ├── PathDataNode.kt │ │ │ ├── PathParser.kt │ │ │ ├── PathParserCompat.kt │ │ │ └── PathParserCompatApi21.kt │ │ │ └── util │ │ │ ├── PathUtils.kt │ │ │ ├── Utils.kt │ │ │ └── XmlParser.kt │ └── res │ │ └── values │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── kotlin │ └── com │ └── richpath │ └── util │ └── UtilsTest.kt ├── screenshots ├── animal_path_morphing.gif ├── header.gif ├── ic_notifications.gif ├── ic_notifications.png └── samples.gif └── settings.gradle /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | docker: 5 | - image: circleci/android:api-27-alpha 6 | 7 | working_directory: ~/richpath 8 | 9 | environment: 10 | JVM_OPTS: -Xmx3200m 11 | CIRCLE_JDK_VERSION: oraclejdk8 12 | GRADLE_OPTS: -Dorg.gradle.daemon=false 13 | 14 | steps: 15 | - checkout 16 | 17 | - restore_cache: 18 | key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }} 19 | 20 | - run: 21 | name: Accept licenses 22 | command: yes | sdkmanager --licenses || true 23 | 24 | - run: 25 | name: Build 26 | command: ./gradlew test --stacktrace 27 | 28 | - run: 29 | name: Koshry 30 | command: ./gradlew koshry 31 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | custom: ["https://www.paypal.me/wahba360"] 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | *.apk 3 | .gradle 4 | local.properties 5 | .idea/ 6 | git 7 | .DS_Store 8 | /build 9 | */build/* 10 | /captures 11 | .externalNativeBuild 12 | *.jks -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## *** Library maintenance is paused until further notice! *** 2 | 3 | 4 | 5 | 6 | 7 | 8 | [![CircleCI](https://circleci.com/gh/tarek360/RichPath.svg?style=svg)](https://circleci.com/gh/tarek360/RichPath) 9 | [![Release](https://jitpack.io/v/tarek360/RichPath.svg)](https://jitpack.io/#tarek360/RichPath) ![API](https://img.shields.io/badge/API-14%2B-brightgreen.svg?style=flat) [![Twitter URL](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=https://github.com/tarek360/RichPath) 10 | 11 | 💪 Rich Android Path. 🤡 Draw as you want. 🎉 Animate much as you can. 12 | 13 | ### Download sample app: 14 | 15 | 16 | 17 | 18 | ### Features 19 | 20 | - **Full Animation Control on Paths and VectorDrawables:** 21 | Animate any attribute in a specific path in the VectorDrawable 22 | 23 | `fillColor`, `strokeColor`, `strokeAlpha`, `fillAlpha`, `size`, `width`, `height`, `scale`, `scaleX`, `scaleY`, `rotation`, `translationX`, `translationY`, `trimPathStart`, `trimPathEnd`, `trimPathOffset`. 24 | 25 | - **Path morphing:** 26 | 27 | 28 | 29 | ```Java 30 | RichPathAnimator.animate(richPath) 31 | .pathData(pathData1, pathData2, ...) 32 | .start(); 33 | ``` 34 | 35 | ## Just 3 Steps to Animate any path. 36 | 37 | #### 1. In your layout. 38 | ```xml 39 | 43 | ``` 44 | 45 | #### 2. Find your richPath. 46 | ```java 47 | // by path name 48 | RichPath richPath = richPathView.findRichPathByName("path_name"); 49 | // or if it contains one path 50 | RichPath richPath = richPathView.findFirstRichPath(); 51 | // or by index 52 | RichPath richPath = richPathView.findRichPathByIndex(0); 53 | ``` 54 | 55 | #### 3. Use the RichPathAnimator to animate your richPath. 56 | ```java 57 | RichPathAnimator.animate(richPath) 58 | .trimPathEnd(value1, value2, ...) 59 | .fillColor(value1, value2, ...) 60 | .start(); 61 | ``` 62 | 63 | ## Example 64 | 65 | #### notification icon vector drawable 66 | 67 | 68 | ```xml 69 | 74 | 75 | 78 | 82 | 83 | 87 | 88 | 89 | ``` 90 | 91 | #### XML 92 | ```xml 93 | 98 | ``` 99 | 100 | #### Java 101 | 102 | 103 | ```java 104 | RichPath top = notificationsRichPathView.findRichPathByName("top"); 105 | RichPath bottom = notificationsRichPathView.findRichPathByName("bottom"); 106 | 107 | RichPathAnimator.animate(top) 108 | .interpolator(new DecelerateInterpolator()) 109 | .rotation(0, 20, -20, 10, -10, 5, -5, 2, -2, 0) 110 | .duration(4000) 111 | .andAnimate(bottom) 112 | .interpolator(new DecelerateInterpolator()) 113 | .rotation(0, 10, -10, 5, -5, 2, -2, 0) 114 | .startDelay(50) 115 | .duration(4000) 116 | .start(); 117 | ``` 118 | 119 | 120 | ### Installation 121 | 122 | Add the following dependency to your module `build.gradle` file: 123 | ```gradle 124 | dependencies { 125 | ... 126 | implementation 'com.github.tarek360.RichPath:animator:0.1.1' 127 | } 128 | ``` 129 | 130 | Add this to your root `build.gradle` file (**not** your module `build.gradle` file) : 131 | ```gradle 132 | allprojects { 133 | repositories { 134 | ... 135 | maven { url "https://jitpack.io" } 136 | } 137 | } 138 | ``` 139 | 140 | ### More Control by the RichPathAnimator 141 | 142 | - **Animate multiple paths sequentially or at the same time** 143 | ```java 144 | RichPathAnimator 145 | .animate(richPath1, richPath2) 146 | .rotation(value1, value2, ...) 147 | 148 | //Animate the same path or another with different animated attributes. 149 | .andAnimate(richPath3) 150 | .scale(value1, value2, ...) 151 | 152 | //Animate after the end of the last animation. 153 | .thenAnimate(richPath4) 154 | .strokeColor(value1, value2, ...) 155 | 156 | // start your animation 🎉 157 | .start(); 158 | ``` 159 | 160 | - **Which one of the paths is clicked?** 161 | ```java 162 | richPathView.setOnPathClickListener(new RichPath.OnPathClickListener() { 163 | @Override 164 | public void onClick(RichPath richPath) { 165 | if (richPath.getName().equals("path_name")) { 166 | //TODO do an action when a specific path is clicked. 167 | } 168 | } 169 | }); 170 | ``` 171 | 172 | ## TODO 173 | 174 | - ~Clickable path~ (Done) 175 | - Support clip-path 176 | - Path animation (animate a RichPath on a path) 177 | - Reverse animation 178 | - ... 179 | 180 | If you have any suggestion please open an issue for it. 181 | 182 | ## Credits 183 | 184 | - [florent37](https://github.com/florent37) He is the creator of [ViewAnimator](https://github.com/florent37/ViewAnimator) which gave me the idea of this project. Some core concepts and ideas were reused, but everything is written from scratch. 185 | - [Android](https://android.com/) Some code is reused form the android source code and the VectorDrawableCompat support library. 186 | - [Alex Lockwood](https://github.com/alexjlockwood) The paths of the morphing sample is extracted from the [Shape Shifter](https://github.com/alexjlockwood/ShapeShifter) demo. 187 | 188 | ## Developed By 189 | 190 | * Ahmed Tarek 191 | * [tarek360.github.io](http://tarek360.github.io/) 192 | * [Twitter](https://twitter.com/a_tarek360) 193 | 194 | 195 | ## License 196 | 197 | >Copyright 2017 Tarek360 198 | 199 | >Licensed under the Apache License, Version 2.0 (the "License"); 200 | you may not use this file except in compliance with the License. 201 | You may obtain a copy of the License at 202 | 203 | > http://www.apache.org/licenses/LICENSE-2.0 204 | 205 | >Unless required by applicable law or agreed to in writing, software 206 | distributed under the License is distributed on an "AS IS" BASIS, 207 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 208 | See the License for the specific language governing permissions and 209 | limitations under the License. 210 | -------------------------------------------------------------------------------- /animator/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /animator/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android-extensions' 3 | apply plugin: 'kotlin-android' 4 | apply plugin: 'com.github.dcendents.android-maven' 5 | group = 'com.github.tarek360' 6 | 7 | android { 8 | compileSdkVersion rootProject.ext.compileSdkVersion 9 | buildToolsVersion rootProject.ext.buildToolsVersion 10 | 11 | defaultConfig { 12 | minSdkVersion rootProject.ext.minSdkVersion 13 | targetSdkVersion rootProject.ext.targetSdkVersion 14 | versionCode 1 15 | versionName "1.0" 16 | 17 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 18 | 19 | } 20 | buildTypes { 21 | release { 22 | minifyEnabled false 23 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 24 | } 25 | } 26 | } 27 | 28 | dependencies { 29 | implementation fileTree(dir: 'libs', include: ['*.jar']) 30 | implementation "androidx.appcompat:appcompat:$appcompatVersion" 31 | implementation project(path: ':richpath') 32 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 33 | 34 | androidTestImplementation("com.android.support.test.espresso:espresso-core:$espressoVersion", { 35 | exclude group: 'com.android.support', module: 'support-annotations' 36 | }) 37 | testImplementation "junit:junit:$junitVersion" 38 | } 39 | -------------------------------------------------------------------------------- /animator/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/tarek/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 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /animator/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /animator/src/main/java/com/richpathanimator/AnimationBuilder.kt: -------------------------------------------------------------------------------- 1 | package com.richpathanimator 2 | 3 | import android.animation.ArgbEvaluator 4 | import android.animation.ObjectAnimator 5 | import android.animation.ValueAnimator 6 | import android.util.Log 7 | import android.view.animation.Interpolator 8 | import com.richpath.RichPath 9 | import com.richpath.pathparser.PathDataNode 10 | import com.richpath.pathparser.PathParserCompat 11 | 12 | class AnimationBuilder(private val richPathAnimator: RichPathAnimator, 13 | private val paths: Array) { 14 | 15 | companion object { 16 | private const val DEFAULT_DURATION = 300L 17 | private const val DEFAULT_START_DELAY = 0L 18 | } 19 | 20 | val animators = arrayListOf() 21 | 22 | private var duration = DEFAULT_DURATION 23 | private var startDelay = DEFAULT_START_DELAY 24 | private var interpolator: Interpolator? = null 25 | private var repeatMode: RepeatMode = RepeatMode.Restart 26 | private var repeatCount = 0 27 | 28 | private fun property(propertyName: String, vararg values: Float) { 29 | for (path in paths) { 30 | val objectAnimator = ObjectAnimator.ofFloat(path, propertyName, *values) 31 | applyAnimatorProperties(objectAnimator, path) 32 | } 33 | } 34 | 35 | fun andAnimate(vararg paths: RichPath): AnimationBuilder { 36 | return richPathAnimator.addAnimationBuilder(paths) 37 | } 38 | 39 | fun thenAnimate(vararg paths: RichPath): AnimationBuilder { 40 | return richPathAnimator.thenAnimate(paths) 41 | } 42 | 43 | /** 44 | * Custom animation builder. 45 | * 46 | * @param listener the AnimationUpdateListener 47 | * @param values A set of values that the animation will animate between over time. 48 | */ 49 | fun custom(listener: AnimationUpdateListener?, vararg values: Float) = apply { 50 | for (path in paths) { 51 | ValueAnimator.ofFloat(*values).apply { 52 | addUpdateListener { animation -> 53 | listener?.update(path, animation.animatedValue as Float) 54 | path.onRichPathUpdatedListener?.onPathUpdated() 55 | } 56 | applyAnimatorProperties(this, path) 57 | } 58 | } 59 | } 60 | 61 | fun start(): RichPathAnimator { 62 | richPathAnimator.start() 63 | return richPathAnimator 64 | } 65 | 66 | @Deprecated("It doesn't make sense to cancel while you are still building") 67 | fun cancel() { 68 | richPathAnimator.cancel() 69 | } 70 | 71 | fun duration(duration: Long) = apply { 72 | this.duration = duration 73 | for (animator in animators) { 74 | animator.duration = duration 75 | } 76 | } 77 | 78 | fun durationSet(duration: Long) = apply { 79 | richPathAnimator.duration = duration 80 | } 81 | 82 | fun startDelay(startDelay: Long) = apply { 83 | this.startDelay = startDelay 84 | for (animator in animators) { 85 | animator.startDelay = startDelay 86 | } 87 | } 88 | 89 | fun startDelaySet(startDelay: Long) = apply { 90 | richPathAnimator.startDelay = startDelay 91 | } 92 | 93 | fun interpolator(interpolator: Interpolator) = apply { 94 | this.interpolator = interpolator 95 | for (animator in animators) { 96 | animator.interpolator = interpolator 97 | } 98 | } 99 | 100 | fun interpolatorSet(interpolator: Interpolator) = apply { 101 | richPathAnimator.interpolator = interpolator 102 | } 103 | 104 | fun repeatMode(repeatMode: RepeatMode) = apply { 105 | this.repeatMode = repeatMode 106 | for (animator in animators) { 107 | animator.repeatMode = repeatMode.value 108 | } 109 | } 110 | 111 | fun repeatModeSet(repeatMode: RepeatMode) = apply { 112 | richPathAnimator.repeatMode = repeatMode 113 | } 114 | 115 | fun repeatCount(repeatCount: Int) = apply { 116 | this.repeatCount = repeatCount 117 | for (animator in animators) { 118 | animator.repeatCount = repeatCount 119 | } 120 | } 121 | 122 | fun repeatCountSet(repeatCount: Int) = apply { 123 | richPathAnimator.repeatCount = repeatCount 124 | } 125 | 126 | fun fillColor(vararg colors: Int) = apply { 127 | color("fillColor", *colors) 128 | } 129 | 130 | fun strokeColor(vararg colors: Int) = apply { 131 | color("strokeColor", *colors) 132 | } 133 | 134 | private fun color(propertyName: String, vararg colors: Int) = apply { 135 | for (path in paths) { 136 | val objectAnimator = ObjectAnimator.ofInt(path, propertyName, *colors) 137 | objectAnimator.setEvaluator(ArgbEvaluator()) 138 | applyAnimatorProperties(objectAnimator, path) 139 | } 140 | } 141 | 142 | fun pathData(vararg pathData: String) = apply { 143 | val pathDataNodes = arrayListOf>() 144 | for (i in pathData.indices) { 145 | PathParserCompat.createNodesFromPathData(pathData[i])?.let { 146 | pathDataNodes.add(it) 147 | } 148 | } 149 | 150 | val pathDataNodesArray = pathDataNodes.toTypedArray() 151 | if (!PathParserCompat.canMorph(pathDataNodesArray)) { 152 | Log.w("RichPathAnimator", "the paths aren't compatible for morphing. The paths should have exact same length of commands, and exact same length of parameters for each command") 153 | return@apply 154 | } 155 | 156 | for (path in paths) { 157 | val objectAnimator = ObjectAnimator.ofObject(path, 158 | "pathDataNodes", PathEvaluator(), *pathDataNodesArray) 159 | applyAnimatorProperties(objectAnimator, path) 160 | } 161 | } 162 | 163 | private fun applyAnimatorProperties(animator: ValueAnimator, path: RichPath?) { 164 | path ?: return 165 | animator.duration = duration 166 | animator.startDelay = startDelay 167 | animator.repeatMode = repeatMode.value 168 | animator.repeatCount = repeatCount 169 | interpolator?.let { animator.interpolator = it } 170 | //add animator to the animators list 171 | this.animators.add(animator) 172 | } 173 | 174 | fun strokeAlpha(vararg alpha: Float) = apply { 175 | property("strokeAlpha", *alpha) 176 | } 177 | 178 | fun fillAlpha(vararg alpha: Float) = apply { 179 | property("fillAlpha", *alpha) 180 | } 181 | 182 | fun size(width: Float, height: Float) = apply { 183 | property("width", width) 184 | property("height", height) 185 | } 186 | 187 | fun scaleX(vararg values: Float) = apply { 188 | property("scaleX", *values) 189 | } 190 | 191 | fun scaleY(vararg values: Float) = apply { 192 | property("scaleY", *values) 193 | } 194 | 195 | fun scale(vararg values: Float) = apply { 196 | scaleX(*values) 197 | scaleY(*values) 198 | } 199 | 200 | fun width(vararg values: Float) = apply { 201 | property("width", *values) 202 | } 203 | 204 | fun height(vararg values: Float) = apply { 205 | property("height", *values) 206 | } 207 | 208 | fun rotation(vararg values: Float) = apply { 209 | property("rotation", *values) 210 | } 211 | 212 | fun translationY(vararg values: Float) = apply { 213 | property("translationY", *values) 214 | } 215 | 216 | fun translationX(vararg values: Float) = apply { 217 | property("translationX", *values) 218 | } 219 | 220 | fun trimPathStart(vararg values: Float) = apply { 221 | property("trimPathStart", *values) 222 | } 223 | 224 | fun trimPathEnd(vararg values: Float) = apply { 225 | property("trimPathEnd", *values) 226 | } 227 | 228 | fun trimPathOffset(vararg values: Float) = apply { 229 | property("trimPathOffset", *values) 230 | } 231 | 232 | fun animationListener(listener: AnimationListener) = apply { 233 | richPathAnimator.animationListener = listener 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /animator/src/main/java/com/richpathanimator/AnimationListener.kt: -------------------------------------------------------------------------------- 1 | package com.richpathanimator 2 | 3 | interface AnimationListener { 4 | 5 | fun onStart() 6 | 7 | fun onStop() 8 | 9 | } 10 | -------------------------------------------------------------------------------- /animator/src/main/java/com/richpathanimator/AnimationUpdateListener.kt: -------------------------------------------------------------------------------- 1 | package com.richpathanimator 2 | 3 | import com.richpath.RichPath 4 | 5 | interface AnimationUpdateListener { 6 | 7 | /** 8 | * Callback method to get the current animated path and the current animated value. 9 | * 10 | * @param path the current animated path 11 | * @param value the current animated value. 12 | */ 13 | fun update(path: RichPath, value: Float) 14 | } 15 | -------------------------------------------------------------------------------- /animator/src/main/java/com/richpathanimator/PathEvaluator.kt: -------------------------------------------------------------------------------- 1 | package com.richpathanimator 2 | 3 | import android.animation.TypeEvaluator 4 | import com.richpath.pathparser.PathDataNode 5 | import com.richpath.pathparser.PathParserCompat 6 | 7 | class PathEvaluator: TypeEvaluator?> { 8 | private var evaluatedNodes: Array? = null 9 | 10 | override fun evaluate(fraction: Float, startPathDataNodes: Array?, endPathDataNodes: Array?): Array? { 11 | if (startPathDataNodes == null || endPathDataNodes == null) return null 12 | val evaluatedNodes = this.evaluatedNodes ?: PathParserCompat.deepCopyNodes(startPathDataNodes) 13 | this.evaluatedNodes = evaluatedNodes 14 | 15 | val startNodeSize = startPathDataNodes.size 16 | for (i in 0 until startNodeSize) { 17 | val nodeParamSize = startPathDataNodes[i].params.size 18 | for (j in 0 until nodeParamSize) { 19 | val startFloat = startPathDataNodes[i].params[j] 20 | val value = startFloat + fraction * (endPathDataNodes[i].params[j] - startFloat) 21 | evaluatedNodes[i].params[j] = value 22 | } 23 | } 24 | return evaluatedNodes 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /animator/src/main/java/com/richpathanimator/RepeatMode.kt: -------------------------------------------------------------------------------- 1 | package com.richpathanimator 2 | 3 | sealed class RepeatMode(val value: Int) { 4 | object None : RepeatMode(-2) 5 | object Restart : RepeatMode(1) 6 | object Reverse : RepeatMode(2) 7 | } 8 | -------------------------------------------------------------------------------- /animator/src/main/java/com/richpathanimator/RichPathAnimator.kt: -------------------------------------------------------------------------------- 1 | package com.richpathanimator 2 | 3 | import android.animation.Animator 4 | import android.animation.AnimatorSet 5 | import android.view.animation.Interpolator 6 | import com.richpath.RichPath 7 | 8 | class RichPathAnimator { 9 | 10 | var duration = -1L 11 | var startDelay = -1L 12 | var interpolator: Interpolator? = null 13 | var repeatMode: RepeatMode = RepeatMode.None 14 | var repeatCount: Int? = null 15 | var animationListener: AnimationListener? = null 16 | 17 | private val animationBuilders = arrayListOf() 18 | 19 | private var animatorSet: AnimatorSet? = null 20 | 21 | private var prev: RichPathAnimator? = null 22 | private var next: RichPathAnimator? = null 23 | 24 | companion object { 25 | const val INFINITE = -1 26 | 27 | @JvmField 28 | val NONE = RepeatMode.None 29 | @JvmField 30 | val RESTART = RepeatMode.Restart 31 | @JvmField 32 | val REVERSE = RepeatMode.Reverse 33 | 34 | @JvmStatic 35 | fun animate(vararg paths: RichPath): AnimationBuilder { 36 | val viewAnimator = RichPathAnimator() 37 | return viewAnimator.addAnimationBuilder(paths) 38 | } 39 | 40 | } 41 | 42 | internal fun addAnimationBuilder(paths: Array): AnimationBuilder { 43 | val animationBuilder = AnimationBuilder(this, paths) 44 | animationBuilders.add(animationBuilder) 45 | return animationBuilder 46 | } 47 | 48 | internal fun thenAnimate(paths: Array): AnimationBuilder { 49 | val nextRichPathAnimator = RichPathAnimator() 50 | this.next = nextRichPathAnimator 51 | nextRichPathAnimator.prev = this 52 | return nextRichPathAnimator.addAnimationBuilder(paths) 53 | } 54 | 55 | private fun createAnimatorSet(): AnimatorSet { 56 | val animators = arrayListOf() 57 | for (animationBuilder in animationBuilders) { 58 | val animatorList = animationBuilder.animators 59 | animators.addAll(animatorList) 60 | if (repeatMode != RepeatMode.None) { 61 | animationBuilder.repeatMode(repeatMode) 62 | } 63 | repeatCount?.let(animationBuilder::repeatCount) 64 | } 65 | 66 | val animatorSet = AnimatorSet() 67 | animatorSet.playTogether(animators) 68 | 69 | if (duration != -1L) { 70 | animatorSet.duration = duration 71 | } 72 | if (startDelay != -1L) { 73 | animatorSet.startDelay = startDelay 74 | } 75 | interpolator?.let { animatorSet.interpolator = it } 76 | 77 | animatorSet.addListener(object : Animator.AnimatorListener { 78 | override fun onAnimationStart(animation: Animator?) { 79 | animationListener?.onStart() 80 | } 81 | 82 | override fun onAnimationEnd(animation: Animator?) { 83 | animationListener?.onStop() 84 | next?.let { 85 | it.prev = null 86 | it.start() 87 | } 88 | } 89 | 90 | override fun onAnimationCancel(animation: Animator?) { 91 | } 92 | 93 | override fun onAnimationRepeat(animation: Animator?) { 94 | } 95 | }) 96 | 97 | return animatorSet 98 | } 99 | 100 | fun start(): RichPathAnimator { 101 | prev?.start() ?: run { 102 | animatorSet = createAnimatorSet().apply { 103 | start() 104 | } 105 | } 106 | return this 107 | } 108 | 109 | fun cancel() { 110 | animatorSet?.let { 111 | if (it.isRunning) { 112 | it.cancel() 113 | } 114 | } 115 | 116 | next?.let { 117 | it.cancel() 118 | next = null 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /animator/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | RichPathAnimator 3 | 4 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion rootProject.ext.compileSdkVersion 5 | buildToolsVersion rootProject.ext.buildToolsVersion 6 | defaultConfig { 7 | applicationId "com.pathanimator.sample" 8 | minSdkVersion rootProject.ext.minSdkVersion 9 | targetSdkVersion rootProject.ext.targetSdkVersion 10 | versionCode 4 11 | versionName "0.1.0" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | vectorDrawables.useSupportLibrary = true 14 | } 15 | buildTypes { 16 | release { 17 | minifyEnabled false 18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 19 | } 20 | } 21 | } 22 | 23 | dependencies { 24 | implementation fileTree(dir: 'libs', include: ['*.jar']) 25 | androidTestImplementation("com.android.support.test.espresso:espresso-core:$espressoVersion", { 26 | exclude group: 'com.android.support', module: 'support-annotations' 27 | }) 28 | 29 | implementation "androidx.appcompat:appcompat:$appcompatVersion" 30 | implementation "androidx.constraintlayout:constraintlayout:2.0.0-beta4" 31 | implementation "androidx.core:core-ktx:$ktxVersion" 32 | 33 | testImplementation "junit:junit:$junitVersion" 34 | implementation project(path: ':richpath') 35 | implementation project(path: ':animator') 36 | } 37 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/tarek/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 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/richpathanimator/sample/AnimationSamplesActivity.java: -------------------------------------------------------------------------------- 1 | package com.richpathanimator.sample; 2 | 3 | import android.os.Bundle; 4 | import android.view.View; 5 | import android.view.animation.DecelerateInterpolator; 6 | import android.view.animation.LinearInterpolator; 7 | 8 | import androidx.appcompat.app.AppCompatActivity; 9 | 10 | import com.richpath.RichPath; 11 | import com.richpath.RichPathView; 12 | import com.richpathanimator.RichPathAnimator; 13 | 14 | public class AnimationSamplesActivity extends AppCompatActivity { 15 | 16 | private RichPathView commandRichPathView; 17 | private RichPathView arrowSearchRichPathView; 18 | private RichPathView notificationsRichPathView; 19 | private RichPathView playlistAddCheckRichPathView; 20 | private RichPathView loveFaceRichPathView; 21 | private RichPathView animalRichPathView; 22 | private boolean reverse = false; 23 | 24 | @Override 25 | protected void onCreate(Bundle savedInstanceState) { 26 | super.onCreate(savedInstanceState); 27 | setContentView(R.layout.activity_animation_samples); 28 | 29 | commandRichPathView = findViewById(R.id.ic_command); 30 | arrowSearchRichPathView = findViewById(R.id.ic_arrow_search); 31 | notificationsRichPathView = findViewById(R.id.ic_notifications); 32 | playlistAddCheckRichPathView = findViewById(R.id.ic_playlist_add_check); 33 | loveFaceRichPathView = findViewById(R.id.love_face); 34 | animalRichPathView = findViewById(R.id.animal); 35 | 36 | animateCommand(); 37 | } 38 | 39 | public void animateCommand() { 40 | 41 | final RichPath part1 = commandRichPathView.findRichPathByName("part1"); 42 | final RichPath part2 = commandRichPathView.findRichPathByName("part2"); 43 | final RichPath part3 = commandRichPathView.findRichPathByName("part3"); 44 | final RichPath part4 = commandRichPathView.findRichPathByName("part4"); 45 | final RichPath part5 = commandRichPathView.findRichPathByName("part5"); 46 | final RichPath part6 = commandRichPathView.findRichPathByName("part6"); 47 | final RichPath part7 = commandRichPathView.findRichPathByName("part7"); 48 | final RichPath part8 = commandRichPathView.findRichPathByName("part8"); 49 | 50 | RichPathAnimator 51 | .animate(part1) 52 | .trimPathOffset(0, 1.0f) 53 | 54 | .andAnimate(part2) 55 | .trimPathOffset(0.125f, 1.125f) 56 | 57 | .andAnimate(part3) 58 | .trimPathOffset(0.250f, 1.250f) 59 | 60 | .andAnimate(part4) 61 | .trimPathOffset(0.375f, 1.375f) 62 | 63 | .andAnimate(part5) 64 | .trimPathOffset(0.500f, 1.500f) 65 | 66 | .andAnimate(part6) 67 | .trimPathOffset(0.625f, 1.625f) 68 | 69 | .andAnimate(part7) 70 | .trimPathOffset(0.750f, 1.750f) 71 | 72 | .andAnimate(part8) 73 | .trimPathOffset(0.875f, 1.875f) 74 | 75 | .durationSet(2000) 76 | .repeatModeSet(RichPathAnimator.RESTART) 77 | .repeatCountSet(RichPathAnimator.INFINITE) 78 | .interpolatorSet(new LinearInterpolator()) 79 | .start(); 80 | } 81 | 82 | public void animateAnimal(View view) { 83 | 84 | String hippoPathData = getString(R.string.hippo_path); 85 | String elephantPathData = getString(R.string.elephant_path); 86 | String bullPathData = getString(R.string.bull_path); 87 | 88 | final RichPath richPath = animalRichPathView.findFirstRichPath(); 89 | 90 | RichPathAnimator 91 | .animate(richPath) 92 | .pathData(elephantPathData) 93 | .duration(600) 94 | 95 | .thenAnimate(richPath) 96 | .pathData(bullPathData) 97 | .duration(600) 98 | 99 | .thenAnimate(richPath) 100 | .pathData(hippoPathData) 101 | .duration(600) 102 | 103 | .start(); 104 | } 105 | 106 | public void animateArrowToSearch(View view) { 107 | 108 | RichPath searchCircle = arrowSearchRichPathView.findRichPathByName("search_circle"); 109 | RichPath stem = arrowSearchRichPathView.findRichPathByName("stem"); 110 | RichPath arrowTop = arrowSearchRichPathView.findRichPathByName("arrow_head_top"); 111 | RichPath arrowBottom = arrowSearchRichPathView.findRichPathByName("arrow_head_bottom"); 112 | 113 | if (reverse) { 114 | RichPathAnimator.animate(stem) 115 | .trimPathStart(0f, 0.75f) 116 | .trimPathEnd(0.185f, 1f) 117 | .andAnimate(searchCircle) 118 | .trimPathEnd(1, 0) 119 | .andAnimate(arrowTop, arrowBottom) 120 | .trimPathEnd(0, 1) 121 | .start(); 122 | } else { 123 | RichPathAnimator.animate(stem) 124 | .trimPathStart(0.75f, 0f) 125 | .trimPathEnd(1f, 0.185f) 126 | .andAnimate(searchCircle) 127 | .trimPathEnd(0, 1) 128 | .andAnimate(arrowTop, arrowBottom) 129 | .trimPathEnd(1, 0) 130 | .start(); 131 | } 132 | reverse = !reverse; 133 | } 134 | 135 | public void animateNotification(View view) { 136 | 137 | final RichPath top = notificationsRichPathView.findRichPathByIndex(0); 138 | final RichPath bottom = notificationsRichPathView.findRichPathByIndex(1); 139 | 140 | RichPathAnimator.animate(top) 141 | .interpolator(new DecelerateInterpolator()) 142 | .rotation(0, 20, -20, 10, -10, 5, -5, 2, -2, 0) 143 | .duration(4000) 144 | .andAnimate(bottom) 145 | .interpolator(new DecelerateInterpolator()) 146 | .rotation(0, 10, -10, 5, -5, 2, -2, 0) 147 | .startDelay(50) 148 | .duration(4000) 149 | .start(); 150 | } 151 | 152 | public void animatePlaylistAddCheck(View view) { 153 | 154 | final RichPath line1 = playlistAddCheckRichPathView.findRichPathByName("line1"); 155 | final RichPath line2 = playlistAddCheckRichPathView.findRichPathByName("line2"); 156 | final RichPath line3 = playlistAddCheckRichPathView.findRichPathByName("line3"); 157 | final RichPath tick = playlistAddCheckRichPathView.findRichPathByName("tick"); 158 | final RichPath line3AndTick = playlistAddCheckRichPathView.findRichPathByName("line3_tick"); 159 | 160 | line1.setTrimPathEnd(0); 161 | line2.setTrimPathEnd(0); 162 | line3.setTrimPathEnd(0); 163 | tick.setTrimPathEnd(0); 164 | line3AndTick.setTrimPathEnd(0); 165 | line3AndTick.setTrimPathStart(0); 166 | 167 | int duration = 400; 168 | 169 | RichPathAnimator.animate(line1) 170 | .trimPathEnd(0, 1) 171 | .interpolator(new DecelerateInterpolator()) 172 | .duration(duration) 173 | 174 | .andAnimate(line2) 175 | .trimPathEnd(0, 1) 176 | .interpolator(new DecelerateInterpolator()) 177 | .startDelay(140) 178 | .duration(duration) 179 | 180 | .andAnimate(line3) 181 | .trimPathEnd(0, 1) 182 | .interpolator(new LinearInterpolator()) 183 | .startDelay(180) 184 | .duration(duration) 185 | 186 | .andAnimate(line3AndTick) 187 | .trimPathEnd(0.33f, 0.428f) 188 | .interpolator(new LinearInterpolator()) 189 | .startDelay(140 + duration) 190 | .duration(duration / 3) 191 | 192 | .thenAnimate(line3AndTick) 193 | .trimPathStart(0.33f, 0.428f) 194 | .interpolator(new LinearInterpolator()) 195 | .duration(duration / 3) 196 | 197 | .andAnimate(tick) 198 | .trimPathEnd(0, 1) 199 | .interpolator(new LinearInterpolator()) 200 | .duration(duration) 201 | 202 | .start(); 203 | } 204 | 205 | public void animateLoveFace(View view) { 206 | 207 | final RichPath rEye = loveFaceRichPathView.findRichPathByName("r_eye"); 208 | final RichPath lEye = loveFaceRichPathView.findRichPathByName("l_eye"); 209 | 210 | rEye.setPivotToCenter(true); 211 | lEye.setPivotToCenter(true); 212 | 213 | RichPathAnimator 214 | .animate(rEye, lEye) 215 | .interpolator(new LinearInterpolator()) 216 | .duration(800) 217 | // .repeatMode(RichPathAnimator.RESTART) 218 | .repeatCount(RichPathAnimator.INFINITE) 219 | .scale(1, 0.9f, 1.07f, 1) 220 | .fillColor(0XFFF52C5B, 0xFFF24976, 0XFFD61A4C, 0XFFF52C5B) 221 | .start(); 222 | } 223 | 224 | } -------------------------------------------------------------------------------- /app/src/main/java/com/richpathanimator/sample/CompoundViewSamplesActivity.java: -------------------------------------------------------------------------------- 1 | package com.richpathanimator.sample; 2 | 3 | import android.os.Bundle; 4 | import android.widget.Toast; 5 | 6 | import androidx.appcompat.app.AppCompatActivity; 7 | 8 | import com.richpath.RichPath; 9 | import com.richpath.RichPathView; 10 | 11 | public class CompoundViewSamplesActivity extends AppCompatActivity { 12 | 13 | @Override 14 | protected void onCreate(Bundle savedInstanceState) { 15 | super.onCreate(savedInstanceState); 16 | setContentView(R.layout.activity_compound_view_samples); 17 | 18 | RichPathView colorPickerRichPathView = findViewById(R.id.color_picker); 19 | final RichPath bluePath = colorPickerRichPathView.findRichPathByName("bluePath"); 20 | final RichPath redPath = colorPickerRichPathView.findRichPathByName("redPath"); 21 | final RichPath greenPath = colorPickerRichPathView.findRichPathByName("greenPath"); 22 | final RichPath purplePath = colorPickerRichPathView.findRichPathByName("purplePath"); 23 | 24 | colorPickerRichPathView.setOnPathClickListener(new RichPath.OnPathClickListener() { 25 | @Override 26 | public void onClick(RichPath clickedRichPath) { 27 | bluePath.setStrokeAlpha(0f); 28 | redPath.setStrokeAlpha(0f); 29 | greenPath.setStrokeAlpha(0f); 30 | purplePath.setStrokeAlpha(0f); 31 | 32 | if (clickedRichPath == bluePath 33 | || clickedRichPath == redPath 34 | || clickedRichPath == greenPath 35 | || clickedRichPath == purplePath) { 36 | clickedRichPath.setStrokeAlpha(0.5f); 37 | showToast(clickedRichPath.getName()); 38 | } 39 | } 40 | }); 41 | } 42 | 43 | private void showToast(String msg) { 44 | Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); 45 | } 46 | } -------------------------------------------------------------------------------- /app/src/main/java/com/richpathanimator/sample/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.richpathanimator.sample; 2 | 3 | import android.content.Intent; 4 | import android.graphics.Color; 5 | import android.os.Bundle; 6 | import android.view.View; 7 | import android.view.animation.AccelerateInterpolator; 8 | 9 | import androidx.appcompat.app.AppCompatActivity; 10 | 11 | import com.richpath.RichPath; 12 | import com.richpath.RichPathView; 13 | import com.richpathanimator.AnimationListener; 14 | import com.richpathanimator.RichPathAnimator; 15 | 16 | public class MainActivity extends AppCompatActivity { 17 | 18 | @Override 19 | protected void onCreate(Bundle savedInstanceState) { 20 | super.onCreate(savedInstanceState); 21 | setContentView(R.layout.activity_main); 22 | } 23 | 24 | @Override 25 | protected void onResume() { 26 | super.onResume(); 27 | animateAndroid(); 28 | } 29 | 30 | public void animateAndroid(View view) { 31 | animateAndroid(); 32 | } 33 | 34 | private void animateAndroid() { 35 | 36 | RichPathView androidRichPathView = findViewById(R.id.ic_android); 37 | 38 | final RichPath[] allPaths = androidRichPathView.findAllRichPaths(); 39 | final RichPath head = androidRichPathView.findRichPathByName("head"); 40 | final RichPath body = androidRichPathView.findRichPathByName("body"); 41 | final RichPath rHand = androidRichPathView.findRichPathByName("r_hand"); 42 | final RichPath lHand = androidRichPathView.findRichPathByName("l_hand"); 43 | 44 | RichPathAnimator.animate(allPaths) 45 | .trimPathEnd(0, 1) 46 | .duration(800) 47 | .animationListener(new AnimationListener() { 48 | @Override 49 | public void onStart() { 50 | head.setFillColor(Color.TRANSPARENT); 51 | body.setFillColor(Color.TRANSPARENT); 52 | rHand.setFillColor(Color.TRANSPARENT); 53 | lHand.setFillColor(Color.TRANSPARENT); 54 | rHand.setRotation(0); 55 | } 56 | 57 | @Override 58 | public void onStop() { 59 | } 60 | }) 61 | .thenAnimate(allPaths) 62 | .fillColor(Color.TRANSPARENT, 0xFFa4c639) 63 | .interpolator(new AccelerateInterpolator()) 64 | .duration(900) 65 | .thenAnimate(rHand) 66 | .rotation(-150) 67 | .duration(700) 68 | .thenAnimate(rHand) 69 | .rotation(-150, -130, -150, -130, -150, -130, -150) 70 | .duration(2000) 71 | .thenAnimate(rHand) 72 | .rotation(0) 73 | .duration(500) 74 | .start(); 75 | } 76 | 77 | public void openAnimationSamples(View view) { 78 | startActivity(new Intent(this, AnimationSamplesActivity.class)); 79 | } 80 | 81 | public void openCompoundViewSamples(View view) { 82 | startActivity(new Intent(this, CompoundViewSamplesActivity.class)); 83 | } 84 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/animal.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/color_picker.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 19 | 20 | 27 | 28 | 35 | 36 | 43 | 44 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/face_love.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | 16 | 20 | 24 | 29 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_android.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | 14 | 20 | 21 | 27 | 28 | 34 | 35 | 38 | 39 | 45 | 46 | 47 | 53 | 54 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_arrow_search.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 21 | 22 | 28 | 29 | 30 | 36 | 37 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_command.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | 16 | 24 | 25 | 33 | 34 | 42 | 43 | 51 | 52 | 60 | 61 | 69 | 70 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_notifications.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | 14 | 15 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_playlist_add_check.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 18 | 19 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_animation_samples.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 | 22 | 23 | 27 | 28 | 38 | 39 | 50 | 51 | 62 | 63 | 74 | 75 | 85 | 86 | 97 | 98 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_compound_view_samples.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 16 | 17 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 18 | 19 | 29 | 30 | 38 | 39 |