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.Animator.AnimatorListener; 21 import android.animation.Animator.AnimatorPauseListener; 22 import android.annotation.IntDef; 23 import android.annotation.NonNull; 24 import android.annotation.Nullable; 25 import android.content.Context; 26 import android.content.res.TypedArray; 27 import android.util.AttributeSet; 28 import android.view.View; 29 import android.view.ViewGroup; 30 import android.view.ViewGroupOverlay; 31 32 import com.android.internal.R; 33 34 import java.lang.annotation.Retention; 35 import java.lang.annotation.RetentionPolicy; 36 37 /** 38 * This transition tracks changes to the visibility of target views in the 39 * start and end scenes. Visibility is determined not just by the 40 * {@link View#setVisibility(int)} state of views, but also whether 41 * views exist in the current view hierarchy. The class is intended to be a 42 * utility for subclasses such as {@link Fade}, which use this visibility 43 * information to determine the specific animations to run when visibility 44 * changes occur. Subclasses should implement one or both of the methods 45 * {@link #onAppear(ViewGroup, TransitionValues, int, TransitionValues, int)}, 46 * {@link #onDisappear(ViewGroup, TransitionValues, int, TransitionValues, int)} or 47 * {@link #onAppear(ViewGroup, View, TransitionValues, TransitionValues)}, 48 * {@link #onDisappear(ViewGroup, View, TransitionValues, TransitionValues)}. 49 */ 50 public abstract class Visibility extends Transition { 51 52 static final String PROPNAME_VISIBILITY = "android:visibility:visibility"; 53 private static final String PROPNAME_PARENT = "android:visibility:parent"; 54 private static final String PROPNAME_SCREEN_LOCATION = "android:visibility:screenLocation"; 55 56 /** @hide */ 57 @Retention(RetentionPolicy.SOURCE) 58 @IntDef(flag = true, value = { 59 MODE_IN, 60 MODE_OUT, 61 Fade.IN, 62 Fade.OUT 63 }) 64 @interface VisibilityMode {} 65 66 /** 67 * Mode used in {@link #setMode(int)} to make the transition 68 * operate on targets that are appearing. Maybe be combined with 69 * {@link #MODE_OUT} to target Visibility changes both in and out. 70 */ 71 public static final int MODE_IN = 0x1; 72 73 /** 74 * Mode used in {@link #setMode(int)} to make the transition 75 * operate on targets that are disappearing. Maybe be combined with 76 * {@link #MODE_IN} to target Visibility changes both in and out. 77 */ 78 public static final int MODE_OUT = 0x2; 79 80 private static final String[] sTransitionProperties = { 81 PROPNAME_VISIBILITY, 82 PROPNAME_PARENT, 83 }; 84 85 private static class VisibilityInfo { 86 boolean visibilityChange; 87 boolean fadeIn; 88 int startVisibility; 89 int endVisibility; 90 ViewGroup startParent; 91 ViewGroup endParent; 92 } 93 94 private int mMode = MODE_IN | MODE_OUT; 95 private boolean mSuppressLayout = true; 96 Visibility()97 public Visibility() {} 98 Visibility(Context context, AttributeSet attrs)99 public Visibility(Context context, AttributeSet attrs) { 100 super(context, attrs); 101 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.VisibilityTransition); 102 int mode = a.getInt(R.styleable.VisibilityTransition_transitionVisibilityMode, 0); 103 a.recycle(); 104 if (mode != 0) { 105 setMode(mode); 106 } 107 } 108 109 /** 110 * This tells the Visibility transition to suppress layout during the transition and release 111 * the suppression after the transition. 112 * @hide 113 */ setSuppressLayout(boolean suppress)114 public void setSuppressLayout(boolean suppress) { 115 this.mSuppressLayout = suppress; 116 } 117 118 /** 119 * Changes the transition to support appearing and/or disappearing Views, depending 120 * on <code>mode</code>. 121 * 122 * @param mode The behavior supported by this transition, a combination of 123 * {@link #MODE_IN} and {@link #MODE_OUT}. 124 * @attr ref android.R.styleable#VisibilityTransition_transitionVisibilityMode 125 */ setMode(@isibilityMode int mode)126 public void setMode(@VisibilityMode int mode) { 127 if ((mode & ~(MODE_IN | MODE_OUT)) != 0) { 128 throw new IllegalArgumentException("Only MODE_IN and MODE_OUT flags are allowed"); 129 } 130 mMode = mode; 131 } 132 133 /** 134 * Returns whether appearing and/or disappearing Views are supported. 135 * 136 * Returns whether appearing and/or disappearing Views are supported. A combination of 137 * {@link #MODE_IN} and {@link #MODE_OUT}. 138 * @attr ref android.R.styleable#VisibilityTransition_transitionVisibilityMode 139 */ 140 @VisibilityMode getMode()141 public int getMode() { 142 return mMode; 143 } 144 145 @Override getTransitionProperties()146 public String[] getTransitionProperties() { 147 return sTransitionProperties; 148 } 149 captureValues(TransitionValues transitionValues)150 private void captureValues(TransitionValues transitionValues) { 151 int visibility = transitionValues.view.getVisibility(); 152 transitionValues.values.put(PROPNAME_VISIBILITY, visibility); 153 transitionValues.values.put(PROPNAME_PARENT, transitionValues.view.getParent()); 154 int[] loc = new int[2]; 155 transitionValues.view.getLocationOnScreen(loc); 156 transitionValues.values.put(PROPNAME_SCREEN_LOCATION, loc); 157 } 158 159 @Override captureStartValues(TransitionValues transitionValues)160 public void captureStartValues(TransitionValues transitionValues) { 161 captureValues(transitionValues); 162 } 163 164 @Override captureEndValues(TransitionValues transitionValues)165 public void captureEndValues(TransitionValues transitionValues) { 166 captureValues(transitionValues); 167 } 168 169 /** 170 * Returns whether the view is 'visible' according to the given values 171 * object. This is determined by testing the same properties in the values 172 * object that are used to determine whether the object is appearing or 173 * disappearing in the {@link 174 * Transition#createAnimator(ViewGroup, TransitionValues, TransitionValues)} 175 * method. This method can be called by, for example, subclasses that want 176 * to know whether the object is visible in the same way that Visibility 177 * determines it for the actual animation. 178 * 179 * @param values The TransitionValues object that holds the information by 180 * which visibility is determined. 181 * @return True if the view reference by <code>values</code> is visible, 182 * false otherwise. 183 */ isVisible(TransitionValues values)184 public boolean isVisible(TransitionValues values) { 185 if (values == null) { 186 return false; 187 } 188 int visibility = (Integer) values.values.get(PROPNAME_VISIBILITY); 189 View parent = (View) values.values.get(PROPNAME_PARENT); 190 191 return visibility == View.VISIBLE && parent != null; 192 } 193 getVisibilityChangeInfo(TransitionValues startValues, TransitionValues endValues)194 private static VisibilityInfo getVisibilityChangeInfo(TransitionValues startValues, 195 TransitionValues endValues) { 196 final VisibilityInfo visInfo = new VisibilityInfo(); 197 visInfo.visibilityChange = false; 198 visInfo.fadeIn = false; 199 if (startValues != null && startValues.values.containsKey(PROPNAME_VISIBILITY)) { 200 visInfo.startVisibility = (Integer) startValues.values.get(PROPNAME_VISIBILITY); 201 visInfo.startParent = (ViewGroup) startValues.values.get(PROPNAME_PARENT); 202 } else { 203 visInfo.startVisibility = -1; 204 visInfo.startParent = null; 205 } 206 if (endValues != null && endValues.values.containsKey(PROPNAME_VISIBILITY)) { 207 visInfo.endVisibility = (Integer) endValues.values.get(PROPNAME_VISIBILITY); 208 visInfo.endParent = (ViewGroup) endValues.values.get(PROPNAME_PARENT); 209 } else { 210 visInfo.endVisibility = -1; 211 visInfo.endParent = null; 212 } 213 if (startValues != null && endValues != null) { 214 if (visInfo.startVisibility == visInfo.endVisibility && 215 visInfo.startParent == visInfo.endParent) { 216 return visInfo; 217 } else { 218 if (visInfo.startVisibility != visInfo.endVisibility) { 219 if (visInfo.startVisibility == View.VISIBLE) { 220 visInfo.fadeIn = false; 221 visInfo.visibilityChange = true; 222 } else if (visInfo.endVisibility == View.VISIBLE) { 223 visInfo.fadeIn = true; 224 visInfo.visibilityChange = true; 225 } 226 // no visibilityChange if going between INVISIBLE and GONE 227 } else if (visInfo.startParent != visInfo.endParent) { 228 if (visInfo.endParent == null) { 229 visInfo.fadeIn = false; 230 visInfo.visibilityChange = true; 231 } else if (visInfo.startParent == null) { 232 visInfo.fadeIn = true; 233 visInfo.visibilityChange = true; 234 } 235 } 236 } 237 } else if (startValues == null && visInfo.endVisibility == View.VISIBLE) { 238 visInfo.fadeIn = true; 239 visInfo.visibilityChange = true; 240 } else if (endValues == null && visInfo.startVisibility == View.VISIBLE) { 241 visInfo.fadeIn = false; 242 visInfo.visibilityChange = true; 243 } 244 return visInfo; 245 } 246 247 @Nullable 248 @Override createAnimator(@onNull ViewGroup sceneRoot, @Nullable TransitionValues startValues, @Nullable TransitionValues endValues)249 public Animator createAnimator(@NonNull ViewGroup sceneRoot, 250 @Nullable TransitionValues startValues, 251 @Nullable TransitionValues endValues) { 252 VisibilityInfo visInfo = getVisibilityChangeInfo(startValues, endValues); 253 if (visInfo.visibilityChange 254 && (visInfo.startParent != null || visInfo.endParent != null)) { 255 if (visInfo.fadeIn) { 256 return onAppear(sceneRoot, startValues, visInfo.startVisibility, 257 endValues, visInfo.endVisibility); 258 } else { 259 return onDisappear(sceneRoot, startValues, visInfo.startVisibility, 260 endValues, visInfo.endVisibility 261 ); 262 } 263 } 264 return null; 265 } 266 267 /** 268 * The default implementation of this method calls 269 * {@link #onAppear(ViewGroup, View, TransitionValues, TransitionValues)}. 270 * Subclasses should override this method or 271 * {@link #onAppear(ViewGroup, View, TransitionValues, TransitionValues)}. 272 * if they need to create an Animator when targets appear. 273 * The method should only be called by the Visibility class; it is 274 * not intended to be called from external classes. 275 * 276 * @param sceneRoot The root of the transition hierarchy 277 * @param startValues The target values in the start scene 278 * @param startVisibility The target visibility in the start scene 279 * @param endValues The target values in the end scene 280 * @param endVisibility The target visibility in the end scene 281 * @return An Animator to be started at the appropriate time in the 282 * overall transition for this scene change. A null value means no animation 283 * should be run. 284 */ onAppear(ViewGroup sceneRoot, TransitionValues startValues, int startVisibility, TransitionValues endValues, int endVisibility)285 public Animator onAppear(ViewGroup sceneRoot, 286 TransitionValues startValues, int startVisibility, 287 TransitionValues endValues, int endVisibility) { 288 if ((mMode & MODE_IN) != MODE_IN || endValues == null) { 289 return null; 290 } 291 if (startValues == null) { 292 VisibilityInfo parentVisibilityInfo = null; 293 View endParent = (View) endValues.view.getParent(); 294 TransitionValues startParentValues = getMatchedTransitionValues(endParent, 295 false); 296 TransitionValues endParentValues = getTransitionValues(endParent, false); 297 parentVisibilityInfo = 298 getVisibilityChangeInfo(startParentValues, endParentValues); 299 if (parentVisibilityInfo.visibilityChange) { 300 return null; 301 } 302 } 303 return onAppear(sceneRoot, endValues.view, startValues, endValues); 304 } 305 306 /** 307 * The default implementation of this method returns a null Animator. Subclasses should 308 * override this method to make targets appear with the desired transition. The 309 * method should only be called from 310 * {@link #onAppear(ViewGroup, TransitionValues, int, TransitionValues, int)}. 311 * 312 * @param sceneRoot The root of the transition hierarchy 313 * @param view The View to make appear. This will be in the target scene's View hierarchy and 314 * will be VISIBLE. 315 * @param startValues The target values in the start scene 316 * @param endValues The target values in the end scene 317 * @return An Animator to be started at the appropriate time in the 318 * overall transition for this scene change. A null value means no animation 319 * should be run. 320 */ onAppear(ViewGroup sceneRoot, View view, TransitionValues startValues, TransitionValues endValues)321 public Animator onAppear(ViewGroup sceneRoot, View view, TransitionValues startValues, 322 TransitionValues endValues) { 323 return null; 324 } 325 326 /** 327 * Subclasses should override this method or 328 * {@link #onDisappear(ViewGroup, View, TransitionValues, TransitionValues)} 329 * if they need to create an Animator when targets disappear. 330 * The method should only be called by the Visibility class; it is 331 * not intended to be called from external classes. 332 * <p> 333 * The default implementation of this method attempts to find a View to use to call 334 * {@link #onDisappear(ViewGroup, View, TransitionValues, TransitionValues)}, 335 * based on the situation of the View in the View hierarchy. For example, 336 * if a View was simply removed from its parent, then the View will be added 337 * into a {@link android.view.ViewGroupOverlay} and passed as the <code>view</code> 338 * parameter in {@link #onDisappear(ViewGroup, View, TransitionValues, TransitionValues)}. 339 * If a visible View is changed to be {@link View#GONE} or {@link View#INVISIBLE}, 340 * then it can be used as the <code>view</code> and the visibility will be changed 341 * to {@link View#VISIBLE} for the duration of the animation. However, if a View 342 * is in a hierarchy which is also altering its visibility, the situation can be 343 * more complicated. In general, if a view that is no longer in the hierarchy in 344 * the end scene still has a parent (so its parent hierarchy was removed, but it 345 * was not removed from its parent), then it will be left alone to avoid side-effects from 346 * improperly removing it from its parent. The only exception to this is if 347 * the previous {@link Scene} was {@link Scene#getSceneForLayout(ViewGroup, int, 348 * android.content.Context) created from a layout resource file}, then it is considered 349 * safe to un-parent the starting scene view in order to make it disappear.</p> 350 * 351 * @param sceneRoot The root of the transition hierarchy 352 * @param startValues The target values in the start scene 353 * @param startVisibility The target visibility in the start scene 354 * @param endValues The target values in the end scene 355 * @param endVisibility The target visibility in the end scene 356 * @return An Animator to be started at the appropriate time in the 357 * overall transition for this scene change. A null value means no animation 358 * should be run. 359 */ onDisappear(ViewGroup sceneRoot, TransitionValues startValues, int startVisibility, TransitionValues endValues, int endVisibility)360 public Animator onDisappear(ViewGroup sceneRoot, 361 TransitionValues startValues, int startVisibility, 362 TransitionValues endValues, int endVisibility) { 363 if ((mMode & MODE_OUT) != MODE_OUT) { 364 return null; 365 } 366 367 if (startValues == null) { 368 // startValues(and startView) will never be null for disappear transition. 369 return null; 370 } 371 372 final View startView = startValues.view; 373 final View endView = (endValues != null) ? endValues.view : null; 374 View overlayView = null; 375 View viewToKeep = null; 376 boolean reusingOverlayView = false; 377 378 View savedOverlayView = (View) startView.getTag(R.id.transition_overlay_view_tag); 379 if (savedOverlayView != null) { 380 // we've already created overlay for the start view. 381 // it means that we are applying two visibility 382 // transitions for the same view 383 overlayView = savedOverlayView; 384 reusingOverlayView = true; 385 } else { 386 boolean needOverlayForStartView = false; 387 388 if (endView == null || endView.getParent() == null) { 389 if (endView != null) { 390 // endView was removed from its parent - add it to the overlay 391 overlayView = endView; 392 } else { 393 needOverlayForStartView = true; 394 } 395 } else { 396 // visibility change 397 if (endVisibility == View.INVISIBLE) { 398 viewToKeep = endView; 399 } else { 400 // Becoming GONE 401 if (startView == endView) { 402 viewToKeep = endView; 403 } else { 404 needOverlayForStartView = true; 405 } 406 } 407 } 408 409 if (needOverlayForStartView) { 410 // endView does not exist. Use startView only under certain 411 // conditions, because placing a view in an overlay necessitates 412 // it being removed from its current parent 413 if (startView.getParent() == null) { 414 // no parent - safe to use 415 overlayView = startView; 416 } else if (startView.getParent() instanceof View) { 417 View startParent = (View) startView.getParent(); 418 TransitionValues startParentValues = getTransitionValues(startParent, true); 419 TransitionValues endParentValues = getMatchedTransitionValues(startParent, 420 true); 421 VisibilityInfo parentVisibilityInfo = 422 getVisibilityChangeInfo(startParentValues, endParentValues); 423 if (!parentVisibilityInfo.visibilityChange) { 424 overlayView = TransitionUtils.copyViewImage(sceneRoot, startView, 425 startParent); 426 } else { 427 int id = startParent.getId(); 428 if (startParent.getParent() == null && id != View.NO_ID 429 && sceneRoot.findViewById(id) != null && mCanRemoveViews) { 430 // no parent, but its parent is unparented but the parent 431 // hierarchy has been replaced by a new hierarchy with the same id 432 // and it is safe to un-parent startView 433 overlayView = startView; 434 } else { 435 // TODO: Handle this case as well 436 } 437 } 438 } 439 } 440 } 441 442 if (overlayView != null) { 443 // TODO: Need to do this for general case of adding to overlay 444 final ViewGroupOverlay overlay; 445 if (!reusingOverlayView) { 446 overlay = sceneRoot.getOverlay(); 447 int[] screenLoc = (int[]) startValues.values.get(PROPNAME_SCREEN_LOCATION); 448 int screenX = screenLoc[0]; 449 int screenY = screenLoc[1]; 450 int[] loc = new int[2]; 451 sceneRoot.getLocationOnScreen(loc); 452 overlayView.offsetLeftAndRight((screenX - loc[0]) - overlayView.getLeft()); 453 overlayView.offsetTopAndBottom((screenY - loc[1]) - overlayView.getTop()); 454 overlay.add(overlayView); 455 } else { 456 overlay = null; 457 } 458 Animator animator = onDisappear(sceneRoot, overlayView, startValues, endValues); 459 if (!reusingOverlayView) { 460 if (animator == null) { 461 overlay.remove(overlayView); 462 } else { 463 startView.setTagInternal(R.id.transition_overlay_view_tag, overlayView); 464 final View finalOverlayView = overlayView; 465 addListener(new TransitionListenerAdapter() { 466 467 @Override 468 public void onTransitionPause(Transition transition) { 469 overlay.remove(finalOverlayView); 470 } 471 472 @Override 473 public void onTransitionResume(Transition transition) { 474 if (finalOverlayView.getParent() == null) { 475 overlay.add(finalOverlayView); 476 } else { 477 cancel(); 478 } 479 } 480 481 @Override 482 public void onTransitionEnd(Transition transition) { 483 startView.setTagInternal(R.id.transition_overlay_view_tag, null); 484 overlay.remove(finalOverlayView); 485 transition.removeListener(this); 486 } 487 }); 488 } 489 } 490 return animator; 491 } 492 493 if (viewToKeep != null) { 494 int originalVisibility = viewToKeep.getVisibility(); 495 viewToKeep.setTransitionVisibility(View.VISIBLE); 496 Animator animator = onDisappear(sceneRoot, viewToKeep, startValues, endValues); 497 if (animator != null) { 498 DisappearListener disappearListener = new DisappearListener(viewToKeep, 499 endVisibility, mSuppressLayout); 500 animator.addListener(disappearListener); 501 animator.addPauseListener(disappearListener); 502 addListener(disappearListener); 503 } else { 504 viewToKeep.setTransitionVisibility(originalVisibility); 505 } 506 return animator; 507 } 508 return null; 509 } 510 511 @Override isTransitionRequired(TransitionValues startValues, TransitionValues newValues)512 public boolean isTransitionRequired(TransitionValues startValues, TransitionValues newValues) { 513 if (startValues == null && newValues == null) { 514 return false; 515 } 516 if (startValues != null && newValues != null && 517 newValues.values.containsKey(PROPNAME_VISIBILITY) != 518 startValues.values.containsKey(PROPNAME_VISIBILITY)) { 519 // The transition wasn't targeted in either the start or end, so it couldn't 520 // have changed. 521 return false; 522 } 523 VisibilityInfo changeInfo = getVisibilityChangeInfo(startValues, newValues); 524 return changeInfo.visibilityChange && (changeInfo.startVisibility == View.VISIBLE || 525 changeInfo.endVisibility == View.VISIBLE); 526 } 527 528 /** 529 * The default implementation of this method returns a null Animator. Subclasses should 530 * override this method to make targets disappear with the desired transition. The 531 * method should only be called from 532 * {@link #onDisappear(ViewGroup, TransitionValues, int, TransitionValues, int)}. 533 * 534 * @param sceneRoot The root of the transition hierarchy 535 * @param view The View to make disappear. This will be in the target scene's View 536 * hierarchy or in an {@link android.view.ViewGroupOverlay} and will be 537 * VISIBLE. 538 * @param startValues The target values in the start scene 539 * @param endValues The target values in the end scene 540 * @return An Animator to be started at the appropriate time in the 541 * overall transition for this scene change. A null value means no animation 542 * should be run. 543 */ onDisappear(ViewGroup sceneRoot, View view, TransitionValues startValues, TransitionValues endValues)544 public Animator onDisappear(ViewGroup sceneRoot, View view, TransitionValues startValues, 545 TransitionValues endValues) { 546 return null; 547 } 548 549 private static class DisappearListener 550 extends TransitionListenerAdapter implements AnimatorListener, AnimatorPauseListener { 551 private final View mView; 552 private final int mFinalVisibility; 553 private final ViewGroup mParent; 554 private final boolean mSuppressLayout; 555 556 private boolean mLayoutSuppressed; 557 boolean mCanceled = false; 558 DisappearListener(View view, int finalVisibility, boolean suppressLayout)559 public DisappearListener(View view, int finalVisibility, boolean suppressLayout) { 560 this.mView = view; 561 this.mFinalVisibility = finalVisibility; 562 this.mParent = (ViewGroup) view.getParent(); 563 this.mSuppressLayout = suppressLayout; 564 // Prevent a layout from including mView in its calculation. 565 suppressLayout(true); 566 } 567 568 @Override onAnimationPause(Animator animation)569 public void onAnimationPause(Animator animation) { 570 if (!mCanceled) { 571 mView.setTransitionVisibility(mFinalVisibility); 572 } 573 } 574 575 @Override onAnimationResume(Animator animation)576 public void onAnimationResume(Animator animation) { 577 if (!mCanceled) { 578 mView.setTransitionVisibility(View.VISIBLE); 579 } 580 } 581 582 @Override onAnimationCancel(Animator animation)583 public void onAnimationCancel(Animator animation) { 584 mCanceled = true; 585 } 586 587 @Override onAnimationRepeat(Animator animation)588 public void onAnimationRepeat(Animator animation) { 589 } 590 591 @Override onAnimationStart(Animator animation)592 public void onAnimationStart(Animator animation) { 593 } 594 595 @Override onAnimationEnd(Animator animation)596 public void onAnimationEnd(Animator animation) { 597 hideViewWhenNotCanceled(); 598 } 599 600 @Override onTransitionEnd(Transition transition)601 public void onTransitionEnd(Transition transition) { 602 hideViewWhenNotCanceled(); 603 transition.removeListener(this); 604 } 605 606 @Override onTransitionPause(Transition transition)607 public void onTransitionPause(Transition transition) { 608 suppressLayout(false); 609 } 610 611 @Override onTransitionResume(Transition transition)612 public void onTransitionResume(Transition transition) { 613 suppressLayout(true); 614 } 615 hideViewWhenNotCanceled()616 private void hideViewWhenNotCanceled() { 617 if (!mCanceled) { 618 // Recreate the parent's display list in case it includes mView. 619 mView.setTransitionVisibility(mFinalVisibility); 620 if (mParent != null) { 621 mParent.invalidate(); 622 } 623 } 624 // Layout is allowed now that the View is in its final state 625 suppressLayout(false); 626 } 627 suppressLayout(boolean suppress)628 private void suppressLayout(boolean suppress) { 629 if (mSuppressLayout && mLayoutSuppressed != suppress && mParent != null) { 630 mLayoutSuppressed = suppress; 631 mParent.suppressLayout(suppress); 632 } 633 } 634 } 635 } 636