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