Created
August 20, 2017 16:28
-
-
Save cmc5788/8b0d0ab9c4c4112aa65eda28aae40d8f to your computer and use it in GitHub Desktop.
Rx animation utils for android
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public final class AnimUtil { | |
public static final ViewAction VISIBLE_RESET = new ViewAction() // | |
.visibility(View.VISIBLE).alpha(1f).scale(1f).translationX(0).translationY(0).lock(); | |
public static final ViewAction GONE_RESET = new ViewAction() // | |
.visibility(View.GONE).alpha(1f).scale(1f).translationX(0).translationY(0).lock(); | |
public static final ViewAction VISIBLE_RESET_ZERO_ALPHA = new ViewAction() // | |
.visibility(View.VISIBLE).alpha(0f).scale(1f).translationX(0).translationY(0).lock(); | |
public static final ViewAction GONE_RESET_ZERO_ALPHA = new ViewAction() // | |
.visibility(View.GONE).alpha(0f).scale(1f).translationX(0).translationY(0).lock(); | |
public static Observable<?> fadeInAndVisible(@NonNull View v) { | |
TimeInterpolator interp = new AccelerateDecelerateInterpolator(); | |
return new ViewAnim() // | |
.alpha(1f) // | |
.interpolator(interp) // | |
.observe(v) // | |
.doOnNext(VISIBLE_RESET_ZERO_ALPHA.consumer()) // | |
.doOnComplete(VISIBLE_RESET.action(v)); | |
} | |
public static Observable<?> fadeOutAndGone(@NonNull View v) { | |
TimeInterpolator interp = new AccelerateDecelerateInterpolator(); | |
return new ViewAnim() // | |
.alpha(0f) // | |
.interpolator(interp) | |
.observe(v) | |
.doOnNext(VISIBLE_RESET.consumer()) | |
.doOnComplete(GONE_RESET.action(v)); | |
} | |
public static Observable<?> fadeOutAndIn(@NonNull View v) { | |
return fadeOutAndIn(v, null); | |
} | |
public static Observable<?> fadeOutAndIn(@NonNull View v, @Nullable ViewAction halfway) { | |
halfway = new ViewAction(VISIBLE_RESET_ZERO_ALPHA).include(halfway); | |
TimeInterpolator interp = new AccelerateDecelerateInterpolator(); | |
return new ViewAnim() // | |
.alpha(0f) // | |
.interpolator(interp) // | |
.observe(v) // | |
.doOnNext(VISIBLE_RESET.consumer()) // | |
.concatWith(new ViewAnim() // | |
.alpha(1f) // | |
.interpolator(interp) // | |
.observe(v) // | |
.doOnNext(halfway.consumer())) // | |
.doOnComplete(VISIBLE_RESET.action(v)); | |
} | |
public static Observable<?> fadeOutAndInSlide(@NonNull View v, int px) { | |
return fadeOutAndInSlide(v, px, null); | |
} | |
public static Observable<?> fadeOutAndInSlide(@NonNull View v, int px, | |
@Nullable ViewAction halfway) { | |
halfway = new ViewAction(VISIBLE_RESET_ZERO_ALPHA).translationX(px).include(halfway); | |
TimeInterpolator interp = new AccelerateDecelerateInterpolator(); | |
return new ViewAnim() // | |
.alpha(0f) // | |
.translationX(-px) // | |
.interpolator(interp) // | |
.observe(v) // | |
.doOnNext(VISIBLE_RESET.consumer()) // | |
.concatWith(new ViewAnim() // | |
.alpha(1f) // | |
.translationX(0) // | |
.interpolator(interp) // | |
.observe(v) // | |
.doOnNext(halfway.consumer())) // | |
.doOnComplete(VISIBLE_RESET.action(v)); | |
} | |
public static Observable<?> fadeOutAndInScale(@NonNull View v, float scale) { | |
return fadeOutAndInScale(v, scale, null); | |
} | |
public static Observable<?> fadeOutAndInScale(@NonNull View v, float scale, | |
@Nullable ViewAction halfway) { | |
halfway = new ViewAction(VISIBLE_RESET_ZERO_ALPHA).scale(scale).include(halfway); | |
TimeInterpolator interp = new AccelerateDecelerateInterpolator(); | |
return new ViewAnim() // | |
.alpha(0f) // | |
.scale(scale) // | |
.interpolator(interp) // | |
.observe(v) // | |
.doOnNext(VISIBLE_RESET.consumer()) // | |
.concatWith(new ViewAnim() // | |
.alpha(1f) // | |
.scale(1f) // | |
.interpolator(interp) // | |
.observe(v) // | |
.doOnNext(halfway.consumer())) // | |
.doOnComplete(VISIBLE_RESET.action(v)); | |
} | |
@IntDef({ View.VISIBLE, View.INVISIBLE, View.GONE }) | |
@Retention(RetentionPolicy.SOURCE) | |
public @interface Visibility { | |
} | |
public static class ViewAction { | |
private Float alpha; | |
private Float scaleX; | |
private Float scaleY; | |
private Float translationX; | |
private Float translationY; | |
private Integer visibility; | |
private CharSequence text; | |
private Integer textSizeUnit; | |
private Float textSizeSize; | |
private List<ViewAction> includes; | |
private boolean locked; | |
public ViewAction() { | |
} | |
public ViewAction(@NonNull ViewAction other) { | |
this.alpha = other.alpha; | |
this.scaleX = other.scaleX; | |
this.scaleY = other.scaleY; | |
this.translationX = other.translationX; | |
this.translationY = other.translationY; | |
this.visibility = other.visibility; | |
this.text = other.text; | |
this.textSizeUnit = other.textSizeUnit; | |
this.textSizeSize = other.textSizeSize; | |
this.includes = other.includes == null | |
? null | |
: new ArrayList<>(other.includes); | |
} | |
public ViewAction alpha(float alpha) { | |
assertNotLocked(); | |
this.alpha = alpha; | |
return this; | |
} | |
public ViewAction scaleX(float scaleX) { | |
assertNotLocked(); | |
this.scaleX = scaleX; | |
return this; | |
} | |
public ViewAction scaleY(float scaleY) { | |
assertNotLocked(); | |
this.scaleY = scaleY; | |
return this; | |
} | |
public ViewAction scale(float scale) { | |
assertNotLocked(); | |
this.scaleX = this.scaleY = scale; | |
return this; | |
} | |
public ViewAction translationX(float translationX) { | |
assertNotLocked(); | |
this.translationX = translationX; | |
return this; | |
} | |
public ViewAction translationY(float translationY) { | |
assertNotLocked(); | |
this.translationY = translationY; | |
return this; | |
} | |
public ViewAction visibility(@Visibility int visibility) { | |
assertNotLocked(); | |
this.visibility = visibility; | |
return this; | |
} | |
public ViewAction text(CharSequence text) { | |
assertNotLocked(); | |
this.text = text; | |
return this; | |
} | |
public ViewAction textSize(int unit, float size) { | |
assertNotLocked(); | |
this.textSizeUnit = unit; | |
this.textSizeSize = size; | |
return this; | |
} | |
public ViewAction include(ViewAction action) { | |
assertNotLocked(); | |
if (action == null) return this; | |
if (this.includes == null) this.includes = new ArrayList<>(); | |
this.includes.add(action); | |
return this; | |
} | |
public ViewAction lock() { | |
assertNotLocked(); | |
this.locked = true; | |
return this; | |
} | |
void assertNotLocked() { | |
if (locked) throw new IllegalStateException("not while locked."); | |
} | |
public Action action(@NonNull View v) { | |
final WeakReference<View> vref = new WeakReference<>(v); | |
final Consumer<View> c = consumer(); | |
return new Action() { | |
@Override | |
public void run() throws Exception { | |
View v = vref.get(); | |
if (v != null) c.accept(v); | |
} | |
}; | |
} | |
public Consumer<View> consumer() { | |
final Float a = alpha; | |
final Float sx = scaleX; | |
final Float sy = scaleY; | |
final Float tx = translationX; | |
final Float ty = translationY; | |
final Integer vis = visibility; | |
final CharSequence txt = text; | |
final Integer txtUnit = textSizeUnit; | |
final Float txtSize = textSizeSize; | |
final List<ViewAction> inc = includes == null | |
? null | |
: new ArrayList<>(includes); | |
return new Consumer<View>() { | |
@SuppressWarnings("WrongConstant") | |
@Override | |
public void accept(@NonNull View v) throws Exception { | |
if (a != null) v.setAlpha(a); | |
if (sx != null) v.setScaleX(sx); | |
if (sy != null) v.setScaleY(sy); | |
if (tx != null) v.setTranslationX(tx); | |
if (ty != null) v.setTranslationY(ty); | |
if (vis != null) v.setVisibility(vis); | |
if (v instanceof TextView) { | |
TextView tv = (TextView) v; | |
if (txt != null) tv.setText(txt); | |
if (txtUnit != null && txtSize != null) tv.setTextSize(txtUnit, txtSize); | |
} | |
if (inc != null) for (ViewAction i : inc) i.action(v).run(); | |
} | |
}; | |
} | |
} | |
public static final class ViewAnim { | |
private Float alpha; | |
private Float scaleX; | |
private Float scaleY; | |
private Float translationX; | |
private Float translationY; | |
private Long duration; | |
private Long startDelay; | |
private TimeInterpolator interpolator; | |
private boolean locked; | |
public ViewAnim() { | |
} | |
public ViewAnim(@NonNull ViewAnim other) { | |
this.alpha = other.alpha; | |
this.scaleX = other.scaleX; | |
this.scaleY = other.scaleY; | |
this.translationX = other.translationX; | |
this.translationY = other.translationY; | |
this.duration = other.duration; | |
this.startDelay = other.startDelay; | |
this.interpolator = other.interpolator; | |
} | |
public ViewAnim alpha(float alpha) { | |
assertNotLocked(); | |
this.alpha = alpha; | |
return this; | |
} | |
public ViewAnim scaleX(float scaleX) { | |
assertNotLocked(); | |
this.scaleX = scaleX; | |
return this; | |
} | |
public ViewAnim scaleY(float scaleY) { | |
assertNotLocked(); | |
this.scaleY = scaleY; | |
return this; | |
} | |
public ViewAnim scale(float scale) { | |
assertNotLocked(); | |
this.scaleX = this.scaleY = scale; | |
return this; | |
} | |
public ViewAnim translationX(float translationX) { | |
assertNotLocked(); | |
this.translationX = translationX; | |
return this; | |
} | |
public ViewAnim translationY(float translationY) { | |
assertNotLocked(); | |
this.translationY = translationY; | |
return this; | |
} | |
public ViewAnim duration(long duration) { | |
assertNotLocked(); | |
this.duration = duration; | |
return this; | |
} | |
public ViewAnim startDelay(long startDelay) { | |
assertNotLocked(); | |
this.startDelay = startDelay; | |
return this; | |
} | |
public ViewAnim interpolator(TimeInterpolator interpolator) { | |
assertNotLocked(); | |
this.interpolator = interpolator; | |
return this; | |
} | |
public ViewAnim lock() { | |
assertNotLocked(); | |
this.locked = true; | |
return this; | |
} | |
void assertNotLocked() { | |
if (locked) throw new IllegalStateException("not while locked."); | |
} | |
public Observable<View> observe(@NonNull View v) { | |
final WeakReference<View> vref = new WeakReference<>(v); | |
final Float a = this.alpha; | |
final Float sx = this.scaleX; | |
final Float sy = this.scaleY; | |
final Float tx = this.translationX; | |
final Float ty = this.translationY; | |
final Long dur = this.duration; | |
final Long del = this.startDelay; | |
final TimeInterpolator interp = this.interpolator; | |
return Observable.create(new ObservableOnSubscribe<View>() { | |
@Override | |
public void subscribe(@NonNull final ObservableEmitter<View> e) { | |
if (e.isDisposed()) return; | |
if (currentThread() != getMainLooper().getThread()) { | |
e.onError(new RuntimeException("cannot animate off main thread")); | |
return; | |
} | |
View v = vref.get(); | |
if (v == null) { | |
e.onComplete(); | |
return; | |
} | |
e.onNext(v); | |
List<PropertyValuesHolder> p = new ArrayList<>(); | |
if (a != null) p.add(PropertyValuesHolder.ofFloat(ALPHA, v.getAlpha(), a)); | |
if (sx != null) p.add(PropertyValuesHolder.ofFloat(SCALE_X, v.getScaleX(), sx)); | |
if (sy != null) p.add(PropertyValuesHolder.ofFloat(SCALE_Y, v.getScaleY(), sy)); | |
if (tx != null) { | |
p.add(PropertyValuesHolder.ofFloat(TRANSLATION_X, v.getTranslationX(), tx)); | |
} | |
if (ty != null) { | |
p.add(PropertyValuesHolder.ofFloat(TRANSLATION_Y, v.getTranslationY(), ty)); | |
} | |
if (p.isEmpty()) { | |
e.onComplete(); | |
return; | |
} | |
final ObjectAnimator oa = | |
ObjectAnimator.ofPropertyValuesHolder(v, (PropertyValuesHolder[]) p.toArray()); | |
if (dur != null) { | |
oa.setDuration(dur); | |
} else { | |
oa.setDuration(new ValueAnimator().getDuration()); | |
} | |
if (del != null) { | |
oa.setStartDelay(del); | |
} else { | |
oa.setStartDelay(0L); | |
} | |
if (interp != null) { | |
oa.setInterpolator(interp); | |
} else { | |
oa.setInterpolator(new ValueAnimator().getInterpolator()); | |
} | |
final AtomicBoolean done = new AtomicBoolean(); | |
oa.addListener(new AnimatorListenerAdapter() { | |
@Override | |
public void onAnimationCancel(Animator a) { | |
if (done.compareAndSet(false, true)) { | |
if (!e.isDisposed()) e.onComplete(); | |
} | |
} | |
@Override | |
public void onAnimationEnd(Animator a) { | |
if (done.compareAndSet(false, true)) { | |
if (!e.isDisposed()) e.onComplete(); | |
} | |
} | |
}); | |
e.setDisposable(Disposables.fromRunnable(new Runnable() { | |
@Override | |
public void run() { | |
if (done.compareAndSet(false, true)) { | |
oa.cancel(); | |
} | |
} | |
})); | |
oa.start(); | |
} | |
}); | |
} | |
} | |
private AnimUtil() { | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment