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