├── .gitignore ├── NOTICE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── ic_launcher-web.png │ ├── java │ └── net │ │ └── frakbot │ │ └── jumpingbeans │ │ └── demo │ │ └── MainActivity.java │ └── res │ ├── layout │ └── activity_main.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── mipmap-xxxhdpi │ └── ic_launcher.png │ ├── values-w820dp │ └── dimens.xml │ └── values │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── art ├── JumpingBeans_Icon (web).svg ├── JumpingBeans_logo.ai ├── JumpingBeans_logo.png ├── JumpingBeans_logo.svg ├── feature_art.jpg ├── feature_art.psd ├── jumpingdots.gif ├── jumpingword.gif ├── screenshot-1.png └── screenshot-2.png ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── keystore.properties.example ├── lib ├── .gitignore ├── build.gradle ├── gradle.properties ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── net │ │ └── frakbot │ │ └── jumpingbeans │ │ ├── JumpingBeans.java │ │ └── JumpingBeansSpan.java │ └── res │ └── values │ └── strings.xml ├── local.properties.example └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # files for the dex VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | build/ 16 | .gradle/ 17 | 18 | # Project files 19 | *.iml 20 | .idea 21 | # .idea/workspace.xml 22 | 23 | # Local configuration file (sdk path, etc) 24 | local.properties 25 | keystore.properties 26 | 27 | # Configuration files 28 | Config.java 29 | 30 | # Windows thumbnail db 31 | .DS_Store 32 | 33 | # Idea non-crucial project fileS 34 | *.iws 35 | 36 | # Sandbox stuff 37 | _sandbox -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright 2015 Frakbot (Sebastiano Poggi and Francesco Pontillo) 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![JumpingBeans icon](app/src/main/res/mipmap-xxhdpi/ic_launcher.png) 2 | 3 | JumpingBeans [![Download](https://api.bintray.com/packages/frakbot/maven/JumpingBeans/images/download.svg) ](https://bintray.com/frakbot/maven/JumpingBeans/_latestVersion) 4 | ============ 5 | 6 | The **JumpingBeans** make your test jump to the eye. Literally! 7 | 8 | ## What are the JumpingBeans 9 | Have you ever used Hangouts? If not, do it and then come back here. Go. Go, I said! 10 | 11 | Good. With that under our belt, we can be confident you've seen at least once those fancy, 12 | nice jumping suspension dots that Hangouts uses to indicate that someone is typing, or some 13 | other kind of ongoing activity (e.g., connecting to a video hangout). 14 | 15 | Well, since there's no official naming for them, and since they remind me of the jumping 16 | Mexican beans, the name for a library that emulates their behaviour has come to be exactly 17 | that: **JumpingBeans**. 18 | 19 | ## See them in action 20 | Please come closer, ladies and gentlemen! Here you have, for your own amusement, the 21 | amazing JumpingBeans: 22 | 23 | ![Jumping dots](art/jumpingdots.gif) 24 | 25 | ![Animated first word](art/jumpingword.gif) 26 | 27 | ## Make your project jump around 28 | In order to use JumpingBeans in your own project, you can use the wonderous remote dependencies capabilities 29 | that Gradle offers you (if you're not using Gradle and Android Studio/IntelliJ offer you, I'm sorry for you.) 30 | (No, I mean it, I'm really sorry for you!) 31 | 32 | Just make sure you have `jcenter()` in your root `build.gradle`: 33 | 34 | ```groovy 35 | allprojects { 36 | repositories { 37 | jcenter() 38 | } 39 | } 40 | ``` 41 | 42 | And then just add the JumpingBeans dependency to your module's `build.gradle`: 43 | 44 | ```groovy 45 | dependencies { 46 | compile 'net.frakbot:jumpingbeans:1.3.0' 47 | } 48 | ``` 49 | 50 | ## What you can do 51 | The library supports two main operation modes: **appending three jumping dots**, 52 | Hangouts-style, or making any arbitrary subsection of a CharSequence jump, either as a 53 | wave or as a single block. 54 | 55 | ### Append jumping dots 56 | This method takes the trailing `...` (or appends them, if the given TextView's text 57 | doesn't end in three dots), and makes them jump like it was -the 70s- Hangouts. 58 | 59 | The defaults emulate the Hangouts L&F as closely as possible, but you can easily change 60 | the animation properties to suit your needs. 61 | 62 | ### Make text jumping 63 | This method takes the specified subsection a the TextView text and animates it as to 64 | make it jump. 65 | 66 | ## Usage 67 | Just create a `JumpingBeans` by using its `Builder` and call the method you want: 68 | 69 | ```java 70 | // Append jumping dots 71 | final TextView textView1 = (TextView) findViewById(R.id.jumping_text_1); 72 | jumpingBeans1 = JumpingBeans.with(textView1) 73 | .appendJumpingDots() 74 | .build(); 75 | 76 | // Make the first word's letters jump 77 | final TextView textView2 = (TextView) findViewById(R.id.jumping_text_2); 78 | jumpingBeans2 = JumpingBeans.with(textView2) 79 | .makeTextJump(0, textView2.getText().toString().indexOf(' ')) 80 | .setIsWave(false) 81 | .setLoopDuration(1000) // ms 82 | .build(); 83 | ``` 84 | 85 | ### Customising the jumpin' beans 86 | Just act on the `Builder`. Don't want the dots to jump in a wave? Call 87 | `setIsWave(false)`. Don't like the default loop duration? `setLoopDuration(int)` 88 | is here to help. Fancy different per-char delays in waves? Well, ya know that 89 | `setWavePerCharDelay(int)` is the one you want. Maybe you wanted to have a 90 | shorter pause between jumping cycles? BAM, `setAnimatedDutyCycle(float)` and 91 | you're all set. 92 | 93 | ### Being a responsible citizen 94 | Since Spans were not really designed to be animated, there's some trickery 95 | going on behind the scenes to make this happen. You needn't be concerned with it, 96 | but **make sure you call the `stopJumping()` method** on your `JumpingBeans` object 97 | whenever you stop using the TextView (it's detaching from the view tree, or the 98 | container Activity or Fragment is going in paused state, ...). 99 | 100 | This allows a deeper cleanup than what the `JumpingBeans` library is trying to 101 | perform if you forget to. **Don't leave stuff lying around if you can!** 102 | 103 | ## Also, a few caveats 104 | Please note that you: 105 | 106 | * **Must not** try to change a jumping beans text in a textview before calling 107 | `stopJumping()` as to avoid unnecessary invalidation calls; 108 | the JumpingBeans class cannot know when this happens and will keep 109 | animating the textview (well, try to, anyway), wasting resources 110 | * **Must not** try to use a jumping beans text in another view; it will not 111 | animate. Just create another jumping beans animation for each new 112 | view 113 | * **Must not** use more than one JumpingBeans instance on a single TextView, as 114 | the first cleanup operation called on any of these JumpingBeans will also cleanup 115 | all other JumpingBeans' stuff. This is most likely not what you want to happen in 116 | some cases. 117 | * **Should not** use JumpingBeans on large chunks of text. Ideally this should 118 | be done on small views with just a few words. We've strived to make it as inexpensive 119 | as possible to use JumpingBeans but invalidating and possibly relayouting a large 120 | TextView can be pretty expensive. 121 | * **Must not** use JumpingBeans in conjunction with code and attributes that strip away 122 | spans or change them. This includes the 123 | [deeply flawed](https://code.google.com/p/android/issues/detail?id=67509) `textAllCaps`. 124 | 125 | ## Demo app 126 | You can find the `JumpingBeans` demo app on the [Google Play Store][1]. 127 | 128 | [![JumpingBeans Demo on Google Play Store](http://developer.android.com/images/brand/en_generic_rgb_wo_60.png)][1] 129 | 130 | ## License 131 | This library was written by **Sebastiano Poggi** and released by Frakbot under the 132 | Apache 2.0 License. 133 | 134 | Please see the [NOTICE](/NOTICE) file for details. 135 | 136 | [1]: http://play.google.com/store/apps/details?id=net.frakbot.jumpingbeans.demo 137 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Frakbot (Sebastiano Poggi and Francesco Pontillo) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | apply plugin: 'com.android.application' 18 | 19 | android { 20 | compileSdkVersion 23 21 | buildToolsVersion '23.0.2' 22 | 23 | defaultConfig { 24 | applicationId "net.frakbot.jumpingbeans.demo" 25 | minSdkVersion 15 26 | targetSdkVersion 23 27 | 28 | versionName VERSION_NAME 29 | versionCode Integer.parseInt(VERSION_CODE) 30 | } 31 | 32 | signingConfigs { 33 | release { 34 | def Properties localProps = new Properties() 35 | localProps.load(new FileInputStream(file('../local.properties'))) 36 | def Properties keyProps = new Properties() 37 | assert localProps['keystore.props.file']; 38 | keyProps.load(new FileInputStream(file(localProps['keystore.props.file']))) 39 | storeFile file(keyProps["store"]) 40 | keyAlias keyProps["alias"] 41 | storePassword keyProps["storePass"] 42 | keyPassword keyProps["pass"] 43 | } 44 | } 45 | 46 | buildTypes { 47 | release { 48 | minifyEnabled false 49 | proguardFiles getDefaultProguardFile('proguard-android.txt'), file('proguard-rules.txt') 50 | signingConfig signingConfigs.release 51 | } 52 | } 53 | 54 | compileOptions { 55 | sourceCompatibility JavaVersion.VERSION_1_7 56 | targetCompatibility JavaVersion.VERSION_1_7 57 | } 58 | } 59 | 60 | dependencies { 61 | compile fileTree(dir: 'libs', include: ['*.jar']) 62 | compile project(':lib') 63 | 64 | compile 'com.android.support:appcompat-v7:23.1.1' 65 | } 66 | -------------------------------------------------------------------------------- /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 /Applications/Android Studio.app/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 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frakbot/JumpingBeans/80d8ffbfd28cb0ffece9e8c75c229871f7d304fa/app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /app/src/main/java/net/frakbot/jumpingbeans/demo/MainActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Frakbot (Sebastiano Poggi and Francesco Pontillo) 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 net.frakbot.jumpingbeans.demo; 18 | 19 | import android.os.Bundle; 20 | import android.support.v7.app.AppCompatActivity; 21 | import android.widget.TextView; 22 | 23 | import net.frakbot.jumpingbeans.JumpingBeans; 24 | 25 | public class MainActivity extends AppCompatActivity { 26 | 27 | private JumpingBeans jumpingBeans1, jumpingBeans2; 28 | 29 | @Override 30 | protected void onCreate(Bundle savedInstanceState) { 31 | super.onCreate(savedInstanceState); 32 | setContentView(R.layout.activity_main); 33 | } 34 | 35 | @Override 36 | protected void onResume() { 37 | super.onResume(); 38 | 39 | // Here you can see that we don't duplicate trailing dots on the text (we reuse 40 | // them or, if it's an ellipsis character, replace it with three dots and animate 41 | // those instead) 42 | final TextView textView1 = (TextView) findViewById(R.id.jumping_text_1); 43 | jumpingBeans1 = JumpingBeans.with(textView1) 44 | .appendJumpingDots() 45 | .build(); 46 | 47 | // Note that, even though we access textView2's text when starting to work on 48 | // the animation builder, we don't alter it in any way, so we're ok 49 | final TextView textView2 = (TextView) findViewById(R.id.jumping_text_2); 50 | jumpingBeans2 = JumpingBeans.with(textView2) 51 | .makeTextJump(0, textView2.getText().toString().indexOf(' ')) 52 | .setIsWave(false) 53 | .setLoopDuration(1000) 54 | .build(); 55 | } 56 | 57 | @Override 58 | protected void onPause() { 59 | super.onPause(); 60 | jumpingBeans1.stopJumping(); 61 | jumpingBeans2.stopJumping(); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 27 | 28 | 34 | 35 | 42 | 43 | 48 | 49 | 53 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frakbot/JumpingBeans/80d8ffbfd28cb0ffece9e8c75c229871f7d304fa/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frakbot/JumpingBeans/80d8ffbfd28cb0ffece9e8c75c229871f7d304fa/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frakbot/JumpingBeans/80d8ffbfd28cb0ffece9e8c75c229871f7d304fa/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frakbot/JumpingBeans/80d8ffbfd28cb0ffece9e8c75c229871f7d304fa/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frakbot/JumpingBeans/80d8ffbfd28cb0ffece9e8c75c229871f7d304fa/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 21 | 64dp 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 16dp 20 | 16dp 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | Jumping Beans 21 | Jumping like a bawss… 22 | Append Jumping Dots 23 | Make first word jump 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /art/JumpingBeans_logo.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frakbot/JumpingBeans/80d8ffbfd28cb0ffece9e8c75c229871f7d304fa/art/JumpingBeans_logo.ai -------------------------------------------------------------------------------- /art/JumpingBeans_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frakbot/JumpingBeans/80d8ffbfd28cb0ffece9e8c75c229871f7d304fa/art/JumpingBeans_logo.png -------------------------------------------------------------------------------- /art/feature_art.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frakbot/JumpingBeans/80d8ffbfd28cb0ffece9e8c75c229871f7d304fa/art/feature_art.jpg -------------------------------------------------------------------------------- /art/feature_art.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frakbot/JumpingBeans/80d8ffbfd28cb0ffece9e8c75c229871f7d304fa/art/feature_art.psd -------------------------------------------------------------------------------- /art/jumpingdots.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frakbot/JumpingBeans/80d8ffbfd28cb0ffece9e8c75c229871f7d304fa/art/jumpingdots.gif -------------------------------------------------------------------------------- /art/jumpingword.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frakbot/JumpingBeans/80d8ffbfd28cb0ffece9e8c75c229871f7d304fa/art/jumpingword.gif -------------------------------------------------------------------------------- /art/screenshot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frakbot/JumpingBeans/80d8ffbfd28cb0ffece9e8c75c229871f7d304fa/art/screenshot-1.png -------------------------------------------------------------------------------- /art/screenshot-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frakbot/JumpingBeans/80d8ffbfd28cb0ffece9e8c75c229871f7d304fa/art/screenshot-2.png -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Frakbot (Sebastiano Poggi and Francesco Pontillo) 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 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 18 | 19 | buildscript { 20 | repositories { 21 | jcenter() 22 | } 23 | dependencies { 24 | classpath 'com.android.tools.build:gradle:1.5.0' 25 | classpath 'com.novoda:bintray-release:0.3.4' 26 | } 27 | } 28 | 29 | def isReleaseBuild() { 30 | return version.contains("SNAPSHOT") == false 31 | } 32 | 33 | allprojects { 34 | repositories { 35 | jcenter() 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015 Frakbot (Sebastiano Poggi and Francesco Pontillo) 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 | VERSION_NAME=1.3.0 18 | VERSION_CODE=4 19 | 20 | # Only used for beta versions 21 | VERSION_BETANUMBER=Beta 1 22 | 23 | # JCenter properties 24 | GROUP_ID=net.frakbot 25 | ARTIFACT_ID=jumpingbeans 26 | UPLOAD_NAME=JumpingBeans 27 | POM_DESCRIPTION=Emulates Hangouts' jumping dots, and animates arbitrary chunks of text 28 | POM_URL=https://github.com/frakbot/JumpingBeans 29 | POM_LICENSE_DIST=repo 30 | POM_DEVELOPER_ID=frakbot 31 | POM_DEVELOPER_NAME=Frakbot 32 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frakbot/JumpingBeans/80d8ffbfd28cb0ffece9e8c75c229871f7d304fa/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon May 18 14:18:58 BST 2015 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /keystore.properties.example: -------------------------------------------------------------------------------- 1 | # This is an example keystore.properties file. 2 | store=mykeystore.keystore 3 | alias=keyname 4 | pass=keyPassword 5 | storePass=keystorePassword 6 | -------------------------------------------------------------------------------- /lib/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /lib/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Frakbot (Sebastiano Poggi and Francesco Pontillo) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | apply plugin: 'com.android.library' 18 | apply plugin: 'com.novoda.bintray-release' 19 | 20 | android { 21 | compileSdkVersion 23 22 | buildToolsVersion '23.0.2' 23 | 24 | defaultConfig { 25 | minSdkVersion 15 26 | targetSdkVersion 23 27 | 28 | versionName VERSION_NAME 29 | versionCode Integer.parseInt(VERSION_CODE) 30 | } 31 | 32 | compileOptions { 33 | sourceCompatibility JavaVersion.VERSION_1_7 34 | targetCompatibility JavaVersion.VERSION_1_7 35 | } 36 | 37 | signingConfigs { 38 | release { 39 | def Properties localProps = new Properties() 40 | localProps.load(new FileInputStream(file('../local.properties'))) 41 | def Properties keyProps = new Properties() 42 | assert localProps['keystore.props.file']; 43 | keyProps.load(new FileInputStream(file(localProps['keystore.props.file']))) 44 | storeFile file(keyProps["store"]) 45 | keyAlias keyProps["alias"] 46 | storePassword keyProps["storePass"] 47 | keyPassword keyProps["pass"] 48 | } 49 | } 50 | 51 | buildTypes { 52 | release { 53 | minifyEnabled false 54 | proguardFiles getDefaultProguardFile('proguard-android.txt'), file('proguard-rules.txt') 55 | signingConfig signingConfigs.release 56 | } 57 | } 58 | } 59 | 60 | dependencies { 61 | compile fileTree(dir: 'libs', include: ['*.jar']) 62 | compile 'com.android.support:support-annotations:23.1.1' 63 | } 64 | 65 | publish { 66 | userOrg = POM_DEVELOPER_ID 67 | groupId = GROUP_ID 68 | artifactId = ARTIFACT_ID 69 | publishVersion = VERSION_NAME 70 | uploadName = UPLOAD_NAME 71 | description = POM_DESCRIPTION 72 | website = POM_URL 73 | } 74 | -------------------------------------------------------------------------------- /lib/gradle.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015 Frakbot (Sebastiano Poggi and Francesco Pontillo) 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | POM_ARTIFACT_ID=jumpingbeans 18 | POM_NAME=JumpingBeans 19 | POM_PACKAGING=aar 20 | -------------------------------------------------------------------------------- /lib/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 /Applications/Android Studio.app/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 | -------------------------------------------------------------------------------- /lib/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /lib/src/main/java/net/frakbot/jumpingbeans/JumpingBeans.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Frakbot (Sebastiano Poggi and Francesco Pontillo) 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 net.frakbot.jumpingbeans; 18 | 19 | import android.support.annotation.FloatRange; 20 | import android.support.annotation.IntRange; 21 | import android.support.annotation.NonNull; 22 | import android.text.SpannableStringBuilder; 23 | import android.text.Spanned; 24 | import android.text.TextUtils; 25 | import android.widget.TextView; 26 | 27 | import java.lang.ref.WeakReference; 28 | 29 | /** 30 | * Provides "jumping beans" functionality for a TextView. 31 | *

32 | * Remember to call the {@link #stopJumping()} method once you're done 33 | * using the JumpingBeans (that is, when you detach the TextView from 34 | * the view tree, you hide it, or the parent Activity/Fragment goes in 35 | * the paused status). This will allow to release the animations and 36 | * free up memory and CPU that would be otherwise wasted. 37 | *

38 | * Please note that you: 39 | *

56 | */ 57 | public final class JumpingBeans { 58 | 59 | private static final String ELLIPSIS_GLYPH = "…"; 60 | private static final String THREE_DOTS_ELLIPSIS = "..."; 61 | private static final int THREE_DOTS_ELLIPSIS_LENGTH = 3; 62 | 63 | private final JumpingBeansSpan[] jumpingBeans; 64 | private final WeakReference textView; 65 | 66 | private JumpingBeans(JumpingBeansSpan[] beans, TextView textView) { 67 | this.jumpingBeans = beans; 68 | this.textView = new WeakReference<>(textView); 69 | } 70 | 71 | /** 72 | * Create an instance of the {@link net.frakbot.jumpingbeans.JumpingBeans.Builder} 73 | * applied to the provided {@code TextView}. 74 | * 75 | * @param textView The TextView to apply the JumpingBeans to 76 | * @return the {@link net.frakbot.jumpingbeans.JumpingBeans.Builder} 77 | */ 78 | public static Builder with(@NonNull TextView textView) { 79 | return new Builder(textView); 80 | } 81 | 82 | /** 83 | * Stops the jumping animation and frees up the animations. 84 | */ 85 | public void stopJumping() { 86 | for (JumpingBeansSpan bean : jumpingBeans) { 87 | if (bean != null) { 88 | bean.teardown(); 89 | } 90 | } 91 | 92 | cleanupSpansFrom(textView.get()); 93 | } 94 | 95 | private static void cleanupSpansFrom(TextView textView) { 96 | if (textView == null) { 97 | return; 98 | } 99 | 100 | CharSequence text = textView.getText(); 101 | if (text instanceof Spanned) { 102 | CharSequence cleanText = removeJumpingBeansSpansFrom((Spanned) text); 103 | textView.setText(cleanText); 104 | } 105 | } 106 | 107 | private static CharSequence removeJumpingBeansSpansFrom(Spanned text) { 108 | SpannableStringBuilder sbb = new SpannableStringBuilder(text.toString()); 109 | Object[] spans = text.getSpans(0, text.length(), Object.class); 110 | for (Object span : spans) { 111 | if (!(span instanceof JumpingBeansSpan)) { 112 | sbb.setSpan(span, text.getSpanStart(span), text.getSpanEnd(span), text.getSpanFlags(span)); 113 | } 114 | } 115 | return sbb; 116 | } 117 | 118 | /** 119 | * Builder class for {@link net.frakbot.jumpingbeans.JumpingBeans} objects. 120 | *

121 | * Provides a way to set the fields of a {@link JumpingBeans} and generate 122 | * the desired jumping beans effect. With this builder you can easily append 123 | * a Hangouts-style trio of jumping suspension points to any TextView, or 124 | * apply the effect to any other subset of a TextView's text. 125 | *

126 | *

Example: 127 | *

128 | *

129 |      * JumpingBeans jumpingBeans = JumpingBeans.with(myTextView)
130 |      *   .appendJumpingDots()
131 |      *   .setLoopDuration(1500)
132 |      *   .build();
133 |      * 
134 | * 135 | * @see JumpingBeans#with(TextView) 136 | */ 137 | public static class Builder { 138 | 139 | private static final float DEFAULT_ANIMATION_DUTY_CYCLE = 0.65f; 140 | private static final int DEFAULT_LOOP_DURATION = 1300; // ms 141 | private static final int DEFAULT_WAVE_CHAR_DELAY = -1; 142 | 143 | private final TextView textView; 144 | 145 | private int startPos; 146 | private int endPos; 147 | 148 | private float animRange = DEFAULT_ANIMATION_DUTY_CYCLE; 149 | private int loopDuration = DEFAULT_LOOP_DURATION; 150 | private int waveCharDelay = DEFAULT_WAVE_CHAR_DELAY; 151 | private CharSequence text; 152 | private boolean wave; 153 | 154 | Builder(TextView textView) { 155 | this.textView = textView; 156 | } 157 | 158 | /** 159 | * Appends three jumping dots to the end of a TextView text. 160 | *

161 | * This implies that the animation will by default be a wave. 162 | *

163 | * If the TextView has no text, the resulting TextView text will 164 | * consist of the three dots only. 165 | *

166 | * The TextView text is cached to the current value at 167 | * this time and set again in the {@link #build()} method, so any 168 | * change to the TextView text done in the meantime will be lost. 169 | * This means that you should do all changes to the TextView text 170 | * before you begin using this builder. 171 | *

172 | * Call the {@link #build()} method once you're done to get the 173 | * resulting {@link net.frakbot.jumpingbeans.JumpingBeans}. 174 | * 175 | * @see #setIsWave(boolean) 176 | */ 177 | @NonNull 178 | public Builder appendJumpingDots() { 179 | CharSequence text = appendThreeDotsEllipsisTo(textView); 180 | 181 | this.text = text; 182 | this.wave = true; 183 | this.startPos = text.length() - THREE_DOTS_ELLIPSIS_LENGTH; 184 | this.endPos = text.length(); 185 | 186 | return this; 187 | } 188 | 189 | private static CharSequence appendThreeDotsEllipsisTo(TextView textView) { 190 | CharSequence text = getTextSafe(textView); 191 | if (text.length() > 0 && endsWithEllipsisGlyph(text)) { 192 | text = text.subSequence(0, text.length() - 1); 193 | } 194 | 195 | if (!endsWithThreeEllipsisDots(text)) { 196 | text = new SpannableStringBuilder(text).append(THREE_DOTS_ELLIPSIS); // Preserve spans in original text 197 | } 198 | return text; 199 | } 200 | 201 | private static CharSequence getTextSafe(TextView textView) { 202 | return !TextUtils.isEmpty(textView.getText()) ? textView.getText() : ""; 203 | } 204 | 205 | private static boolean endsWithEllipsisGlyph(CharSequence text) { 206 | CharSequence lastChar = text.subSequence(text.length() - 1, text.length()); 207 | return ELLIPSIS_GLYPH.equals(lastChar); 208 | } 209 | 210 | @SuppressWarnings("SimplifiableIfStatement") // For readability 211 | private static boolean endsWithThreeEllipsisDots(CharSequence text) { 212 | if (text.length() < THREE_DOTS_ELLIPSIS_LENGTH) { 213 | // TODO we should try to normalize "invalid" ellipsis (e.g., ".." or "....") 214 | return false; 215 | } 216 | CharSequence lastThreeChars = text.subSequence(text.length() - THREE_DOTS_ELLIPSIS_LENGTH, text.length()); 217 | return THREE_DOTS_ELLIPSIS.equals(lastThreeChars); 218 | } 219 | 220 | /** 221 | * Appends three jumping dots to the end of a TextView text. 222 | *

223 | * This implies that the animation will by default be a wave. 224 | *

225 | * If the TextView has no text, the resulting TextView text will 226 | * consist of the three dots only. 227 | *

228 | * The TextView text is cached to the current value at 229 | * this time and set again in the {@link #build()} method, so any 230 | * change to the TextView text done in the meantime will be lost. 231 | * This means that you should do all changes to the TextView text 232 | * before you begin using this builder. 233 | *

234 | * Call the {@link #build()} method once you're done to get the 235 | * resulting {@link net.frakbot.jumpingbeans.JumpingBeans}. 236 | * 237 | * @param startPos The position of the first character to animate 238 | * @param endPos The position after the one the animated range ends at 239 | * (just like in {@link String#substring(int)}) 240 | * @see #setIsWave(boolean) 241 | */ 242 | @NonNull 243 | public Builder makeTextJump(@IntRange(from = 0) int startPos, @IntRange(from = 0) int endPos) { 244 | CharSequence text = textView.getText(); 245 | ensureTextCanJump(startPos, endPos, text); 246 | 247 | this.text = text; 248 | this.wave = true; 249 | this.startPos = startPos; 250 | this.endPos = endPos; 251 | 252 | return this; 253 | } 254 | 255 | private static CharSequence ensureTextCanJump(int startPos, int endPos, CharSequence text) { 256 | if (text == null) { 257 | throw new NullPointerException("The textView text must not be null"); 258 | } 259 | 260 | if (endPos < startPos) { 261 | throw new IllegalArgumentException("The start position must be smaller than the end position"); 262 | } 263 | 264 | if (startPos < 0) { 265 | throw new IndexOutOfBoundsException("The start position must be non-negative"); 266 | } 267 | 268 | if (endPos > text.length()) { 269 | throw new IndexOutOfBoundsException("The end position must be smaller than the text length"); 270 | } 271 | return text; 272 | } 273 | 274 | /** 275 | * Sets the fraction of the animation loop time spent actually animating. 276 | * The rest of the time will be spent "resting". 277 | * 278 | * @param animatedRange The fraction of the animation loop time spent 279 | * actually animating the characters 280 | */ 281 | @NonNull 282 | public Builder setAnimatedDutyCycle(@FloatRange(from = 0f, to = 1f, fromInclusive = false) float animatedRange) { 283 | if (animatedRange <= 0f || animatedRange > 1f) { 284 | throw new IllegalArgumentException("The animated range must be in the (0, 1] range"); 285 | } 286 | this.animRange = animatedRange; 287 | return this; 288 | } 289 | 290 | /** 291 | * Sets the jumping loop duration. 292 | * 293 | * @param loopDuration The jumping animation loop duration, in milliseconds 294 | */ 295 | @NonNull 296 | public Builder setLoopDuration(@IntRange(from = 1) int loopDuration) { 297 | if (loopDuration < 1) { 298 | throw new IllegalArgumentException("The loop duration must be bigger than zero"); 299 | } 300 | this.loopDuration = loopDuration; 301 | return this; 302 | } 303 | 304 | /** 305 | * Sets the delay for starting the animation of every single dot over the 306 | * start of the previous one, in milliseconds. The default value is 307 | * the loop length divided by three times the number of character animated 308 | * by this instance of JumpingBeans. 309 | *

310 | * Only has a meaning when the animation is a wave. 311 | * 312 | * @param waveCharOffset The start delay for the animation of every single 313 | * character over the previous one, in milliseconds 314 | * @see #setIsWave(boolean) 315 | */ 316 | @NonNull 317 | public Builder setWavePerCharDelay(@IntRange(from = 0) int waveCharOffset) { 318 | if (waveCharOffset < 0) { 319 | throw new IllegalArgumentException("The wave char offset must be non-negative"); 320 | } 321 | this.waveCharDelay = waveCharOffset; 322 | return this; 323 | } 324 | 325 | /** 326 | * Sets a flag that determines if the characters will jump in a wave 327 | * (i.e., with a delay between each other) or all at the same 328 | * time. 329 | * 330 | * @param wave If true, the animation is going to be a wave; if 331 | * false, all characters will jump ay the same time 332 | * @see #setWavePerCharDelay(int) 333 | */ 334 | @NonNull 335 | public Builder setIsWave(boolean wave) { 336 | this.wave = wave; 337 | return this; 338 | } 339 | 340 | /** 341 | * Combine all of the options that have been set and return a new 342 | * {@link net.frakbot.jumpingbeans.JumpingBeans} instance. 343 | *

344 | * Remember to call the {@link #stopJumping()} method once you're done 345 | * using the JumpingBeans (that is, when you detach the TextView from 346 | * the view tree, you hide it, or the parent Activity/Fragment goes in 347 | * the paused status). This will allow to release the animations and 348 | * free up memory and CPU that would be otherwise wasted. 349 | */ 350 | @NonNull 351 | public JumpingBeans build() { 352 | SpannableStringBuilder sbb = new SpannableStringBuilder(text); 353 | JumpingBeansSpan[] spans; 354 | if (wave) { 355 | spans = buildWavingSpans(sbb); 356 | } else { 357 | spans = buildSingleSpan(sbb); 358 | } 359 | 360 | textView.setText(sbb); 361 | return new JumpingBeans(spans, textView); 362 | } 363 | 364 | @SuppressWarnings("Range") // Lint bug: the if makes sure waveCharDelay >= 0 365 | private JumpingBeansSpan[] buildWavingSpans(SpannableStringBuilder sbb) { 366 | JumpingBeansSpan[] spans; 367 | if (waveCharDelay == DEFAULT_WAVE_CHAR_DELAY) { 368 | waveCharDelay = loopDuration / (3 * (endPos - startPos)); 369 | } 370 | 371 | spans = new JumpingBeansSpan[endPos - startPos]; 372 | for (int pos = startPos; pos < endPos; pos++) { 373 | JumpingBeansSpan jumpingBean = 374 | new JumpingBeansSpan(textView, loopDuration, pos - startPos, waveCharDelay, animRange); 375 | sbb.setSpan(jumpingBean, pos, pos + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 376 | spans[pos - startPos] = jumpingBean; 377 | } 378 | return spans; 379 | } 380 | 381 | private JumpingBeansSpan[] buildSingleSpan(SpannableStringBuilder sbb) { 382 | JumpingBeansSpan[] spans; 383 | spans = new JumpingBeansSpan[]{new JumpingBeansSpan(textView, loopDuration, 0, 0, animRange)}; 384 | sbb.setSpan(spans[0], startPos, endPos, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 385 | return spans; 386 | } 387 | 388 | } 389 | 390 | } 391 | -------------------------------------------------------------------------------- /lib/src/main/java/net/frakbot/jumpingbeans/JumpingBeansSpan.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Frakbot (Sebastiano Poggi and Francesco Pontillo) 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 net.frakbot.jumpingbeans; 18 | 19 | import android.animation.TimeInterpolator; 20 | import android.animation.ValueAnimator; 21 | import android.os.Build; 22 | import android.support.annotation.FloatRange; 23 | import android.support.annotation.IntRange; 24 | import android.support.annotation.NonNull; 25 | import android.text.TextPaint; 26 | import android.text.style.SuperscriptSpan; 27 | import android.util.Log; 28 | import android.view.View; 29 | import android.widget.TextView; 30 | 31 | import java.lang.ref.WeakReference; 32 | 33 | final class JumpingBeansSpan extends SuperscriptSpan implements ValueAnimator.AnimatorUpdateListener { 34 | 35 | private final WeakReference textView; 36 | private final int delay; 37 | private final int loopDuration; 38 | private final float animatedRange; 39 | private int shift; 40 | private ValueAnimator jumpAnimator; 41 | 42 | public JumpingBeansSpan(@NonNull TextView textView, 43 | @IntRange(from = 1) int loopDuration, 44 | @IntRange(from = 0) int position, 45 | @IntRange(from = 0) int waveCharOffset, 46 | @FloatRange(from = 0, to = 1, fromInclusive = false) float animatedRange) { 47 | this.textView = new WeakReference<>(textView); 48 | this.delay = waveCharOffset * position; 49 | this.loopDuration = loopDuration; 50 | this.animatedRange = animatedRange; 51 | } 52 | 53 | @Override 54 | public void updateMeasureState(TextPaint tp) { 55 | initIfNecessary(tp.ascent()); 56 | tp.baselineShift = shift; 57 | } 58 | 59 | @Override 60 | public void updateDrawState(TextPaint tp) { 61 | initIfNecessary(tp.ascent()); 62 | tp.baselineShift = shift; 63 | } 64 | 65 | private void initIfNecessary(float ascent) { 66 | if (jumpAnimator != null) { 67 | return; 68 | } 69 | 70 | this.shift = 0; 71 | int maxShift = (int) ascent / 2; 72 | jumpAnimator = ValueAnimator.ofInt(0, maxShift); 73 | jumpAnimator 74 | .setDuration(loopDuration) 75 | .setStartDelay(delay); 76 | jumpAnimator.setInterpolator(new JumpInterpolator(animatedRange)); 77 | jumpAnimator.setRepeatCount(ValueAnimator.INFINITE); 78 | jumpAnimator.setRepeatMode(ValueAnimator.RESTART); 79 | jumpAnimator.addUpdateListener(this); 80 | jumpAnimator.start(); 81 | } 82 | 83 | @Override 84 | public void onAnimationUpdate(ValueAnimator animation) { 85 | // No need for synchronization as this always runs on main thread anyway 86 | TextView v = textView.get(); 87 | if (v != null) { 88 | updateAnimationFor(animation, v); 89 | } else { 90 | cleanupAndComplainAboutUserBeingAFool(); 91 | } 92 | } 93 | 94 | private void updateAnimationFor(ValueAnimator animation, TextView v) { 95 | if (isAttachedToHierarchy(v)) { 96 | shift = (int) animation.getAnimatedValue(); 97 | v.invalidate(); 98 | } 99 | } 100 | 101 | private static boolean isAttachedToHierarchy(View v) { 102 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 103 | return v.isAttachedToWindow(); 104 | } 105 | return v.getParent() != null; // Best-effort fallback (without adding support-v4 just for this...) 106 | } 107 | 108 | private void cleanupAndComplainAboutUserBeingAFool() { 109 | // The textview has been destroyed and teardown() hasn't been called 110 | teardown(); 111 | Log.w("JumpingBeans", "!!! Remember to call JumpingBeans.stopJumping() when appropriate !!!"); 112 | } 113 | 114 | public void teardown() { 115 | if (jumpAnimator != null) { 116 | jumpAnimator.cancel(); 117 | jumpAnimator.removeAllListeners(); 118 | } 119 | if (textView.get() != null) { 120 | textView.clear(); 121 | } 122 | } 123 | 124 | /** 125 | * A tweaked {@link android.view.animation.AccelerateDecelerateInterpolator} 126 | * that covers the full range in a fraction of its input range, and holds on 127 | * the final value on the rest of the input range. By default, this fraction 128 | * is 65% of the full range. 129 | * 130 | * @see JumpingBeans.Builder#DEFAULT_ANIMATION_DUTY_CYCLE 131 | */ 132 | private static class JumpInterpolator implements TimeInterpolator { 133 | 134 | private final float animRange; 135 | 136 | public JumpInterpolator(float animatedRange) { 137 | animRange = Math.abs(animatedRange); 138 | } 139 | 140 | @Override 141 | public float getInterpolation(float input) { 142 | // We want to map the [0, PI] sine range onto [0, animRange] 143 | if (input > animRange) { 144 | return 0f; 145 | } 146 | double radians = (input / animRange) * Math.PI; 147 | return (float) Math.sin(radians); 148 | } 149 | 150 | } 151 | 152 | } 153 | -------------------------------------------------------------------------------- /lib/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | JumpingBeans 19 | 20 | -------------------------------------------------------------------------------- /local.properties.example: -------------------------------------------------------------------------------- 1 | ## This file is automatically generated by Android Studio. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must *NOT* be checked into Version Control Systems, 5 | # as it contains information specific to your local configuration. 6 | # 7 | # Location of the SDK. This is only used by Gradle. 8 | # For customization when using a Version Control System, please read the 9 | # header note. 10 | #Sun Feb 16 13:39:45 GMT 2014 11 | sdk.dir=/your/path/to/android-sdk 12 | keystore.props.file=../keystore.properties 13 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':lib' 2 | --------------------------------------------------------------------------------