diff --git a/README.md b/README.md
index ec0dbe9..91b84b9 100644
--- a/README.md
+++ b/README.md
@@ -8,6 +8,14 @@
Rotating text is an Android library that can be used to make text switching painless and beautiful, with the use of interpolators, typefaces and more customisations.
+## ✨ New Features
+
+- **Stop at Last Entry** - Rotation stops at the last word instead of cycling infinitely
+- **XML Attributes** - Configure text size, stop behavior, and more directly in XML
+- **Layout Preview** - See preview text in Android Studio's design view
+- **Last Interpolator** - Use a different animation for the final word transition
+- **Improved Text Sizing** - Text size now works correctly at any value
+
# Usage
Just add the following dependency in your app's `build.gradle`
```
@@ -16,21 +24,23 @@ dependencies {
}
```
-## Example Usage 1 (Simple)
+## Example Usage 1 (Simple with XML Attributes)
#### XML
-```
+```xml
+ android:layout_height="wrap_content"
+ app:textSize="35sp"
+ app:stopAtLast="false"
+ app:previewText="This is Word" />
```
#### Java
-```
-RotatingTextWrapper rotatingTextWrapper = (RotatingTextWrapper) findViewById(R.id.custom_switcher);
-rotatingTextWrapper.setSize(35);
+```java
+RotatingTextWrapper rotatingTextWrapper = findViewById(R.id.custom_switcher);
Rotatable rotatable = new Rotatable(Color.parseColor("#FFA036"), 1000, "Word", "Word01", "Word02");
rotatable.setSize(35);
@@ -42,57 +52,60 @@ rotatingTextWrapper.setContent("This is ?", rotatable);
#### Result
-## Example Usage 2 (Typeface + Interpolator)
+## Example Usage 2 (Stop at Last with Custom Interpolator)
#### XML
-```
+```xml
+ android:layout_height="wrap_content"
+ app:textSize="35sp"
+ app:stopAtLast="true" />
```
#### Java
-```
-Typeface typeface = Typeface.createFromAsset(getAssets(), "fonts/Raleway-Light.ttf");
-Typeface typeface2 = Typeface.createFromAsset(getAssets(), "fonts/Reckoner_Bold.ttf");
+```java
+Typeface typeface = ResourcesCompat.getFont(this, R.font.your_font);
-RotatingTextWrapper rotatingTextWrapper = (RotatingTextWrapper) findViewById(R.id.custom_switcher);
-rotatingTextWrapper.setSize(35);
-rotatingTextWrapper.setTypeface(typeface2);
+RotatingTextWrapper rotatingTextWrapper = findViewById(R.id.custom_switcher);
+rotatingTextWrapper.setTypeface(typeface);
-Rotatable rotatable = new Rotatable(Color.parseColor("#FFA036"), 1000, "Word", "Word01", "Word02");
+Rotatable rotatable = new Rotatable(Color.parseColor("#FFA036"), 1000, "Loading", "Processing", "Done!");
rotatable.setSize(35);
rotatable.setAnimationDuration(500);
rotatable.setTypeface(typeface);
rotatable.setInterpolator(new BounceInterpolator());
+// Different animation for last word!
+rotatable.setLastInterpolator(new OvershootInterpolator(2.0f));
-rotatingTextWrapper.setContent("This is ?", rotatable);
+rotatingTextWrapper.setContent("Status: ?", rotatable);
```
#### Result
+The text will rotate through "Loading" → "Processing" → "Done!" and stop. The last word "Done!" will use a dramatic overshoot animation!
+
## Example Usage 3 (Multiple Rotatables)
#### XML
-```
+```xml
+ android:layout_height="wrap_content"
+ app:textSize="35sp" />
```
#### Java
-```
-Typeface typeface = Typeface.createFromAsset(getAssets(), "fonts/Raleway-Light.ttf");
-Typeface typeface2 = Typeface.createFromAsset(getAssets(), "fonts/Reckoner_Bold.ttf");
+```java
+Typeface typeface = ResourcesCompat.getFont(this, R.font.your_font);
-RotatingTextWrapper rotatingTextWrapper = (RotatingTextWrapper) findViewById(R.id.custom_switcher);
-rotatingTextWrapper.setSize(35);
-rotatingTextWrapper.setTypeface(typeface2);
+RotatingTextWrapper rotatingTextWrapper = findViewById(R.id.custom_switcher);
+rotatingTextWrapper.setTypeface(typeface);
Rotatable rotatable = new Rotatable(Color.parseColor("#FFA036"), 1000, "Word", "Word01", "Word02");
rotatable.setSize(35);
@@ -112,13 +125,72 @@ rotatingTextWrapper.setContent("This is ? and ?", rotatable, rotatable2);
#### Result
+## Example Usage 4 (Modern Countdown)
+#### XML
+
+```xml
+
+```
+
+#### Java
+
+```java
+Typeface typeface = ResourcesCompat.getFont(this, R.font.barlow_condensed_semi_bold);
+
+RotatingTextWrapper wrapper = findViewById(R.id.countdown_text);
+wrapper.setTypeface(typeface);
+
+// Create countdown: 10, 9, 8, ... 2, 1, 0
+String[] countdown = createCountdownArray(10);
+
+Rotatable rotatable = new Rotatable(
+ ContextCompat.getColor(this, R.color.white),
+ 200, // Fast transitions
+ countdown
+);
+rotatable.setSize(38);
+rotatable.setStopAtLast(true); // Stop at 0
+rotatable.setAnimationDuration(200);
+rotatable.setCenter(true);
+rotatable.setTypeface(typeface);
+rotatable.setInterpolator(new LinearInterpolator()); // Smooth countdown
+rotatable.setLastInterpolator(new BounceInterpolator()); // Bounce on final number!
+
+wrapper.setContent("?", rotatable);
+```
+
# Documentation
Rotating text is made of two parts : `RotatingTextWrapper` and `Rotatable`.
Each rotatable encapsulates the collection of words that are two be periodically switched and also defines various properties related to these words, like, size, color, animation interpolator etc.
Each Rotatable must be a part of a `RotatingTextWrapper`. This defines the actual layout of the text and the positions of the rotating text.
-For eg : `rotatingTextWrapper.setContent("This is ?", rotatble);`. Here the `?` denotes the postion of the `rotatable`.
+For eg : `rotatingTextWrapper.setContent("This is ?", rotatable);`. Here the `?` denotes the position of the `rotatable`.
+
+## XML Attributes
+
+Configure RotatingTextWrapper directly in XML:
+
+|Attribute |Type |Description |
+|------------------|---------------|----------------------------------------|
+|`app:textSize` | dimension | Text size (e.g., "24sp", "16dp") |
+|`app:stopAtLast` | boolean | Stop at last word instead of cycling |
+|`app:previewText` | string | Preview text in layout editor |
+|`app:adaptable` | boolean | Adapt to parent width |
+
+**Example:**
+```xml
+
+```
## RotatingTextWrapper
|Property |Function |Description |
@@ -126,6 +198,9 @@ For eg : `rotatingTextWrapper.setContent("This is ?", rotatble);`. Here the `?`
|Content | setContent(...) | Set the actual content. Composed of a String and array of Rotatables. |
|Typeface | setTypeface(...) | Set the typeface of the non-rotating text |
|Size | setSize(...) | Set the size of the non-rotating text |
+|Stop At Last | setStopAtLast(...) | Stop rotation at last word instead of cycling |
+|Preview Text | setPreviewText(...) | Set preview text for layout editor |
+|Last Interpolator | setLastInterpolator(...) | Set special interpolator for last word animation |
|Pause | pause(x) | Method to pause the 'x'th rotatable |
|Resume | resume(x) | Method to resume the 'x'th rotatable |
@@ -136,9 +211,48 @@ For eg : `rotatingTextWrapper.setContent("This is ?", rotatble);`. Here the `?`
|Size | setSize(...) | Set the size of the rotating text associated with this rotatable |
|Typeface | setTypeface(...) | Set the typeface of the rotating text associated with this rotatable |
|Interpolator | setInterpolator(...) | Set the animation interpolator used while switching text |
+|Last Interpolator | setLastInterpolator(...) | Set different interpolator for animating to last word |
|Update Duration | setUpdateDuration(...) | Set the interval between switching the words |
|Animation Duration | setAnimationDuration(...) | Set the duration of the switching animation |
-|Center Align | setCenter(...) |Align the rotating text to center of the textview if set to **true** |
+|Center Align | setCenter(...) | Align the rotating text to center of the textview if set to **true** |
+|Stop At Last | setStopAtLast(...) | Stop at last word in the array instead of cycling |
+
+## New Features Guide
+
+### Stop at Last Entry
+Stop rotation at the last word instead of cycling back:
+
+```java
+rotatable.setStopAtLast(true);
+// or
+wrapper.setStopAtLast(true); // Applies to all rotatables
+```
+
+### Last Interpolator
+Use a different animation for the final word:
+
+```java
+rotatable.setInterpolator(new BounceInterpolator()); // For all words
+rotatable.setLastInterpolator(new OvershootInterpolator(2.0f)); // Special finish!
+```
+
+**Perfect for:**
+- Loading sequences with satisfying completions
+- Countdowns with dramatic endings
+- Status updates with emphasis on final state
+
+### Preview in Layout Editor
+See your text in Android Studio's design view:
+
+```xml
+
+```
+
+📚 **For more examples and detailed documentation, see:**
+- [NEW_FEATURES.md](NEW_FEATURES.md) - Quick start guide
+- [FEATURE_DOCUMENTATION.md](FEATURE_DOCUMENTATION.md) - Complete usage guide
+- [LAST_INTERPOLATOR_FEATURE.md](LAST_INTERPOLATOR_FEATURE.md) - Last interpolator deep dive
# License
diff --git a/app/build.gradle b/app/build.gradle
index 1a64fce..105b4df 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,16 +1,17 @@
apply plugin: 'com.android.application'
android {
- compileSdkVersion 27
- buildToolsVersion '27.0.3'
+ compileSdkVersion 36
+ buildToolsVersion = '30.0.3'
defaultConfig {
applicationId "com.sdsmdg.harjot.rotatingtextlibrary"
minSdkVersion 16
- targetSdkVersion 27
+ targetSdkVersion 36
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
+ namespace "com.sdsmdg.harjot.rotatingtextlibrary"
buildTypes {
release {
minifyEnabled false
@@ -20,6 +21,6 @@ android {
}
dependencies {
- implementation 'com.android.support:appcompat-v7:27.1.1'
+ implementation 'androidx.appcompat:appcompat:1.6.1'
implementation project(":rotatingtext")
-}
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index aa50730..35efb46 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,6 +1,6 @@
+>
-
+
diff --git a/app/src/main/java/com/sdsmdg/harjot/rotatingtextlibrary/MainActivity.java b/app/src/main/java/com/sdsmdg/harjot/rotatingtextlibrary/MainActivity.java
index 8f89744..a28f072 100644
--- a/app/src/main/java/com/sdsmdg/harjot/rotatingtextlibrary/MainActivity.java
+++ b/app/src/main/java/com/sdsmdg/harjot/rotatingtextlibrary/MainActivity.java
@@ -1,25 +1,37 @@
package com.sdsmdg.harjot.rotatingtextlibrary;
+import static java.security.AccessController.getContext;
+
import android.graphics.Color;
import android.graphics.Typeface;
import android.os.Bundle;
-import android.support.v7.app.AppCompatActivity;
+
import android.text.TextUtils;
+import android.util.Log;
import android.view.View;
import android.view.animation.AccelerateInterpolator;
+import android.view.animation.BounceInterpolator;
import android.view.animation.DecelerateInterpolator;
+import android.view.animation.LinearInterpolator;
+import android.view.animation.OvershootInterpolator;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Spinner;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.content.ContextCompat;
+import androidx.core.content.res.ResourcesCompat;
+
import com.sdsmdg.harjot.rotatingtext.RotatingTextSwitcher;
import com.sdsmdg.harjot.rotatingtext.RotatingTextWrapper;
import com.sdsmdg.harjot.rotatingtext.models.Rotatable;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
+import java.util.stream.IntStream;
public class MainActivity extends AppCompatActivity {
@@ -41,15 +53,20 @@ protected void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.activity_main);
rotatingTextSwitcher = new RotatingTextSwitcher(this);
- Typeface typeface = Typeface.createFromAsset(getAssets(), "fonts/Raleway-Light.ttf");
- Typeface typeface2 = Typeface.createFromAsset(getAssets(), "fonts/Reckoner_Bold.ttf");
rotatingTextWrapper = findViewById(R.id.custom_switcher);
+
+ // Option 1: Use the modern countdown implementation
+ // setRotating(rotatingTextWrapper, 10);
+
+ // Option 2: Classic example with custom colors and typefaces
+ Typeface typeface = ResourcesCompat.getFont(this, R.font.love);
+
rotatingTextWrapper.setSize(30);
- rotatingTextWrapper.setTypeface(typeface2);
+ rotatingTextWrapper.setTypeface(typeface);
-// rotatable = new Rotatable(Color.parseColor("#FFA036"), 1000, "Word00", "Word01", "Word02");
- rotatable = new Rotatable(color , 1000, "rotating", "text", "library");
+ // Example with color array
+ rotatable = new Rotatable(color, 1000, "rotating", "text", "library");
rotatable.setSize(25);
rotatable.setTypeface(typeface);
rotatable.setInterpolator(new AccelerateInterpolator());
@@ -61,16 +78,15 @@ protected void onCreate(Bundle savedInstanceState) {
rotatable2.setInterpolator(new DecelerateInterpolator());
rotatable2.setAnimationDuration(500);
-
word = rotatable.getTextAt(0);
+ // Use multiple rotatables
rotatingTextWrapper.setContent("?abc ? abc", rotatable, rotatable2);
-// rotatingTextWrapper.setContent("? abc", rotatable);
+ // Or use single rotatable
+ // rotatingTextWrapper.setContent("? abc", rotatable);
s1 = (Spinner) findViewById(R.id.spinner);
-
-
List list = new ArrayList();
list.add(1);
list.add(2);
@@ -122,11 +138,60 @@ public void onClick(View view) {
public void onClick(View view) {
boolean apply = rotatable.getApplyHorizontal();
rotatable.setApplyHorizontal(!apply);
- rotatable2.setApplyHorizontal(!apply);
+ if (rotatable2 != null) {
+ rotatable2.setApplyHorizontal(!apply);
+ }
}
});
+
+ // Add button to toggle stopAtLast feature
+ Button stopAtLastButton = findViewById(R.id.stop_at_last_button);
+ stopAtLastButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ boolean currentStopAtLast = rotatingTextWrapper.isStopAtLast();
+ rotatingTextWrapper.setStopAtLast(!currentStopAtLast);
+
+ // Restart the rotation to apply the change
+ if (rotatingTextWrapper.getSwitcherList().size() > 0) {
+ rotatingTextWrapper.resume(0);
+ }
+ }
+ });
+ }
+
+ private void setRotating(RotatingTextWrapper wrapper, int toCount) {
+ Typeface typeface = ResourcesCompat.getFont(this, R.font.love);
+
+ wrapper.setTypeface(typeface);
+
+ // Create array of numbers from 0 to toCount
+ rotatable = new Rotatable(
+ ContextCompat.getColor(this, android.R.color.black),
+ 200, // 200ms between transitions
+ createTextListForInteger(toCount)
+ );
+ rotatable.setCycles(0);
+ rotatable.setSize(38);
+ rotatable.setStopAtLast(true); // Stop at last number
+ rotatable.setAnimationDuration(200);
+ rotatable.setCenter(true);
+ rotatable.setTypeface(typeface);
+ rotatable.setInterpolator(new LinearInterpolator()); // Smooth linear animation
+ rotatable.setLastInterpolator(new BounceInterpolator()); // Bounce on final number!
+
+ // Store the first word for cycles feature
+ word = rotatable.getTextAt(0);
+
+ wrapper.setContent("?", rotatable);
}
+ private String[] createTextListForInteger(int n) {
+ List integerList = IntStream.rangeClosed(0, n)
+ .boxed()
+ .map(i -> i.toString()).toList();
+ return integerList.toArray(new String[]{});
+ }
public void replaceWord(View view) {
String newWord = e1.getText().toString();
if (TextUtils.isEmpty(newWord)) e1.setText("can't be left empty");
diff --git a/app/src/main/assets/fonts/LoveYaLikeASister.ttf b/app/src/main/res/font/love.ttf
similarity index 100%
rename from app/src/main/assets/fonts/LoveYaLikeASister.ttf
rename to app/src/main/res/font/love.ttf
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 14f9618..8ccb8f0 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -1,5 +1,6 @@
+ android:layout_marginLeft="10dp"
+ app:textSize="38sp"
+ app:stopAtLast="true"
+ app:previewText="0 1 2 3..."
+ app:adaptable="false" />
+
+
diff --git a/build.gradle b/build.gradle
index 94eeace..a6e80a1 100644
--- a/build.gradle
+++ b/build.gradle
@@ -6,19 +6,21 @@ buildscript {
google()
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.2.1'
+ classpath 'com.android.tools.build:gradle:8.13.0'
classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5'
- classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7'
+// classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
+
}
allprojects {
repositories {
- jcenter()
google()
+ mavenCentral()
+
}
}
diff --git a/gradle.properties b/gradle.properties
index aac7c9b..29b531a 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -10,7 +10,7 @@
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx1536m
-
+android.useAndroidX=true
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 3252d70..bfdb8f3 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Fri Dec 07 16:49:45 IST 2018
+#Wed Nov 19 22:44:10 CET 2025
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip
diff --git a/rotatingtext/build.gradle b/rotatingtext/build.gradle
index 85c6b67..5a26d5b 100644
--- a/rotatingtext/build.gradle
+++ b/rotatingtext/build.gradle
@@ -25,18 +25,19 @@ ext {
}
android {
- compileSdkVersion 27
- buildToolsVersion '27.0.3'
+ compileSdkVersion 35
+ buildToolsVersion = '30.0.3'
defaultConfig {
minSdkVersion 16
- targetSdkVersion 27
+ targetSdkVersion 35
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
+ namespace "com.sdsmdg.harjot.rotatingtextlibrary"
buildTypes {
release {
minifyEnabled false
@@ -45,15 +46,14 @@ android {
}
}
+
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
- implementation 'com.android.support:appcompat-v7:27.1.1'
+ //implementation 'com.android.support:appcompat-v7:27.1.1'
+ implementation 'androidx.appcompat:appcompat:1.6.1'
+ implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
+ implementation 'io.reactivex.rxjava2:rxjava:2.2.21'
- implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
- implementation 'io.reactivex.rxjava2:rxjava:2.1.10'
}
-apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/installv1.gradle'
-apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/bintrayv1.gradle'
-
diff --git a/rotatingtext/src/androidTest/java/com/sdsmdg/harjot/rotatingtext/ExampleInstrumentedTest.java b/rotatingtext/src/androidTest/java/com/sdsmdg/harjot/rotatingtext/ExampleInstrumentedTest.java
deleted file mode 100644
index c66de45..0000000
--- a/rotatingtext/src/androidTest/java/com/sdsmdg/harjot/rotatingtext/ExampleInstrumentedTest.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package com.sdsmdg.harjot.rotatingtext;
-
-import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import static org.junit.Assert.*;
-
-/**
- * Instrumentation test, which will execute on an Android device.
- *
- * @see Testing documentation
- */
-@RunWith(AndroidJUnit4.class)
-public class ExampleInstrumentedTest {
- @Test
- public void useAppContext() throws Exception {
- // Context of the app under test.
- Context appContext = InstrumentationRegistry.getTargetContext();
-
- assertEquals("com.sdsmdg.harjot.rotatingtext.test", appContext.getPackageName());
- }
-}
diff --git a/rotatingtext/src/main/AndroidManifest.xml b/rotatingtext/src/main/AndroidManifest.xml
index 91ac8a5..2ddac0f 100644
--- a/rotatingtext/src/main/AndroidManifest.xml
+++ b/rotatingtext/src/main/AndroidManifest.xml
@@ -1,5 +1,5 @@
+>
diff --git a/rotatingtext/src/main/java/com/sdsmdg/harjot/rotatingtext/RotatingTextSwitcher.java b/rotatingtext/src/main/java/com/sdsmdg/harjot/rotatingtext/RotatingTextSwitcher.java
index c5af2d1..db81a23 100644
--- a/rotatingtext/src/main/java/com/sdsmdg/harjot/rotatingtext/RotatingTextSwitcher.java
+++ b/rotatingtext/src/main/java/com/sdsmdg/harjot/rotatingtext/RotatingTextSwitcher.java
@@ -50,6 +50,7 @@ public class RotatingTextSwitcher extends TextView {
private boolean isPaused = false;
+
public RotatingTextSwitcher(Context context) {
super(context);
this.context = context;
@@ -120,6 +121,7 @@ public void accept(Long aLong) throws Exception {
updateWordTimer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
+
((Activity) context).runOnUiThread(new Runnable() {
@Override
public void run() {
@@ -127,10 +129,20 @@ public void run() {
pauseRender();
}
else {
+ // Check if we should stop at last word
+ if (rotatable.isStopAtLast() && rotatable.isAtLastWord()) {
+ isPaused = true;
+ pauseRender();
+ return;
+ }
+
+ // Check if next word will be the last word
+ boolean isAnimatingToLast = rotatable.peekNextWord().equals(rotatable.getTextAt(rotatable.getWordCount() - 1));
+
animationInterface.setAnimationRunning(true);
resumeRender();
- animateInHorizontal();
- animateOutHorizontal();
+ animateInHorizontal(isAnimatingToLast);
+ animateOutHorizontal(isAnimatingToLast);
oldText = currentText;
currentText = rotatable.getNextWord();
}
@@ -165,15 +177,19 @@ protected void onDraw(Canvas canvas) {
if (rotatable.getPathOut() != null) {
canvas.drawTextOnPath(oldText, rotatable.getPathOut(), 0.0f, 0.0f, paint);
- if(number < arrayLength && rotatable.useArray()) {
- paint.setColor(rotatable.getColorFromArray(number));
- }
+ if(number < arrayLength && rotatable.useArray()) {
+ paint.setColor(rotatable.getColorFromArray(number));
+ }
}
}
}
}
private void animateInHorizontal() {
+ animateInHorizontal(false);
+ }
+
+ private void animateInHorizontal(boolean isAnimatingToLast) {
ValueAnimator animator;
if(!rotatable.getApplyHorizontal()) {
animator = ValueAnimator.ofFloat(0.0f, getHeight());
@@ -207,12 +223,17 @@ public void onAnimationEnd(Animator animation)
animationInterface.setAnimationRunning(false);
}
});
- animator.setInterpolator(rotatable.getInterpolator());
+ // Use last interpolator if animating to last word and it's set
+ animator.setInterpolator(rotatable.getActiveInterpolator(isAnimatingToLast));
animator.setDuration(rotatable.getAnimationDuration());
animator.start();
}
private void animateOutHorizontal() {
+ animateOutHorizontal(false);
+ }
+
+ private void animateOutHorizontal(boolean isAnimatingToLast) {
ValueAnimator animator;
if(!rotatable.getApplyHorizontal()) {
animator = ValueAnimator.ofFloat(getHeight(), getHeight() * 2.0f);
@@ -238,7 +259,8 @@ public void onAnimationUpdate(ValueAnimator valueAnimator) {
}
});
}
- animator.setInterpolator(rotatable.getInterpolator());
+ // Use last interpolator if animating to last word and it's set
+ animator.setInterpolator(rotatable.getActiveInterpolator(isAnimatingToLast));
animator.setDuration(rotatable.getAnimationDuration());
animator.start();
}
@@ -385,12 +407,22 @@ public void run() {
}
else {
+ // Check if we should stop at last word
+ if (rotatable.isStopAtLast() && rotatable.isAtLastWord()) {
+ isPaused = true;
+ pauseRender();
+ return;
+ }
+
+ // Check if next word will be the last word
+ boolean isAnimatingToLast = rotatable.peekNextWord().equals(rotatable.getTextAt(rotatable.getWordCount() - 1));
+
oldText = currentText;
currentText = rotatable.getNextWord();
animationInterface.setAnimationRunning(true);
resumeRender();
- animateInHorizontal();
- animateOutHorizontal();
+ animateInHorizontal(isAnimatingToLast);
+ animateOutHorizontal(isAnimatingToLast);
}
}
}
@@ -403,5 +435,6 @@ public void run() {
public boolean isPaused() {
return isPaused;
}
+
}
diff --git a/rotatingtext/src/main/java/com/sdsmdg/harjot/rotatingtext/RotatingTextWrapper.java b/rotatingtext/src/main/java/com/sdsmdg/harjot/rotatingtext/RotatingTextWrapper.java
index b7b60fa..61c6d0d 100644
--- a/rotatingtext/src/main/java/com/sdsmdg/harjot/rotatingtext/RotatingTextWrapper.java
+++ b/rotatingtext/src/main/java/com/sdsmdg/harjot/rotatingtext/RotatingTextWrapper.java
@@ -1,6 +1,7 @@
package com.sdsmdg.harjot.rotatingtext;
import android.content.Context;
+import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
@@ -10,11 +11,13 @@
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
+import android.view.animation.Interpolator;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.sdsmdg.harjot.rotatingtext.models.Rotatable;
import com.sdsmdg.harjot.rotatingtext.utils.Utils;
+import com.sdsmdg.harjot.rotatingtextlibrary.R;
import java.util.ArrayList;
import java.util.Collections;
@@ -44,6 +47,9 @@ public class RotatingTextWrapper extends RelativeLayout {
private double changedSize = 0;
private boolean adaptable = false;
+ private boolean stopAtLast = false;
+ private String previewText = "";
+
public RotatingTextWrapper(Context context) {
super(context);
@@ -53,11 +59,52 @@ public RotatingTextWrapper(Context context) {
public RotatingTextWrapper(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
+ initAttributes(attrs);
}
public RotatingTextWrapper(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.context = context;
+ initAttributes(attrs);
+ }
+
+ private void initAttributes(AttributeSet attrs) {
+ if (attrs != null) {
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RotatingTextWrapper);
+
+ // Read text size (getDimensionPixelSize returns px, convert to sp for consistency)
+ if (a.hasValue(R.styleable.RotatingTextWrapper_textSize)) {
+ // getDimension returns pixels, we need to convert to SP
+ float textSizePx = a.getDimension(R.styleable.RotatingTextWrapper_textSize, size * getResources().getDisplayMetrics().scaledDensity);
+ // Convert pixels to SP (don't cast to int to avoid precision loss)
+ size = Math.round(textSizePx / getResources().getDisplayMetrics().scaledDensity);
+ }
+
+ // Read stopAtLast flag
+ stopAtLast = a.getBoolean(R.styleable.RotatingTextWrapper_stopAtLast, false);
+
+ // Read preview text
+ previewText = a.getString(R.styleable.RotatingTextWrapper_previewText);
+ if (previewText == null) {
+ previewText = "";
+ }
+
+ // Read adaptable flag
+ adaptable = a.getBoolean(R.styleable.RotatingTextWrapper_adaptable, false);
+
+ a.recycle();
+
+ // Show preview text in layout editor if available
+ if (isInEditMode() && !TextUtils.isEmpty(previewText)) {
+ TextView previewTextView = new TextView(context);
+ previewTextView.setText(previewText);
+ previewTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
+ if (typeface != null) {
+ previewTextView.setTypeface(typeface);
+ }
+ addView(previewTextView);
+ }
+ }
}
public void setContent(String text, Rotatable... rotatables) {
@@ -66,14 +113,32 @@ public void setContent(String text, Rotatable... rotatables) {
switcherList = new ArrayList<>();
textViews = new ArrayList<>();
Collections.addAll(rotatableList, rotatables);
+
+ // Apply stopAtLast to all rotatables if set from XML
+ if (stopAtLast) {
+ for (Rotatable rotatable : rotatableList) {
+ rotatable.setStopAtLast(true);
+ }
+ }
+
isContentSet = true;
requestLayout();
}
+
+
public void setContent(String text, ArrayList rotatables) {
this.text = text;
rotatableList = new ArrayList<>(rotatables);
switcherList = new ArrayList<>();
+
+ // Apply stopAtLast to all rotatables if set from XML
+ if (stopAtLast) {
+ for (Rotatable rotatable : rotatableList) {
+ rotatable.setStopAtLast(true);
+ }
+ }
+
isContentSet = true;
requestLayout();
}
@@ -88,6 +153,7 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (array.length == 0) {
final RotatingTextSwitcher textSwitcher = new RotatingTextSwitcher(context);
+
switcherList.add(textSwitcher);
textSwitcher.setRotatable(rotatableList.get(0));
@@ -107,6 +173,7 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
addView(textSwitcher, lp);
+
}
for (int i = 0; i < array.length; i++) {
@@ -119,7 +186,7 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
} else {
textView.setId(View.generateViewId());
}
- textView.setTextSize(size);
+ textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
textViews.add(textView);
if (typeface != null)
@@ -287,8 +354,6 @@ public void addWord(int rotatableIndex, int wordIndex, String newWord) {
}
}
-
-
private void setChanges(RotatingTextSwitcher switcher, Rotatable toChange) {
switcher.setText(toChange.getLargestWordWithSpace());
@@ -298,7 +363,7 @@ private void setChanges(RotatingTextSwitcher switcher, Rotatable toChange) {
else reduceSize(changedSize / getSize());
}
-
+
}
private int availablePixels() {
@@ -343,7 +408,7 @@ public void reduceSize(double factor) {
}
for (TextView id : textViews) {
- id.setTextSize((float) newWrapperSize);
+ id.setTextSize(TypedValue.COMPLEX_UNIT_SP, (float) newWrapperSize);
}
MarginLayoutParams margins = MarginLayoutParams.class.cast(getLayoutParams());
@@ -401,4 +466,60 @@ public void resume(int position) {
public List getSwitcherList() {
return switcherList;
}
+
+ /**
+ * Set whether rotation should stop at the last word instead of cycling back to the first.
+ * This will apply to all rotatables in this wrapper.
+ *
+ * @param stopAtLast true to stop at last word, false to cycle infinitely
+ */
+ public void setStopAtLast(boolean stopAtLast) {
+ this.stopAtLast = stopAtLast;
+ if (rotatableList != null) {
+ for (Rotatable rotatable : rotatableList) {
+ rotatable.setStopAtLast(stopAtLast);
+ }
+ }
+ }
+
+ /**
+ * Get whether rotation stops at the last word.
+ *
+ * @return true if rotation stops at last word, false if it cycles infinitely
+ */
+ public boolean isStopAtLast() {
+ return stopAtLast;
+ }
+
+ /**
+ * Set the preview text that will be displayed in the Android Studio layout editor.
+ *
+ * @param previewText the text to show in the layout editor
+ */
+ public void setPreviewText(String previewText) {
+ this.previewText = previewText;
+ }
+
+ /**
+ * Get the preview text.
+ *
+ * @return the preview text
+ */
+ public String getPreviewText() {
+ return previewText;
+ }
+
+ /**
+ * Set a special interpolator to use when animating to the last word for all rotatables.
+ * This is especially useful with stopAtLast to create a distinctive finishing animation.
+ *
+ * @param lastInterpolator the interpolator to use for the last rotation, or null to use default
+ */
+ public void setLastInterpolator(Interpolator lastInterpolator) {
+ if (rotatableList != null) {
+ for (Rotatable rotatable : rotatableList) {
+ rotatable.setLastInterpolator(lastInterpolator);
+ }
+ }
+ }
}
diff --git a/rotatingtext/src/main/java/com/sdsmdg/harjot/rotatingtext/models/Rotatable.java b/rotatingtext/src/main/java/com/sdsmdg/harjot/rotatingtext/models/Rotatable.java
index 8a08186..e993567 100644
--- a/rotatingtext/src/main/java/com/sdsmdg/harjot/rotatingtext/models/Rotatable.java
+++ b/rotatingtext/src/main/java/com/sdsmdg/harjot/rotatingtext/models/Rotatable.java
@@ -29,6 +29,7 @@ public class Rotatable {
private Path pathIn, pathOut;
private Interpolator interpolator = new LinearInterpolator();
+ private Interpolator lastInterpolator = null; // Special interpolator for last word animation
private Typeface typeface;
@@ -43,6 +44,8 @@ public class Rotatable {
private boolean applyHorizontal = false;
+ private boolean stopAtLast = false;
+
public Rotatable(int updateDuration, String... text) {
this.updateDuration = updateDuration;
this.text = text;
@@ -117,6 +120,14 @@ public boolean getApplyHorizontal() {
return applyHorizontal;
}
+ public void setStopAtLast(boolean stopAtLast) {
+ this.stopAtLast = stopAtLast;
+ }
+
+ public boolean isStopAtLast() {
+ return stopAtLast;
+ }
+
public String[] getText() {
return text;
}
@@ -204,11 +215,19 @@ public void setUpdateDuration(int updateDuration) {
}
public int getNextWordNumber() {
- //provides next word number circularly
+ //provides next word number circularly, or stops at last if stopAtLast is true
+ if (stopAtLast && currentWordNumber == text.length - 1) {
+ // Already at last word, don't advance
+ return currentWordNumber;
+ }
currentWordNumber = (currentWordNumber + 1) % text.length;
return currentWordNumber;
}
+ public boolean isAtLastWord() {
+ return currentWordNumber == text.length - 1;
+ }
+
public String peekNextWord() {
//doesnot increases currentwordnumber
return text[(currentWordNumber + 1) % text.length];
@@ -282,6 +301,40 @@ public void setInterpolator(Interpolator interpolator) {
setUpdated(true);
}
+ /**
+ * Set a special interpolator to use when animating to the last word.
+ * This is especially useful with stopAtLast to create a distinctive finishing animation.
+ *
+ * @param lastInterpolator the interpolator to use for the last rotation, or null to use default
+ */
+ public void setLastInterpolator(Interpolator lastInterpolator) {
+ this.lastInterpolator = lastInterpolator;
+ setUpdated(true);
+ }
+
+ /**
+ * Get the interpolator used for the last word animation.
+ *
+ * @return the last interpolator, or null if using default
+ */
+ public Interpolator getLastInterpolator() {
+ return lastInterpolator;
+ }
+
+ /**
+ * Get the appropriate interpolator for the current animation.
+ * Returns lastInterpolator if set and animating to last word, otherwise returns regular interpolator.
+ *
+ * @param isAnimatingToLast whether we're animating to the last word
+ * @return the interpolator to use
+ */
+ public Interpolator getActiveInterpolator(boolean isAnimatingToLast) {
+ if (isAnimatingToLast && lastInterpolator != null) {
+ return lastInterpolator;
+ }
+ return interpolator;
+ }
+
public Typeface getTypeface() {
return typeface;
}
diff --git a/rotatingtext/src/main/res/values/attrs.xml b/rotatingtext/src/main/res/values/attrs.xml
new file mode 100644
index 0000000..57a64a3
--- /dev/null
+++ b/rotatingtext/src/main/res/values/attrs.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/rotatingtext/src/test/java/com/sdsmdg/harjot/rotatingtext/ExampleUnitTest.java b/rotatingtext/src/test/java/com/sdsmdg/harjot/rotatingtext/ExampleUnitTest.java
deleted file mode 100644
index 4474a04..0000000
--- a/rotatingtext/src/test/java/com/sdsmdg/harjot/rotatingtext/ExampleUnitTest.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package com.sdsmdg.harjot.rotatingtext;
-
-import org.junit.Test;
-
-import static org.junit.Assert.*;
-
-/**
- * Example local unit test, which will execute on the development machine (host).
- *
- * @see Testing documentation
- */
-public class ExampleUnitTest {
- @Test
- public void addition_isCorrect() throws Exception {
- assertEquals(4, 2 + 2);
- }
-}
\ No newline at end of file