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