1 /* 2 * Copyright (C) 2010 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.app; 18 19 import com.android.internal.util.FastPrintWriter; 20 21 import android.graphics.Rect; 22 import android.os.Parcel; 23 import android.os.Parcelable; 24 import android.text.TextUtils; 25 import android.transition.Transition; 26 import android.transition.TransitionManager; 27 import android.transition.TransitionSet; 28 import android.transition.TransitionUtils; 29 import android.util.ArrayMap; 30 import android.util.Log; 31 import android.util.LogWriter; 32 import android.util.Pair; 33 import android.util.SparseArray; 34 import android.view.View; 35 import android.view.ViewGroup; 36 import android.view.ViewTreeObserver; 37 38 import java.io.FileDescriptor; 39 import java.io.PrintWriter; 40 import java.util.ArrayList; 41 import java.util.List; 42 43 final class BackStackState implements Parcelable { 44 final int[] mOps; 45 final int mTransition; 46 final int mTransitionStyle; 47 final String mName; 48 final int mIndex; 49 final int mBreadCrumbTitleRes; 50 final CharSequence mBreadCrumbTitleText; 51 final int mBreadCrumbShortTitleRes; 52 final CharSequence mBreadCrumbShortTitleText; 53 final ArrayList<String> mSharedElementSourceNames; 54 final ArrayList<String> mSharedElementTargetNames; 55 56 public BackStackState(FragmentManagerImpl fm, BackStackRecord bse) { 57 int numRemoved = 0; 58 BackStackRecord.Op op = bse.mHead; 59 while (op != null) { 60 if (op.removed != null) { 61 numRemoved += op.removed.size(); 62 } 63 op = op.next; 64 } 65 mOps = new int[bse.mNumOp * 7 + numRemoved]; 66 67 if (!bse.mAddToBackStack) { 68 throw new IllegalStateException("Not on back stack"); 69 } 70 71 op = bse.mHead; 72 int pos = 0; 73 while (op != null) { 74 mOps[pos++] = op.cmd; 75 mOps[pos++] = op.fragment != null ? op.fragment.mIndex : -1; 76 mOps[pos++] = op.enterAnim; 77 mOps[pos++] = op.exitAnim; 78 mOps[pos++] = op.popEnterAnim; 79 mOps[pos++] = op.popExitAnim; 80 if (op.removed != null) { 81 final int N = op.removed.size(); 82 mOps[pos++] = N; 83 for (int i = 0; i < N; i++) { 84 mOps[pos++] = op.removed.get(i).mIndex; 85 } 86 } else { 87 mOps[pos++] = 0; 88 } 89 op = op.next; 90 } 91 mTransition = bse.mTransition; 92 mTransitionStyle = bse.mTransitionStyle; 93 mName = bse.mName; 94 mIndex = bse.mIndex; 95 mBreadCrumbTitleRes = bse.mBreadCrumbTitleRes; 96 mBreadCrumbTitleText = bse.mBreadCrumbTitleText; 97 mBreadCrumbShortTitleRes = bse.mBreadCrumbShortTitleRes; 98 mBreadCrumbShortTitleText = bse.mBreadCrumbShortTitleText; 99 mSharedElementSourceNames = bse.mSharedElementSourceNames; 100 mSharedElementTargetNames = bse.mSharedElementTargetNames; 101 } 102 103 public BackStackState(Parcel in) { 104 mOps = in.createIntArray(); 105 mTransition = in.readInt(); 106 mTransitionStyle = in.readInt(); 107 mName = in.readString(); 108 mIndex = in.readInt(); 109 mBreadCrumbTitleRes = in.readInt(); 110 mBreadCrumbTitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 111 mBreadCrumbShortTitleRes = in.readInt(); 112 mBreadCrumbShortTitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 113 mSharedElementSourceNames = in.createStringArrayList(); 114 mSharedElementTargetNames = in.createStringArrayList(); 115 } 116 117 public BackStackRecord instantiate(FragmentManagerImpl fm) { 118 BackStackRecord bse = new BackStackRecord(fm); 119 int pos = 0; 120 int num = 0; 121 while (pos < mOps.length) { 122 BackStackRecord.Op op = new BackStackRecord.Op(); 123 op.cmd = mOps[pos++]; 124 if (FragmentManagerImpl.DEBUG) { 125 Log.v(FragmentManagerImpl.TAG, 126 "Instantiate " + bse + " op #" + num + " base fragment #" + mOps[pos]); 127 } 128 int findex = mOps[pos++]; 129 if (findex >= 0) { 130 Fragment f = fm.mActive.get(findex); 131 op.fragment = f; 132 } else { 133 op.fragment = null; 134 } 135 op.enterAnim = mOps[pos++]; 136 op.exitAnim = mOps[pos++]; 137 op.popEnterAnim = mOps[pos++]; 138 op.popExitAnim = mOps[pos++]; 139 final int N = mOps[pos++]; 140 if (N > 0) { 141 op.removed = new ArrayList<Fragment>(N); 142 for (int i = 0; i < N; i++) { 143 if (FragmentManagerImpl.DEBUG) { 144 Log.v(FragmentManagerImpl.TAG, 145 "Instantiate " + bse + " set remove fragment #" + mOps[pos]); 146 } 147 Fragment r = fm.mActive.get(mOps[pos++]); 148 op.removed.add(r); 149 } 150 } 151 bse.addOp(op); 152 num++; 153 } 154 bse.mTransition = mTransition; 155 bse.mTransitionStyle = mTransitionStyle; 156 bse.mName = mName; 157 bse.mIndex = mIndex; 158 bse.mAddToBackStack = true; 159 bse.mBreadCrumbTitleRes = mBreadCrumbTitleRes; 160 bse.mBreadCrumbTitleText = mBreadCrumbTitleText; 161 bse.mBreadCrumbShortTitleRes = mBreadCrumbShortTitleRes; 162 bse.mBreadCrumbShortTitleText = mBreadCrumbShortTitleText; 163 bse.mSharedElementSourceNames = mSharedElementSourceNames; 164 bse.mSharedElementTargetNames = mSharedElementTargetNames; 165 bse.bumpBackStackNesting(1); 166 return bse; 167 } 168 169 public int describeContents() { 170 return 0; 171 } 172 173 public void writeToParcel(Parcel dest, int flags) { 174 dest.writeIntArray(mOps); 175 dest.writeInt(mTransition); 176 dest.writeInt(mTransitionStyle); 177 dest.writeString(mName); 178 dest.writeInt(mIndex); 179 dest.writeInt(mBreadCrumbTitleRes); 180 TextUtils.writeToParcel(mBreadCrumbTitleText, dest, 0); 181 dest.writeInt(mBreadCrumbShortTitleRes); 182 TextUtils.writeToParcel(mBreadCrumbShortTitleText, dest, 0); 183 dest.writeStringList(mSharedElementSourceNames); 184 dest.writeStringList(mSharedElementTargetNames); 185 } 186 187 public static final Parcelable.Creator<BackStackState> CREATOR 188 = new Parcelable.Creator<BackStackState>() { 189 public BackStackState createFromParcel(Parcel in) { 190 return new BackStackState(in); 191 } 192 193 public BackStackState[] newArray(int size) { 194 return new BackStackState[size]; 195 } 196 }; 197 } 198 199 /** 200 * @hide Entry of an operation on the fragment back stack. 201 */ 202 final class BackStackRecord extends FragmentTransaction implements 203 FragmentManager.BackStackEntry, Runnable { 204 static final String TAG = FragmentManagerImpl.TAG; 205 206 final FragmentManagerImpl mManager; 207 208 static final int OP_NULL = 0; 209 static final int OP_ADD = 1; 210 static final int OP_REPLACE = 2; 211 static final int OP_REMOVE = 3; 212 static final int OP_HIDE = 4; 213 static final int OP_SHOW = 5; 214 static final int OP_DETACH = 6; 215 static final int OP_ATTACH = 7; 216 217 static final class Op { 218 Op next; 219 Op prev; 220 int cmd; 221 Fragment fragment; 222 int enterAnim; 223 int exitAnim; 224 int popEnterAnim; 225 int popExitAnim; 226 ArrayList<Fragment> removed; 227 } 228 229 Op mHead; 230 Op mTail; 231 int mNumOp; 232 int mEnterAnim; 233 int mExitAnim; 234 int mPopEnterAnim; 235 int mPopExitAnim; 236 int mTransition; 237 int mTransitionStyle; 238 boolean mAddToBackStack; 239 boolean mAllowAddToBackStack = true; 240 String mName; 241 boolean mCommitted; 242 int mIndex = -1; 243 244 int mBreadCrumbTitleRes; 245 CharSequence mBreadCrumbTitleText; 246 int mBreadCrumbShortTitleRes; 247 CharSequence mBreadCrumbShortTitleText; 248 249 ArrayList<String> mSharedElementSourceNames; 250 ArrayList<String> mSharedElementTargetNames; 251 252 @Override 253 public String toString() { 254 StringBuilder sb = new StringBuilder(128); 255 sb.append("BackStackEntry{"); 256 sb.append(Integer.toHexString(System.identityHashCode(this))); 257 if (mIndex >= 0) { 258 sb.append(" #"); 259 sb.append(mIndex); 260 } 261 if (mName != null) { 262 sb.append(" "); 263 sb.append(mName); 264 } 265 sb.append("}"); 266 return sb.toString(); 267 } 268 269 public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { 270 dump(prefix, writer, true); 271 } 272 273 void dump(String prefix, PrintWriter writer, boolean full) { 274 if (full) { 275 writer.print(prefix); 276 writer.print("mName="); 277 writer.print(mName); 278 writer.print(" mIndex="); 279 writer.print(mIndex); 280 writer.print(" mCommitted="); 281 writer.println(mCommitted); 282 if (mTransition != FragmentTransaction.TRANSIT_NONE) { 283 writer.print(prefix); 284 writer.print("mTransition=#"); 285 writer.print(Integer.toHexString(mTransition)); 286 writer.print(" mTransitionStyle=#"); 287 writer.println(Integer.toHexString(mTransitionStyle)); 288 } 289 if (mEnterAnim != 0 || mExitAnim != 0) { 290 writer.print(prefix); 291 writer.print("mEnterAnim=#"); 292 writer.print(Integer.toHexString(mEnterAnim)); 293 writer.print(" mExitAnim=#"); 294 writer.println(Integer.toHexString(mExitAnim)); 295 } 296 if (mPopEnterAnim != 0 || mPopExitAnim != 0) { 297 writer.print(prefix); 298 writer.print("mPopEnterAnim=#"); 299 writer.print(Integer.toHexString(mPopEnterAnim)); 300 writer.print(" mPopExitAnim=#"); 301 writer.println(Integer.toHexString(mPopExitAnim)); 302 } 303 if (mBreadCrumbTitleRes != 0 || mBreadCrumbTitleText != null) { 304 writer.print(prefix); 305 writer.print("mBreadCrumbTitleRes=#"); 306 writer.print(Integer.toHexString(mBreadCrumbTitleRes)); 307 writer.print(" mBreadCrumbTitleText="); 308 writer.println(mBreadCrumbTitleText); 309 } 310 if (mBreadCrumbShortTitleRes != 0 || mBreadCrumbShortTitleText != null) { 311 writer.print(prefix); 312 writer.print("mBreadCrumbShortTitleRes=#"); 313 writer.print(Integer.toHexString(mBreadCrumbShortTitleRes)); 314 writer.print(" mBreadCrumbShortTitleText="); 315 writer.println(mBreadCrumbShortTitleText); 316 } 317 } 318 319 if (mHead != null) { 320 writer.print(prefix); 321 writer.println("Operations:"); 322 String innerPrefix = prefix + " "; 323 Op op = mHead; 324 int num = 0; 325 while (op != null) { 326 String cmdStr; 327 switch (op.cmd) { 328 case OP_NULL: 329 cmdStr = "NULL"; 330 break; 331 case OP_ADD: 332 cmdStr = "ADD"; 333 break; 334 case OP_REPLACE: 335 cmdStr = "REPLACE"; 336 break; 337 case OP_REMOVE: 338 cmdStr = "REMOVE"; 339 break; 340 case OP_HIDE: 341 cmdStr = "HIDE"; 342 break; 343 case OP_SHOW: 344 cmdStr = "SHOW"; 345 break; 346 case OP_DETACH: 347 cmdStr = "DETACH"; 348 break; 349 case OP_ATTACH: 350 cmdStr = "ATTACH"; 351 break; 352 default: 353 cmdStr = "cmd=" + op.cmd; 354 break; 355 } 356 writer.print(prefix); 357 writer.print(" Op #"); 358 writer.print(num); 359 writer.print(": "); 360 writer.print(cmdStr); 361 writer.print(" "); 362 writer.println(op.fragment); 363 if (full) { 364 if (op.enterAnim != 0 || op.exitAnim != 0) { 365 writer.print(innerPrefix); 366 writer.print("enterAnim=#"); 367 writer.print(Integer.toHexString(op.enterAnim)); 368 writer.print(" exitAnim=#"); 369 writer.println(Integer.toHexString(op.exitAnim)); 370 } 371 if (op.popEnterAnim != 0 || op.popExitAnim != 0) { 372 writer.print(innerPrefix); 373 writer.print("popEnterAnim=#"); 374 writer.print(Integer.toHexString(op.popEnterAnim)); 375 writer.print(" popExitAnim=#"); 376 writer.println(Integer.toHexString(op.popExitAnim)); 377 } 378 } 379 if (op.removed != null && op.removed.size() > 0) { 380 for (int i = 0; i < op.removed.size(); i++) { 381 writer.print(innerPrefix); 382 if (op.removed.size() == 1) { 383 writer.print("Removed: "); 384 } else { 385 if (i == 0) { 386 writer.println("Removed:"); 387 } 388 writer.print(innerPrefix); 389 writer.print(" #"); 390 writer.print(i); 391 writer.print(": "); 392 } 393 writer.println(op.removed.get(i)); 394 } 395 } 396 op = op.next; 397 num++; 398 } 399 } 400 } 401 402 public BackStackRecord(FragmentManagerImpl manager) { 403 mManager = manager; 404 } 405 406 public int getId() { 407 return mIndex; 408 } 409 410 public int getBreadCrumbTitleRes() { 411 return mBreadCrumbTitleRes; 412 } 413 414 public int getBreadCrumbShortTitleRes() { 415 return mBreadCrumbShortTitleRes; 416 } 417 418 public CharSequence getBreadCrumbTitle() { 419 if (mBreadCrumbTitleRes != 0) { 420 return mManager.mActivity.getText(mBreadCrumbTitleRes); 421 } 422 return mBreadCrumbTitleText; 423 } 424 425 public CharSequence getBreadCrumbShortTitle() { 426 if (mBreadCrumbShortTitleRes != 0) { 427 return mManager.mActivity.getText(mBreadCrumbShortTitleRes); 428 } 429 return mBreadCrumbShortTitleText; 430 } 431 432 void addOp(Op op) { 433 if (mHead == null) { 434 mHead = mTail = op; 435 } else { 436 op.prev = mTail; 437 mTail.next = op; 438 mTail = op; 439 } 440 op.enterAnim = mEnterAnim; 441 op.exitAnim = mExitAnim; 442 op.popEnterAnim = mPopEnterAnim; 443 op.popExitAnim = mPopExitAnim; 444 mNumOp++; 445 } 446 447 public FragmentTransaction add(Fragment fragment, String tag) { 448 doAddOp(0, fragment, tag, OP_ADD); 449 return this; 450 } 451 452 public FragmentTransaction add(int containerViewId, Fragment fragment) { 453 doAddOp(containerViewId, fragment, null, OP_ADD); 454 return this; 455 } 456 457 public FragmentTransaction add(int containerViewId, Fragment fragment, String tag) { 458 doAddOp(containerViewId, fragment, tag, OP_ADD); 459 return this; 460 } 461 462 private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) { 463 fragment.mFragmentManager = mManager; 464 465 if (tag != null) { 466 if (fragment.mTag != null && !tag.equals(fragment.mTag)) { 467 throw new IllegalStateException("Can't change tag of fragment " 468 + fragment + ": was " + fragment.mTag 469 + " now " + tag); 470 } 471 fragment.mTag = tag; 472 } 473 474 if (containerViewId != 0) { 475 if (fragment.mFragmentId != 0 && fragment.mFragmentId != containerViewId) { 476 throw new IllegalStateException("Can't change container ID of fragment " 477 + fragment + ": was " + fragment.mFragmentId 478 + " now " + containerViewId); 479 } 480 fragment.mContainerId = fragment.mFragmentId = containerViewId; 481 } 482 483 Op op = new Op(); 484 op.cmd = opcmd; 485 op.fragment = fragment; 486 addOp(op); 487 } 488 489 public FragmentTransaction replace(int containerViewId, Fragment fragment) { 490 return replace(containerViewId, fragment, null); 491 } 492 493 public FragmentTransaction replace(int containerViewId, Fragment fragment, String tag) { 494 if (containerViewId == 0) { 495 throw new IllegalArgumentException("Must use non-zero containerViewId"); 496 } 497 498 doAddOp(containerViewId, fragment, tag, OP_REPLACE); 499 return this; 500 } 501 502 public FragmentTransaction remove(Fragment fragment) { 503 Op op = new Op(); 504 op.cmd = OP_REMOVE; 505 op.fragment = fragment; 506 addOp(op); 507 508 return this; 509 } 510 511 public FragmentTransaction hide(Fragment fragment) { 512 Op op = new Op(); 513 op.cmd = OP_HIDE; 514 op.fragment = fragment; 515 addOp(op); 516 517 return this; 518 } 519 520 public FragmentTransaction show(Fragment fragment) { 521 Op op = new Op(); 522 op.cmd = OP_SHOW; 523 op.fragment = fragment; 524 addOp(op); 525 526 return this; 527 } 528 529 public FragmentTransaction detach(Fragment fragment) { 530 Op op = new Op(); 531 op.cmd = OP_DETACH; 532 op.fragment = fragment; 533 addOp(op); 534 535 return this; 536 } 537 538 public FragmentTransaction attach(Fragment fragment) { 539 Op op = new Op(); 540 op.cmd = OP_ATTACH; 541 op.fragment = fragment; 542 addOp(op); 543 544 return this; 545 } 546 547 public FragmentTransaction setCustomAnimations(int enter, int exit) { 548 return setCustomAnimations(enter, exit, 0, 0); 549 } 550 551 public FragmentTransaction setCustomAnimations(int enter, int exit, 552 int popEnter, int popExit) { 553 mEnterAnim = enter; 554 mExitAnim = exit; 555 mPopEnterAnim = popEnter; 556 mPopExitAnim = popExit; 557 return this; 558 } 559 560 public FragmentTransaction setTransition(int transition) { 561 mTransition = transition; 562 return this; 563 } 564 565 @Override 566 public FragmentTransaction addSharedElement(View sharedElement, String name) { 567 String transitionName = sharedElement.getTransitionName(); 568 if (transitionName == null) { 569 throw new IllegalArgumentException("Unique transitionNames are required for all" + 570 " sharedElements"); 571 } 572 if (mSharedElementSourceNames == null) { 573 mSharedElementSourceNames = new ArrayList<String>(); 574 mSharedElementTargetNames = new ArrayList<String>(); 575 } 576 mSharedElementSourceNames.add(transitionName); 577 mSharedElementTargetNames.add(name); 578 return this; 579 } 580 581 /** TODO: remove this */ 582 @Override 583 public FragmentTransaction setSharedElement(View sharedElement, String name) { 584 String transitionName = sharedElement.getTransitionName(); 585 if (transitionName == null) { 586 throw new IllegalArgumentException("Unique transitionNames are required for all" + 587 " sharedElements"); 588 } 589 mSharedElementSourceNames = new ArrayList<String>(1); 590 mSharedElementSourceNames.add(transitionName); 591 592 mSharedElementTargetNames = new ArrayList<String>(1); 593 mSharedElementTargetNames.add(name); 594 return this; 595 } 596 597 /** TODO: remove this */ 598 @Override 599 public FragmentTransaction setSharedElements(Pair<View, String>... sharedElements) { 600 if (sharedElements == null || sharedElements.length == 0) { 601 mSharedElementSourceNames = null; 602 mSharedElementTargetNames = null; 603 } else { 604 ArrayList<String> sourceNames = new ArrayList<String>(sharedElements.length); 605 ArrayList<String> targetNames = new ArrayList<String>(sharedElements.length); 606 for (int i = 0; i < sharedElements.length; i++) { 607 String transitionName = sharedElements[i].first.getTransitionName(); 608 if (transitionName == null) { 609 throw new IllegalArgumentException("Unique transitionNames are required for all" 610 + " sharedElements"); 611 } 612 sourceNames.add(transitionName); 613 targetNames.add(sharedElements[i].second); 614 } 615 mSharedElementSourceNames = sourceNames; 616 mSharedElementTargetNames = targetNames; 617 } 618 return this; 619 } 620 621 public FragmentTransaction setTransitionStyle(int styleRes) { 622 mTransitionStyle = styleRes; 623 return this; 624 } 625 626 public FragmentTransaction addToBackStack(String name) { 627 if (!mAllowAddToBackStack) { 628 throw new IllegalStateException( 629 "This FragmentTransaction is not allowed to be added to the back stack."); 630 } 631 mAddToBackStack = true; 632 mName = name; 633 return this; 634 } 635 636 public boolean isAddToBackStackAllowed() { 637 return mAllowAddToBackStack; 638 } 639 640 public FragmentTransaction disallowAddToBackStack() { 641 if (mAddToBackStack) { 642 throw new IllegalStateException( 643 "This transaction is already being added to the back stack"); 644 } 645 mAllowAddToBackStack = false; 646 return this; 647 } 648 649 public FragmentTransaction setBreadCrumbTitle(int res) { 650 mBreadCrumbTitleRes = res; 651 mBreadCrumbTitleText = null; 652 return this; 653 } 654 655 public FragmentTransaction setBreadCrumbTitle(CharSequence text) { 656 mBreadCrumbTitleRes = 0; 657 mBreadCrumbTitleText = text; 658 return this; 659 } 660 661 public FragmentTransaction setBreadCrumbShortTitle(int res) { 662 mBreadCrumbShortTitleRes = res; 663 mBreadCrumbShortTitleText = null; 664 return this; 665 } 666 667 public FragmentTransaction setBreadCrumbShortTitle(CharSequence text) { 668 mBreadCrumbShortTitleRes = 0; 669 mBreadCrumbShortTitleText = text; 670 return this; 671 } 672 673 void bumpBackStackNesting(int amt) { 674 if (!mAddToBackStack) { 675 return; 676 } 677 if (FragmentManagerImpl.DEBUG) { 678 Log.v(TAG, "Bump nesting in " + this 679 + " by " + amt); 680 } 681 Op op = mHead; 682 while (op != null) { 683 if (op.fragment != null) { 684 op.fragment.mBackStackNesting += amt; 685 if (FragmentManagerImpl.DEBUG) { 686 Log.v(TAG, "Bump nesting of " 687 + op.fragment + " to " + op.fragment.mBackStackNesting); 688 } 689 } 690 if (op.removed != null) { 691 for (int i = op.removed.size() - 1; i >= 0; i--) { 692 Fragment r = op.removed.get(i); 693 r.mBackStackNesting += amt; 694 if (FragmentManagerImpl.DEBUG) { 695 Log.v(TAG, "Bump nesting of " 696 + r + " to " + r.mBackStackNesting); 697 } 698 } 699 } 700 op = op.next; 701 } 702 } 703 704 public int commit() { 705 return commitInternal(false); 706 } 707 708 public int commitAllowingStateLoss() { 709 return commitInternal(true); 710 } 711 712 int commitInternal(boolean allowStateLoss) { 713 if (mCommitted) { 714 throw new IllegalStateException("commit already called"); 715 } 716 if (FragmentManagerImpl.DEBUG) { 717 Log.v(TAG, "Commit: " + this); 718 LogWriter logw = new LogWriter(Log.VERBOSE, TAG); 719 PrintWriter pw = new FastPrintWriter(logw, false, 1024); 720 dump(" ", null, pw, null); 721 pw.flush(); 722 } 723 mCommitted = true; 724 if (mAddToBackStack) { 725 mIndex = mManager.allocBackStackIndex(this); 726 } else { 727 mIndex = -1; 728 } 729 mManager.enqueueAction(this, allowStateLoss); 730 return mIndex; 731 } 732 733 public void run() { 734 if (FragmentManagerImpl.DEBUG) { 735 Log.v(TAG, "Run: " + this); 736 } 737 738 if (mAddToBackStack) { 739 if (mIndex < 0) { 740 throw new IllegalStateException("addToBackStack() called after commit()"); 741 } 742 } 743 744 bumpBackStackNesting(1); 745 746 SparseArray<Fragment> firstOutFragments = new SparseArray<Fragment>(); 747 SparseArray<Fragment> lastInFragments = new SparseArray<Fragment>(); 748 calculateFragments(firstOutFragments, lastInFragments); 749 beginTransition(firstOutFragments, lastInFragments, false); 750 751 Op op = mHead; 752 while (op != null) { 753 switch (op.cmd) { 754 case OP_ADD: { 755 Fragment f = op.fragment; 756 f.mNextAnim = op.enterAnim; 757 mManager.addFragment(f, false); 758 } 759 break; 760 case OP_REPLACE: { 761 Fragment f = op.fragment; 762 if (mManager.mAdded != null) { 763 for (int i = 0; i < mManager.mAdded.size(); i++) { 764 Fragment old = mManager.mAdded.get(i); 765 if (FragmentManagerImpl.DEBUG) { 766 Log.v(TAG, 767 "OP_REPLACE: adding=" + f + " old=" + old); 768 } 769 if (f == null || old.mContainerId == f.mContainerId) { 770 if (old == f) { 771 op.fragment = f = null; 772 } else { 773 if (op.removed == null) { 774 op.removed = new ArrayList<Fragment>(); 775 } 776 op.removed.add(old); 777 old.mNextAnim = op.exitAnim; 778 if (mAddToBackStack) { 779 old.mBackStackNesting += 1; 780 if (FragmentManagerImpl.DEBUG) { 781 Log.v(TAG, "Bump nesting of " 782 + old + " to " + old.mBackStackNesting); 783 } 784 } 785 mManager.removeFragment(old, mTransition, mTransitionStyle); 786 } 787 } 788 } 789 } 790 if (f != null) { 791 f.mNextAnim = op.enterAnim; 792 mManager.addFragment(f, false); 793 } 794 } 795 break; 796 case OP_REMOVE: { 797 Fragment f = op.fragment; 798 f.mNextAnim = op.exitAnim; 799 mManager.removeFragment(f, mTransition, mTransitionStyle); 800 } 801 break; 802 case OP_HIDE: { 803 Fragment f = op.fragment; 804 f.mNextAnim = op.exitAnim; 805 mManager.hideFragment(f, mTransition, mTransitionStyle); 806 } 807 break; 808 case OP_SHOW: { 809 Fragment f = op.fragment; 810 f.mNextAnim = op.enterAnim; 811 mManager.showFragment(f, mTransition, mTransitionStyle); 812 } 813 break; 814 case OP_DETACH: { 815 Fragment f = op.fragment; 816 f.mNextAnim = op.exitAnim; 817 mManager.detachFragment(f, mTransition, mTransitionStyle); 818 } 819 break; 820 case OP_ATTACH: { 821 Fragment f = op.fragment; 822 f.mNextAnim = op.enterAnim; 823 mManager.attachFragment(f, mTransition, mTransitionStyle); 824 } 825 break; 826 default: { 827 throw new IllegalArgumentException("Unknown cmd: " + op.cmd); 828 } 829 } 830 831 op = op.next; 832 } 833 834 mManager.moveToState(mManager.mCurState, mTransition, 835 mTransitionStyle, true); 836 837 if (mAddToBackStack) { 838 mManager.addBackStackState(this); 839 } 840 } 841 842 private static void setFirstOut(SparseArray<Fragment> fragments, Fragment fragment) { 843 if (fragment != null) { 844 int containerId = fragment.mContainerId; 845 if (containerId != 0 && !fragment.isHidden() && fragment.isAdded() && 846 fragment.getView() != null && fragments.get(containerId) == null) { 847 fragments.put(containerId, fragment); 848 } 849 } 850 } 851 852 private void setLastIn(SparseArray<Fragment> fragments, Fragment fragment) { 853 if (fragment != null) { 854 int containerId = fragment.mContainerId; 855 if (containerId != 0) { 856 fragments.put(containerId, fragment); 857 } 858 } 859 } 860 861 /** 862 * Finds the first removed fragment and last added fragments when going forward. 863 * If none of the fragments have transitions, then both lists will be empty. 864 * 865 * @param firstOutFragments The list of first fragments to be removed, keyed on the 866 * container ID. This list will be modified by the method. 867 * @param lastInFragments The list of last fragments to be added, keyed on the 868 * container ID. This list will be modified by the method. 869 */ 870 private void calculateFragments(SparseArray<Fragment> firstOutFragments, 871 SparseArray<Fragment> lastInFragments) { 872 if (!mManager.mContainer.hasView()) { 873 return; // nothing to see, so no transitions 874 } 875 Op op = mHead; 876 while (op != null) { 877 switch (op.cmd) { 878 case OP_ADD: 879 setLastIn(lastInFragments, op.fragment); 880 break; 881 case OP_REPLACE: { 882 Fragment f = op.fragment; 883 if (mManager.mAdded != null) { 884 for (int i = 0; i < mManager.mAdded.size(); i++) { 885 Fragment old = mManager.mAdded.get(i); 886 if (f == null || old.mContainerId == f.mContainerId) { 887 if (old == f) { 888 f = null; 889 } else { 890 setFirstOut(firstOutFragments, old); 891 } 892 } 893 } 894 } 895 setLastIn(lastInFragments, f); 896 break; 897 } 898 case OP_REMOVE: 899 setFirstOut(firstOutFragments, op.fragment); 900 break; 901 case OP_HIDE: 902 setFirstOut(firstOutFragments, op.fragment); 903 break; 904 case OP_SHOW: 905 setLastIn(lastInFragments, op.fragment); 906 break; 907 case OP_DETACH: 908 setFirstOut(firstOutFragments, op.fragment); 909 break; 910 case OP_ATTACH: 911 setLastIn(lastInFragments, op.fragment); 912 break; 913 } 914 915 op = op.next; 916 } 917 } 918 919 /** 920 * Finds the first removed fragment and last added fragments when popping the back stack. 921 * If none of the fragments have transitions, then both lists will be empty. 922 * 923 * @param firstOutFragments The list of first fragments to be removed, keyed on the 924 * container ID. This list will be modified by the method. 925 * @param lastInFragments The list of last fragments to be added, keyed on the 926 * container ID. This list will be modified by the method. 927 */ 928 public void calculateBackFragments(SparseArray<Fragment> firstOutFragments, 929 SparseArray<Fragment> lastInFragments) { 930 if (!mManager.mContainer.hasView()) { 931 return; // nothing to see, so no transitions 932 } 933 Op op = mHead; 934 while (op != null) { 935 switch (op.cmd) { 936 case OP_ADD: 937 setFirstOut(firstOutFragments, op.fragment); 938 break; 939 case OP_REPLACE: 940 if (op.removed != null) { 941 for (int i = op.removed.size() - 1; i >= 0; i--) { 942 setLastIn(lastInFragments, op.removed.get(i)); 943 } 944 } 945 setFirstOut(firstOutFragments, op.fragment); 946 break; 947 case OP_REMOVE: 948 setLastIn(lastInFragments, op.fragment); 949 break; 950 case OP_HIDE: 951 setLastIn(lastInFragments, op.fragment); 952 break; 953 case OP_SHOW: 954 setFirstOut(firstOutFragments, op.fragment); 955 break; 956 case OP_DETACH: 957 setLastIn(lastInFragments, op.fragment); 958 break; 959 case OP_ATTACH: 960 setFirstOut(firstOutFragments, op.fragment); 961 break; 962 } 963 964 op = op.next; 965 } 966 } 967 968 /** 969 * When custom fragment transitions are used, this sets up the state for each transition 970 * and begins the transition. A different transition is started for each fragment container 971 * and consists of up to 3 different transitions: the exit transition, a shared element 972 * transition and an enter transition. 973 * 974 * <p>The exit transition operates against the leaf nodes of the first fragment 975 * with a view that was removed. If no such fragment was removed, then no exit 976 * transition is executed. The exit transition comes from the outgoing fragment.</p> 977 * 978 * <p>The enter transition operates against the last fragment that was added. If 979 * that fragment does not have a view or no fragment was added, then no enter 980 * transition is executed. The enter transition comes from the incoming fragment.</p> 981 * 982 * <p>The shared element transition operates against all views and comes either 983 * from the outgoing fragment or the incoming fragment, depending on whether this 984 * is going forward or popping the back stack. When going forward, the incoming 985 * fragment's enter shared element transition is used, but when going back, the 986 * outgoing fragment's return shared element transition is used. Shared element 987 * transitions only operate if there is both an incoming and outgoing fragment.</p> 988 * 989 * @param firstOutFragments The list of first fragments to be removed, keyed on the 990 * container ID. 991 * @param lastInFragments The list of last fragments to be added, keyed on the 992 * container ID. 993 * @param isBack true if this is popping the back stack or false if this is a 994 * forward operation. 995 * @return The TransitionState used to complete the operation of the transition 996 * in {@link #setNameOverrides(android.app.BackStackRecord.TransitionState, java.util.ArrayList, 997 * java.util.ArrayList)}. 998 */ 999 private TransitionState beginTransition(SparseArray<Fragment> firstOutFragments, 1000 SparseArray<Fragment> lastInFragments, boolean isBack) { 1001 TransitionState state = new TransitionState(); 1002 1003 // Adding a non-existent target view makes sure that the transitions don't target 1004 // any views by default. They'll only target the views we tell add. If we don't 1005 // add any, then no views will be targeted. 1006 state.nonExistentView = new View(mManager.mActivity); 1007 1008 // Go over all leaving fragments. 1009 for (int i = 0; i < firstOutFragments.size(); i++) { 1010 int containerId = firstOutFragments.keyAt(i); 1011 configureTransitions(containerId, state, isBack, firstOutFragments, 1012 lastInFragments); 1013 } 1014 1015 // Now go over all entering fragments that didn't have a leaving fragment. 1016 for (int i = 0; i < lastInFragments.size(); i++) { 1017 int containerId = lastInFragments.keyAt(i); 1018 if (firstOutFragments.get(containerId) == null) { 1019 configureTransitions(containerId, state, isBack, firstOutFragments, 1020 lastInFragments); 1021 } 1022 } 1023 return state; 1024 } 1025 1026 private static Transition cloneTransition(Transition transition) { 1027 if (transition != null) { 1028 transition = transition.clone(); 1029 } 1030 return transition; 1031 } 1032 1033 private static Transition getEnterTransition(Fragment inFragment, boolean isBack) { 1034 if (inFragment == null) { 1035 return null; 1036 } 1037 return cloneTransition(isBack ? inFragment.getReenterTransition() : 1038 inFragment.getEnterTransition()); 1039 } 1040 1041 private static Transition getExitTransition(Fragment outFragment, boolean isBack) { 1042 if (outFragment == null) { 1043 return null; 1044 } 1045 return cloneTransition(isBack ? outFragment.getReturnTransition() : 1046 outFragment.getExitTransition()); 1047 } 1048 1049 private static Transition getSharedElementTransition(Fragment inFragment, Fragment outFragment, 1050 boolean isBack) { 1051 if (inFragment == null || outFragment == null) { 1052 return null; 1053 } 1054 return cloneTransition(isBack ? outFragment.getSharedElementReturnTransition() : 1055 inFragment.getSharedElementEnterTransition()); 1056 } 1057 1058 private static ArrayList<View> captureExitingViews(Transition exitTransition, 1059 Fragment outFragment, ArrayMap<String, View> namedViews, View nonExistentView) { 1060 ArrayList<View> viewList = null; 1061 if (exitTransition != null) { 1062 viewList = new ArrayList<View>(); 1063 View root = outFragment.getView(); 1064 root.captureTransitioningViews(viewList); 1065 if (namedViews != null) { 1066 viewList.removeAll(namedViews.values()); 1067 } 1068 if (!viewList.isEmpty()) { 1069 viewList.add(nonExistentView); 1070 addTargets(exitTransition, viewList); 1071 } 1072 } 1073 return viewList; 1074 } 1075 1076 private ArrayMap<String, View> remapSharedElements(TransitionState state, Fragment outFragment, 1077 boolean isBack) { 1078 ArrayMap<String, View> namedViews = new ArrayMap<String, View>(); 1079 if (mSharedElementSourceNames != null) { 1080 outFragment.getView().findNamedViews(namedViews); 1081 if (isBack) { 1082 namedViews.retainAll(mSharedElementTargetNames); 1083 } else { 1084 namedViews = remapNames(mSharedElementSourceNames, mSharedElementTargetNames, 1085 namedViews); 1086 } 1087 } 1088 1089 if (isBack) { 1090 outFragment.mEnterTransitionCallback.onMapSharedElements( 1091 mSharedElementTargetNames, namedViews); 1092 setBackNameOverrides(state, namedViews, false); 1093 } else { 1094 outFragment.mExitTransitionCallback.onMapSharedElements( 1095 mSharedElementTargetNames, namedViews); 1096 setNameOverrides(state, namedViews, false); 1097 } 1098 1099 return namedViews; 1100 } 1101 1102 /** 1103 * Prepares the enter transition by adding a non-existent view to the transition's target list 1104 * and setting it epicenter callback. By adding a non-existent view to the target list, 1105 * we can prevent any view from being targeted at the beginning of the transition. 1106 * We will add to the views before the end state of the transition is captured so that the 1107 * views will appear. At the start of the transition, we clear the list of targets so that 1108 * we can restore the state of the transition and use it again. 1109 * 1110 * <p>The shared element transition maps its shared elements immediately prior to 1111 * capturing the final state of the Transition.</p> 1112 */ 1113 private ArrayList<View> addTransitionTargets(final TransitionState state, 1114 final Transition enterTransition, final Transition sharedElementTransition, 1115 final Transition overallTransition, final View container, 1116 final Fragment inFragment, final Fragment outFragment, 1117 final ArrayList<View> hiddenFragmentViews, final boolean isBack, 1118 final ArrayList<View> sharedElementTargets) { 1119 if (enterTransition == null && sharedElementTransition == null && 1120 overallTransition == null) { 1121 return null; 1122 } 1123 final ArrayList<View> enteringViews = new ArrayList<View>(); 1124 container.getViewTreeObserver().addOnPreDrawListener( 1125 new ViewTreeObserver.OnPreDrawListener() { 1126 @Override 1127 public boolean onPreDraw() { 1128 container.getViewTreeObserver().removeOnPreDrawListener(this); 1129 1130 // Don't include any newly-hidden fragments in the transition. 1131 excludeHiddenFragments(hiddenFragmentViews, inFragment.mContainerId, 1132 overallTransition); 1133 1134 ArrayMap<String, View> namedViews = null; 1135 if (sharedElementTransition != null) { 1136 namedViews = mapSharedElementsIn(state, isBack, inFragment); 1137 removeTargets(sharedElementTransition, sharedElementTargets); 1138 sharedElementTargets.clear(); 1139 sharedElementTargets.add(state.nonExistentView); 1140 sharedElementTargets.addAll(namedViews.values()); 1141 1142 addTargets(sharedElementTransition, sharedElementTargets); 1143 1144 setEpicenterIn(namedViews, state); 1145 1146 callSharedElementEnd(state, inFragment, outFragment, isBack, 1147 namedViews); 1148 } 1149 1150 if (enterTransition != null) { 1151 View view = inFragment.getView(); 1152 if (view != null) { 1153 view.captureTransitioningViews(enteringViews); 1154 if (namedViews != null) { 1155 enteringViews.removeAll(namedViews.values()); 1156 } 1157 enteringViews.add(state.nonExistentView); 1158 // We added this earlier to prevent any views being targeted. 1159 enterTransition.removeTarget(state.nonExistentView); 1160 addTargets(enterTransition, enteringViews); 1161 } 1162 setSharedElementEpicenter(enterTransition, state); 1163 } 1164 return true; 1165 } 1166 }); 1167 return enteringViews; 1168 } 1169 1170 private void callSharedElementEnd(TransitionState state, Fragment inFragment, 1171 Fragment outFragment, boolean isBack, ArrayMap<String, View> namedViews) { 1172 SharedElementCallback sharedElementCallback = isBack ? 1173 outFragment.mEnterTransitionCallback : 1174 inFragment.mEnterTransitionCallback; 1175 ArrayList<String> names = new ArrayList<String>(namedViews.keySet()); 1176 ArrayList<View> views = new ArrayList<View>(namedViews.values()); 1177 sharedElementCallback.onSharedElementEnd(names, views, null); 1178 } 1179 1180 private void setEpicenterIn(ArrayMap<String, View> namedViews, TransitionState state) { 1181 if (mSharedElementTargetNames != null && !namedViews.isEmpty()) { 1182 // now we know the epicenter of the entering transition. 1183 View epicenter = namedViews 1184 .get(mSharedElementTargetNames.get(0)); 1185 if (epicenter != null) { 1186 state.enteringEpicenterView = epicenter; 1187 } 1188 } 1189 } 1190 1191 private ArrayMap<String, View> mapSharedElementsIn(TransitionState state, 1192 boolean isBack, Fragment inFragment) { 1193 // Now map the shared elements in the incoming fragment 1194 ArrayMap<String, View> namedViews = mapEnteringSharedElements(state, inFragment, isBack); 1195 1196 // remap shared elements and set the name mapping used 1197 // in the shared element transition. 1198 if (isBack) { 1199 inFragment.mExitTransitionCallback.onMapSharedElements( 1200 mSharedElementTargetNames, namedViews); 1201 setBackNameOverrides(state, namedViews, true); 1202 } else { 1203 inFragment.mEnterTransitionCallback.onMapSharedElements( 1204 mSharedElementTargetNames, namedViews); 1205 setNameOverrides(state, namedViews, true); 1206 } 1207 return namedViews; 1208 } 1209 1210 private static Transition mergeTransitions(Transition enterTransition, 1211 Transition exitTransition, Transition sharedElementTransition, Fragment inFragment, 1212 boolean isBack) { 1213 boolean overlap = true; 1214 if (enterTransition != null && exitTransition != null) { 1215 overlap = isBack ? inFragment.getAllowReturnTransitionOverlap() : 1216 inFragment.getAllowEnterTransitionOverlap(); 1217 } 1218 1219 // Wrap the transitions. Explicit targets like in enter and exit will cause the 1220 // views to be targeted regardless of excluded views. If that happens, then the 1221 // excluded fragments views (hidden fragments) will still be in the transition. 1222 1223 Transition transition; 1224 if (overlap) { 1225 // Regular transition -- do it all together 1226 TransitionSet transitionSet = new TransitionSet(); 1227 if (enterTransition != null) { 1228 transitionSet.addTransition(enterTransition); 1229 } 1230 if (exitTransition != null) { 1231 transitionSet.addTransition(exitTransition); 1232 } 1233 if (sharedElementTransition != null) { 1234 transitionSet.addTransition(sharedElementTransition); 1235 } 1236 transition = transitionSet; 1237 } else { 1238 // First do exit, then enter, but allow shared element transition to happen 1239 // during both. 1240 Transition staggered = null; 1241 if (exitTransition != null && enterTransition != null) { 1242 staggered = new TransitionSet() 1243 .addTransition(exitTransition) 1244 .addTransition(enterTransition) 1245 .setOrdering(TransitionSet.ORDERING_SEQUENTIAL); 1246 } else if (exitTransition != null) { 1247 staggered = exitTransition; 1248 } else if (enterTransition != null) { 1249 staggered = enterTransition; 1250 } 1251 if (sharedElementTransition != null) { 1252 TransitionSet together = new TransitionSet(); 1253 if (staggered != null) { 1254 together.addTransition(staggered); 1255 } 1256 together.addTransition(sharedElementTransition); 1257 transition = together; 1258 } else { 1259 transition = staggered; 1260 } 1261 } 1262 return transition; 1263 } 1264 1265 /** 1266 * Configures custom transitions for a specific fragment container. 1267 * 1268 * @param containerId The container ID of the fragments to configure the transition for. 1269 * @param state The Transition State keeping track of the executing transitions. 1270 * @param firstOutFragments The list of first fragments to be removed, keyed on the 1271 * container ID. 1272 * @param lastInFragments The list of last fragments to be added, keyed on the 1273 * container ID. 1274 * @param isBack true if this is popping the back stack or false if this is a 1275 * forward operation. 1276 */ 1277 private void configureTransitions(int containerId, TransitionState state, boolean isBack, 1278 SparseArray<Fragment> firstOutFragments, SparseArray<Fragment> lastInFragments) { 1279 ViewGroup sceneRoot = (ViewGroup) mManager.mContainer.findViewById(containerId); 1280 if (sceneRoot != null) { 1281 Fragment inFragment = lastInFragments.get(containerId); 1282 Fragment outFragment = firstOutFragments.get(containerId); 1283 1284 Transition enterTransition = getEnterTransition(inFragment, isBack); 1285 Transition sharedElementTransition = getSharedElementTransition(inFragment, outFragment, 1286 isBack); 1287 Transition exitTransition = getExitTransition(outFragment, isBack); 1288 1289 if (enterTransition == null && sharedElementTransition == null && 1290 exitTransition == null) { 1291 return; // no transitions! 1292 } 1293 if (enterTransition != null) { 1294 enterTransition.addTarget(state.nonExistentView); 1295 } 1296 ArrayMap<String, View> namedViews = null; 1297 ArrayList<View> sharedElementTargets = new ArrayList<View>(); 1298 if (sharedElementTransition != null) { 1299 namedViews = remapSharedElements(state, outFragment, isBack); 1300 sharedElementTargets.add(state.nonExistentView); 1301 sharedElementTargets.addAll(namedViews.values()); 1302 addTargets(sharedElementTransition, sharedElementTargets); 1303 1304 // Notify the start of the transition. 1305 SharedElementCallback callback = isBack ? 1306 outFragment.mEnterTransitionCallback : 1307 inFragment.mEnterTransitionCallback; 1308 ArrayList<String> names = new ArrayList<String>(namedViews.keySet()); 1309 ArrayList<View> views = new ArrayList<View>(namedViews.values()); 1310 callback.onSharedElementStart(names, views, null); 1311 } 1312 1313 ArrayList<View> exitingViews = captureExitingViews(exitTransition, outFragment, 1314 namedViews, state.nonExistentView); 1315 if (exitingViews == null || exitingViews.isEmpty()) { 1316 exitTransition = null; 1317 } 1318 1319 // Set the epicenter of the exit transition 1320 if (mSharedElementTargetNames != null && namedViews != null) { 1321 View epicenterView = namedViews.get(mSharedElementTargetNames.get(0)); 1322 if (epicenterView != null) { 1323 if (exitTransition != null) { 1324 setEpicenter(exitTransition, epicenterView); 1325 } 1326 if (sharedElementTransition != null) { 1327 setEpicenter(sharedElementTransition, epicenterView); 1328 } 1329 } 1330 } 1331 1332 Transition transition = mergeTransitions(enterTransition, exitTransition, 1333 sharedElementTransition, inFragment, isBack); 1334 1335 if (transition != null) { 1336 ArrayList<View> hiddenFragments = new ArrayList<View>(); 1337 ArrayList<View> enteringViews = addTransitionTargets(state, enterTransition, 1338 sharedElementTransition, transition, sceneRoot, inFragment, outFragment, 1339 hiddenFragments, isBack, sharedElementTargets); 1340 1341 transition.setNameOverrides(state.nameOverrides); 1342 // We want to exclude hidden views later, so we need a non-null list in the 1343 // transition now. 1344 transition.excludeTarget(state.nonExistentView, true); 1345 // Now exclude all currently hidden fragments. 1346 excludeHiddenFragments(hiddenFragments, containerId, transition); 1347 TransitionManager.beginDelayedTransition(sceneRoot, transition); 1348 // Remove the view targeting after the transition starts 1349 removeTargetedViewsFromTransitions(sceneRoot, state.nonExistentView, 1350 enterTransition, enteringViews, exitTransition, exitingViews, 1351 sharedElementTransition, sharedElementTargets, transition, hiddenFragments); 1352 } 1353 } 1354 } 1355 1356 /** 1357 * After the transition has started, remove all targets that we added to the transitions 1358 * so that the transitions are left in a clean state. 1359 */ 1360 private void removeTargetedViewsFromTransitions( 1361 final ViewGroup sceneRoot, final View nonExistingView, 1362 final Transition enterTransition, final ArrayList<View> enteringViews, 1363 final Transition exitTransition, final ArrayList<View> exitingViews, 1364 final Transition sharedElementTransition, final ArrayList<View> sharedElementTargets, 1365 final Transition overallTransition, final ArrayList<View> hiddenViews) { 1366 if (overallTransition != null) { 1367 sceneRoot.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { 1368 @Override 1369 public boolean onPreDraw() { 1370 sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this); 1371 if (enterTransition != null) { 1372 enterTransition.removeTarget(nonExistingView); 1373 removeTargets(enterTransition, enteringViews); 1374 } 1375 if (exitTransition != null) { 1376 removeTargets(exitTransition, exitingViews); 1377 } 1378 if (sharedElementTransition != null) { 1379 removeTargets(sharedElementTransition, sharedElementTargets); 1380 } 1381 int numViews = hiddenViews.size(); 1382 for (int i = 0; i < numViews; i++) { 1383 overallTransition.excludeTarget(hiddenViews.get(i), false); 1384 } 1385 overallTransition.excludeTarget(nonExistingView, false); 1386 return true; 1387 } 1388 }); 1389 } 1390 } 1391 1392 /** 1393 * This method removes the views from transitions that target ONLY those views. 1394 * The views list should match those added in addTargets and should contain 1395 * one view that is not in the view hierarchy (state.nonExistentView). 1396 */ 1397 public static void removeTargets(Transition transition, ArrayList<View> views) { 1398 if (transition instanceof TransitionSet) { 1399 TransitionSet set = (TransitionSet) transition; 1400 int numTransitions = set.getTransitionCount(); 1401 for (int i = 0; i < numTransitions; i++) { 1402 Transition child = set.getTransitionAt(i); 1403 removeTargets(child, views); 1404 } 1405 } else if (!hasSimpleTarget(transition)) { 1406 List<View> targets = transition.getTargets(); 1407 if (targets != null && targets.size() == views.size() && 1408 targets.containsAll(views)) { 1409 // We have an exact match. We must have added these earlier in addTargets 1410 for (int i = views.size() - 1; i >= 0; i--) { 1411 transition.removeTarget(views.get(i)); 1412 } 1413 } 1414 } 1415 } 1416 1417 /** 1418 * This method adds views as targets to the transition, but only if the transition 1419 * doesn't already have a target. It is best for views to contain one View object 1420 * that does not exist in the view hierarchy (state.nonExistentView) so that 1421 * when they are removed later, a list match will suffice to remove the targets. 1422 * Otherwise, if you happened to have targeted the exact views for the transition, 1423 * the removeTargets call will remove them unexpectedly. 1424 */ 1425 public static void addTargets(Transition transition, ArrayList<View> views) { 1426 if (transition instanceof TransitionSet) { 1427 TransitionSet set = (TransitionSet) transition; 1428 int numTransitions = set.getTransitionCount(); 1429 for (int i = 0; i < numTransitions; i++) { 1430 Transition child = set.getTransitionAt(i); 1431 addTargets(child, views); 1432 } 1433 } else if (!hasSimpleTarget(transition)) { 1434 List<View> targets = transition.getTargets(); 1435 if (isNullOrEmpty(targets)) { 1436 // We can just add the target views 1437 int numViews = views.size(); 1438 for (int i = 0; i < numViews; i++) { 1439 transition.addTarget(views.get(i)); 1440 } 1441 } 1442 } 1443 } 1444 1445 private static boolean hasSimpleTarget(Transition transition) { 1446 return !isNullOrEmpty(transition.getTargetIds()) || 1447 !isNullOrEmpty(transition.getTargetNames()) || 1448 !isNullOrEmpty(transition.getTargetTypes()); 1449 } 1450 1451 private static boolean isNullOrEmpty(List list) { 1452 return list == null || list.isEmpty(); 1453 } 1454 1455 /** 1456 * Remaps a name-to-View map, substituting different names for keys. 1457 * 1458 * @param inMap A list of keys found in the map, in the order in toGoInMap 1459 * @param toGoInMap A list of keys to use for the new map, in the order of inMap 1460 * @param namedViews The current mapping 1461 * @return a new Map after it has been mapped with the new names as keys. 1462 */ 1463 private static ArrayMap<String, View> remapNames(ArrayList<String> inMap, 1464 ArrayList<String> toGoInMap, ArrayMap<String, View> namedViews) { 1465 ArrayMap<String, View> remappedViews = new ArrayMap<String, View>(); 1466 if (!namedViews.isEmpty()) { 1467 int numKeys = inMap.size(); 1468 for (int i = 0; i < numKeys; i++) { 1469 View view = namedViews.get(inMap.get(i)); 1470 1471 if (view != null) { 1472 remappedViews.put(toGoInMap.get(i), view); 1473 } 1474 } 1475 } 1476 return remappedViews; 1477 } 1478 1479 /** 1480 * Maps shared elements to views in the entering fragment. 1481 * 1482 * @param state The transition State as returned from {@link #beginTransition( 1483 * android.util.SparseArray, android.util.SparseArray, boolean)}. 1484 * @param inFragment The last fragment to be added. 1485 * @param isBack true if this is popping the back stack or false if this is a 1486 * forward operation. 1487 */ 1488 private ArrayMap<String, View> mapEnteringSharedElements(TransitionState state, 1489 Fragment inFragment, boolean isBack) { 1490 ArrayMap<String, View> namedViews = new ArrayMap<String, View>(); 1491 View root = inFragment.getView(); 1492 if (root != null) { 1493 if (mSharedElementSourceNames != null) { 1494 root.findNamedViews(namedViews); 1495 if (isBack) { 1496 namedViews = remapNames(mSharedElementSourceNames, 1497 mSharedElementTargetNames, namedViews); 1498 } else { 1499 namedViews.retainAll(mSharedElementTargetNames); 1500 } 1501 } 1502 } 1503 return namedViews; 1504 } 1505 1506 private void excludeHiddenFragments(final ArrayList<View> hiddenFragmentViews, int containerId, 1507 Transition transition) { 1508 if (mManager.mAdded != null) { 1509 for (int i = 0; i < mManager.mAdded.size(); i++) { 1510 Fragment fragment = mManager.mAdded.get(i); 1511 if (fragment.mView != null && fragment.mContainer != null && 1512 fragment.mContainerId == containerId) { 1513 if (fragment.mHidden) { 1514 if (!hiddenFragmentViews.contains(fragment.mView)) { 1515 transition.excludeTarget(fragment.mView, true); 1516 hiddenFragmentViews.add(fragment.mView); 1517 } 1518 } else { 1519 transition.excludeTarget(fragment.mView, false); 1520 hiddenFragmentViews.remove(fragment.mView); 1521 } 1522 } 1523 } 1524 } 1525 } 1526 1527 private static void setEpicenter(Transition transition, View view) { 1528 final Rect epicenter = new Rect(); 1529 view.getBoundsOnScreen(epicenter); 1530 1531 transition.setEpicenterCallback(new Transition.EpicenterCallback() { 1532 @Override 1533 public Rect onGetEpicenter(Transition transition) { 1534 return epicenter; 1535 } 1536 }); 1537 } 1538 1539 private void setSharedElementEpicenter(Transition transition, final TransitionState state) { 1540 transition.setEpicenterCallback(new Transition.EpicenterCallback() { 1541 private Rect mEpicenter; 1542 1543 @Override 1544 public Rect onGetEpicenter(Transition transition) { 1545 if (mEpicenter == null && state.enteringEpicenterView != null) { 1546 mEpicenter = new Rect(); 1547 state.enteringEpicenterView.getBoundsOnScreen(mEpicenter); 1548 } 1549 return mEpicenter; 1550 } 1551 }); 1552 } 1553 1554 public TransitionState popFromBackStack(boolean doStateMove, TransitionState state, 1555 SparseArray<Fragment> firstOutFragments, SparseArray<Fragment> lastInFragments) { 1556 if (FragmentManagerImpl.DEBUG) { 1557 Log.v(TAG, "popFromBackStack: " + this); 1558 LogWriter logw = new LogWriter(Log.VERBOSE, TAG); 1559 PrintWriter pw = new FastPrintWriter(logw, false, 1024); 1560 dump(" ", null, pw, null); 1561 pw.flush(); 1562 } 1563 1564 if (state == null) { 1565 if (firstOutFragments.size() != 0 || lastInFragments.size() != 0) { 1566 state = beginTransition(firstOutFragments, lastInFragments, true); 1567 } 1568 } else if (!doStateMove) { 1569 setNameOverrides(state, mSharedElementTargetNames, mSharedElementSourceNames); 1570 } 1571 1572 bumpBackStackNesting(-1); 1573 1574 Op op = mTail; 1575 while (op != null) { 1576 switch (op.cmd) { 1577 case OP_ADD: { 1578 Fragment f = op.fragment; 1579 f.mNextAnim = op.popExitAnim; 1580 mManager.removeFragment(f, 1581 FragmentManagerImpl.reverseTransit(mTransition), 1582 mTransitionStyle); 1583 } 1584 break; 1585 case OP_REPLACE: { 1586 Fragment f = op.fragment; 1587 if (f != null) { 1588 f.mNextAnim = op.popExitAnim; 1589 mManager.removeFragment(f, 1590 FragmentManagerImpl.reverseTransit(mTransition), 1591 mTransitionStyle); 1592 } 1593 if (op.removed != null) { 1594 for (int i = 0; i < op.removed.size(); i++) { 1595 Fragment old = op.removed.get(i); 1596 old.mNextAnim = op.popEnterAnim; 1597 mManager.addFragment(old, false); 1598 } 1599 } 1600 } 1601 break; 1602 case OP_REMOVE: { 1603 Fragment f = op.fragment; 1604 f.mNextAnim = op.popEnterAnim; 1605 mManager.addFragment(f, false); 1606 } 1607 break; 1608 case OP_HIDE: { 1609 Fragment f = op.fragment; 1610 f.mNextAnim = op.popEnterAnim; 1611 mManager.showFragment(f, 1612 FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle); 1613 } 1614 break; 1615 case OP_SHOW: { 1616 Fragment f = op.fragment; 1617 f.mNextAnim = op.popExitAnim; 1618 mManager.hideFragment(f, 1619 FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle); 1620 } 1621 break; 1622 case OP_DETACH: { 1623 Fragment f = op.fragment; 1624 f.mNextAnim = op.popEnterAnim; 1625 mManager.attachFragment(f, 1626 FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle); 1627 } 1628 break; 1629 case OP_ATTACH: { 1630 Fragment f = op.fragment; 1631 f.mNextAnim = op.popExitAnim; 1632 mManager.detachFragment(f, 1633 FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle); 1634 } 1635 break; 1636 default: { 1637 throw new IllegalArgumentException("Unknown cmd: " + op.cmd); 1638 } 1639 } 1640 1641 op = op.prev; 1642 } 1643 1644 if (doStateMove) { 1645 mManager.moveToState(mManager.mCurState, 1646 FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle, true); 1647 state = null; 1648 } 1649 1650 if (mIndex >= 0) { 1651 mManager.freeBackStackIndex(mIndex); 1652 mIndex = -1; 1653 } 1654 return state; 1655 } 1656 1657 private static void setNameOverride(ArrayMap<String, String> overrides, 1658 String source, String target) { 1659 if (source != null && target != null && !source.equals(target)) { 1660 for (int index = 0; index < overrides.size(); index++) { 1661 if (source.equals(overrides.valueAt(index))) { 1662 overrides.setValueAt(index, target); 1663 return; 1664 } 1665 } 1666 overrides.put(source, target); 1667 } 1668 } 1669 1670 private static void setNameOverrides(TransitionState state, ArrayList<String> sourceNames, 1671 ArrayList<String> targetNames) { 1672 if (sourceNames != null) { 1673 for (int i = 0; i < sourceNames.size(); i++) { 1674 String source = sourceNames.get(i); 1675 String target = targetNames.get(i); 1676 setNameOverride(state.nameOverrides, source, target); 1677 } 1678 } 1679 } 1680 1681 private void setBackNameOverrides(TransitionState state, ArrayMap<String, View> namedViews, 1682 boolean isEnd) { 1683 int count = mSharedElementTargetNames.size(); 1684 for (int i = 0; i < count; i++) { 1685 String source = mSharedElementSourceNames.get(i); 1686 String originalTarget = mSharedElementTargetNames.get(i); 1687 View view = namedViews.get(originalTarget); 1688 if (view != null) { 1689 String target = view.getTransitionName(); 1690 if (isEnd) { 1691 setNameOverride(state.nameOverrides, source, target); 1692 } else { 1693 setNameOverride(state.nameOverrides, target, source); 1694 } 1695 } 1696 } 1697 } 1698 1699 private void setNameOverrides(TransitionState state, ArrayMap<String, View> namedViews, 1700 boolean isEnd) { 1701 int count = namedViews.size(); 1702 for (int i = 0; i < count; i++) { 1703 String source = namedViews.keyAt(i); 1704 String target = namedViews.valueAt(i).getTransitionName(); 1705 if (isEnd) { 1706 setNameOverride(state.nameOverrides, source, target); 1707 } else { 1708 setNameOverride(state.nameOverrides, target, source); 1709 } 1710 } 1711 } 1712 1713 public String getName() { 1714 return mName; 1715 } 1716 1717 public int getTransition() { 1718 return mTransition; 1719 } 1720 1721 public int getTransitionStyle() { 1722 return mTransitionStyle; 1723 } 1724 1725 public boolean isEmpty() { 1726 return mNumOp == 0; 1727 } 1728 1729 public class TransitionState { 1730 public ArrayMap<String, String> nameOverrides = new ArrayMap<String, String>(); 1731 public View enteringEpicenterView; 1732 public View nonExistentView; 1733 } 1734 } 1735