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