user3175580 user3175580 - 2 months ago 30
Android Question

ObjectAnimator on Path not working on Marshmallow?

ObjectAnimator.ofFloat(Object target, String xPropertyName, String yPropertyName, Path path)
seems to work in API 21 but not 23?
Simple reproduction: Here's the activity and layout. Try long press on the text view, both level will work. But, just tap on the view: 21 works but not 23.

public class MainActivity extends Activity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final View hello = findViewById(R.id.hello);
final Path p = new Path();
p.addCircle(-200, 0, 100, Path.Direction.CW);
hello.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ObjectAnimator a = ObjectAnimator.ofFloat(hello, "translationX", "translationY", p);
a.setDuration(1000);
a.start();
}
});
hello.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
ObjectAnimator a = ObjectAnimator.ofFloat(hello, "scaleX", 2);
a.setDuration(1000);
a.start();
return true;
}
});
}
}


--

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="letstwinkle.com.pathtest.MainActivity">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
android:layout_alignParentTop="true"
android:layout_alignParentEnd="true"
android:layout_marginEnd="63dp"
android:layout_marginTop="210dp"
android:id="@+id/hello"
android:background="#880033"/>
</RelativeLayout>


This may be the same as this bug reported in June. https://code.google.com/p/android/issues/detail?id=212776

Answer

As a workaround, you can use a ValueAnimator with a custom AnimatorUpdateListener.

First you need a PathMeasure for your Path. This will give you access to data like path length and coordinates on the path for a specific distance (0 <= distance <= path length).

Path p = new Path();
p.addCircle(-200, 0, 100, Path.Direction.CW);
PathMeasure pathMeasure = new PathMeasure(p, true);

Then you set the ValueAnimator to animate from 0 to the path length.

final ValueAnimator a = ValueAnimator.ofFloat(0,pathMeasure.getLength());
a.setDuration(1000);

Next, you add an AnimatorUpdateListener to actually manipulate the View. I'll show first how to add it:

MyAnimatorUpdateListener myListener = new MyAnimatorUpdateListener(hello, pathMeasure);
a.addUpdateListener(myListener);

Continue with the OnClickListener:

hello.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        a.start();
    }
});

Please note that I changed the code and declared/ initialized the ValueAnimator outside of the OnClickListener to get a better performance: this way, it will not be newly created each time I click the TextView and so the garbage collector will be less busy :).

Finally, the AnimatorUpdateListener code:

class MyAnimatorUpdateListener implements ValueAnimator.AnimatorUpdateListener
{
    private View view;
    private PathMeasure pathMeasure;
    private float[] coordinates = new float[2];

    public MyAnimatorUpdateListener(View view, PathMeasure pathMeasure)
    {
        this.view = view;
        this.pathMeasure = pathMeasure;
    }

    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        float distance = (float) animation.getAnimatedValue();
        pathMeasure.getPosTan(distance, coordinates, null);
        view.setTranslationX(coordinates[0]);
        view.setTranslationY(coordinates[1]);
    }
}
Comments