Created
June 7, 2017 13:41
-
-
Save praslnx8/e23773aeb423c172a740d71fe43685ae to your computer and use it in GitHub Desktop.
List of animated android components
This file contains 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
/* | |
* @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(); | |
} |
This file contains 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
/* | |
* @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(); | |
} | |
} |
This file contains 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
/* | |
* @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