/*
 * Copyright (C) 2013 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.transition;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.graphics.Color;
import android.util.Log;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.TextView;

import java.util.Map;

This transition tracks changes to the text in TextView targets. If the text changes between the start and end scenes, the transition ensures that the starting text stays until the transition ends, at which point it changes to the end text. This is useful in situations where you want to resize a text view to its new size before displaying the text that goes there.
@hide
/** * This transition tracks changes to the text in TextView targets. If the text * changes between the start and end scenes, the transition ensures that the * starting text stays until the transition ends, at which point it changes * to the end text. This is useful in situations where you want to resize a * text view to its new size before displaying the text that goes there. * * @hide */
public class ChangeText extends Transition { private static final String LOG_TAG = "TextChange"; private static final String PROPNAME_TEXT = "android:textchange:text"; private static final String PROPNAME_TEXT_SELECTION_START = "android:textchange:textSelectionStart"; private static final String PROPNAME_TEXT_SELECTION_END = "android:textchange:textSelectionEnd"; private static final String PROPNAME_TEXT_COLOR = "android:textchange:textColor"; private int mChangeBehavior = CHANGE_BEHAVIOR_KEEP;
Flag specifying that the text in affected/changing TextView targets will keep their original text during the transition, setting it to the final text when the transition ends. This is the default behavior.
See Also:
  • setChangeBehavior(int)
/** * Flag specifying that the text in affected/changing TextView targets will keep * their original text during the transition, setting it to the final text when * the transition ends. This is the default behavior. * * @see #setChangeBehavior(int) */
public static final int CHANGE_BEHAVIOR_KEEP = 0;
Flag specifying that the text changing animation should first fade out the original text completely. The new text is set on the target view at the end of the fade-out animation. This transition is typically used with a later CHANGE_BEHAVIOR_IN transition, allowing more flexibility than the CHANGE_BEHAVIOR_OUT_IN by allowing other transitions to be run sequentially or in parallel with these fades.
See Also:
/** * Flag specifying that the text changing animation should first fade * out the original text completely. The new text is set on the target * view at the end of the fade-out animation. This transition is typically * used with a later {@link #CHANGE_BEHAVIOR_IN} transition, allowing more * flexibility than the {@link #CHANGE_BEHAVIOR_OUT_IN} by allowing other * transitions to be run sequentially or in parallel with these fades. * * @see #setChangeBehavior(int) */
public static final int CHANGE_BEHAVIOR_OUT = 1;
Flag specifying that the text changing animation should fade in the end text into the affected target view(s). This transition is typically used in conjunction with an earlier CHANGE_BEHAVIOR_OUT transition, possibly with other transitions running as well, such as a sequence to fade out, then resize the view, then fade in.
See Also:
/** * Flag specifying that the text changing animation should fade in the * end text into the affected target view(s). This transition is typically * used in conjunction with an earlier {@link #CHANGE_BEHAVIOR_OUT} * transition, possibly with other transitions running as well, such as * a sequence to fade out, then resize the view, then fade in. * * @see #setChangeBehavior(int) */
public static final int CHANGE_BEHAVIOR_IN = 2;
Flag specifying that the text changing animation should first fade out the original text completely and then fade in the new text.
See Also:
  • setChangeBehavior(int)
/** * Flag specifying that the text changing animation should first fade * out the original text completely and then fade in the * new text. * * @see #setChangeBehavior(int) */
public static final int CHANGE_BEHAVIOR_OUT_IN = 3; private static final String[] sTransitionProperties = { PROPNAME_TEXT, PROPNAME_TEXT_SELECTION_START, PROPNAME_TEXT_SELECTION_END };
Sets the type of changing animation that will be run, one of CHANGE_BEHAVIOR_KEEP, CHANGE_BEHAVIOR_OUT, CHANGE_BEHAVIOR_IN, and CHANGE_BEHAVIOR_OUT_IN.
Params:
  • changeBehavior – The type of fading animation to use when this transition is run.
Returns:this textChange object.
/** * Sets the type of changing animation that will be run, one of * {@link #CHANGE_BEHAVIOR_KEEP}, {@link #CHANGE_BEHAVIOR_OUT}, * {@link #CHANGE_BEHAVIOR_IN}, and {@link #CHANGE_BEHAVIOR_OUT_IN}. * * @param changeBehavior The type of fading animation to use when this * transition is run. * @return this textChange object. */
public ChangeText setChangeBehavior(int changeBehavior) { if (changeBehavior >= CHANGE_BEHAVIOR_KEEP && changeBehavior <= CHANGE_BEHAVIOR_OUT_IN) { mChangeBehavior = changeBehavior; } return this; } @Override public String[] getTransitionProperties() { return sTransitionProperties; }
Returns the type of changing animation that will be run.
Returns:either CHANGE_BEHAVIOR_KEEP, CHANGE_BEHAVIOR_OUT, CHANGE_BEHAVIOR_IN, or CHANGE_BEHAVIOR_OUT_IN.
/** * Returns the type of changing animation that will be run. * * @return either {@link #CHANGE_BEHAVIOR_KEEP}, {@link #CHANGE_BEHAVIOR_OUT}, * {@link #CHANGE_BEHAVIOR_IN}, or {@link #CHANGE_BEHAVIOR_OUT_IN}. */
public int getChangeBehavior() { return mChangeBehavior; } private void captureValues(TransitionValues transitionValues) { if (transitionValues.view instanceof TextView) { TextView textview = (TextView) transitionValues.view; transitionValues.values.put(PROPNAME_TEXT, textview.getText()); if (textview instanceof EditText) { transitionValues.values.put(PROPNAME_TEXT_SELECTION_START, textview.getSelectionStart()); transitionValues.values.put(PROPNAME_TEXT_SELECTION_END, textview.getSelectionEnd()); } if (mChangeBehavior > CHANGE_BEHAVIOR_KEEP) { transitionValues.values.put(PROPNAME_TEXT_COLOR, textview.getCurrentTextColor()); } } } @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) { if (startValues == null || endValues == null || !(startValues.view instanceof TextView) || !(endValues.view instanceof TextView)) { return null; } final TextView view = (TextView) endValues.view; Map<String, Object> startVals = startValues.values; Map<String, Object> endVals = endValues.values; final CharSequence startText = startVals.get(PROPNAME_TEXT) != null ? (CharSequence) startVals.get(PROPNAME_TEXT) : ""; final CharSequence endText = endVals.get(PROPNAME_TEXT) != null ? (CharSequence) endVals.get(PROPNAME_TEXT) : ""; final int startSelectionStart, startSelectionEnd, endSelectionStart, endSelectionEnd; if (view instanceof EditText) { startSelectionStart = startVals.get(PROPNAME_TEXT_SELECTION_START) != null ? (Integer) startVals.get(PROPNAME_TEXT_SELECTION_START) : -1; startSelectionEnd = startVals.get(PROPNAME_TEXT_SELECTION_END) != null ? (Integer) startVals.get(PROPNAME_TEXT_SELECTION_END) : startSelectionStart; endSelectionStart = endVals.get(PROPNAME_TEXT_SELECTION_START) != null ? (Integer) endVals.get(PROPNAME_TEXT_SELECTION_START) : -1; endSelectionEnd = endVals.get(PROPNAME_TEXT_SELECTION_END) != null ? (Integer) endVals.get(PROPNAME_TEXT_SELECTION_END) : endSelectionStart; } else { startSelectionStart = startSelectionEnd = endSelectionStart = endSelectionEnd = -1; } if (!startText.equals(endText)) { final int startColor; final int endColor; if (mChangeBehavior != CHANGE_BEHAVIOR_IN) { view.setText(startText); if (view instanceof EditText) { setSelection(((EditText) view), startSelectionStart, startSelectionEnd); } } Animator anim; if (mChangeBehavior == CHANGE_BEHAVIOR_KEEP) { startColor = endColor = 0; anim = ValueAnimator.ofFloat(0, 1); anim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (startText.equals(view.getText())) { // Only set if it hasn't been changed since anim started view.setText(endText); if (view instanceof EditText) { setSelection(((EditText) view), endSelectionStart, endSelectionEnd); } } } }); } else { startColor = (Integer) startVals.get(PROPNAME_TEXT_COLOR); endColor = (Integer) endVals.get(PROPNAME_TEXT_COLOR); // Fade out start text ValueAnimator outAnim = null, inAnim = null; if (mChangeBehavior == CHANGE_BEHAVIOR_OUT_IN || mChangeBehavior == CHANGE_BEHAVIOR_OUT) { outAnim = ValueAnimator.ofInt(Color.alpha(startColor), 0); outAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { int currAlpha = (Integer) animation.getAnimatedValue(); view.setTextColor(currAlpha << 24 | startColor & 0xffffff); } }); outAnim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (startText.equals(view.getText())) { // Only set if it hasn't been changed since anim started view.setText(endText); if (view instanceof EditText) { setSelection(((EditText) view), endSelectionStart, endSelectionEnd); } } // restore opaque alpha and correct end color view.setTextColor(endColor); } }); } if (mChangeBehavior == CHANGE_BEHAVIOR_OUT_IN || mChangeBehavior == CHANGE_BEHAVIOR_IN) { inAnim = ValueAnimator.ofInt(0, Color.alpha(endColor)); inAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { int currAlpha = (Integer) animation.getAnimatedValue(); view.setTextColor(currAlpha << 24 | endColor & 0xffffff); } }); inAnim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationCancel(Animator animation) { // restore opaque alpha and correct end color view.setTextColor(endColor); } }); } if (outAnim != null && inAnim != null) { anim = new AnimatorSet(); ((AnimatorSet) anim).playSequentially(outAnim, inAnim); } else if (outAnim != null) { anim = outAnim; } else { // Must be an in-only animation anim = inAnim; } } TransitionListener transitionListener = new TransitionListenerAdapter() { int mPausedColor = 0; @Override public void onTransitionPause(Transition transition) { if (mChangeBehavior != CHANGE_BEHAVIOR_IN) { view.setText(endText); if (view instanceof EditText) { setSelection(((EditText) view), endSelectionStart, endSelectionEnd); } } if (mChangeBehavior > CHANGE_BEHAVIOR_KEEP) { mPausedColor = view.getCurrentTextColor(); view.setTextColor(endColor); } } @Override public void onTransitionResume(Transition transition) { if (mChangeBehavior != CHANGE_BEHAVIOR_IN) { view.setText(startText); if (view instanceof EditText) { setSelection(((EditText) view), startSelectionStart, startSelectionEnd); } } if (mChangeBehavior > CHANGE_BEHAVIOR_KEEP) { view.setTextColor(mPausedColor); } } @Override public void onTransitionEnd(Transition transition) { transition.removeListener(this); } }; addListener(transitionListener); if (DBG) { Log.d(LOG_TAG, "createAnimator returning " + anim); } return anim; } return null; } private void setSelection(EditText editText, int start, int end) { if (start >= 0 && end >= 0) { editText.setSelection(start, end); } } }