Animate a tint in android via scene transition animation - android

So in my app, I have a scene transition on an image view that transitions it from one activity to another:
v.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent i = new Intent(v.getContext(),tracks.class);
i.putExtra("album", al);
ActivityOptions transitionActivityOptions = ActivityOptions.makeSceneTransitionAnimation((item_display) v.getContext(), cover, "pic");
v.getContext().startActivity(i, transitionActivityOptions.toBundle());
}
});
Now, the scale and positioning transition works fine. However, on the imageview in the 2nd activity, I have a tint attribute in the xml to make it darker (style choice). The tint does not animate, so the whole effect is slightly more jarring that I would like. Is there any way to get the tint to smoothly transition back and forth like the rest of the image?

Use a custom transition. Something like this.
public class TintTransition extends Transition {
private static final String PROPNAME_TINT = "com.example:TintTransition:tint";
public TintTransition(Context context, AttributeSet attrs) {
super(context, attrs);
}
private void captureValues(TransitionValues values) {
if (values.view instanceof AppCompatImageView) {
values.values.put(PROPNAME_TINT, ((AppCompatImageView) values.view).getImageTintList());
}
}
#Override
public void captureStartValues(TransitionValues transitionValues) {
captureValues(transitionValues);
}
#Override
public void captureEndValues(TransitionValues transitionValues) {
captureValues(transitionValues);
}
#Override
public Animator createAnimator(ViewGroup sceneRoot, final TransitionValues startValues, final TransitionValues endValues) {
if (endValues == null) {
return null;
}
if (!(endValues.view instanceof AppCompatImageView)) {
return null;
}
ColorStateList startColorStateList = (ColorStateList) startValues.values.get(PROPNAME_TINT);
ColorStateList endColorStateList = (ColorStateList) endValues.values.get(PROPNAME_TINT);
final int endColor = endColorStateList.getDefaultColor();
final int startColor = startColorStateList == null
? Color.TRANSPARENT
: startColorStateList.getDefaultColor();
ValueAnimator animator = ValueAnimator.ofObject(new ArgbEvaluator(), startColor, endColor);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
Integer color = (Integer) animation.getAnimatedValue();
if (color != null) {
((AppCompatImageView) endValues.view).setImageTintList(ColorStateList.valueOf(color));
}
}
});
return animator;
}
}
If you need to inflate from XML, this is the way to do it:
<?xml version="1.0" encoding="utf-8"?>
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
<transition class="com.example.transitions.TintTransition" android:duration="400" android:interpolator="#android:interpolator/fast_out_slow_in"/>
</transitionSet>

Related

How to use custom transitions from resource file

I've just written a new custom transition but I'm not sure how to completely define it so can be used in the transition resource files for example res/transition/rotate_transition.xml:
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="275"
android:interpolator="#android:interpolator/accelerate_decelerate"/>
Here is my rotate transition code below:
public class Rotate extends Transition {
private static final String PROPNAME_ROTATION = "com.example.transition:rotate:rotation";
#Override
public void captureStartValues(TransitionValues transitionValues) {
captureValues(transitionValues);
}
#Override
public void captureEndValues(TransitionValues transitionValues) {
captureValues(transitionValues);
}
#Override
public Animator createAnimator(
ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) {
float startValue = (float) startValues.values.get(PROPNAME_ROTATION);
float endValue = (float) endValues.values.get(PROPNAME_ROTATION);
View view = endValues.view;
ValueAnimator animator = ValueAnimator.ofFloat(startValue, endValue);
animator.addUpdateListener(
animation -> {
float value = (float) animation.getAnimatedValue();
view.setRotation(value);
});
return animator;
}
private void captureValues(TransitionValues values) {
View view = values.view;
values.values.put(PROPNAME_ROTATION, view.getRotation());
}
}
My question is what else should I initialize in order to get this work because now I am only getting java.lang.RuntimeException: Unknown scene name: rotate?

Transition from TextView inside Fragment to title of Toolbar

I got a fragment inside of an Activity which contains a TextView and a Button.
If I click the Button another Fragment loads and the text of the TextView in the first Fragment is now the title of the Toolbar. I want to animate this change with the Transition Framework and something like changeBounds but I don't know how to set a transition for the Toolbar.
Could someone point me towards a solution?
You can create a custom transition that animates a TextView's text size as follows:
public class TextSizeTransition extends Transition {
private static final String PROPNAME_TEXT_SIZE = "lychmanit:transition:textsize";
private static final String[] TRANSITION_PROPERTIES = { PROPNAME_TEXT_SIZE };
private static final Property<TextView, Float> TEXT_SIZE_PROPERTY =
new Property<TextView, Float>(Float.class, "textSize") {
#Override
public Float get(TextView textView) {
return textView.getTextSize();
}
#Override
public void set(TextView textView, Float textSizePixels) {
textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSizePixels);
}
};
public TextSizeTransition() {
}
public TextSizeTransition(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public String[] getTransitionProperties() {
return TRANSITION_PROPERTIES;
}
#Override
public void captureStartValues(TransitionValues transitionValues) {
captureValues(transitionValues);
}
#Override
public void captureEndValues(TransitionValues transitionValues) {
captureValues(transitionValues);
}
private void captureValues(TransitionValues transitionValues) {
if (transitionValues.view instanceof TextView) {
TextView textView = (TextView) transitionValues.view;
transitionValues.values.put(PROPNAME_TEXT_SIZE, textView.getTextSize());
}
}
#Override
public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
TransitionValues endValues) {
if (startValues == null || endValues == null) {
return null;
}
Float startSize = (Float) startValues.values.get(PROPNAME_TEXT_SIZE);
Float endSize = (Float) endValues.values.get(PROPNAME_TEXT_SIZE);
if (startSize == null || endSize == null ||
startSize.floatValue() == endSize.floatValue()) {
return null;
}
TextView view = (TextView) endValues.view;
view.setTextSize(TypedValue.COMPLEX_UNIT_PX, startSize);
return ObjectAnimator.ofFloat(view, TEXT_SIZE_PROPERTY, startSize, endSize);
}
}
Since changing the TextView's text size will cause its layout bounds to change during the course of the animation, getting the transition to work properly will take a little more effort than simply throwing a ChangeBounds transition into the same TransitionSet. What you will need to do instead is manually measure/layout the view in its end state in a SharedElementCallback.

Why Fade transition doesn't work on Shared Element

I have 2 Activities , the Shared Element transition works fine.ChangeBounds is the only the transition applied.
I want to apply a fade transition while the shared element moves, so the ordering is ORDERING_TOGETHER.
public class TransitionUtils {
public static Transition makeSharedElementEnterTransition(final Context context, final long duration) {
TransitionSet set = new TransitionSet();
set.setOrdering(TransitionSet.ORDERING_TOGETHER);
set.setDuration(duration);
Transition changeBounds = new ChangeBounds();
changeBounds.addTarget(context.getString(R.string.transition_name_search_text));
set.addTransition(changeBounds);
Transition fade = new Fade(Fade.OUT);
fade.addTarget(context.getString(R.string.transition_name_search_text));
set.addTransition(fade);
return set;
}
}
The startActivity calls ActivityOptions.makeSceneTransitionAnimation
In the EndActivity , the enter shared element transition is set
public class EndActivity extends Activity{
#Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.blabla);
getWindow().setSharedElementEnterTransition(TransitionUtils.makeSharedElementEnterTransition(this,2000));
}
}
Notes : I noticed that
Fade() is often applied to getWindow().setEnterTransition()
setting a
duration to TransitionSet applies to all Transistions contained
except Fade.
How to apply a Fade Transition to a sharedElement ?
What am I doing wrong ?
android.transition.Fade uses TransitionAlpha , which cannot be resolved in my IDE. android.transition.Fade works for enter and exit transition between activities.
So I created my own Fade to use Alpha. An Android view's opacity is set by alpha. And shared element uses View.
You call it like this :
Transition fadeOut = new FadeTransition(1f, 0f, new LinearInterpolator());
fadeOut.addTarget(transitionName);
The full code is here
#TargetApi(21)
public class FadeTransition extends Transition {
private static final String PROPNAME_BACKGROUND = "android:faderay:background";
private static final String PROPNAME_TEXT_COLOR = "android:faderay:textColor";
private static final String PROPNAME_ALPHA = "android:faderay:alpha";
private float startAlpha;
private float endAlpha;
private TimeInterpolator timeInterpolator;
public FadeTransition(final float startAlpha, final float endAlpha, final TimeInterpolator timeInterpolator) {
this.startAlpha = startAlpha;
this.endAlpha = endAlpha;
this.timeInterpolator = timeInterpolator;
}
public FadeTransition(final Context context, final AttributeSet attrs) {
super(context, attrs);
}
private void captureValues(final TransitionValues transitionValues) {
transitionValues.values.put(PROPNAME_BACKGROUND, transitionValues.view.getBackground());
transitionValues.values.put(PROPNAME_ALPHA, transitionValues.view.getAlpha());
if (transitionValues.view instanceof TextView) {
transitionValues.values.put(PROPNAME_TEXT_COLOR, ((TextView) transitionValues.view).getCurrentTextColor());
}
}
#Override
public void captureStartValues(final TransitionValues transitionValues) {
captureValues(transitionValues);
}
#Override
public void captureEndValues(final TransitionValues transitionValues) {
captureValues(transitionValues);
}
#SuppressLint("NewApi")
#Override
public Animator createAnimator(final ViewGroup sceneRoot, final TransitionValues startValues,
final TransitionValues endValues) {
TextView textView = (TextView) endValues.view;
if (startAlpha != endAlpha) {
textView.setAlpha(endAlpha);
}
ObjectAnimator fade = ObjectAnimator.ofFloat(textView, View.ALPHA, startAlpha, endAlpha);
fade.setInterpolator(timeInterpolator);
return fade;
}
}

RecyclerView Q&A

I'm creating a Q&A where each question is a card. The answer starts showing the first line, but when its clicked it should expanded to show the full answer.
When an answer is expanded/collapsed the rest of the RecyclerView should animate to make room for the expansion or collapse to avoid showing a blank space.
I watched the talk on RecyclerView animations, and believe I want a custom ItemAnimator, where I override animateChange. At that point I should create an ObjectAnimator to animate the height of the View's LayoutParams. Unfortunately I'm having a hard time tying it all together. I also return true when overriding canReuseUpdatedViewHolder, so we reuse the same viewholder.
#Override
public boolean canReuseUpdatedViewHolder(RecyclerView.ViewHolder viewHolder) {
return true;
}
#Override
public boolean animateChange(#NonNull RecyclerView.ViewHolder oldHolder,
#NonNull final RecyclerView.ViewHolder newHolder,
#NonNull ItemHolderInfo preInfo,
#NonNull ItemHolderInfo postInfo) {
Log.d("test", "Run custom animation.");
final ColorsAdapter.ColorViewHolder holder = (ColorsAdapter.ColorViewHolder) newHolder;
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) holder.tvColor.getLayoutParams();
ObjectAnimator halfSize = ObjectAnimator.ofInt(holder.tvColor.getLayoutParams(), "height", params.height, 0);
halfSize.start();
return super.animateChange(oldHolder, newHolder, preInfo, postInfo);
}
Right now I'm just trying to get something to animate, but nothing happens... Any ideas?
I think your animation was not working because you cannot animate LayoutParams that way although it would be neat if you could. I tried the code you had and all it did was make my view jump to the new height. Only way I found to get this to work was to use a ValueAnimator as you can see in the example below.
I noticed some shortcomings when using the DefaultItemAnimator to show/hide a view by updating its visibility. Although it did make room for the new view and animated the rest of the items up and down based on the visibility of the expandable view, I noticed it did not animate the height of the expandable view. It simply faded into place and out of place using alpha value only.
Below is a custom ItemAnimator that has size and alpha animations based on hiding/showing a LinearLayout in the ViewHolder layout. It also allows the reuse of the same ViewHolder and attempts handling partial animations correctly if the user taps the header quickly:
public static class MyAnimator extends DefaultItemAnimator {
#Override
public boolean canReuseUpdatedViewHolder(RecyclerView.ViewHolder viewHolder) {
return true;
}
private HashMap<RecyclerView.ViewHolder, AnimatorState> animatorMap = new HashMap<>();
#Override
public boolean animateChange(#NonNull RecyclerView.ViewHolder oldHolder, #NonNull final RecyclerView.ViewHolder newHolder, #NonNull ItemHolderInfo preInfo, #NonNull ItemHolderInfo postInfo) {
final ValueAnimator heightAnim;
final ObjectAnimator alphaAnim;
final CustomAdapter.ViewHolder vh = (CustomAdapter.ViewHolder) newHolder;
final View expandableView = vh.getExpandableView();
final int toHeight; // save height for later in case reversing animation
if(vh.isExpanded()) {
expandableView.setVisibility(View.VISIBLE);
// measure expandable view to get correct height
expandableView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
toHeight = expandableView.getMeasuredHeight();
alphaAnim = ObjectAnimator.ofFloat(expandableView, "alpha", 1f);
} else {
toHeight = 0;
alphaAnim = ObjectAnimator.ofFloat(expandableView, "alpha", 0f);
}
heightAnim = ValueAnimator.ofInt(expandableView.getHeight(), toHeight);
heightAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
expandableView.getLayoutParams().height = (Integer) heightAnim.getAnimatedValue();
expandableView.requestLayout();
}
});
AnimatorSet animSet = new AnimatorSet()
.setDuration(getChangeDuration());
animSet.playTogether(heightAnim, alphaAnim);
animSet.addListener(new Animator.AnimatorListener() {
private boolean isCanceled;
#Override
public void onAnimationStart(Animator animation) { }
#Override
public void onAnimationEnd(Animator animation) {
if(!vh.isExpanded() && !isCanceled) {
expandableView.setVisibility(View.GONE);
}
dispatchChangeFinished(vh, false);
animatorMap.remove(newHolder);
}
#Override
public void onAnimationCancel(Animator animation) {
isCanceled = true;
}
#Override
public void onAnimationRepeat(Animator animation) { }
});
AnimatorState animatorState = animatorMap.get(newHolder);
if(animatorState != null) {
animatorState.animSet.cancel();
// animation already running. Set start current play time of
// new animations to keep them smooth for reverse animation
alphaAnim.setCurrentPlayTime(animatorState.alphaAnim.getCurrentPlayTime());
heightAnim.setCurrentPlayTime(animatorState.heightAnim.getCurrentPlayTime());
animatorMap.remove(newHolder);
}
animatorMap.put(newHolder, new AnimatorState(alphaAnim, heightAnim, animSet));
dispatchChangeStarting(newHolder, false);
animSet.start();
return false;
}
public static class AnimatorState {
final ValueAnimator alphaAnim, heightAnim;
final AnimatorSet animSet;
public AnimatorState(ValueAnimator alphaAnim, ValueAnimator heightAnim, AnimatorSet animSet) {
this.alphaAnim = alphaAnim;
this.heightAnim = heightAnim;
this.animSet = animSet;
}
}
}
This is the result using a slightly modified RecyclerView demo.
Update:
Just noticed your use case is actually a bit different after rereading the question. You have a text view and only want to show a single line of it and then later expand it to show all lines. Fortunately that simplifies the custom animator:
public static class MyAnimator extends DefaultItemAnimator {
#Override
public boolean canReuseUpdatedViewHolder(RecyclerView.ViewHolder viewHolder) {
return true;
}
private HashMap<RecyclerView.ViewHolder, ValueAnimator> animatorMap = new HashMap<>();
#Override
public boolean animateChange(#NonNull RecyclerView.ViewHolder oldHolder, #NonNull final RecyclerView.ViewHolder newHolder, #NonNull ItemHolderInfo preInfo, #NonNull ItemHolderInfo postInfo) {
ValueAnimator prevAnim = animatorMap.get(newHolder);
if(prevAnim != null) {
prevAnim.reverse();
return false;
}
final ValueAnimator heightAnim;
final CustomAdapter.ViewHolder vh = (CustomAdapter.ViewHolder) newHolder;
final TextView tv = vh.getExpandableTextView();
if(vh.isExpanded()) {
tv.measure(View.MeasureSpec.makeMeasureSpec(((View) tv.getParent()).getWidth(), View.MeasureSpec.AT_MOST), View.MeasureSpec.UNSPECIFIED);
heightAnim = ValueAnimator.ofInt(tv.getHeight(), tv.getMeasuredHeight());
} else {
Paint.FontMetrics fm = tv.getPaint().getFontMetrics();
heightAnim = ValueAnimator.ofInt(tv.getHeight(), (int)(Math.abs(fm.top) + Math.abs(fm.bottom)));
}
heightAnim.setDuration(getChangeDuration());
heightAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
tv.getLayoutParams().height = (Integer) heightAnim.getAnimatedValue();
tv.requestLayout();
}
});
heightAnim.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationEnd(Animator animation) {
dispatchChangeFinished(vh, false);
animatorMap.remove(newHolder);
}
#Override
public void onAnimationCancel(Animator animation) { }
#Override
public void onAnimationStart(Animator animation) { }
#Override
public void onAnimationRepeat(Animator animation) { }
});
animatorMap.put(newHolder, heightAnim);
dispatchChangeStarting(newHolder, false);
heightAnim.start();
return false;
}
}
And the new demo:
You don't have to implement a custom ItemAnimator the default DefaultItemAnimator already supports what you need. However you need to tell this Animator which views changed. I guess you are calling notifyDataSetChanged() in your adapter. This prevents the animation for a single changed item in the RecyclerView (in your case the expand/collapse of the item).
You should use notifyItemChanged(int position) for the items that were changed. Here is a short itemClicked(int position) method that expands/collapses views in the RecyclerView. The field expandedPosition keeps track of the currently expanded item:
private void itemClicked(int position) {
if (expandedPosition == -1) {
// selected first item
expandedPosition = position;
notifyItemChanged(position);
} else if (expandedPosition == position) {
// collapse currently expanded item
expandedPosition = -1;
notifyItemChanged(position);
} else {
// collapse previously expanded item and expand new item
int oldExpanded = expandedPosition;
expandedPosition = position;
notifyItemChanged(oldExpanded);
notifyItemChanged(position);
}
}
This is the result:
According the documentation, you need to return false in animateChange or call runPendingAnimations later. Try returning false.
http://developer.android.com/reference/android/support/v7/widget/RecyclerView.ItemAnimator.html
Try this class:
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.graphics.Paint;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.TextView;
/**
* Created by ankitagrawal on 2/14/16.
*/
public class AnimatedViewHolder extends RecyclerView.ViewHolder
implements View.OnClickListener {
private int originalHeight = 0;
private boolean mIsViewExpanded = false;
private TextView textView;
// ..... CODE ..... //
public AnimatedViewHolder(View v) {
super(v);
v.setOnClickListener(this);
// Initialize other views, like TextView, ImageView, etc. here
// If isViewExpanded == false then set the visibility
// of whatever will be in the expanded to GONE
if (!mIsViewExpanded) {
// Set Views to View.GONE and .setEnabled(false)
textView.setLines(1);
}
}
#Override
public void onClick(final View view) {
// Declare a ValueAnimator object
ValueAnimator valueAnimator;
if(mIsViewExpanded) {
view.measure(View.MeasureSpec.makeMeasureSpec(((View) view.getParent()).getWidth(), View.MeasureSpec.AT_MOST), View.MeasureSpec.UNSPECIFIED);
mIsViewExpanded = false;
valueAnimator = ValueAnimator.ofInt(view.getHeight(), view.getMeasuredHeight());
} else {
Paint.FontMetrics fm = ((TextView)view).getPaint().getFontMetrics();
valueAnimator = ValueAnimator.ofInt(view.getHeight(), (int) (Math.abs(fm.top) + Math.abs(fm.bottom)));
mIsViewExpanded = true;
}
valueAnimator.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationEnd(Animator animation) {
}
#Override
public void onAnimationCancel(Animator animation) { }
#Override
public void onAnimationStart(Animator animation) { }
#Override
public void onAnimationRepeat(Animator animation) { }
});
valueAnimator.setDuration(200);
valueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
public void onAnimationUpdate(ValueAnimator animation) {
view.getLayoutParams().height = (Integer) animation.getAnimatedValue();
view.requestLayout();
}
});
valueAnimator.start();
}
}
The Advantage of this approach is it only add animation to onClick event and that best suits your requirement.
adding animation to viewholder will be too burdensome to your requirement.
and itemAnimator as per doc are animation for layout out items so also not best suits your requirement.
For expand & collapse animation android there is github library for it.
ExpandableRecyclerView
1).Add dependencies in the build.gradle file
dependencies {
compile 'com.android.support:recyclerview-v7:22.2.0'
compile 'com.bignerdranch.android:expandablerecyclerview:1.0.3'
}
Image of Expand & Collapse Animation
2) Expand & Collapse animation for RecyclerView animation
public static class ExampleViewHolder extends RecyclerView.ViewHolder
implements View.OnClickListener {
private int originalHeight = 0;
private boolean isViewExpanded = false;
private YourCustomView yourCustomView
public ExampleViewHolder(View v) {
super(v);
v.setOnClickListener(this);
// Initialize other views, like TextView, ImageView, etc. here
// If isViewExpanded == false then set the visibility
// of whatever will be in the expanded to GONE
if (isViewExpanded == false) {
// Set Views to View.GONE and .setEnabled(false)
yourCustomView.setVisibility(View.GONE);
yourCustomView.setEnabled(false);
}
}
#Override
public void onClick(final View view) {
// If the originalHeight is 0 then find the height of the View being used
// This would be the height of the cardview
if (originalHeight == 0) {
originalHeight = view.getHeight();
}
// Declare a ValueAnimator object
ValueAnimator valueAnimator;
if (!mIsViewExpanded) {
yourCustomView.setVisibility(View.VISIBLE);
yourCustomView.setEnabled(true);
mIsViewExpanded = true;
valueAnimator = ValueAnimator.ofInt(originalHeight, originalHeight + (int) (originalHeight * 2.0)); // These values in this method can be changed to expand however much you like
} else {
mIsViewExpanded = false;
valueAnimator = ValueAnimator.ofInt(originalHeight + (int) (originalHeight * 2.0), originalHeight);
Animation a = new AlphaAnimation(1.00f, 0.00f); // Fade out
a.setDuration(200);
// Set a listener to the animation and configure onAnimationEnd
a.setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
}
#Override
public void onAnimationEnd(Animation animation) {
yourCustomView.setVisibility(View.INVISIBLE);
yourCustomView.setEnabled(false);
}
#Override
public void onAnimationRepeat(Animation animation) {
}
});
// Set the animation on the custom view
yourCustomView.startAnimation(a);
}
valueAnimator.setDuration(200);
valueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
public void onAnimationUpdate(ValueAnimator animation) {
Integer value = (Integer) animation.getAnimatedValue();
view.getLayoutParams().height = value.intValue();
view.requestLayout();
}
});
valueAnimator.start();
}
}
Hope this will help you.

Text color animation

Is there a way to animate a text color change (from anycolor to white)?
The only variant I came up with, is placing two textviews (with the same text) in one place, and fading the top one, so the bottom one (that has a white color) will become visible.
P.S. I scrapped the variant of the 2 TextViews since it looked weird (edges weren't smooth and, since I have a lot of such elements on the screen it was really lagging the scrolling). What I did, was a crazy hack that does the animation with the use of a Thread and setTextColor (that also forces redraw of a textview).
Since I needed only 2 color changes (from red to white, and from green to white) I hardcoded the values and all of the transition colors between them. So here's how it looks:
public class BlinkingTextView extends TextView {
public BlinkingTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void animateBlink(final boolean red) {
if (animator != null) {
animator.drop();
}
animator = new Animator(this, red);
animator.start();
}
public void clearBlinkAnimation() {
if (animator != null) {
animator.drop();
}
}
private Animator animator;
private final static class Animator extends Thread {
public Animator(final TextView textView, final boolean red) {
this.textView = textView;
if (red) {
SET_TO_USE = RED;
} else {
SET_TO_USE = GREEN;
}
}
private TextView textView;
private final int[] SET_TO_USE;
private final static int[] RED = {
-2142396,
-2008754,
-1874854,
-1740697,
-1540490,
-1405563,
-1205099,
-1004634,
-804170,
-669243,
-469036,
-334879,
-200979,
-67337,
-1
};
private final static int[] GREEN = {
-6959821,
-6565826,
-6106293,
-5646758,
-5055894,
-4530309,
-3939444,
-3283042,
-2692177,
-2166592,
-1575728,
-1116193,
-656660,
-262665,
-1
};
private boolean stop;
#Override
public void run() {
int i = 0;
while (i < 15) {
if (stop) break;
final int color = SET_TO_USE[i];
if (stop) break;
textView.post(new Runnable() {
#Override
public void run() {
if (!stop) {
textView.setTextColor(color);
}
}
});
if (stop) break;
i++;
if (stop) break;
try {
Thread.sleep(66);
} catch (InterruptedException e) {}
if (stop) break;
}
}
public void drop() {
stop = true;
}
}
}
You can use new Property Animation Api for color animation:
Integer colorFrom = getResources().getColor(R.color.red);
Integer colorTo = getResources().getColor(R.color.blue);
ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), colorFrom, colorTo);
colorAnimation.addUpdateListener(new AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animator) {
textView.setTextColor((Integer)animator.getAnimatedValue());
}
});
colorAnimation.start();
For backward compatability with Android 2.x use Nine Old Androids library from Jake Wharton.
The Easiest solution will be to use Object Animators :
ObjectAnimator colorAnim = ObjectAnimator.ofInt(yourTextView, "textColor",
Color.RED, Color.GREEN);
colorAnim.setEvaluator(new ArgbEvaluator());
colorAnim.start();
No need to keep handles to the two text views. First add the fadeIn/fadeOut animations:
textSwitcher.setInAnimation(AnimationUtils.loadAnimation(this, android.R.anim.fade_in));
textSwitcher.setOutAnimation(AnimationUtils.loadAnimation(this, android.R.anim.fade_out));
then:
TextView currentTextView = (TextView)(textSwitcher.getNextView().equals(
textSwitcher.getChildAt(0)) ?
textSwitcher.getChildAt(1) : textSwitcher.getChildAt(0)
);
// setCurrentText() first to be the same as newText if you need to
textSwitcher.setTextColor(fadeOutColor);
((TextView) textSwitcher.getNextView()).setTextColor(Color.WHITE);
textSwitcher.setText(newText);
Just implemented it like this so proven to work.
best way use ValueAnimator and ColorUtils.blendARGB
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
valueAnimator.setDuration(325);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float fractionAnim = (float) valueAnimator.getAnimatedValue();
textView.setTextColor(ColorUtils.blendARGB(Color.parseColor("#FFFFFF")
, Color.parseColor("#000000")
, fractionAnim));
}
});
valueAnimator.start();
Although I haven't found a totally distinct method, I have tried to use a TextSwitcher (with the fade animation) to create the colour-change effect. A TextSwitcher is a kind of ViewSwitcher which literally animates between two (internal) TextViews. Did you manually implement the same system unknowingly? ;) It manages a bit more of the process for you, so you may find it easier to work with (especially if you want to try more involved animations). I would create new subclass of TextSwitcher and some methods e.g. setColour() which can set the new colour and then trigger an animation. The animation code can then be moved outside of your main application.
make sure you keep a handle on the two TextViews that are put into the switcher
change the colour of the other TextView and call setText() to animate between them
If you are already using a ViewSwitcher then I don't think there is an easier way to implement this.
As others mention, using ObjectAnimator solves for this. However, in the existing posts - I wasn't seeing how to set duration. For me the color change would happen immediately.
The solution below shows:
setting the animation with some interval; thanks to post: https://plus.google.com/+CyrilMottier/posts/X4yoNHHszwq
a way to continuously cycle back and forth between the 2 colors
void animateTextViewColors(TextView textView, Integer colorTo) {
final Property<TextView, Integer> property = new Property<TextView, Integer>(int.class, "textColor") {
#Override
public Integer get(TextView object) {
return object.getCurrentTextColor();
}
#Override
public void set(TextView object, Integer value) {
object.setTextColor(value);
}
};
final ObjectAnimator animator = ObjectAnimator.ofInt(textView, property, colorTo);
animator.setDuration(8533L);
animator.setEvaluator(new ArgbEvaluator());
animator.setInterpolator(new DecelerateInterpolator(2));
animator.start();
}
void oscillateDemo(final TextView textView) {
final int whiteColor = ContextCompat.getColor(TheApp.getAppContext(), R.color.white);
final int yellowColor = ContextCompat.getColor(TheApp.getAppContext(), R.color.yellow);
final int counter = 100;
Thread oscillateThread = new Thread() {
#Override
public void run() {
for (int i = 0; i < counter; i++) {
final int fadeToColor = (i % 2 == 0)
? yellowColor
: whiteColor;
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
animateTextViewColors(textView, fadeToColor);
}
});
try {
Thread.sleep(2450);
}
catch (InterruptedException iEx) {}
}
}
};
oscillateThread.start();
}
I scrapped the variant of the 2 TextViews since it looked weird (edges weren't smooth and, since I have a lot of such elements on the screen it was really lagging the scrolling). What I did, was a crazy hack that does the animation with the use of a Thread and setTextColor (that also forces redraw of a textview).
Since I needed only 2 color changes (from red to white, and from green to white) I hardcoded the values and all of the transition colors between them. So here's how it looks:
public class BlinkingTextView extends TextView {
public BlinkingTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void animateBlink(final boolean red) {
if (animator != null) {
animator.drop();
}
animator = new Animator(this, red);
animator.start();
}
public void clearBlinkAnimation() {
if (animator != null) {
animator.drop();
}
}
private Animator animator;
private final static class Animator extends Thread {
public Animator(final TextView textView, final boolean red) {
this.textView = textView;
if (red) {
SET_TO_USE = RED;
} else {
SET_TO_USE = GREEN;
}
}
private TextView textView;
private final int[] SET_TO_USE;
private final static int[] RED = {
-2142396,
-2008754,
-1874854,
-1740697,
-1540490,
-1405563,
-1205099,
-1004634,
-804170,
-669243,
-469036,
-334879,
-200979,
-67337,
-1
};
private final static int[] GREEN = {
-6959821,
-6565826,
-6106293,
-5646758,
-5055894,
-4530309,
-3939444,
-3283042,
-2692177,
-2166592,
-1575728,
-1116193,
-656660,
-262665,
-1
};
private boolean stop;
#Override
public void run() {
int i = 0;
while (i < 15) {
if (stop) break;
final int color = SET_TO_USE[i];
if (stop) break;
textView.post(new Runnable() {
#Override
public void run() {
if (!stop) {
textView.setTextColor(color);
}
}
});
if (stop) break;
i++;
if (stop) break;
try {
Thread.sleep(66);
} catch (InterruptedException e) {}
if (stop) break;
}
}
public void drop() {
stop = true;
}
}
}
The issue I found with valueAnimator as well as ObjectAnimator is that the animator iterates through a number of random colors, and the transition doesn't look smooth. I wrote the following code which worked smoothly. Hope it helps someone else also.
public static void changeTextColor(final TextView textView, int startColor, int endColor,
final long animDuration, final long animUnit){
if (textView == null) return;
final int startRed = Color.red(startColor);
final int startBlue = Color.blue(startColor);
final int startGreen = Color.green(startColor);
final int endRed = Color.red(endColor);
final int endBlue = Color.blue(endColor);
final int endGreen = Color.green(endColor);
new CountDownTimer(animDuration, animUnit){
//animDuration is the time in ms over which to run the animation
//animUnit is the time unit in ms, update color after each animUnit
#Override
public void onTick(long l) {
int red = (int) (endRed + (l * (startRed - endRed) / animDuration));
int blue = (int) (endBlue + (l * (startBlue - endBlue) / animDuration));
int green = (int) (endGreen + (l * (startGreen - endGreen) / animDuration));
textView.setTextColor(Color.rgb(red, green, blue));
}
#Override
public void onFinish() {
textView.setTextColor(Color.rgb(endRed, endGreen, endBlue));
}
}.start();
}

Categories

Resources