Skip to content

Instantly share code, notes, and snippets.

@praslnx8
Created June 7, 2017 13:41
Show Gist options
  • Save praslnx8/e23773aeb423c172a740d71fe43685ae to your computer and use it in GitHub Desktop.
Save praslnx8/e23773aeb423c172a740d71fe43685ae to your computer and use it in GitHub Desktop.
List of animated android components
/*
* @category Daimler
* @copyright Copyright (C) 2017 Contus. All rights reserved.
* @license http://www.apache.org/licenses/LICENSE-2.0
*/
package daimler.com.loadcarrier.uiux;
import android.animation.Animator;
/**
* The animation listener class that handles the animation callbacks and triggers any registered callback
* instance of {@link AnimatedDialogFragment.DialogAnimationCallback}.
*
* @author ContusTeam <[email protected]>
* @version 1.0
*/
abstract class AnimatedDialogAnimatorListener implements Animator.AnimatorListener {
private final AnimatedDialogFragment dialogFragment;
private final boolean shouldReverse;
AnimatedDialogFragment.DialogAnimationCallback dialogAnimationCallback;
/**
* Constructor to initialize this listener class.
*
* @param loadsFilterDialog The dialog reference this animation listener works with.
* @param shouldReverse The boolean that decides whether to reverse the animation for in and out transitions
* respectively.
* @param callback The callback that will be called in the animation lifecycle callback.
*/
AnimatedDialogAnimatorListener(AnimatedDialogFragment loadsFilterDialog, boolean shouldReverse,
AnimatedDialogFragment.DialogAnimationCallback callback) {
this.dialogAnimationCallback = callback;
this.dialogFragment = loadsFilterDialog;
this.shouldReverse = shouldReverse;
}
@Override
public void onAnimationStart(Animator animation) {
//No implementation needed.
}
@Override
public void onAnimationEnd(Animator animation) {
if (!dialogFragment.isDetached() && dialogFragment.getDialog() != null && dialogFragment.getDialog().isShowing
() && dialogFragment.getDialogContainer() != null) {
if (shouldReverse) {
onOutAnimationEnd();
} else {
onInAnimationEnd();
}
if (dialogAnimationCallback != null)
dialogAnimationCallback.onDialogAnimationComplete();
}
}
@Override
public void onAnimationCancel(Animator animation) {
//No implementation needed.
}
@Override
public void onAnimationRepeat(Animator animation) {
//No implementation needed.
}
/**
* This method will be called when the entry animation on the dialog is completed.
*/
protected abstract void onInAnimationEnd();
/**
* This method will be called when the exit animation on the dialog is completed.
*/
protected abstract void onOutAnimationEnd();
}
/*
* @category Daimler
* @copyright Copyright (C) 2017 Contus. All rights reserved.
* @license http://www.apache.org/licenses/LICENSE-2.0
*/
package daimler.com.loadcarrier.uiux;
import android.animation.Animator;
import android.annotation.TargetApi;
import android.app.Dialog;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.v7.app.AppCompatDialog;
import android.support.v7.app.AppCompatDialogFragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewAnimationUtils;
import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.TranslateAnimation;
import android.widget.FrameLayout;
import daimler.com.loadcarrier.R;
import daimler.com.loadcarrier.base.BaseActivity;
import daimler.com.loadcarrier.utils.DisplayUtils;
import daimler.com.loadcarrier.utils.Logger;
/**
* The dialog fragment class, that has custom animations applied for the entry and exit transitions.
*
* @author ContusTeam <[email protected]>
* @version 1.0
*/
public abstract class AnimatedDialogFragment extends AppCompatDialogFragment {
private FrameLayout dialogContainer;
private static final int REVEAL_DURATION = 600;
private static final int TRANSLATION_DURATION = REVEAL_DURATION;
private static final int DELAY_DELTA = 150;
@NonNull
@Override
public final Dialog onCreateDialog(Bundle savedInstanceState) {
AppCompatDialog dialog = new AppCompatDialog(getActivity(), R.style.AppTheme_Transparent) {
@Override
public void onBackPressed() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
animateDialogOut(null);
} else
super.onBackPressed();
}
};
dialogContainer
= (FrameLayout) LayoutInflater.from(getContext()).inflate(R.layout.dialog_animated_dialog_container,
(ViewGroup) getActivity().findViewById(android.R.id.content), false);
dialog.setContentView(dialogContainer);
dialogContainer.addView(onCreateDialogView(savedInstanceState, dialog));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
dialogContainer.post(new Runnable() {
@Override
public void run() {
animateDialogIn(null);
}
});
}
return dialog;
}
/**
* This method triggers enter animation on the dialog.
*
* @param callback The animation callback that will be invoked, when the enter animation is completed.
*/
protected void animateDialogIn(DialogAnimationCallback callback) {
animateDialog(false, callback);
}
/**
* This method triggers exit animation on the dialog.
*
* @param callback The animation callback that will be invoked, when the exit animation is completed.
*/
protected void animateDialogOut(DialogAnimationCallback callback) {
animateDialog(true, callback);
}
/**
* Called when the dialog has been animated into view. However, this method will not be called, if the activity,
* to which this dialog fragment is attached is not running anymore.
*/
protected void onInAnimationCompleted() {
}
/**
* This method animates the entrance and exit of the dialog. It also notifies the callback
* {@link DialogAnimationCallback} if passed in, as parameter. For exit animation, the method just reverses the
* enter animation with minor tweaks in the animation timings.
*
* @param shouldReverse The boolean variable that decides whether the animations is for entrance or exit. If true,
* it means the animations are for the exit of the dialog.
* @param callback The callback that will be notified of the animation statuses.
*/
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void animateDialog(final boolean shouldReverse, final DialogAnimationCallback callback) {
dialogContainer.clearAnimation();
View originatingView = getOriginatingView();
int[] animationOriginatingLocation = null;
if (originatingView != null && originatingView.getHeight() > 0 && originatingView.getWidth() > 0) {
animationOriginatingLocation = new int[2];
originatingView.getLocationOnScreen(animationOriginatingLocation);
} else {
Logger.debug("AnimatedDialogFragment : No valid originating view found. Falling back to default origin.");
}
double revealRadius = 0.5 * Math.sqrt(dialogContainer.getWidth() * dialogContainer.getWidth()
+ dialogContainer.getHeight() * dialogContainer.getHeight());
Animator circularAnim = ViewAnimationUtils.createCircularReveal(dialogContainer, dialogContainer
.getWidth() / 2, dialogContainer.getHeight() / 2, shouldReverse ? (float) revealRadius : 0,
shouldReverse ? 0 : (float) revealRadius);
circularAnim.setDuration(REVEAL_DURATION);
circularAnim.setInterpolator(new DecelerateInterpolator(1.0f));
circularAnim.addListener(new AnimatedDialogAnimatorListener(this, shouldReverse, callback) {
@Override
protected void onInAnimationEnd() {
if (!notifyInAnimationEnd()) {
//Remove the callback, as it should not be propagated, when the activity is not running.
dialogAnimationCallback = null;
}
}
@Override
protected void onOutAnimationEnd() {
if (!dismissProperly()) {
//Remove the callback, as it should not be propagated, when the activity is not running.
dialogAnimationCallback = null;
}
}
});
circularAnim.start();
final TranslateAnimation translateAnimation = getTranslateAnimation(shouldReverse, animationOriginatingLocation,
originatingView);
translateAnimation.setInterpolator(new DecelerateInterpolator(1.4f));
/*
* Based on the 'shouldReverse' boolean value, we can make minor adjustments to the timings during
* enter and exit animations, that will make the transition look much better. The value
* 'TRANSLATION_DURATION' combined with 'DELAY_DELTA' changes the animation durations and start times which
* makes the animations better for both enter and exit transitions.
* */
translateAnimation.setDuration(shouldReverse ? (TRANSLATION_DURATION - DELAY_DELTA) : TRANSLATION_DURATION);
if (shouldReverse) {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
dialogContainer.startAnimation(translateAnimation);
}
}, DELAY_DELTA);
} else dialogContainer.startAnimation(translateAnimation);
}
private TranslateAnimation getTranslateAnimation(boolean shouldReverse, int[] animationOriginatingLocation,
View originatingView) {
/*
* If there is a valid view that can be used as the originating point of animation, that will be used.
* Otherwise the animation will start from the default position which is the bottom of the screen.
* */
TranslateAnimation translateAnimation;
float translateHeight = DisplayUtils.getScreenHeight(getActivity()) - dialogContainer.getHeight() / 2;
if (animationOriginatingLocation != null && originatingView != null) {
int translationX = (animationOriginatingLocation[0] +
originatingView.getWidth() / 2) - (dialogContainer.getWidth() / 2);
int translationY = (animationOriginatingLocation[1]) - (dialogContainer.getHeight() / 2);
int resolvedFromTranslationX = shouldReverse ? 0 : translationX;
int resolvedToTranslationX = shouldReverse ? translationX : 0;
int resolvedFromTranslationY = shouldReverse ? 0 : translationY;
int resolvedToTranslationY = shouldReverse ? translationY : 0;
translateAnimation = new TranslateAnimation(resolvedFromTranslationX, resolvedToTranslationX,
resolvedFromTranslationY, resolvedToTranslationY);
} else {
translateAnimation = new TranslateAnimation(0, 0, shouldReverse ? 0 : translateHeight,
shouldReverse ? translateHeight : 0);
}
return translateAnimation;
}
/**
* This method handles dismissing the dialog in a proper way, based on the lifecycle methods of its hosting
* activity. And also returns whether the dialog has been properly dismissed.
*
* @return Returns true if the dialog was dismissed properly by calling {@link #dismiss()}, and false when it is
* dismissed by calling {@link #dismissAllowingStateLoss()}.
*/
private boolean dismissProperly() {
if (getActivity() != null && ((BaseActivity) getActivity()).isActivityRunning()) {
super.dismiss();
return true;
} else {
/*
* The activity hosting this dialog fragment is not running anymore. So dismiss the dialog
* allowing state loss.
*/
Logger.warn("AnimationDialogFragment : Dismissing the dialog. State loss allowed!");
super.dismissAllowingStateLoss();
return false;
}
}
/**
* This method calls {@link #onInAnimationCompleted()} when the dialog entry animation has been completed and the
* activity hosting is still running in foreground.
*
* @return Returns true if the {@link #onInAnimationCompleted()} was called, false otherwise.
*/
private boolean notifyInAnimationEnd() {
if (getActivity() != null && ((BaseActivity) getActivity()).isActivityRunning()) {
onInAnimationCompleted();
return true;
} else {
Logger.warn("AnimationDialogFragment : Activity not running. Animation callbacks will be ignored.");
return false;
}
}
/**
* Method returns the container viewGroup reference of {@link AnimatedDialogFragment}.
*
* @return The container viewGroup of {@link AnimatedDialogFragment}.
*/
public View getDialogContainer() {
return dialogContainer;
}
@Override
public void dismiss() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
animateDialogOut(null);
else
super.dismiss();
}
/**
* This method is returns the content view that will be added to the container ViewGroup of
* {@link AnimatedDialogFragment}.
*
* @param savedInstanceState The bundle containing the saved instance state of this dialog fragment.
* @param dialog The instance of the dialog used by this dialog fragment.
* @return The content view of the dialog.
*/
protected abstract View onCreateDialogView(Bundle savedInstanceState, AppCompatDialog dialog);
/**
* This method returns the view, from which the dialog starts the reveal and translation animation. If the view
* is null, the default location will be used to perform the animations.
*
* @return The view anchor to be used as the origination point of animation.
*/
protected abstract View getOriginatingView();
/**
* The interface that has callback methods that will be called when the entry/exit animation on the dialog
* fragment has completed.
*/
public interface DialogAnimationCallback {
/**
* This method will be called when the entry/exit animation on the filter dialog has been completed.
*/
void onDialogAnimationComplete();
}
}
/*
* @category Daimler
* @copyright Copyright (C) 2017 Contus. All rights reserved.
* @license http://www.apache.org/licenses/LICENSE-2.0
*/
package daimler.com.loadcarrier.uiux;
import android.content.Context;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
/**
* This is an extension of {@link RecyclerView} that supports initial animations of items in its layout manager
* when they are populated.
* <p>
* Code modified from the following SO answer.
*
* @author ContusTeam <[email protected]>
* @version 1.0
* @see <a href=http://stackoverflow.com/a/32369318/>
*/
public class AnimatedRecyclerView extends RecyclerView {
private boolean mScrollable = false;
private boolean shouldAnimate = true;
private static final int ANIMATION_TIME = 300;
private static final int ANIMATION_DELAY_BETWEEN_ITEMS = 100;
private Runnable delayRunnable = new Runnable() {
@Override
public void run() {
mScrollable = true;
shouldAnimate = false;
}
};
/**
* Default constructor
*
* @param context The context of this recycler view.
*/
public AnimatedRecyclerView(Context context) {
super(context);
}
/**
* Default constructor
*
* @param context The context of this recycler view.
* @param attrs The attribute set for this recycler view.
*/
public AnimatedRecyclerView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
/**
* Default constructor
*
* @param context The context of this recycler view.
* @param attrs The attribute set for this recycler view.
* @param defStyle The style for this recycler view.
*/
public AnimatedRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
return mScrollable && super.dispatchTouchEvent(ev);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if (shouldAnimate) {
for (int i = 0; i < getChildCount(); i++) {
animate(getChildAt(i), i);
if (i == getChildCount() - 1) {
getHandler().postDelayed(delayRunnable, ANIMATION_TIME + (ANIMATION_DELAY_BETWEEN_ITEMS * i));
}
}
}
}
private void animate(View view, final int pos) {
view.animate().cancel();
view.setTranslationY(200);
view.setPivotX(-50);
view.setPivotY(0);
view.setRotation(4);
view.setAlpha(0);
view.animate().alpha(1.0f).rotation(0).translationY(0).setDuration(ANIMATION_TIME)
.setStartDelay(pos * ANIMATION_DELAY_BETWEEN_ITEMS);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment