├── .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 | 
2 |
3 | JumpingBeans [ ](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 | 
24 |
25 | 
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 | [][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 | *
40 | * - Must not try to change a jumping beans text in a textview before calling
41 | * {@link #stopJumping()} as to avoid unnecessary invalidation calls;
42 | * the JumpingBeans class cannot know when this happens and will keep
43 | * animating the textview (well, try to, anyway), wasting resources
44 | * - Must not try to use a jumping beans text in another view; it will not
45 | * animate. Just create another jumping beans animation for each new
46 | * view
47 | * - Must not use more than one JumpingBeans instance on a single TextView, as
48 | * the first cleanup operation called on any of these JumpingBeans will also cleanup
49 | * all other JumpingBeans' stuff. This is most likely not what you want to happen in
50 | * most cases.
51 | * - Should not use JumpingBeans on large chunks of text. Ideally this should
52 | * be done on small views with just a few words. We've strived to make it as inexpensive
53 | * as possible to use JumpingBeans but invalidating and possibly relayouting a large
54 | * TextView can be pretty expensive.
55 | *
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 |
--------------------------------------------------------------------------------