Last active
January 18, 2018 15:03
-
-
Save guilhermesgb/2db3efd5122fe6eaad1b749e24fe6120 to your computer and use it in GitHub Desktop.
TransitionManager transition for animating CollapsingToolbarLayout's contentScrim color changes
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
package com.mcontigo.unycos.utils; | |
import android.animation.Animator; | |
import android.animation.ObjectAnimator; | |
import android.animation.TypeEvaluator; | |
import android.graphics.Color; | |
import android.graphics.drawable.ColorDrawable; | |
import android.graphics.drawable.Drawable; | |
import android.support.annotation.NonNull; | |
import android.support.design.widget.CollapsingToolbarLayout; | |
import android.support.transition.Transition; | |
import android.support.transition.TransitionValues; | |
import android.util.Property; | |
import android.view.View; | |
import android.view.ViewGroup; | |
//Usage: | |
// | |
// Consider you have a plain regular CoordinatorLayout -> AppBarLayout -> CollapsingToolbarLayout hiearchy in your XML layout. | |
// | |
// Example goes: | |
// | |
// <android.support.design.widget.CoordinatorLayout> | |
// | |
// <android.support.design.widget.AppBarLayout | |
// android:id="@+id/appBar"> | |
// | |
// <android.support.design.widget.CollapsingToolbarLayout | |
// app:contentScrim="@color/someColor"> | |
// | |
// <ImageView | |
// android:id="@+id/backgroundImage" | |
// android:layout_width="match_parent" | |
// android:layout_height="match_parent" | |
// android:src="@drawable/someImage" | |
// android:scaleType="centerCrop" | |
// android:contentDescription="@null"/> | |
// | |
// </android.support.design.widget.CollapsingToolbarLayout> | |
// | |
// </android.support.design.widget.AppBarLayout> | |
// | |
// </android.support.design.widget.CoordinatorLayout> | |
// | |
// | |
// Now let's suppose that, often, you have to programmatically change the CollapsingToolbarLayout contentScrim | |
// as well as and its child ImageView src (common use case: whenever the user moves from one TabLayout tab to another). | |
// | |
// You would have some code loading another image into the imageView alongside some code for changing | |
// the contentScrimColor of the collapsingToolbarLayout. | |
// | |
// So, if you do have a parallax background image, make sure you have your commands for loading | |
// another image come first, before your code for setting a new content scrim into the collapsingToolbarLayout! | |
// | |
// Now it's time to schedule our transition: precede your content scrim setter call by a "beginDelayedTransition" operation, | |
// precisely using this method below: | |
// | |
// `TransitionManager.beginDelayedTransition(View, Transition)`, | |
// | |
// passing the parent AppBarLayout view as the transition target and an instance of this class below as the actual transition. | |
// | |
// Then, in your code, simply set a new content_scrim to the CollapsingToolbarLayout via the some of the available choices below: | |
// | |
// `CollapsingToolbarLayout.contentScrimColor(@ColorInt int)`, | |
// | |
// `CollapsingToolbarLayout.setContentScrim(Drawable)` or | |
// | |
// `CollapsingToolbarLayout.setContentScrimResource(@DrawableRes int)` | |
// | |
// and that's it! | |
// You'll notice the CollapsingToolbarLayout smoothly animating a transition from the old contentScrim into the new one, | |
// whenever your view is collapsed. When you decide to swap content scrims while the view is fully expanded (which means | |
// the underlying imageView is visible), this transition object will actually skip the transition, properly setting the | |
// new content scrim directly and you will only be able to notice the change later when you collapse your view again. | |
// | |
// | |
// For example: | |
// | |
// //Some code for loading a new image into the collapsingToolbarLayout's underlying imageView (with parallax effect enabled). | |
// TransitionManager.beginDelayedTransition(appBarLayout, new CollapsingToolbarContentScrimTransition()); | |
// collapsingToolbarLayout.setContentScrim(new ColorDrawable(resolvedColorInt)); | |
// | |
// | |
public class CollapsingToolbarContentScrimTransition extends Transition { | |
private final String PROPNAME_CONTENT_SCRIM = "app:recolor:contentScrim"; | |
private void captureValues(@NonNull TransitionValues transitionValues) { | |
if (transitionValues.view instanceof CollapsingToolbarLayout) { | |
transitionValues.values.put(PROPNAME_CONTENT_SCRIM, | |
((CollapsingToolbarLayout) transitionValues.view).getContentScrim()); | |
} | |
} | |
@Override | |
public void captureStartValues(@NonNull TransitionValues transitionValues) { | |
captureValues(transitionValues); | |
} | |
@Override | |
public void captureEndValues(@NonNull TransitionValues transitionValues) { | |
captureValues(transitionValues); | |
} | |
@Override | |
public Animator createAnimator(@NonNull ViewGroup sceneRoot, | |
TransitionValues startValues, TransitionValues endValues) { | |
if (startValues == null || endValues == null) { | |
return null; | |
} | |
Drawable startContentScrim = (Drawable) startValues.values.get(PROPNAME_CONTENT_SCRIM); | |
Drawable endContentScrim = (Drawable) endValues.values.get(PROPNAME_CONTENT_SCRIM); | |
View view = endValues.view; | |
//Skipping any views that aren't CollapsingToolbarLayouts since they're not supported. | |
// | |
//Also skipping CollapsingToolbarLayouts if they're provided Drawables that aren't plain colors since the | |
// transition animation is implemented by means of interpolating RGB colors (you might want to replace this | |
// by a better color interpolator by writing another `evaluate` method). | |
// | |
//Also skipping CollapsingToolbarLayouts that have been passed transparent colors because otherwise, this could mess up | |
// with the CollapsingToolbarLayout's child image view which is meant to be shown only while the view is expanded, | |
// making it visible also while the CollapsingToolbarLayout is in its collapsed state. | |
// Since whenever the underlying image view is meant to be shown, that's actually achieved internally by the | |
// CollapsingToolbarLayout by setting its contentScrim color alpha to zero, I use that as a way to determine whether the | |
// CollapsingToolbarLayout is in its collapsed or expanded state so that I can skip the transition in the latter case. | |
if (view instanceof CollapsingToolbarLayout | |
&& startContentScrim instanceof ColorDrawable | |
&& endContentScrim instanceof ColorDrawable | |
&& isFullyOpaque(((ColorDrawable) endContentScrim).getColor())) { | |
ColorDrawable startColor = (ColorDrawable) startContentScrim; | |
ColorDrawable endColor = (ColorDrawable) endContentScrim; | |
//Also there's no need to animate if the colors won't end up changing anyways. | |
if (startColor.getColor() != endColor.getColor()) { | |
final CollapsingToolbarLayout collapsingToolbarLayout = (CollapsingToolbarLayout) view; | |
return ObjectAnimator.ofObject(collapsingToolbarLayout, | |
Property.of(CollapsingToolbarLayout.class, Drawable.class, "contentScrim"), | |
new TypeEvaluator<Drawable>() { | |
//The RGB color interpolation needed by the transition animation is being done below. | |
@Override | |
public Drawable evaluate(float fraction, | |
Drawable startValue, Drawable endValue) { | |
int sColor = ((ColorDrawable) startValue).getColor(); | |
float sR = ((sColor >> 16) & 0xff) / 255.0f; | |
float sG = ((sColor >> 8) & 0xff) / 255.0f; | |
float sB = ((sColor ) & 0xff) / 255.0f; | |
float sA = ((sColor >> 24) & 0xff) / 255.0f; | |
int eColor = ((ColorDrawable) endValue).getColor(); | |
float eR = ((eColor >> 16) & 0xff) / 255.0f; | |
float eG = ((eColor >> 8) & 0xff) / 255.0f; | |
float eB = ((eColor ) & 0xff) / 255.0f; | |
float eA = ((eColor >> 24) & 0xff) / 255.0f; | |
float tR = sR + (eR - sR) * fraction; | |
float tG = sG + (eG - sG) * fraction; | |
float tB = sB + (eB - sB) * fraction; | |
float tA = sA + (eA - sA) * fraction; | |
return new ColorDrawable(Color.argb( | |
(int) (tA * 255), | |
(int) (tR * 255), | |
(int) (tG * 255), | |
(int) (tB * 255) | |
)); | |
} | |
}, startColor, endColor); | |
} | |
} | |
return null; | |
} | |
//Checking if the alpha channel of the color is totally max. | |
private boolean isFullyOpaque(int color) { | |
return ((color >> 24) & 0xff) / 255.0f >= 1; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment