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

Latest commit

 

Git stats

Files

Permalink
Failed to load latest commit information.
Type
Name
Latest commit message
Commit time
art
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

logo

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

Installation

// 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

Usage

The API is pretty much the same:

final View view = findViewById(R.id.view);
Tween.to(view, Translation.XY, 2.F)
        .target(0, 0)
        .ease(Cubic.INOUT)
        .start(ViewTweenManager.get(view));

Here Translation.XY is a predefined TweenType<View> (found in io.noties.tumbleweed.android.types.* 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(R.id.view);
Timeline.createParallel()
        .push(Tween.to(view, Alpha.VIEW, 2.F).target(.0F))
        .push(Tween.to(view, Scale.XY, 2.F).target(.5F, .5F))
        .start(ViewTweenManager.get(view));

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

val view = findViewById<View>(R.id.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
    this.killAll()
    
    /*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) {
                    to(Rotation.I).target(0.0F).ease(Bounce.OUT)
                    to(Argb.BACKGROUND).target(*Color.RED.toArgbArray())
                }
                // View has `tween` extension that expands to `Tween.to(view, /**/)`
                push(view.tween(Alpha.VIEW).target(1.0F))
            }
            // repeat endlessly with 1 second delay between repeats
            .repeat(-1, 1.0F)
}

Android predefined TweenTypes

io.noties.tumbleweed.android.types.*:


  • 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> {
    @Override
    protected int getColor(@NonNull MyObject myObject) {
        return myObject.getColor();
    }

    @Override
    protected void setColor(@NonNull MyObject myObject, int color) {
		myObject.setColor(color);
    }
}
Tween.to(myObject, new MyObjectArgb(), 2.F)
        .target(Argb.toArray(0xFFff0000))
        .start();

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);
}

Tween.to(start, Graphics.RECT, 2.F)
        .target(target)
        .action(view::setClipBounds)
        .start(ViewTweenManager.create(view));

  • 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:

@NonNull
public static final Translation XY = new TweenType<View>() {
    @Override
    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;
    }

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

    @Override
    public void setValues(@NonNull View view, @NonNull float[] values) {
        view.setTranslationX(values[0]);
        view.setTranslationY(values[1]);
    }
};

Android TweenManagers

ViewTweenManager

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:

ViewTweenManager.get(view);

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.

Timeline.createSequence()
        .push(Tween.to(view, Scale.XY, 0.4F).target(0.25F, 0.25F))
        .push(Tween.to(view, Scale.XY, 0.4F).target(1.0F, 1.0F))
        .start(ViewTweenManager.get(view));

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


DrawableTweenManager

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

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)

AnimatorTweenManager

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

@Override
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();
    
    Timeline.createSequence()
            .push(Tween.to(v, Rotation.I, .25F).target(360))
            .push(Tween.to(v, Scale.XY, .25F).target(.5F, .5F))
            .push(Tween.to(v, Scale.XY, .25F).target(1, 1))
            .start(tweenManager);

    return tweenManager.animator();
}

Android Kotlin extensions

View

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

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

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

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

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

Drawable

// 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)

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

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

// apply intrinsic bounds 
drawable.applyIntrinsicBounds()

// apply intrinsic bounds if current bounds are empty 
drawable.applyIntrinsicBoundsIfEmpty()

TweenManager

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

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

Timeline

Timeline.createParallel()
        // push nested timeline
        .then(Timeline.createSequence()) {
            push(Tween.to(view, Pivot.XY).target(0F, 0F))
            push(Tween.to(view, 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)
        }

Callbacks

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)
Tween.to(view, Alpha.VIEW, 0.5F)
        .onBegin {
            // tween started
        }
        .onComplete {
            // tween completed
        }

Duration

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

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

Argb

val color = Color.RED

// convert Int color to Argb array
color.toArgbArray()

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

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