├── .gitignore ├── LICENSE ├── README.md ├── build.gradle ├── example-action.gif ├── example-reveal.gif ├── example.apk ├── gradle.properties ├── maven.gradle └── src └── main ├── AndroidManifest.xml ├── java └── at │ └── markushi │ └── ui │ ├── ActionView.java │ ├── RevealColorView.java │ ├── action │ ├── Action.java │ ├── BackAction.java │ ├── CloseAction.java │ ├── DrawerAction.java │ ├── LineSegment.java │ └── PlusAction.java │ └── util │ ├── BakedBezierInterpolator.java │ └── UiHelper.java └── res └── values ├── attrs.xml └── values.xml /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | 3 | .gradle 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Deprecated 2 | ==================== 3 | This library is deprecated and no further development is taking place. 4 | 5 | # android-ui 6 | Android library for UI components.
7 | Gradle integration: 8 | 9 | ```groovy 10 | repositories { 11 | mavenCentral() 12 | } 13 | 14 | dependencies { 15 | compile 'com.github.markushi:android-ui:1.2' 16 | } 17 | ``` 18 | 19 | Requires API level 14+
20 | [Download example apk](example.apk) 21 | 22 | ## Components 23 | 24 | ### ActionView 25 | ActionView Example
26 | A widget which can dynamically animate between defined Actions. 27 | ```xml 28 | 35 | ``` 36 | You can dynamically change the action with: 37 | ```java 38 | actionView.setAction(new BackAction(), ActionView.ROTATE_COUNTER_CLOCKWISE); 39 | ``` 40 | 41 | The following Actions are built in: 42 | 43 | * DrawerAction 44 | * BackAction 45 | * CloseAction 46 | * PlusAction 47 | 48 | Please note: ActionView is still under development. The API might change at any time. 49 | 50 | ### RevealColorView 51 | RevealColorView Example
52 | A component which mimics parts of the circular reveal/hide animation introduced [in the Android-L preview](http://developer.android.com/preview/material/animations.html#reveal).
53 | Note: This is not a backport of the original reveal/hide effect. 54 | 55 | See [this example gist](https://gist.github.com/markushi/68ce8df77bed164b6275) on how to use it. 56 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | mavenCentral() 4 | } 5 | dependencies { 6 | classpath 'com.android.tools.build:gradle:0.12.+' 7 | } 8 | } 9 | apply plugin: 'android-library' 10 | 11 | repositories { 12 | mavenCentral() 13 | } 14 | 15 | android { 16 | compileSdkVersion 19 17 | buildToolsVersion "20" 18 | 19 | defaultConfig { 20 | minSdkVersion 14 21 | versionName = VERSION_NAME 22 | versionCode = VERSION_CODE 23 | } 24 | } 25 | 26 | dependencies { 27 | 28 | } 29 | 30 | allprojects { 31 | version = VERSION_NAME 32 | group = GROUP 33 | 34 | repositories { 35 | mavenCentral() 36 | } 37 | } 38 | 39 | apply from: 'maven.gradle' -------------------------------------------------------------------------------- /example-action.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markushi/android-ui/a589fad7b74ace063c2b0e90741d43225b200a18/example-action.gif -------------------------------------------------------------------------------- /example-reveal.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markushi/android-ui/a589fad7b74ace063c2b0e90741d43225b200a18/example-reveal.gif -------------------------------------------------------------------------------- /example.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markushi/android-ui/a589fad7b74ace063c2b0e90741d43225b200a18/example.apk -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | VERSION_NAME=1.2 2 | VERSION_CODE=1 3 | GROUP=com.github.markushi 4 | 5 | POM_NAME=Android UI Library 6 | POM_ARTIFACT_ID=android-ui 7 | POM_PACKAGING=aar 8 | 9 | POM_DESCRIPTION=Android UI Library 10 | POM_URL=https://github.com/markushi/android-ui 11 | POM_SCM_URL=https://github.com/markushi/android-ui 12 | POM_SCM_CONNECTION=scm:git@github.com:markushi/android-ui.git 13 | POM_SCM_DEV_CONNECTION=scm:git@github.com:markushi/android-ui.git 14 | POM_LICENCE_NAME=The Apache Software License, Version 2.0 15 | POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt 16 | POM_LICENCE_DIST=repo 17 | POM_DEVELOPER_ID=markushi 18 | POM_DEVELOPER_NAME=Markus Hintersteiner 19 | -------------------------------------------------------------------------------- /maven.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'maven' 2 | apply plugin: 'signing' 3 | 4 | ext { 5 | version = VERSION_NAME 6 | group = GROUP 7 | } 8 | 9 | def isReleaseBuild() { 10 | return VERSION_NAME.contains("SNAPSHOT") == false 11 | } 12 | 13 | if (isReleaseBuild()) { 14 | println 'RELEASE BUILD' 15 | } else { 16 | println 'SNAPSHOT BUILD' 17 | } 18 | 19 | def getRepositoryUrl() { 20 | if (isReleaseBuild()) { 21 | return "https://oss.sonatype.org/service/local/staging/deploy/maven2/" 22 | } else { 23 | return "https://oss.sonatype.org/content/repositories/snapshots/" 24 | } 25 | } 26 | 27 | def getRepositoryUsername() { 28 | return hasProperty('nexusUsername') ? nexusUsername : "" 29 | } 30 | 31 | def getRepositoryPassword() { 32 | return hasProperty('nexusPassword') ? nexusPassword : "" 33 | } 34 | 35 | afterEvaluate { project -> 36 | uploadArchives { 37 | repositories { 38 | mavenDeployer { 39 | beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } 40 | 41 | pom.artifactId = POM_ARTIFACT_ID 42 | 43 | repository(url: getRepositoryUrl()) { 44 | authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) 45 | } 46 | 47 | pom.project { 48 | name POM_NAME 49 | packaging POM_PACKAGING 50 | description POM_DESCRIPTION 51 | url POM_URL 52 | 53 | scm { 54 | url POM_SCM_URL 55 | connection POM_SCM_CONNECTION 56 | developerConnection POM_SCM_DEV_CONNECTION 57 | } 58 | 59 | licenses { 60 | license { 61 | name POM_LICENCE_NAME 62 | url POM_LICENCE_URL 63 | distribution POM_LICENCE_DIST 64 | } 65 | } 66 | 67 | developers { 68 | developer { 69 | id POM_DEVELOPER_ID 70 | name POM_DEVELOPER_NAME 71 | } 72 | } 73 | } 74 | } 75 | } 76 | } 77 | 78 | signing { 79 | required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") } 80 | sign configurations.archives 81 | } 82 | 83 | task androidJavadocs(type: Javadoc) { 84 | options { 85 | linksOffline "http://d.android.com/reference", "${android.sdkDirectory}/docs/reference" 86 | } 87 | source = android.sourceSets.main.java.sourceFiles 88 | classpath += project.files(project.android.getBootClasspath().join(File.pathSeparator)) 89 | } 90 | 91 | task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) { 92 | classifier = 'javadoc' 93 | from androidJavadocs.destinationDir 94 | } 95 | 96 | task androidSourcesJar(type: Jar) { 97 | classifier = 'sources' 98 | from android.sourceSets.main.java.sourceFiles 99 | } 100 | 101 | artifacts { 102 | archives androidSourcesJar 103 | archives androidJavadocsJar 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/main/java/at/markushi/ui/ActionView.java: -------------------------------------------------------------------------------- 1 | package at.markushi.ui; 2 | 3 | import android.animation.ObjectAnimator; 4 | import android.content.Context; 5 | import android.content.res.TypedArray; 6 | import android.graphics.Canvas; 7 | import android.graphics.Paint; 8 | import android.graphics.Path; 9 | import android.os.Parcel; 10 | import android.os.Parcelable; 11 | import android.util.AttributeSet; 12 | import android.util.TypedValue; 13 | import android.view.View; 14 | 15 | import at.markushi.ui.action.Action; 16 | import at.markushi.ui.action.BackAction; 17 | import at.markushi.ui.action.CloseAction; 18 | import at.markushi.ui.action.DrawerAction; 19 | import at.markushi.ui.action.LineSegment; 20 | import at.markushi.ui.action.PlusAction; 21 | import at.markushi.ui.util.BakedBezierInterpolator; 22 | import at.markushi.ui.util.UiHelper; 23 | 24 | /** 25 | * ActionView allows you to dynamically display action and automatically animate from one action to another. 26 | *

27 | * ActionView can be included in your layout xml quite easy: 28 | *

 29 |  * {@code
 30 |  * 
 37 |  * }
38 | * 39 | * In order to start a transition to another {@link at.markushi.ui.action.Action} call {@link #setAction(at.markushi.ui.action.Action)} 40 | * 41 | * @see at.markushi.ui.action.BackAction 42 | * @see at.markushi.ui.action.CloseAction 43 | * @see at.markushi.ui.action.DrawerAction 44 | * @see at.markushi.ui.action.PlusAction 45 | */ 46 | public class ActionView extends View { 47 | 48 | public static final int ROTATE_CLOCKWISE = 0; 49 | public static final int ROTATE_COUNTER_CLOCKWISE = 1; 50 | private static final int STROKE_SIZE_DIP = 2; 51 | 52 | private int color; 53 | private Action oldAction; 54 | private Action currentAction; 55 | private Action animatedAction; 56 | 57 | private float animationProgress; 58 | private float scale; 59 | private int padding; 60 | private Path path; 61 | private Paint paint; 62 | 63 | private float centerX; 64 | private float centerY; 65 | private boolean ready = false; 66 | private boolean animateWhenReady = false; 67 | private int rotation = ROTATE_CLOCKWISE; 68 | private long animationDuration; 69 | private int size; 70 | 71 | public ActionView(Context context) { 72 | this(context, null); 73 | } 74 | 75 | public ActionView(Context context, AttributeSet attrs) { 76 | this(context, attrs, 0); 77 | } 78 | 79 | public ActionView(Context context, AttributeSet attrs, int defStyleAttr) { 80 | super(context, attrs, defStyleAttr); 81 | animationDuration = getResources().getInteger(R.integer.av_animationDuration); 82 | animatedAction = new Action(new float[Action.ACTION_SIZE], null); 83 | 84 | final float strokeSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, STROKE_SIZE_DIP, context.getResources().getDisplayMetrics()); 85 | path = new Path(); 86 | paint = new Paint(Paint.ANTI_ALIAS_FLAG); 87 | paint.setColor(0xDDFFFFFF); 88 | paint.setStrokeWidth(strokeSize); 89 | paint.setStyle(Paint.Style.STROKE); 90 | 91 | if (attrs == null) { 92 | return; 93 | } 94 | final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ActionView); 95 | final int color = a.getColor(R.styleable.ActionView_av_color, 0xDDFFFFF); 96 | final int actionId = a.getInt(R.styleable.ActionView_av_action, 0); 97 | a.recycle(); 98 | 99 | paint.setColor(color); 100 | final Action action = getActionFromEnum(actionId); 101 | setAction(action); 102 | } 103 | 104 | @Override 105 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 106 | super.onSizeChanged(w, h, oldw, oldh); 107 | this.centerX = w / 2; 108 | this.centerY = h / 2; 109 | padding = getPaddingLeft(); 110 | size = Math.min(w, h); 111 | scale = Math.min(w, h) - (2 * padding); 112 | ready = true; 113 | transformActions(); 114 | 115 | if (animateWhenReady) { 116 | animateWhenReady = false; 117 | startAnimation(); 118 | } 119 | } 120 | 121 | @Override 122 | protected void onDraw(Canvas canvas) { 123 | super.onDraw(canvas); 124 | 125 | if (currentAction == null) { 126 | return; 127 | } 128 | 129 | if (oldAction == null) { 130 | updatePath(currentAction); 131 | } else { 132 | final float inverseProgress = 1f - animationProgress; 133 | final float[] current = currentAction.getLineData(); 134 | final float[] old = oldAction.getLineData(); 135 | final float[] animated = animatedAction.getLineData(); 136 | for (int i = 0; i < animated.length; i++) { 137 | animated[i] = current[i] * animationProgress + old[i] * inverseProgress; 138 | } 139 | updatePath(animatedAction); 140 | } 141 | 142 | canvas.rotate((rotation == ROTATE_CLOCKWISE ? 180f : -180f) * animationProgress, centerX, centerY); 143 | canvas.drawPath(path, paint); 144 | } 145 | 146 | @SuppressWarnings("UnusedDeclaration") 147 | public float getAnimationProgress() { 148 | return animationProgress; 149 | } 150 | 151 | @SuppressWarnings("UnusedDeclaration") 152 | public void setAnimationProgress(float animationProgress) { 153 | this.animationProgress = animationProgress; 154 | UiHelper.postInvalidateOnAnimation(this); 155 | } 156 | 157 | /** 158 | * Set the color used for drawing an {@link at.markushi.ui.action.Action}. 159 | * 160 | * @param color 161 | */ 162 | public void setColor(final int color) { 163 | this.color = color; 164 | paint.setColor(color); 165 | UiHelper.postInvalidateOnAnimation(this); 166 | } 167 | 168 | /** 169 | * @return the current action 170 | */ 171 | public Action getAction() { 172 | return currentAction; 173 | } 174 | 175 | /** 176 | * Sets the new action. If an action was set before a transition will be started. 177 | * 178 | * @param action 179 | * @see #setAction(at.markushi.ui.action.Action, boolean) 180 | * @see #setAction(at.markushi.ui.action.Action, boolean, int) 181 | * @see #setAction(at.markushi.ui.action.Action, at.markushi.ui.action.Action, int, long) 182 | */ 183 | public void setAction(final Action action) { 184 | setAction(action, true, ROTATE_CLOCKWISE); 185 | } 186 | 187 | /** 188 | * Sets the new action. If an action was set before a transition will be started. 189 | * 190 | * @param action 191 | * @param rotation Can be either {@link #ROTATE_CLOCKWISE} or {@link #ROTATE_COUNTER_CLOCKWISE}. 192 | */ 193 | public void setAction(final Action action, final int rotation) { 194 | setAction(action, true, rotation); 195 | } 196 | 197 | /** 198 | * Sets the new action. 199 | * 200 | * @param action 201 | * @param animate If a prior action was set and {@code true} a transition will be started, otherwise not. 202 | */ 203 | public void setAction(final Action action, final boolean animate) { 204 | setAction(action, animate, ROTATE_CLOCKWISE); 205 | } 206 | 207 | /** 208 | * Sets a new action transition. 209 | * 210 | * @param fromAction The initial action. 211 | * @param toAction The target action. 212 | * @param rotation The rotation direction used for the transition {@link #ROTATE_CLOCKWISE} or {@link #ROTATE_COUNTER_CLOCKWISE}. 213 | * @param delay The delay in ms before the transition is started. 214 | */ 215 | public void setAction(final Action fromAction, final Action toAction, final int rotation, long delay) { 216 | setAction(fromAction, false, ROTATE_CLOCKWISE); 217 | postDelayed(new Runnable() { 218 | @Override 219 | public void run() { 220 | if (!isAttachedToWindow()) { 221 | return; 222 | } 223 | setAction(toAction, true, rotation); 224 | } 225 | }, delay); 226 | } 227 | 228 | /** 229 | * Set the animation duration used for Action transitions 230 | * 231 | * @param animationDuration the duration in ms 232 | */ 233 | public void setAnimationDuration(long animationDuration) { 234 | this.animationDuration = animationDuration; 235 | } 236 | 237 | @Override 238 | protected Parcelable onSaveInstanceState() { 239 | final Parcelable superState = super.onSaveInstanceState(); 240 | final SavedState ss = new SavedState(superState); 241 | ss.currentAction = currentAction; 242 | ss.color = color; 243 | return ss; 244 | } 245 | 246 | @Override 247 | protected void onRestoreInstanceState(Parcelable state) { 248 | SavedState ss = (SavedState) state; 249 | super.onRestoreInstanceState(ss.getSuperState()); 250 | this.color = ss.color; 251 | this.currentAction = ss.currentAction; 252 | this.animationProgress = 1f; 253 | } 254 | 255 | private void setAction(Action action, boolean animate, int rotation) { 256 | if (action == null) { 257 | return; 258 | } 259 | 260 | this.rotation = rotation; 261 | if (currentAction == null) { 262 | currentAction = action; 263 | currentAction.flipHorizontally(); 264 | animationProgress = 1f; 265 | UiHelper.postInvalidateOnAnimation(this); 266 | return; 267 | } 268 | 269 | if (currentAction.getClass().equals(action.getClass())) { 270 | return; 271 | } 272 | 273 | oldAction = currentAction; 274 | currentAction = action; 275 | 276 | if (animate) { 277 | animationProgress = 0f; 278 | if (ready) { 279 | startAnimation(); 280 | } else { 281 | animateWhenReady = true; 282 | } 283 | } else { 284 | animationProgress = 1f; 285 | UiHelper.postInvalidateOnAnimation(this); 286 | } 287 | } 288 | 289 | private void updatePath(Action action) { 290 | path.reset(); 291 | 292 | final float[] data = action.getLineData(); 293 | // Once we're near the end of the animation we use the action segments to draw linked lines 294 | if (animationProgress > 0.95f && !action.getLineSegments().isEmpty()) { 295 | for (LineSegment s : action.getLineSegments()) { 296 | path.moveTo(data[s.getStartIdx() + 0], data[s.getStartIdx() + 1]); 297 | path.lineTo(data[s.getStartIdx() + 2], data[s.getStartIdx() + 3]); 298 | for (int i = 1; i < s.indexes.length; i++) { 299 | path.lineTo(data[s.indexes[i] + 0], data[s.indexes[i] + 1]); 300 | path.lineTo(data[s.indexes[i] + 2], data[s.indexes[i] + 3]); 301 | } 302 | } 303 | } else { 304 | for (int i = 0; i < data.length; i += 4) { 305 | path.moveTo(data[i + 0], data[i + 1]); 306 | path.lineTo(data[i + 2], data[i + 3]); 307 | } 308 | } 309 | } 310 | 311 | private void transformActions() { 312 | if (currentAction != null && !currentAction.isTransformed()) { 313 | currentAction.transform(padding, padding, scale, size); 314 | } 315 | 316 | if (oldAction != null && !oldAction.isTransformed()) { 317 | oldAction.transform(padding, padding, scale, size); 318 | } 319 | } 320 | 321 | private void startAnimation() { 322 | oldAction.flipHorizontally(); 323 | currentAction.flipHorizontally(); 324 | 325 | transformActions(); 326 | 327 | animatedAction.setLineSegments(currentAction.getLineSegments()); 328 | final ObjectAnimator animator = ObjectAnimator.ofFloat(ActionView.this, "animationProgress", 0f, 1f); 329 | animator.setInterpolator(BakedBezierInterpolator.getInstance()); 330 | animator.setDuration(animationDuration).start(); 331 | } 332 | 333 | private Action getActionFromEnum(int id) { 334 | switch (id) { 335 | case 0: 336 | return new DrawerAction(); 337 | case 1: 338 | return new BackAction(); 339 | case 2: 340 | return new CloseAction(); 341 | case 3: 342 | return new PlusAction(); 343 | } 344 | 345 | return null; 346 | } 347 | 348 | static class SavedState extends BaseSavedState { 349 | 350 | Action currentAction; 351 | int color; 352 | 353 | public SavedState(Parcelable superState) { 354 | super(superState); 355 | } 356 | 357 | public SavedState(Parcel source) { 358 | super(source); 359 | currentAction = source.readParcelable(getClass().getClassLoader()); 360 | color = source.readInt(); 361 | } 362 | 363 | @Override 364 | public void writeToParcel(Parcel dest, int flags) { 365 | super.writeToParcel(dest, flags); 366 | dest.writeParcelable(currentAction, 0); 367 | dest.writeInt(color); 368 | } 369 | 370 | public static final Creator CREATOR = new Creator() { 371 | 372 | @Override 373 | public SavedState createFromParcel(Parcel parcel) { 374 | return new SavedState(parcel); 375 | } 376 | 377 | @Override 378 | public SavedState[] newArray(int size) { 379 | return new SavedState[size]; 380 | } 381 | }; 382 | } 383 | } 384 | -------------------------------------------------------------------------------- /src/main/java/at/markushi/ui/RevealColorView.java: -------------------------------------------------------------------------------- 1 | package at.markushi.ui; 2 | 3 | import android.animation.Animator; 4 | import android.content.Context; 5 | import android.graphics.Color; 6 | import android.graphics.drawable.ShapeDrawable; 7 | import android.graphics.drawable.shapes.OvalShape; 8 | import android.os.Build; 9 | import android.util.AttributeSet; 10 | import android.view.View; 11 | import android.view.ViewGroup; 12 | import android.view.ViewPropertyAnimator; 13 | 14 | import at.markushi.ui.util.BakedBezierInterpolator; 15 | import at.markushi.ui.util.UiHelper; 16 | 17 | public class RevealColorView extends ViewGroup { 18 | 19 | public static final int ANIMATION_REVEAL = 0; 20 | public static final int ANIMATION_HIDE = 2; 21 | 22 | private static final float SCALE = 8f; 23 | 24 | private View inkView; 25 | private int inkColor; 26 | private ShapeDrawable circle; 27 | private ViewPropertyAnimator animator; 28 | 29 | public RevealColorView(Context context) { 30 | this(context, null); 31 | } 32 | 33 | public RevealColorView(Context context, AttributeSet attrs) { 34 | this(context, attrs, 0); 35 | } 36 | 37 | public RevealColorView(Context context, AttributeSet attrs, int defStyleAttr) { 38 | super(context, attrs, defStyleAttr); 39 | 40 | if (isInEditMode()) { 41 | return; 42 | } 43 | 44 | inkView = new View(context); 45 | addView(inkView); 46 | 47 | circle = new ShapeDrawable(new OvalShape()); 48 | 49 | UiHelper.setBackground(inkView, circle); 50 | inkView.setVisibility(View.INVISIBLE); 51 | } 52 | 53 | @Override 54 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 55 | inkView.layout(left, top, left + inkView.getMeasuredWidth(), top + inkView.getMeasuredHeight()); 56 | } 57 | 58 | @Override 59 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 60 | final int width = MeasureSpec.getSize(widthMeasureSpec); 61 | final int height = MeasureSpec.getSize(heightMeasureSpec); 62 | setMeasuredDimension(width, height); 63 | 64 | final float circleSize = (float) Math.sqrt(width * width + height * height) * 2f; 65 | final int size = (int) (circleSize / SCALE); 66 | final int sizeSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY); 67 | inkView.measure(sizeSpec, sizeSpec); 68 | } 69 | 70 | public void reveal(final int x, final int y, final int color, Animator.AnimatorListener listener) { 71 | final int duration = getResources().getInteger(R.integer.rcv_animationDurationReveal); 72 | reveal(x, y, color, 0, duration, listener); 73 | } 74 | 75 | public void reveal(final int x, final int y, final int color, final int startRadius, long duration, final Animator.AnimatorListener listener) { 76 | if (color == inkColor) { 77 | return; 78 | } 79 | inkColor = color; 80 | 81 | if (animator != null) { 82 | animator.cancel(); 83 | } 84 | 85 | circle.getPaint().setColor(color); 86 | inkView.setVisibility(View.VISIBLE); 87 | 88 | final float startScale = startRadius * 2f / inkView.getHeight(); 89 | final float finalScale = calculateScale(x, y) * SCALE; 90 | 91 | prepareView(inkView, x, y, startScale); 92 | animator = inkView.animate().scaleX(finalScale).scaleY(finalScale).setDuration(duration).setListener(new Animator.AnimatorListener() { 93 | @Override 94 | public void onAnimationStart(Animator animator) { 95 | if (listener != null) { 96 | listener.onAnimationStart(animator); 97 | } 98 | } 99 | 100 | @Override 101 | public void onAnimationEnd(Animator animation) { 102 | setBackgroundColor(color); 103 | inkView.setVisibility(View.INVISIBLE); 104 | if (listener != null) { 105 | listener.onAnimationEnd(animation); 106 | } 107 | } 108 | 109 | @Override 110 | public void onAnimationCancel(Animator animator) { 111 | if (listener != null) { 112 | listener.onAnimationCancel(animator); 113 | } 114 | } 115 | 116 | @Override 117 | public void onAnimationRepeat(Animator animator) { 118 | if (listener != null) { 119 | listener.onAnimationRepeat(animator); 120 | } 121 | } 122 | }); 123 | prepareAnimator(animator, ANIMATION_REVEAL); 124 | animator.start(); 125 | } 126 | 127 | public void hide(final int x, final int y, final int color, final Animator.AnimatorListener listener) { 128 | final int duration = getResources().getInteger(R.integer.rcv_animationDurationHide); 129 | hide(x, y, color, 0, duration, listener); 130 | } 131 | 132 | public void hide(final int x, final int y, final int color, final int endRadius, final long duration, final Animator.AnimatorListener listener) { 133 | inkColor = Color.TRANSPARENT; 134 | 135 | if (animator != null) { 136 | animator.cancel(); 137 | } 138 | 139 | inkView.setVisibility(View.VISIBLE); 140 | setBackgroundColor(color); 141 | 142 | final float startScale = calculateScale(x, y) * SCALE; 143 | final float finalScale = endRadius * SCALE / inkView.getWidth(); 144 | 145 | prepareView(inkView, x, y, startScale); 146 | 147 | animator = inkView.animate().scaleX(finalScale).scaleY(finalScale).setDuration(duration).setListener(new Animator.AnimatorListener() { 148 | @Override 149 | public void onAnimationStart(Animator animator) { 150 | if (listener != null) { 151 | listener.onAnimationStart(animator); 152 | } 153 | } 154 | 155 | @Override 156 | public void onAnimationEnd(Animator animation) { 157 | inkView.setVisibility(View.INVISIBLE); 158 | if (listener != null) { 159 | listener.onAnimationEnd(animation); 160 | } 161 | } 162 | 163 | @Override 164 | public void onAnimationCancel(Animator animator) { 165 | if (listener != null) { 166 | listener.onAnimationCancel(animator); 167 | } 168 | } 169 | 170 | @Override 171 | public void onAnimationRepeat(Animator animator) { 172 | if (listener != null) { 173 | listener.onAnimationRepeat(animator); 174 | } 175 | } 176 | }); 177 | prepareAnimator(animator, ANIMATION_HIDE); 178 | animator.start(); 179 | } 180 | 181 | public ViewPropertyAnimator prepareAnimator(ViewPropertyAnimator animator, int type) { 182 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 183 | animator.withLayer(); 184 | } 185 | animator.setInterpolator(BakedBezierInterpolator.getInstance()); 186 | return animator; 187 | } 188 | 189 | private void prepareView(View view, int x, int y, float scale) { 190 | final int centerX = (view.getWidth() / 2); 191 | final int centerY = (view.getHeight() / 2); 192 | view.setTranslationX(x - centerX); 193 | view.setTranslationY(y - centerY); 194 | view.setPivotX(centerX); 195 | view.setPivotY(centerY); 196 | view.setScaleX(scale); 197 | view.setScaleY(scale); 198 | } 199 | 200 | /** 201 | * calculates the required scale of the ink-view to fill the whole view 202 | * 203 | * @param x circle center x 204 | * @param y circle center y 205 | * @return 206 | */ 207 | private float calculateScale(int x, int y) { 208 | final float centerX = getWidth() / 2f; 209 | final float centerY = getHeight() / 2f; 210 | final float maxDistance = (float) Math.sqrt(centerX * centerX + centerY * centerY); 211 | 212 | final float deltaX = centerX - x; 213 | final float deltaY = centerY - y; 214 | final float distance = (float) Math.sqrt(deltaX * deltaX + deltaY * deltaY); 215 | final float scale = 0.5f + (distance / maxDistance) * 0.5f; 216 | return scale; 217 | } 218 | } -------------------------------------------------------------------------------- /src/main/java/at/markushi/ui/action/Action.java: -------------------------------------------------------------------------------- 1 | package at.markushi.ui.action; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import android.graphics.Matrix; 7 | import android.os.Parcel; 8 | import android.os.Parcelable; 9 | 10 | /** 11 | * A action to be drawn by {@link at.markushi.ui.ActionView} 12 | */ 13 | public class Action implements Parcelable { 14 | 15 | // 3 Lines * 4 points each 16 | public static final int ACTION_SIZE = 12; 17 | 18 | // combination of x/y positions describing the lines 19 | protected float[] lineData; 20 | protected List lineSegments = new ArrayList(3); 21 | protected boolean transformed = false; 22 | protected float size = 1f; 23 | 24 | public Action() { 25 | 26 | } 27 | 28 | public Action(float[] lineData, List lineSegments) { 29 | this.lineData = lineData; 30 | if (lineSegments != null) { 31 | this.lineSegments.addAll(lineSegments); 32 | } 33 | } 34 | 35 | public void transform(float translationX, float translationY, float scale, float size) { 36 | 37 | this.size = size; 38 | this.transformed = true; 39 | 40 | final Matrix m = new Matrix(); 41 | m.preScale(scale, scale); 42 | m.postTranslate(translationX, translationY); 43 | m.mapPoints(lineData); 44 | } 45 | 46 | public void flipHorizontally() { 47 | // flip x coordinates 48 | for (int i = 0; i < lineData.length; i += 2) { 49 | lineData[i] = size - lineData[i]; 50 | } 51 | 52 | // flip line direction 53 | for (int i = 0; i < lineData.length; i += 4) { 54 | float x0 = lineData[i]; 55 | float y0 = lineData[i + 1]; 56 | lineData[i + 0] = lineData[i + 2]; 57 | lineData[i + 1] = lineData[i + 3]; 58 | lineData[i + 2] = x0; 59 | lineData[i + 3] = y0; 60 | } 61 | } 62 | 63 | public float[] getLineData() { 64 | return lineData; 65 | } 66 | 67 | public List getLineSegments() { 68 | return lineSegments; 69 | } 70 | 71 | public void setLineSegments(List lineSegments) { 72 | this.lineSegments = lineSegments; 73 | } 74 | 75 | public boolean isTransformed() { 76 | return transformed; 77 | } 78 | 79 | @Override 80 | public int describeContents() { 81 | return 0; 82 | } 83 | 84 | @Override 85 | public void writeToParcel(Parcel dest, int flags) { 86 | dest.writeFloatArray(this.lineData); 87 | dest.writeTypedList(lineSegments); 88 | } 89 | 90 | private Action(Parcel in) { 91 | this.lineData = in.createFloatArray(); 92 | in.readTypedList(lineSegments, LineSegment.CREATOR); 93 | } 94 | 95 | public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { 96 | public Action createFromParcel(Parcel source) { 97 | return new Action(source); 98 | } 99 | 100 | public Action[] newArray(int size) { 101 | return new Action[size]; 102 | } 103 | }; 104 | } -------------------------------------------------------------------------------- /src/main/java/at/markushi/ui/action/BackAction.java: -------------------------------------------------------------------------------- 1 | package at.markushi.ui.action; 2 | 3 | public class BackAction extends Action { 4 | 5 | public BackAction() { 6 | 7 | final float endX = 0.82f; 8 | final float startX = 0.21875f; 9 | final float center = 0.5f; 10 | 11 | lineData = new float[]{ 12 | // line 1 13 | startX, center, 0.52f, 0.2f, 14 | // line 2 15 | startX, center, endX, center, 16 | // line 2 17 | startX, center, 0.52f, 0.8f,}; 18 | 19 | lineSegments.add(new LineSegment(0, 8)); 20 | lineSegments.add(new LineSegment(4)); 21 | } 22 | } -------------------------------------------------------------------------------- /src/main/java/at/markushi/ui/action/CloseAction.java: -------------------------------------------------------------------------------- 1 | package at.markushi.ui.action; 2 | 3 | public class CloseAction extends Action { 4 | 5 | public CloseAction() { 6 | final float start = 0.239375f; 7 | final float end = 1f - start; 8 | 9 | lineData = new float[]{ 10 | // line 1 11 | start, start, end, end, 12 | // line 2 13 | start, end, end, start, 14 | // line 3 15 | start, start, end, end,}; 16 | } 17 | } -------------------------------------------------------------------------------- /src/main/java/at/markushi/ui/action/DrawerAction.java: -------------------------------------------------------------------------------- 1 | package at.markushi.ui.action; 2 | 3 | public class DrawerAction extends Action { 4 | 5 | public DrawerAction() { 6 | 7 | final float startX = 0.1375f; 8 | final float endX = 1f - startX; 9 | final float endY = 0.707f; 10 | final float startY = 1f - endY; 11 | final float center = 0.5f; 12 | 13 | lineData = new float[]{ 14 | // line 1 15 | startX, startY, endX, startY, 16 | // line 2 17 | startX, center, endX, center, 18 | // line 3 19 | startX, endY, endX, endY,}; 20 | } 21 | } -------------------------------------------------------------------------------- /src/main/java/at/markushi/ui/action/LineSegment.java: -------------------------------------------------------------------------------- 1 | package at.markushi.ui.action; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | /** 7 | * A LineSegment describes which lines within an Action are linked together 8 | */ 9 | public class LineSegment implements Parcelable { 10 | 11 | public int[] indexes; 12 | 13 | public LineSegment(int... indexes) { 14 | this.indexes = indexes; 15 | } 16 | 17 | public int getStartIdx() { 18 | return indexes[0]; 19 | } 20 | 21 | @Override 22 | public int describeContents() { 23 | return 0; 24 | } 25 | 26 | @Override 27 | public void writeToParcel(Parcel dest, int flags) { 28 | dest.writeIntArray(this.indexes); 29 | } 30 | 31 | private LineSegment(Parcel in) { 32 | this.indexes = in.createIntArray(); 33 | } 34 | 35 | public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { 36 | public LineSegment createFromParcel(Parcel source) { 37 | return new LineSegment(source); 38 | } 39 | 40 | public LineSegment[] newArray(int size) { 41 | return new LineSegment[size]; 42 | } 43 | }; 44 | } -------------------------------------------------------------------------------- /src/main/java/at/markushi/ui/action/PlusAction.java: -------------------------------------------------------------------------------- 1 | package at.markushi.ui.action; 2 | 3 | public class PlusAction extends Action { 4 | 5 | public PlusAction() { 6 | 7 | final float bottom = 76f / 96f; 8 | final float top = 1f - bottom; 9 | final float left = 20f / 96f; 10 | final float right = 1f - left; 11 | final float center = 0.5f; 12 | 13 | lineData = new float[]{ 14 | // line 1 15 | center, top, center, bottom, 16 | // line 2 17 | left, center, right, center, 18 | // line 3 19 | center, top, center, bottom,}; 20 | } 21 | } -------------------------------------------------------------------------------- /src/main/java/at/markushi/ui/util/BakedBezierInterpolator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 The Android Open Source Project 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.markushi.ui.util; 18 | 19 | import android.view.animation.Interpolator; 20 | 21 | /** 22 | * A pre-baked bezier-curved interpolator for indeterminate progress animations. 23 | */ 24 | public final class BakedBezierInterpolator implements Interpolator { 25 | private static final BakedBezierInterpolator INSTANCE = new BakedBezierInterpolator(); 26 | 27 | public final static BakedBezierInterpolator getInstance() { 28 | return INSTANCE; 29 | } 30 | 31 | /** 32 | * Use getInstance instead of instantiating. 33 | */ 34 | private BakedBezierInterpolator() { 35 | super(); 36 | } 37 | 38 | /** 39 | * Lookup table values. 40 | * Generated using a Bezier curve from (0,0) to (1,1) with control points: 41 | * P0 (0,0) 42 | * P1 (0.4, 0) 43 | * P2 (0.2, 1.0) 44 | * P3 (1.0, 1.0) 45 | * 46 | * Values sampled with x at regular intervals between 0 and 1. 47 | */ 48 | private static final float[] VALUES = new float[] { 49 | 0.0f, 0.0002f, 0.0009f, 0.0019f, 0.0036f, 0.0059f, 0.0086f, 0.0119f, 0.0157f, 0.0209f, 50 | 0.0257f, 0.0321f, 0.0392f, 0.0469f, 0.0566f, 0.0656f, 0.0768f, 0.0887f, 0.1033f, 0.1186f, 51 | 0.1349f, 0.1519f, 0.1696f, 0.1928f, 0.2121f, 0.237f, 0.2627f, 0.2892f, 0.3109f, 0.3386f, 52 | 0.3667f, 0.3952f, 0.4241f, 0.4474f, 0.4766f, 0.5f, 0.5234f, 0.5468f, 0.5701f, 0.5933f, 53 | 0.6134f, 0.6333f, 0.6531f, 0.6698f, 0.6891f, 0.7054f, 0.7214f, 0.7346f, 0.7502f, 0.763f, 54 | 0.7756f, 0.7879f, 0.8f, 0.8107f, 0.8212f, 0.8326f, 0.8415f, 0.8503f, 0.8588f, 0.8672f, 55 | 0.8754f, 0.8833f, 0.8911f, 0.8977f, 0.9041f, 0.9113f, 0.9165f, 0.9232f, 0.9281f, 0.9328f, 56 | 0.9382f, 0.9434f, 0.9476f, 0.9518f, 0.9557f, 0.9596f, 0.9632f, 0.9662f, 0.9695f, 0.9722f, 57 | 0.9753f, 0.9777f, 0.9805f, 0.9826f, 0.9847f, 0.9866f, 0.9884f, 0.9901f, 0.9917f, 0.9931f, 58 | 0.9944f, 0.9955f, 0.9964f, 0.9973f, 0.9981f, 0.9986f, 0.9992f, 0.9995f, 0.9998f, 1.0f, 1.0f 59 | }; 60 | 61 | private static final float STEP_SIZE = 1.0f / (VALUES.length - 1); 62 | 63 | @Override 64 | public float getInterpolation(float input) { 65 | if (input >= 1.0f) { 66 | return 1.0f; 67 | } 68 | 69 | if (input <= 0f) { 70 | return 0f; 71 | } 72 | 73 | int position = Math.min( 74 | (int)(input * (VALUES.length - 1)), 75 | VALUES.length - 2); 76 | 77 | float quantized = position * STEP_SIZE; 78 | float difference = input - quantized; 79 | float weight = difference / STEP_SIZE; 80 | 81 | return VALUES[position] + weight * (VALUES[position + 1] - VALUES[position]); 82 | } 83 | 84 | } -------------------------------------------------------------------------------- /src/main/java/at/markushi/ui/util/UiHelper.java: -------------------------------------------------------------------------------- 1 | package at.markushi.ui.util; 2 | 3 | import android.graphics.drawable.Drawable; 4 | import android.os.Build; 5 | import android.view.View; 6 | 7 | public class UiHelper { 8 | 9 | @SuppressWarnings("deprecation") 10 | public static void setBackground(View view, Drawable d) { 11 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 12 | view.setBackground(d); 13 | } else { 14 | view.setBackgroundDrawable(d); 15 | } 16 | } 17 | 18 | public static void postInvalidateOnAnimation(View view) { 19 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 20 | view.postInvalidateOnAnimation(); 21 | } else { 22 | view.invalidate(); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/main/res/values/values.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 420 4 | 420 5 | 340 6 | 7 | --------------------------------------------------------------------------------