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