Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

'pulse_startFromScratch' fix for new Android versions #29

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package pl.bclogic.pulsator4droid.library;

import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Build;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
Expand Down Expand Up @@ -48,7 +48,16 @@ public class PulsatorLayout extends RelativeLayout {
private int mInterpolator;

private final List<View> mViews = new ArrayList<>();
private AnimatorSet mAnimatorSet;
/**
* {@link android.animation.AnimatorSet} seems to be having issues with
* {@link android.animation.ValueAnimator#setCurrentPlayTime(long)} being used for its encapsulated animations. We
* have to handle them (start them) manually one by one to avoid that.
* More precisely Android versions O and P do not take current play time setting into consideration and play all the
* animations at the same timing when started using an {@link android.animation.AnimatorSet}.
* <p>
* (The play time can be fast-forwarded for the whole set, but not before API 26.)
*/
private List<Animator> mAnimators;
private Paint mPaint;
private float mRadius;
private float mCenterX;
Expand Down Expand Up @@ -128,20 +137,34 @@ public PulsatorLayout(Context context, AttributeSet attrs, int defStyleAttr) {
* Start pulse animation.
*/
public synchronized void start() {
if (mAnimatorSet == null || mIsStarted) {
if (mAnimators == null || mIsStarted) {
return;
}

mAnimatorSet.start();

if (!mStartFromScratch) {
ArrayList<Animator> animators = mAnimatorSet.getChildAnimations();
for (Animator animator : animators) {
ObjectAnimator objectAnimator = (ObjectAnimator) animator;
for (int x = 0; x < mAnimators.size(); x++) {
ObjectAnimator objectAnimator = (ObjectAnimator) mAnimators.get(x);

if (!mStartFromScratch) {
// instead of delaying the animation, fast-forward it
long delay = objectAnimator.getStartDelay();
objectAnimator.setStartDelay(0);

// This is where it starts to get tricky. The documentation of
// ValueAnimator#setCurrentPlayTime(long) is a bit confusing about whether it should be called before
// or after starting the animation itself. The truth is, it seems the behavior differs between Android
// versions. If it gets called at a wrong time, only some of the animations will start while others will
// not or the animated object won't be visible at all.
boolean shouldStartBeforeSettingCurrentTime = Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP_MR1;

if(shouldStartBeforeSettingCurrentTime){
objectAnimator.start();
}
objectAnimator.setCurrentPlayTime(mDuration - delay);
if(!shouldStartBeforeSettingCurrentTime){
objectAnimator.start();
}
} else {
objectAnimator.start();
}
}
}
Expand All @@ -150,15 +173,16 @@ public synchronized void start() {
* Stop pulse animation.
*/
public synchronized void stop() {
if (mAnimatorSet == null || !mIsStarted) {
if (mAnimators == null || !mIsStarted) {
return;
}

mAnimatorSet.end();
for (Animator animator : mAnimators) {
animator.end();
}
}

public synchronized boolean isStarted() {
return (mAnimatorSet != null && mIsStarted);
return (mAnimators != null && mIsStarted);
}

/**
Expand Down Expand Up @@ -297,7 +321,7 @@ private void build() {

int repeatCount = (mRepeat == INFINITE) ? ObjectAnimator.INFINITE : mRepeat;

List<Animator> animators = new ArrayList<>();
mAnimators = new ArrayList<>(3 * mCount);
for (int index = 0; index < mCount; index++) {
// setup view
PulseView pulseView = new PulseView(getContext());
Expand All @@ -312,29 +336,32 @@ private void build() {

// setup animators
ObjectAnimator scaleXAnimator = ObjectAnimator.ofFloat(pulseView, "ScaleX", 0f, 1f);
scaleXAnimator.setRepeatCount(repeatCount);
scaleXAnimator.setRepeatMode(ObjectAnimator.RESTART);
scaleXAnimator.setStartDelay(delay);
animators.add(scaleXAnimator);
mAnimators.add(scaleXAnimator);

ObjectAnimator scaleYAnimator = ObjectAnimator.ofFloat(pulseView, "ScaleY", 0f, 1f);
scaleYAnimator.setRepeatCount(repeatCount);
scaleYAnimator.setRepeatMode(ObjectAnimator.RESTART);
scaleYAnimator.setStartDelay(delay);
animators.add(scaleYAnimator);
mAnimators.add(scaleYAnimator);

ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(pulseView, "Alpha", 1f, 0f);
alphaAnimator.setRepeatCount(repeatCount);
alphaAnimator.setRepeatMode(ObjectAnimator.RESTART);
alphaAnimator.setStartDelay(delay);
animators.add(alphaAnimator);
mAnimators.add(alphaAnimator);
}

for (Animator animator : mAnimators) {
ObjectAnimator objectAnimator = (ObjectAnimator) animator;
objectAnimator.setRepeatCount(repeatCount);
objectAnimator.setRepeatMode(ObjectAnimator.RESTART);
objectAnimator.setInterpolator(createInterpolator(mInterpolator));
objectAnimator.setDuration(mDuration);
}

mAnimatorSet = new AnimatorSet();
mAnimatorSet.playTogether(animators);
mAnimatorSet.setInterpolator(createInterpolator(mInterpolator));
mAnimatorSet.setDuration(mDuration);
mAnimatorSet.addListener(mAnimatorListener);
if (mAnimators.isEmpty()) {
mAnimators = null;
} else {
mAnimators.get(0).addListener(mAnimatorStartListener);
mAnimators.get(mAnimators.size() - 1).addListener(mAnimatorEndListener);
}
}

/**
Expand Down Expand Up @@ -374,9 +401,11 @@ private static Interpolator createInterpolator(int type) {
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();

if (mAnimatorSet != null) {
mAnimatorSet.cancel();
mAnimatorSet = null;
if(mAnimators != null) {
for (Animator animator : mAnimators) {
animator.cancel();
}
mAnimators = null;
}
}

Expand All @@ -393,13 +422,40 @@ protected void onDraw(Canvas canvas) {

}

private final Animator.AnimatorListener mAnimatorListener = new Animator.AnimatorListener() {
private class AnimatorSimpleListener implements Animator.AnimatorListener {

@Override
public void onAnimationStart(Animator animation) {

}

@Override
public void onAnimationEnd(Animator animation) {

}

@Override
public void onAnimationCancel(Animator animation) {

}

@Override
public void onAnimationRepeat(Animator animation) {

}
}

private final AnimatorSimpleListener mAnimatorStartListener = new AnimatorSimpleListener() {

@Override
public void onAnimationStart(Animator animator) {
mIsStarted = true;
}

};

private final AnimatorSimpleListener mAnimatorEndListener = new AnimatorSimpleListener() {

@Override
public void onAnimationEnd(Animator animator) {
mIsStarted = false;
Expand All @@ -410,10 +466,6 @@ public void onAnimationCancel(Animator animator) {
mIsStarted = false;
}

@Override
public void onAnimationRepeat(Animator animator) {
}

};

}