Java tutorial
/* * ================================================================================================= * Copyright (C) 2016 Martin Albedinsky * ================================================================================================= * Licensed under the Apache License, Version 2.0 or later (further "License" only). * ------------------------------------------------------------------------------------------------- * You may use this file only in compliance with the License. More details and copy of this License * you may obtain at * * http://www.apache.org/licenses/LICENSE-2.0 * * You can redistribute, modify or publish any part of the code written within this file but as it * is described in the License, the software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES or CONDITIONS OF ANY KIND. * * See the License for the specific language governing permissions and limitations under the License. * ================================================================================================= */ package com.albedinsky.android.ui.animation; import android.animation.Animator; import android.animation.TimeInterpolator; import android.annotation.TargetApi; import android.os.Build; import android.support.annotation.IntDef; import android.support.annotation.NonNull; import android.support.v4.util.ArrayMap; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; /** * A {@link Animator} implementation that can be used to wrap an instance of animator in order to * 'suppress' some of its features like pausing and resuming for instance. * * @author Martin Albedinsky */ @TargetApi(Build.VERSION_CODES.HONEYCOMB) public class AnimatorWrapper extends Animator { /** * Interface =================================================================================== */ /** * Constants =================================================================================== */ /** * Log TAG. */ // private static final String TAG = "AnimatorWrapper"; /** * Defines an annotation for determining set of allowed features for AnimatorWrapper. * * @see #requestFeatures(int) * @see #requestFeature(int) */ @Retention(RetentionPolicy.SOURCE) @IntDef(flag = true, value = { START, PAUSE, RESUME, END, CANCEL }) public @interface WrapperFeatures { } /** * Flag for indicating to the animator wrapper that it should support <b>START</b> feature for * its wrapped animator. * * @see #start() */ public static final int START = 0x00000001; /** * Flag for indicating to the animator wrapper that it should support <b>PAUSE</b> feature for * its wrapped animator. * * @see #RESUME * @see #pause() */ public static final int PAUSE = 0x00000001 << 1; /** * Flag for indicating to the animator wrapper that it should support <b>RESUME</b> feature for * its wrapped animator. * <p> * <b>Note, that if there is no {@link #PAUSE} feature requested, this feature flag is ignored.</b> * * @see #resume() */ public static final int RESUME = 0x00000001 << 2; /** * Flag for indicating to the animator wrapper that it should support <b>END</b> feature for * its wrapped animator. * <p> * <b>Note, that if there is no {@link #START} feature requested, this feature flag is ignored.</b> * * @see #end() */ public static final int END = 0x00000001 << 3; /** * Flag for indicating to the animator wrapper that it should support <b>CANCEL</b> feature for * its wrapped animator. * <p> * <b>Note, that if there is no {@link #START} feature requested, this feature flag is ignored.</b> * * @see #cancel() */ public static final int CANCEL = 0x00000001 << 4; /** * Flag grouping all wrapper features in one. */ public static final int ALL = START | PAUSE | RESUME | END | CANCEL; /** * Static members ============================================================================== */ /** * Members ===================================================================================== */ /** * Wrapped animator instance. */ protected final Animator mAnimator; /** * Set of feature flags specified for this wrapper. */ private int mFeatures = ALL; /** * Map containing animator listener wrappers mapped to theirs wrapped listeners used for purpose * of adding/removing of listeners via {@link #addListener(AnimatorListener)} and * {@link #removeListener(AnimatorListener)}. */ private ArrayMap<AnimatorListener, AnimatorListenerWrapper> mListenerWrappers; /** * Map containing animator pause listener wrappers mapped to theirs wrapped listeners used for * purpose of adding/removing of pause listeners via {@link #addPauseListener(AnimatorPauseListener)} * and {@link #removePauseListener(AnimatorPauseListener)}. */ private ArrayMap<AnimatorPauseListener, AnimatorPauseListenerWrapper> mPauseListenerWrappers; /** * Constructors ================================================================================ */ /** * Creates a new instance of AnimatorWrapper to wrap the given instance of <var>animator</var>. * * @param animator The animator to be wrapped. */ public AnimatorWrapper(@NonNull Animator animator) { this.mAnimator = animator; } /** * Methods ===================================================================================== */ /** * Specifies set of features for this wrapper. * <p> * <b>Note</b>, that this will override all current features to the specified ones. * * @param features The desired features to be set for this wrapper. * @see #requestFeature(int) * @see #hasFeature(int) * @see #removeFeature(int) */ public void requestFeatures(@WrapperFeatures int features) { this.mFeatures = features; } /** * Adds the specified <var>feature</var> to the registered ones. * * @param feature The desired feature to add. * @see #requestFeatures(int) * @see #hasFeature(int) * @see #removeFeature(int) */ public void requestFeature(@WrapperFeatures int feature) { this.mFeatures |= feature; } /** * Removes the specified <var>feature</var> from the registered ones. * * @param feature The desired feature to remove. * @see #requestFeature(int) * @see #hasFeature(int) */ public void removeFeature(@WrapperFeatures int feature) { this.mFeatures &= ~feature; } /** * Checks whether the specified <var>feature</var> has been requested for this wrapper or not. * * @param feature The desired feature to check for. * @return {@code True} if the feature has been requested via {@link #requestFeature(int)} or * specified by default, {@code false} otherwise. * @see #requestFeature(int) */ public boolean hasFeature(@WrapperFeatures int feature) { return (mFeatures & feature) != 0; } /** */ @Override public void addListener(AnimatorListener listener) { this.ensureListenerWrappers(); if (!mListenerWrappers.containsKey(listener)) { final AnimatorListenerWrapper wrapper = new AnimatorListenerWrapper(listener, this); mListenerWrappers.put(listener, wrapper); mAnimator.addListener(wrapper); } } /** */ @Override public void removeListener(AnimatorListener listener) { this.ensureListenerWrappers(); final AnimatorListenerWrapper wrapper = mListenerWrappers.get(listener); if (wrapper != null) { mListenerWrappers.remove(listener); mAnimator.removeListener(wrapper); } } /** * Ensures that the map with listener wrappers is initialized. */ private void ensureListenerWrappers() { if (mListenerWrappers == null) this.mListenerWrappers = new ArrayMap<>(1); } /** * Ignored if there are no {@link #PAUSE} and {@link #RESUME} features requested. * * @see #requestFeature(int) * @see #removeFeature(int) */ @Override public void addPauseListener(AnimatorPauseListener listener) { if (!hasFeature(PAUSE) && !hasFeature(RESUME)) return; if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return; this.ensurePauseListenerWrappers(); if (!mPauseListenerWrappers.containsKey(listener)) { final AnimatorPauseListenerWrapper wrapper = new AnimatorPauseListenerWrapper(listener, this); mPauseListenerWrappers.put(listener, wrapper); mAnimator.addPauseListener(wrapper); } } /** */ @Override public void removePauseListener(AnimatorPauseListener listener) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return; this.ensurePauseListenerWrappers(); final AnimatorPauseListenerWrapper wrapper = mPauseListenerWrappers.get(listener); if (wrapper != null) { mPauseListenerWrappers.remove(listener); mAnimator.removePauseListener(wrapper); } } /** * Ensures that the map with pause listener wrappers is initialized. */ private void ensurePauseListenerWrappers() { if (mPauseListenerWrappers == null) this.mPauseListenerWrappers = new ArrayMap<>(1); } /** */ @Override public ArrayList<AnimatorListener> getListeners() { return mListenerWrappers != null ? new ArrayList<>(mListenerWrappers.keySet()) : new ArrayList<AnimatorListener>(0); } /** */ @Override public void removeAllListeners() { if (mListenerWrappers != null) { this.mListenerWrappers.clear(); this.mListenerWrappers = null; } if (mPauseListenerWrappers != null) { this.mPauseListenerWrappers.clear(); this.mPauseListenerWrappers = null; } mAnimator.removeAllListeners(); } /** */ @Override public void setStartDelay(long startDelay) { mAnimator.setStartDelay(startDelay); } /** */ @Override public long getStartDelay() { return mAnimator.getStartDelay(); } /** */ @Override public Animator setDuration(long duration) { mAnimator.setDuration(duration); return this; } /** */ @Override public long getDuration() { return mAnimator.getDuration(); } /** */ @Override public void setInterpolator(TimeInterpolator interpolator) { mAnimator.setInterpolator(interpolator); } /** */ @Override @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) public TimeInterpolator getInterpolator() { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2 ? mAnimator.getInterpolator() : null; } /** */ @Override public void setTarget(Object target) { mAnimator.setTarget(target); } /** * Ignored if there is no {@link #START} feature requested. * * @see #requestFeature(int) * @see #removeFeature(int) */ @Override public void start() { if (!hasFeature(START)) return; mAnimator.start(); } /** */ @Override public boolean isStarted() { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH && mAnimator.isStarted(); } /** * Ignored if there is no {@link #PAUSE} feature requested. * * @see #requestFeature(int) * @see #removeFeature(int) */ @Override public void pause() { if (!hasFeature(PAUSE)) return; if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return; mAnimator.pause(); } /** */ @Override public boolean isPaused() { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && mAnimator.isPaused(); } /** * Ignored if there are no {@link #PAUSE} and {@link #RESUME} features requested. * * @see #requestFeature(int) * @see #removeFeature(int) */ @Override public void resume() { if (!hasFeature(PAUSE) && !hasFeature(RESUME)) return; if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return; mAnimator.resume(); } /** * Ignored if there are no {@link #START} and {@link #END} features requested. * * @see #requestFeature(int) * @see #removeFeature(int) */ @Override public void end() { if (!hasFeature(START) && !hasFeature(END)) return; mAnimator.end(); } /** * Ignored if there are no {@link #START} and {@link #CANCEL} features requested. * * @see #requestFeature(int) * @see #removeFeature(int) */ @Override public void cancel() { if (!hasFeature(START) && !hasFeature(CANCEL)) return; mAnimator.cancel(); } /** */ @Override public boolean isRunning() { return mAnimator.isRunning(); } /** * Inner classes =============================================================================== */ /** * Base class for animator listener wrappers used by {@link AnimatorWrapper} to support proper * dispatching of animation callbacks to the wrapped listener. * * @param <L> Type of the listener to wrap. */ private static abstract class BaseAnimatorListenerWrapper<L> { /** * Wrapped instance of animator listener. */ final L listener; /** * Animator wrapper to be dispatched with occurred animation callbacks. */ final Animator animatorWrapper; /** * Creates a new instance of BaseAnimatorListenerWrapper to wrap the given instance of animator * <var>listener</var>. * * @param listener The listener to be wrapped. * @param animatorWrapper The animator wrapper to be dispatched with occurred animation callbacks * to ensure that the original listener always communicates with the * wrapped animator through its wrapper. */ BaseAnimatorListenerWrapper(L listener, Animator animatorWrapper) { this.listener = listener; this.animatorWrapper = animatorWrapper; } } /** * A {@link BaseAnimatorListenerWrapper} implementation to wrap {@link AnimatorListener}. */ private static final class AnimatorListenerWrapper extends BaseAnimatorListenerWrapper<AnimatorListener> implements AnimatorListener { /** * Creates a new instance of AnimatorListenerWrapper to wrap the given instance of animator * <var>listener</var>. * * @see BaseAnimatorListenerWrapper#BaseAnimatorListenerWrapper(Object, Animator) */ AnimatorListenerWrapper(AnimatorListener listener, Animator animatorWrapper) { super(listener, animatorWrapper); } /** */ @Override public void onAnimationStart(Animator animation) { listener.onAnimationStart(animatorWrapper); } /** */ @Override public void onAnimationEnd(Animator animation) { listener.onAnimationEnd(animatorWrapper); } /** */ @Override public void onAnimationCancel(Animator animation) { listener.onAnimationCancel(animatorWrapper); } /** */ @Override public void onAnimationRepeat(Animator animation) { listener.onAnimationRepeat(animatorWrapper); } } /** * A {@link BaseAnimatorListenerWrapper} implementation to wrap {@link AnimatorPauseListener}. */ @TargetApi(Build.VERSION_CODES.KITKAT) private static final class AnimatorPauseListenerWrapper extends BaseAnimatorListenerWrapper<AnimatorPauseListener> implements AnimatorPauseListener { /** * Creates a new instance of AnimatorPauseListenerWrapper to wrap the given instance of animator * <var>listener</var>. * * @see BaseAnimatorListenerWrapper#BaseAnimatorListenerWrapper(Object, Animator) */ AnimatorPauseListenerWrapper(AnimatorPauseListener listener, Animator animatorWrapper) { super(listener, animatorWrapper); } /** */ @Override public void onAnimationPause(Animator animation) { listener.onAnimationPause(animatorWrapper); } /** */ @Override public void onAnimationResume(Animator animation) { listener.onAnimationResume(animatorWrapper); } } }