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(255, 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 & 0xff0000 | 219 startColor & 0xff00 | startColor & 0xff); 220 } 221 }); 222 outAnim.addListener(new AnimatorListenerAdapter() { 223 @Override 224 public void onAnimationEnd(Animator animation) { 225 if (startText.equals(view.getText())) { 226 // Only set if it hasn't been changed since anim started 227 view.setText(endText); 228 if (view instanceof EditText) { 229 setSelection(((EditText) view), endSelectionStart, 230 endSelectionEnd); 231 } 232 } 233 // restore opaque alpha and correct end color 234 view.setTextColor(endColor); 235 } 236 }); 237 } 238 if (mChangeBehavior == CHANGE_BEHAVIOR_OUT_IN || 239 mChangeBehavior == CHANGE_BEHAVIOR_IN) { 240 inAnim = ValueAnimator.ofInt(0, 255); 241 inAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 242 @Override 243 public void onAnimationUpdate(ValueAnimator animation) { 244 int currAlpha = (Integer) animation.getAnimatedValue(); 245 view.setTextColor(currAlpha << 24 | Color.red(endColor) << 16 | 246 Color.green(endColor) << 8 | Color.red(endColor)); 247 } 248 }); 249 inAnim.addListener(new AnimatorListenerAdapter() { 250 @Override 251 public void onAnimationCancel(Animator animation) { 252 // restore opaque alpha and correct end color 253 view.setTextColor(endColor); 254 } 255 }); 256 } 257 if (outAnim != null && inAnim != null) { 258 anim = new AnimatorSet(); 259 ((AnimatorSet) anim).playSequentially(outAnim, inAnim); 260 } else if (outAnim != null) { 261 anim = outAnim; 262 } else { 263 // Must be an in-only animation 264 anim = inAnim; 265 } 266 } 267 TransitionListener transitionListener = new TransitionListenerAdapter() { 268 int mPausedColor = 0; 269 270 @Override 271 public void onTransitionPause(Transition transition) { 272 if (mChangeBehavior != CHANGE_BEHAVIOR_IN) { 273 view.setText(endText); 274 if (view instanceof EditText) { 275 setSelection(((EditText) view), endSelectionStart, endSelectionEnd); 276 } 277 } 278 if (mChangeBehavior > CHANGE_BEHAVIOR_KEEP) { 279 mPausedColor = view.getCurrentTextColor(); 280 view.setTextColor(endColor); 281 } 282 } 283 284 @Override 285 public void onTransitionResume(Transition transition) { 286 if (mChangeBehavior != CHANGE_BEHAVIOR_IN) { 287 view.setText(startText); 288 if (view instanceof EditText) { 289 setSelection(((EditText) view), startSelectionStart, startSelectionEnd); 290 } 291 } 292 if (mChangeBehavior > CHANGE_BEHAVIOR_KEEP) { 293 view.setTextColor(mPausedColor); 294 } 295 } 296 }; 297 addListener(transitionListener); 298 if (DBG) { 299 Log.d(LOG_TAG, "createAnimator returning " + anim); 300 } 301 return anim; 302 } 303 return null; 304 } 305 setSelection(EditText editText, int start, int end)306 private void setSelection(EditText editText, int start, int end) { 307 if (start >= 0 && end >= 0) { 308 editText.setSelection(start, end); 309 } 310 } 311 } 312