1 /* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.transition; 18 19 import android.animation.Animator; 20 import android.animation.AnimatorListenerAdapter; 21 import android.animation.AnimatorSet; 22 import android.animation.ValueAnimator; 23 import android.graphics.Color; 24 import android.util.Log; 25 import android.view.ViewGroup; 26 import android.widget.EditText; 27 import android.widget.TextView; 28 29 import java.util.Map; 30 31 /** 32 * This transition tracks changes to the text in TextView targets. If the text 33 * changes between the start and end scenes, the transition ensures that the 34 * starting text stays until the transition ends, at which point it changes 35 * to the end text. This is useful in situations where you want to resize a 36 * text view to its new size before displaying the text that goes there. 37 * 38 * @hide 39 */ 40 public class ChangeText extends Transition { 41 42 private static final String LOG_TAG = "TextChange"; 43 44 private static final String PROPNAME_TEXT = "android:textchange:text"; 45 private static final String PROPNAME_TEXT_SELECTION_START = 46 "android:textchange:textSelectionStart"; 47 private static final String PROPNAME_TEXT_SELECTION_END = 48 "android:textchange:textSelectionEnd"; 49 private static final String PROPNAME_TEXT_COLOR = "android:textchange:textColor"; 50 51 private int mChangeBehavior = CHANGE_BEHAVIOR_KEEP; 52 53 /** 54 * Flag specifying that the text in affected/changing TextView targets will keep 55 * their original text during the transition, setting it to the final text when 56 * the transition ends. This is the default behavior. 57 * 58 * @see #setChangeBehavior(int) 59 */ 60 public static final int CHANGE_BEHAVIOR_KEEP = 0; 61 /** 62 * Flag specifying that the text changing animation should first fade 63 * out the original text completely. The new text is set on the target 64 * view at the end of the fade-out animation. This transition is typically 65 * used with a later {@link #CHANGE_BEHAVIOR_IN} transition, allowing more 66 * flexibility than the {@link #CHANGE_BEHAVIOR_OUT_IN} by allowing other 67 * transitions to be run sequentially or in parallel with these fades. 68 * 69 * @see #setChangeBehavior(int) 70 */ 71 public static final int CHANGE_BEHAVIOR_OUT = 1; 72 /** 73 * Flag specifying that the text changing animation should fade in the 74 * end text into the affected target view(s). This transition is typically 75 * used in conjunction with an earlier {@link #CHANGE_BEHAVIOR_OUT} 76 * transition, possibly with other transitions running as well, such as 77 * a sequence to fade out, then resize the view, then fade in. 78 * 79 * @see #setChangeBehavior(int) 80 */ 81 public static final int CHANGE_BEHAVIOR_IN = 2; 82 /** 83 * Flag specifying that the text changing animation should first fade 84 * out the original text completely and then fade in the 85 * new text. 86 * 87 * @see #setChangeBehavior(int) 88 */ 89 public static final int CHANGE_BEHAVIOR_OUT_IN = 3; 90 91 private static final String[] sTransitionProperties = { 92 PROPNAME_TEXT, 93 PROPNAME_TEXT_SELECTION_START, 94 PROPNAME_TEXT_SELECTION_END 95 }; 96 97 /** 98 * Sets the type of changing animation that will be run, one of 99 * {@link #CHANGE_BEHAVIOR_KEEP}, {@link #CHANGE_BEHAVIOR_OUT}, 100 * {@link #CHANGE_BEHAVIOR_IN}, and {@link #CHANGE_BEHAVIOR_OUT_IN}. 101 * 102 * @param changeBehavior The type of fading animation to use when this 103 * transition is run. 104 * @return this textChange object. 105 */ setChangeBehavior(int changeBehavior)106 public ChangeText setChangeBehavior(int changeBehavior) { 107 if (changeBehavior >= CHANGE_BEHAVIOR_KEEP && changeBehavior <= CHANGE_BEHAVIOR_OUT_IN) { 108 mChangeBehavior = changeBehavior; 109 } 110 return this; 111 } 112 113 @Override getTransitionProperties()114 public String[] getTransitionProperties() { 115 return sTransitionProperties; 116 } 117 118 /** 119 * Returns the type of changing animation that will be run. 120 * 121 * @return either {@link #CHANGE_BEHAVIOR_KEEP}, {@link #CHANGE_BEHAVIOR_OUT}, 122 * {@link #CHANGE_BEHAVIOR_IN}, or {@link #CHANGE_BEHAVIOR_OUT_IN}. 123 */ getChangeBehavior()124 public int getChangeBehavior() { 125 return mChangeBehavior; 126 } 127 captureValues(TransitionValues transitionValues)128 private void captureValues(TransitionValues transitionValues) { 129 if (transitionValues.view instanceof TextView) { 130 TextView textview = (TextView) transitionValues.view; 131 transitionValues.values.put(PROPNAME_TEXT, textview.getText()); 132 if (textview instanceof EditText) { 133 transitionValues.values.put(PROPNAME_TEXT_SELECTION_START, 134 textview.getSelectionStart()); 135 transitionValues.values.put(PROPNAME_TEXT_SELECTION_END, 136 textview.getSelectionEnd()); 137 } 138 if (mChangeBehavior > CHANGE_BEHAVIOR_KEEP) { 139 transitionValues.values.put(PROPNAME_TEXT_COLOR, textview.getCurrentTextColor()); 140 } 141 } 142 } 143 144 @Override captureStartValues(TransitionValues transitionValues)145 public void captureStartValues(TransitionValues transitionValues) { 146 captureValues(transitionValues); 147 } 148 149 @Override captureEndValues(TransitionValues transitionValues)150 public void captureEndValues(TransitionValues transitionValues) { 151 captureValues(transitionValues); 152 } 153 154 @Override createAnimator(ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues)155 public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, 156 TransitionValues endValues) { 157 if (startValues == null || endValues == null || 158 !(startValues.view instanceof TextView) || !(endValues.view instanceof TextView)) { 159 return null; 160 } 161 final TextView view = (TextView) endValues.view; 162 Map<String, Object> startVals = startValues.values; 163 Map<String, Object> endVals = endValues.values; 164 final CharSequence startText = startVals.get(PROPNAME_TEXT) != null ? 165 (CharSequence) startVals.get(PROPNAME_TEXT) : ""; 166 final CharSequence endText = endVals.get(PROPNAME_TEXT) != null ? 167 (CharSequence) endVals.get(PROPNAME_TEXT) : ""; 168 final int startSelectionStart, startSelectionEnd, endSelectionStart, endSelectionEnd; 169 if (view instanceof EditText) { 170 startSelectionStart = startVals.get(PROPNAME_TEXT_SELECTION_START) != null ? 171 (Integer) startVals.get(PROPNAME_TEXT_SELECTION_START) : -1; 172 startSelectionEnd = startVals.get(PROPNAME_TEXT_SELECTION_END) != null ? 173 (Integer) startVals.get(PROPNAME_TEXT_SELECTION_END) : startSelectionStart; 174 endSelectionStart = endVals.get(PROPNAME_TEXT_SELECTION_START) != null ? 175 (Integer) endVals.get(PROPNAME_TEXT_SELECTION_START) : -1; 176 endSelectionEnd = endVals.get(PROPNAME_TEXT_SELECTION_END) != null ? 177 (Integer) endVals.get(PROPNAME_TEXT_SELECTION_END) : endSelectionStart; 178 } else { 179 startSelectionStart = startSelectionEnd = endSelectionStart = endSelectionEnd = -1; 180 } 181 if (!startText.equals(endText)) { 182 final int startColor; 183 final int endColor; 184 if (mChangeBehavior != CHANGE_BEHAVIOR_IN) { 185 view.setText(startText); 186 if (view instanceof EditText) { 187 setSelection(((EditText) view), startSelectionStart, startSelectionEnd); 188 } 189 } 190 Animator anim; 191 if (mChangeBehavior == CHANGE_BEHAVIOR_KEEP) { 192 startColor = endColor = 0; 193 anim = ValueAnimator.ofFloat(0, 1); 194 anim.addListener(new AnimatorListenerAdapter() { 195 @Override 196 public void onAnimationEnd(Animator animation) { 197 if (startText.equals(view.getText())) { 198 // Only set if it hasn't been changed since anim started 199 view.setText(endText); 200 if (view instanceof EditText) { 201 setSelection(((EditText) view), endSelectionStart, endSelectionEnd); 202 } 203 } 204 } 205 }); 206 } else { 207 startColor = (Integer) startVals.get(PROPNAME_TEXT_COLOR); 208 endColor = (Integer) endVals.get(PROPNAME_TEXT_COLOR); 209 // Fade out start text 210 ValueAnimator outAnim = null, inAnim = null; 211 if (mChangeBehavior == CHANGE_BEHAVIOR_OUT_IN || 212 mChangeBehavior == CHANGE_BEHAVIOR_OUT) { 213 outAnim = ValueAnimator.ofInt(Color.alpha(startColor), 0); 214 outAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 215 @Override 216 public void onAnimationUpdate(ValueAnimator animation) { 217 int currAlpha = (Integer) animation.getAnimatedValue(); 218 view.setTextColor(currAlpha << 24 | startColor & 0xffffff); 219 } 220 }); 221 outAnim.addListener(new AnimatorListenerAdapter() { 222 @Override 223 public void onAnimationEnd(Animator animation) { 224 if (startText.equals(view.getText())) { 225 // Only set if it hasn't been changed since anim started 226 view.setText(endText); 227 if (view instanceof EditText) { 228 setSelection(((EditText) view), endSelectionStart, 229 endSelectionEnd); 230 } 231 } 232 // restore opaque alpha and correct end color 233 view.setTextColor(endColor); 234 } 235 }); 236 } 237 if (mChangeBehavior == CHANGE_BEHAVIOR_OUT_IN || 238 mChangeBehavior == CHANGE_BEHAVIOR_IN) { 239 inAnim = ValueAnimator.ofInt(0, Color.alpha(endColor)); 240 inAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 241 @Override 242 public void onAnimationUpdate(ValueAnimator animation) { 243 int currAlpha = (Integer) animation.getAnimatedValue(); 244 view.setTextColor(currAlpha << 24 | endColor & 0xffffff); 245 } 246 }); 247 inAnim.addListener(new AnimatorListenerAdapter() { 248 @Override 249 public void onAnimationCancel(Animator animation) { 250 // restore opaque alpha and correct end color 251 view.setTextColor(endColor); 252 } 253 }); 254 } 255 if (outAnim != null && inAnim != null) { 256 anim = new AnimatorSet(); 257 ((AnimatorSet) anim).playSequentially(outAnim, inAnim); 258 } else if (outAnim != null) { 259 anim = outAnim; 260 } else { 261 // Must be an in-only animation 262 anim = inAnim; 263 } 264 } 265 TransitionListener transitionListener = new TransitionListenerAdapter() { 266 int mPausedColor = 0; 267 268 @Override 269 public void onTransitionPause(Transition transition) { 270 if (mChangeBehavior != CHANGE_BEHAVIOR_IN) { 271 view.setText(endText); 272 if (view instanceof EditText) { 273 setSelection(((EditText) view), endSelectionStart, endSelectionEnd); 274 } 275 } 276 if (mChangeBehavior > CHANGE_BEHAVIOR_KEEP) { 277 mPausedColor = view.getCurrentTextColor(); 278 view.setTextColor(endColor); 279 } 280 } 281 282 @Override 283 public void onTransitionResume(Transition transition) { 284 if (mChangeBehavior != CHANGE_BEHAVIOR_IN) { 285 view.setText(startText); 286 if (view instanceof EditText) { 287 setSelection(((EditText) view), startSelectionStart, startSelectionEnd); 288 } 289 } 290 if (mChangeBehavior > CHANGE_BEHAVIOR_KEEP) { 291 view.setTextColor(mPausedColor); 292 } 293 } 294 295 @Override 296 public void onTransitionEnd(Transition transition) { 297 transition.removeListener(this); 298 } 299 }; 300 addListener(transitionListener); 301 if (DBG) { 302 Log.d(LOG_TAG, "createAnimator returning " + anim); 303 } 304 return anim; 305 } 306 return null; 307 } 308 setSelection(EditText editText, int start, int end)309 private void setSelection(EditText editText, int start, int end) { 310 if (start >= 0 && end >= 0) { 311 editText.setSelection(start, end); 312 } 313 } 314 } 315