Skip to content
Switch branches/tags
This branch is up to date with master.

Latest commit


Git stats


Failed to load latest commit information.
Latest commit message
Commit time


tumbleweed tumbleweed-android tumbleweed-android-kt

Tumbleweed is a fork of Universal-Tween-Engine by Aurelien Ribon. To quote the parent project:

allows you to create smooth interpolations on every attribute from every object in your projects

Tumbleweed comes with few changes and differences:

  • decreased mutation of Tweens and Timelines (split definition and execution of tweens)
  • encapsulated interpolation by introducing specific type (TweenType<T>)
  • removed pooling (a constant source of unexpected behaviour)
  • fixed Circ.IN equation
  • standalone module targeted on Android with a set of available TweenManagers for a View, Drawable and Handler; a set of predefined interpolated types: color, alpha, translation, scale, rotation, etc; a TweenInterpolator bridge in order to use equations with native Android Animation and Animator
  • some utility methods and helpers


// base java module
implementation 'io.noties:tumbleweed:${tumbleweed_version}'

// android module
implementation 'io.noties:tumbleweed-android:${tumbleweed_version}'

// kotlin extensions for android module
implementation 'io.noties:tumbleweed-android-kt:${tumbleweed_version}'

All modules have no external dependencies except for support-annotations

!!! Important notice for 2.0.0 version

Package name changed from ru.noties.tumbleweed.* to io.noties.tumbleweed.* (regular find-n-replace can be used)

Maven artifact group-id change to io.noties


The API is pretty much the same:

final View view = findViewById(;, Translation.XY, 2.F)
        .target(0, 0)

Here Translation.XY is a predefined TweenType<View> (found in* package) that applies translation X and Y.

Cubic.INOUT is predefined equation (found in io.noties.tumbleweed.equations.*)

Please note that all durations are measured in seconds, so 2.F is 2 seconds

final View view = findViewById(;
        .push(, Alpha.VIEW, 2.F).target(.0F))
        .push(, Scale.XY, 2.F).target(.5F, .5F))

With tumbleweed-android-kt Kotlin module the following usage is possible:

val view = findViewById<View>(

// `view.tweenManager` is an extension property for a view
// `startWhenReady` is an extension method to start configured tween 
//      when associated view has dimensions
view.tweenManager.startWhenReady {

    // `this` is ViewTweenManager
    /*return */Timeline.createSequence()
            // `then` extension method to push nested configured timeline
            // `0.75F` is the default duration for tweens without duration specified
            .then(Timeline.createParallel(0.75F)) {
                // `with` extension method to configure tweens for a single target
                with(view) {
                // View has `tween` extension that expands to `, /**/)`
            // repeat endlessly with 1 second delay between repeats
            .repeat(-1, 1.0F)

Android predefined TweenTypes*:

  • Alpha.VIEW (applies alpha to a View: view.setAlpha(..)). Range: 0.0-1.0
  • Alpha.DRAWABLE (drawable.setAlpha(..), available for devices running KITKAT and up) Range: 0.0-1.0
  • Alpha.PAINT (paint.setAlpha(..)). Range: 0.0-1.0

  • Argb.BACKGROUND (view.setBackgroundColor(..))
  • Argb.PAINT (paint.setColor(..))
  • Argb.TEXT_COLOR (textView.setTextColor(..))
  • Argb.STATUS_BAR (window.setStatusBarColor(..), available for devices running Lollipop and up)
  • Argb.COLOR_DRAWABLE (colorDrawable.setColor(..))

Argb also can be subclassed:

public class MyObjectArgb extends Argb<MyObject> {
    protected int getColor(@NonNull MyObject myObject) {
        return myObject.getColor();

    protected void setColor(@NonNull MyObject myObject, int color) {
}, new MyObjectArgb(), 2.F)

Please note that target color must be destructed to float[4] (argb values are interpolated individually). You can do it by calling: Argb.toArray(int) and Argb.toArray(int, float[])

  • Elevation.I (view.setElevation(), available for devices with Lollipop with up)

  • Graphics.RECT (interpolates left, top, right and bottom of a Rect)
  • Graphics.RECT_F (interpolates left, top, right and bottom of a RectF)
  • Graphics.POINT (interpolates x and y of a Point)
  • Graphics.POINT_F (interpolates x and y of a PointF)

There is also special Graphics.points(List<PointF>) that creates interpolation for arbitrary list of PointF.

To receive a notification when rect or point have changed, the action method can be used:

final View view = getView(); // obtain some view
final int width = view.getWidth();
final int height = view.getHeight();

final Rect start = new Rect(0, 0, width, height);
final Rect target;
    final int targetSide = Math.min(width, height) / 2;
    final int left = (width - targetSide) / 2;
    final int top = (height - targetSide) / 2;
    target = new Rect(left, top, left + targetSide, top + targetSide);
}, Graphics.RECT, 2.F)

  • Pivot.X (view.setPivotX(..))
  • Pivot.Y (view.setPivotY(..))
  • Pivot.XY (view.setPivotX(..), view.setPivotY(..))

  • Position.X (view.setX(..))
  • Position.Y (view.setY(..))
  • Position.Z (view.setZ(..), available for devices running Lollipop and up)
  • Position.XY (view.setX(..), view.setY(..))
  • Position.XYZ (view.setX(..), view.setY(..), view.setZ(..), available for devices running Lollipop and up)

  • Rotation.I (view.setRotation(..))
  • Rotation.X (view.setRotationX(..))
  • Rotation.Y (view.setRotationY(..))
  • Rotation.XY (view.setRotationX(..), view.setRotationY(..))

  • Scale.X (view.setScaleX(..))
  • Scale.Y (view.setScaleY(..))
  • Scale.XY (view.setScaleX(..), view.setScaleY(..))

  • Scroll.X (view.setScrollX(..))
  • Scroll.Y (view.setScrollY(..))
  • Scroll.XY (view.setScrollX(..), view.setScrollY(..))

  • Translation.X (view.setTranslationX(..))
  • Translation.Y (view.setTranslationY(..))
  • Translation.Z (view.setTranslationZ(..) available for devices running Lollipop and up)
  • Translation.XY (view.setTranslationX(..), view.setTranslationY(..))
  • Translation.XYZ (view.setTranslationX(..), view.setTranslationY(..) and view.setTransaltionZ(..) available for devices running Lollipop and up)

These are just helpers and provided for faster iterations. They all implement the base TweenType<T> interface that is used by Tween:

public interface TweenType<T> {

    int getValuesSize();

    void getValues(@NonNull T t, @NonNull float[] values);

    void setValues(@NonNull T t, @NonNull float[] values);

For example in case of Translation.XY:

public static final Translation XY = new TweenType<View>() {
    public int getValuesSize() {
        // we are interpolating x and y, so it's 2
        // `getValues` and `setValues` methods will be called
        // with an array of the returned size
        return 2;

    public void getValues(@NonNull View view, @NonNull float[] values) {
        values[0] = view.getTranslationX();
        values[1] = view.getTranslationY();

    public void setValues(@NonNull View view, @NonNull float[] values) {

Android TweenManagers


ViewTweenManager attaches to View draw cycle and invalidates it via view.postInvalidateOnAnimation(). It will be automatically disposed when a View to which it is attached to is detached from a window.

To obtain a ViewTweenManager call:


Normally you would want to ensure that a view has only one instance of a ViewTweenManager. Since version 2.0.0 ViewTweenManager does it automatically by caching created instance with View.setTag(int, Object) call.

        .push(, Scale.XY, 0.4F).target(0.25F, 0.25F))
        .push(, Scale.XY, 0.4F).target(1.0F, 1.0F))

ViewTweenManager will be automatically disposed when view is detached from a window.


DrawableTweenManager can be used with a Drawable (sample application heavily uses it).

To obtain an instance:

  • DrawableTweenManager.create(Drawable)
  • DrawableTweenManager.create(Drawable, float) - the second argument is update interval (FPS), default one is: 1.F / 60 (all durations are in seconds), so equals to 60 frames per second.

In order to function correctly Drawable must be attached to a View or have manually set Drawable.Callback (internally uses invalidateSelf() and scheduleSelf())


HandlerTweenManager uses Handler as a dispatcher for update calls.

To obtain an instance:

  • HandlerTweenManager.create() - creates an instance with main thread Looper and 60 updates per second (60 FPS)
  • HandlerTweenManager.create(float) - creates an instance with main thread Looper and specified update interval (in seconds, so 1.F / 60 would be equal to 60 FPS)
  • HandlerTweenManager.create(float, Handler) - creates an instance with specified Handler and update interval (in seconds)


In 2.0.0 version AnimatorTweenManager is added. It lets using Tumbleweed animations in Android-native way (for example in custom transitions):

public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) {

    /*obtain transition values, validate their presence*/

    final View v = endValues.view;

    final AnimatorTweenManager tweenManager = AnimatorTweenManager.create();
            .push(, Rotation.I, .25F).target(360))
            .push(, Scale.XY, .25F).target(.5F, .5F))
            .push(, Scale.XY, .25F).target(1, 1))

    return tweenManager.animator();

Android Kotlin extensions


// obtain a ViewTweenManager
view.tweenManager // => ViewTweenManager.get(view)

//, Alpha.VIEW, 0.25F)
view.tween(Alpha.VIEW, 0.25F)

//, Alpha.VIEW), NB duration must be set explicitly
// via `duration(float)` method

// execute when view has dimensions
// will check if dimensions are present or register a OnPreDrawListener
view.whenReady {

// calculate position of a view relative to its parent
val point = view.relativeTo(parent)


// create an instance of DrawableTweenManager
drawable.tweenManager() // => DrawableTweenManager.create()

// create an instance of DrawableTweenManager with
// specified update interval in seconds
drawable.tweenManager(1.0F / 120.0F)

//, Alpha.DRAWABLE, 0.25F)
drawable.tween(Alpha.DRAWABLE, 0.25F)

//, Alpha.DRAWABLE), NB to set duration
// explicitly via `duration(float)` method

// apply intrinsic bounds 

// apply intrinsic bounds if current bounds are empty 


// all tween managers
view.tweenManager.start {, Rotation.I, 0.4F).target(180.0F)

// view-tween-manager only
// start only after associated view has dimensions
view.tweenManager.startWhenReady {, Translation.Y, 0.75F).target(view.height / 2.0F)


        // push nested timeline
        .then(Timeline.createSequence()) {
            push(, Pivot.XY).target(0F, 0F))
            push(, Scale.XY).target(0.5F, 0.5F))
        // configure tweens for a single target
        .with(view) {
            to(Pivot.XY).target(0F, 0F)
            to(Scale.XY).target(0.5F, 0.5F)


Both Tween and Timeline has extensions for simplified callbacks addition:

  • onBegin - once tween has started (called only once)
  • onStart - once tween started and on each repetition start
  • onEnd - once tween completed and on each completion of repetition
  • onComplete - once tween has completed (called only once), Alpha.VIEW, 0.5F)
        .onBegin {
            // tween started
        .onComplete {
            // tween completed


// Long: convert milliseconds to float seconds
1000L.toFloatSeconds() // => 1.0F

// Int: convert milliseconds to float seconds
450.toFloatSeconds() // => 0.45F


val color = Color.RED

// convert Int color to Argb array

// can be used like this:
// notice the _spread_ operator, Argb.BACKGROUND, 0.25F).target(*color.toArgbArray())
val array = Color.RED.toArgbArray()

// convert Argb float array to color
val color: Int = array.toColor()