mirror of
https://github.com/yuliskov/SmartTube.git
synced 2025-12-30 18:40:27 -06:00
add modded androidx.fragment lib
This commit is contained in:
@@ -54,6 +54,8 @@ allprojects {
|
||||
|
||||
// Replace with modded library (leanback-1.0.0)
|
||||
exclude group: 'androidx.leanback', module: 'leanback'
|
||||
// Replace with modded library (fragment-1.1.0)
|
||||
exclude group: 'androidx.fragment', module: 'fragment'
|
||||
|
||||
// Don't work! Replace with modded library (leanback-1.0.0)
|
||||
// resolutionStrategy.dependencySubstitution {
|
||||
|
||||
@@ -54,6 +54,7 @@ dependencies {
|
||||
androidTestImplementation 'androidx.test.ext:junit:' + testXSupportLibraryVersion
|
||||
|
||||
implementation project(':sharedutils')
|
||||
implementation project(':fragment-1.1.0')
|
||||
implementation project(':mediaserviceinterfaces')
|
||||
implementation project(':youtubeapi')
|
||||
|
||||
|
||||
1
fragment-1.1.0/.gitignore
vendored
Normal file
1
fragment-1.1.0/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
41
fragment-1.1.0/build.gradle
Normal file
41
fragment-1.1.0/build.gradle
Normal file
@@ -0,0 +1,41 @@
|
||||
apply from: gradle.ext.sharedModulesConstants
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
// FIX: Default interface methods are only supported starting with Android N (--min-api 24)
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
compileSdkVersion project.properties.compileSdkVersion
|
||||
buildToolsVersion project.properties.buildToolsVersion
|
||||
testOptions.unitTests.includeAndroidResources = true
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion project.properties.minSdkVersion
|
||||
targetSdkVersion project.properties.targetSdkVersion
|
||||
versionCode 10
|
||||
versionName "1.0.0"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
consumerProguardFiles 'consumer-rules.pro'
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
|
||||
api 'androidx.activity:activity:' + leanbackXLibraryVersion // used inside other app modules
|
||||
implementation 'androidx.annotation:annotation:' + annotationXLibraryVersion
|
||||
implementation 'androidx.core:core:' + kotlinCoreVersion
|
||||
implementation 'androidx.viewpager:viewpager:' + leanbackXLibraryVersion
|
||||
implementation 'androidx.loader:loader:' + leanbackXLibraryVersion
|
||||
}
|
||||
22
fragment-1.1.0/src/main/AndroidManifest.xml
Normal file
22
fragment-1.1.0/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2014 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="androidx.fragment" >
|
||||
|
||||
<!--<uses-sdk android:minSdkVersion="17" />-->
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,667 @@
|
||||
/*
|
||||
* Copyright 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.fragment.app;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.util.LogWriter;
|
||||
import androidx.lifecycle.Lifecycle;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Entry of an operation on the fragment back stack.
|
||||
*/
|
||||
final class BackStackRecord extends FragmentTransaction implements
|
||||
FragmentManager.BackStackEntry, FragmentManagerImpl.OpGenerator {
|
||||
static final String TAG = FragmentManagerImpl.TAG;
|
||||
|
||||
final FragmentManagerImpl mManager;
|
||||
|
||||
boolean mCommitted;
|
||||
int mIndex = -1;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder(128);
|
||||
sb.append("BackStackEntry{");
|
||||
sb.append(Integer.toHexString(System.identityHashCode(this)));
|
||||
if (mIndex >= 0) {
|
||||
sb.append(" #");
|
||||
sb.append(mIndex);
|
||||
}
|
||||
if (mName != null) {
|
||||
sb.append(" ");
|
||||
sb.append(mName);
|
||||
}
|
||||
sb.append("}");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public void dump(String prefix, PrintWriter writer) {
|
||||
dump(prefix, writer, true);
|
||||
}
|
||||
|
||||
public void dump(String prefix, PrintWriter writer, boolean full) {
|
||||
if (full) {
|
||||
writer.print(prefix); writer.print("mName="); writer.print(mName);
|
||||
writer.print(" mIndex="); writer.print(mIndex);
|
||||
writer.print(" mCommitted="); writer.println(mCommitted);
|
||||
if (mTransition != FragmentTransaction.TRANSIT_NONE) {
|
||||
writer.print(prefix); writer.print("mTransition=#");
|
||||
writer.print(Integer.toHexString(mTransition));
|
||||
writer.print(" mTransitionStyle=#");
|
||||
writer.println(Integer.toHexString(mTransitionStyle));
|
||||
}
|
||||
if (mEnterAnim != 0 || mExitAnim !=0) {
|
||||
writer.print(prefix); writer.print("mEnterAnim=#");
|
||||
writer.print(Integer.toHexString(mEnterAnim));
|
||||
writer.print(" mExitAnim=#");
|
||||
writer.println(Integer.toHexString(mExitAnim));
|
||||
}
|
||||
if (mPopEnterAnim != 0 || mPopExitAnim !=0) {
|
||||
writer.print(prefix); writer.print("mPopEnterAnim=#");
|
||||
writer.print(Integer.toHexString(mPopEnterAnim));
|
||||
writer.print(" mPopExitAnim=#");
|
||||
writer.println(Integer.toHexString(mPopExitAnim));
|
||||
}
|
||||
if (mBreadCrumbTitleRes != 0 || mBreadCrumbTitleText != null) {
|
||||
writer.print(prefix); writer.print("mBreadCrumbTitleRes=#");
|
||||
writer.print(Integer.toHexString(mBreadCrumbTitleRes));
|
||||
writer.print(" mBreadCrumbTitleText=");
|
||||
writer.println(mBreadCrumbTitleText);
|
||||
}
|
||||
if (mBreadCrumbShortTitleRes != 0 || mBreadCrumbShortTitleText != null) {
|
||||
writer.print(prefix); writer.print("mBreadCrumbShortTitleRes=#");
|
||||
writer.print(Integer.toHexString(mBreadCrumbShortTitleRes));
|
||||
writer.print(" mBreadCrumbShortTitleText=");
|
||||
writer.println(mBreadCrumbShortTitleText);
|
||||
}
|
||||
}
|
||||
|
||||
if (!mOps.isEmpty()) {
|
||||
writer.print(prefix); writer.println("Operations:");
|
||||
final int numOps = mOps.size();
|
||||
for (int opNum = 0; opNum < numOps; opNum++) {
|
||||
final Op op = mOps.get(opNum);
|
||||
String cmdStr;
|
||||
switch (op.mCmd) {
|
||||
case OP_NULL: cmdStr="NULL"; break;
|
||||
case OP_ADD: cmdStr="ADD"; break;
|
||||
case OP_REPLACE: cmdStr="REPLACE"; break;
|
||||
case OP_REMOVE: cmdStr="REMOVE"; break;
|
||||
case OP_HIDE: cmdStr="HIDE"; break;
|
||||
case OP_SHOW: cmdStr="SHOW"; break;
|
||||
case OP_DETACH: cmdStr="DETACH"; break;
|
||||
case OP_ATTACH: cmdStr="ATTACH"; break;
|
||||
case OP_SET_PRIMARY_NAV: cmdStr="SET_PRIMARY_NAV"; break;
|
||||
case OP_UNSET_PRIMARY_NAV: cmdStr="UNSET_PRIMARY_NAV";break;
|
||||
case OP_SET_MAX_LIFECYCLE: cmdStr = "OP_SET_MAX_LIFECYCLE"; break;
|
||||
default: cmdStr = "cmd=" + op.mCmd; break;
|
||||
}
|
||||
writer.print(prefix); writer.print(" Op #"); writer.print(opNum);
|
||||
writer.print(": "); writer.print(cmdStr);
|
||||
writer.print(" "); writer.println(op.mFragment);
|
||||
if (full) {
|
||||
if (op.mEnterAnim != 0 || op.mExitAnim != 0) {
|
||||
writer.print(prefix); writer.print("enterAnim=#");
|
||||
writer.print(Integer.toHexString(op.mEnterAnim));
|
||||
writer.print(" exitAnim=#");
|
||||
writer.println(Integer.toHexString(op.mExitAnim));
|
||||
}
|
||||
if (op.mPopEnterAnim != 0 || op.mPopExitAnim != 0) {
|
||||
writer.print(prefix); writer.print("popEnterAnim=#");
|
||||
writer.print(Integer.toHexString(op.mPopEnterAnim));
|
||||
writer.print(" popExitAnim=#");
|
||||
writer.println(Integer.toHexString(op.mPopExitAnim));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public BackStackRecord(FragmentManagerImpl manager) {
|
||||
mManager = manager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getId() {
|
||||
return mIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBreadCrumbTitleRes() {
|
||||
return mBreadCrumbTitleRes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBreadCrumbShortTitleRes() {
|
||||
return mBreadCrumbShortTitleRes;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public CharSequence getBreadCrumbTitle() {
|
||||
if (mBreadCrumbTitleRes != 0) {
|
||||
return mManager.mHost.getContext().getText(mBreadCrumbTitleRes);
|
||||
}
|
||||
return mBreadCrumbTitleText;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public CharSequence getBreadCrumbShortTitle() {
|
||||
if (mBreadCrumbShortTitleRes != 0) {
|
||||
return mManager.mHost.getContext().getText(mBreadCrumbShortTitleRes);
|
||||
}
|
||||
return mBreadCrumbShortTitleText;
|
||||
}
|
||||
|
||||
@Override
|
||||
void doAddOp(int containerViewId, Fragment fragment, @Nullable String tag, int opcmd) {
|
||||
super.doAddOp(containerViewId, fragment, tag, opcmd);
|
||||
fragment.mFragmentManager = mManager;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public FragmentTransaction remove(@NonNull Fragment fragment) {
|
||||
if (fragment.mFragmentManager != null && fragment.mFragmentManager != mManager) {
|
||||
throw new IllegalStateException("Cannot remove Fragment attached to "
|
||||
+ "a different FragmentManager. Fragment " + fragment.toString() + " is already"
|
||||
+ " attached to a FragmentManager.");
|
||||
}
|
||||
return super.remove(fragment);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public FragmentTransaction hide(@NonNull Fragment fragment) {
|
||||
if (fragment.mFragmentManager != null && fragment.mFragmentManager != mManager) {
|
||||
throw new IllegalStateException("Cannot hide Fragment attached to "
|
||||
+ "a different FragmentManager. Fragment " + fragment.toString() + " is already"
|
||||
+ " attached to a FragmentManager.");
|
||||
}
|
||||
return super.hide(fragment);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public FragmentTransaction show(@NonNull Fragment fragment) {
|
||||
if (fragment.mFragmentManager != null && fragment.mFragmentManager != mManager) {
|
||||
throw new IllegalStateException("Cannot show Fragment attached to "
|
||||
+ "a different FragmentManager. Fragment " + fragment.toString() + " is already"
|
||||
+ " attached to a FragmentManager.");
|
||||
}
|
||||
return super.show(fragment);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public FragmentTransaction detach(@NonNull Fragment fragment) {
|
||||
if (fragment.mFragmentManager != null && fragment.mFragmentManager != mManager) {
|
||||
throw new IllegalStateException("Cannot detach Fragment attached to "
|
||||
+ "a different FragmentManager. Fragment " + fragment.toString() + " is already"
|
||||
+ " attached to a FragmentManager.");
|
||||
}
|
||||
return super.detach(fragment);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public FragmentTransaction setPrimaryNavigationFragment(@Nullable Fragment fragment) {
|
||||
if (fragment != null
|
||||
&& fragment.mFragmentManager != null && fragment.mFragmentManager != mManager) {
|
||||
throw new IllegalStateException("Cannot setPrimaryNavigation for Fragment attached to "
|
||||
+ "a different FragmentManager. Fragment " + fragment.toString() + " is already"
|
||||
+ " attached to a FragmentManager.");
|
||||
}
|
||||
return super.setPrimaryNavigationFragment(fragment);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public FragmentTransaction setMaxLifecycle(@NonNull Fragment fragment,
|
||||
@NonNull Lifecycle.State state) {
|
||||
if (fragment.mFragmentManager != mManager) {
|
||||
throw new IllegalArgumentException("Cannot setMaxLifecycle for Fragment not attached to"
|
||||
+ " FragmentManager " + mManager);
|
||||
}
|
||||
if (!state.isAtLeast(Lifecycle.State.CREATED)) {
|
||||
throw new IllegalArgumentException("Cannot set maximum Lifecycle below "
|
||||
+ Lifecycle.State.CREATED);
|
||||
}
|
||||
return super.setMaxLifecycle(fragment, state);
|
||||
}
|
||||
|
||||
void bumpBackStackNesting(int amt) {
|
||||
if (!mAddToBackStack) {
|
||||
return;
|
||||
}
|
||||
if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting in " + this
|
||||
+ " by " + amt);
|
||||
final int numOps = mOps.size();
|
||||
for (int opNum = 0; opNum < numOps; opNum++) {
|
||||
final Op op = mOps.get(opNum);
|
||||
if (op.mFragment != null) {
|
||||
op.mFragment.mBackStackNesting += amt;
|
||||
if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting of "
|
||||
+ op.mFragment + " to " + op.mFragment.mBackStackNesting);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void runOnCommitRunnables() {
|
||||
if (mCommitRunnables != null) {
|
||||
for (int i = 0; i < mCommitRunnables.size(); i++) {
|
||||
mCommitRunnables.get(i).run();
|
||||
}
|
||||
mCommitRunnables = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int commit() {
|
||||
return commitInternal(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int commitAllowingStateLoss() {
|
||||
return commitInternal(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commitNow() {
|
||||
disallowAddToBackStack();
|
||||
mManager.execSingleAction(this, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commitNowAllowingStateLoss() {
|
||||
disallowAddToBackStack();
|
||||
mManager.execSingleAction(this, true);
|
||||
}
|
||||
|
||||
int commitInternal(boolean allowStateLoss) {
|
||||
if (mCommitted) throw new IllegalStateException("commit already called");
|
||||
if (FragmentManagerImpl.DEBUG) {
|
||||
Log.v(TAG, "Commit: " + this);
|
||||
LogWriter logw = new LogWriter(TAG);
|
||||
PrintWriter pw = new PrintWriter(logw);
|
||||
dump(" ", pw);
|
||||
pw.close();
|
||||
}
|
||||
mCommitted = true;
|
||||
if (mAddToBackStack) {
|
||||
mIndex = mManager.allocBackStackIndex(this);
|
||||
} else {
|
||||
mIndex = -1;
|
||||
}
|
||||
mManager.enqueueAction(this, allowStateLoss);
|
||||
return mIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of {@link FragmentManagerImpl.OpGenerator}.
|
||||
* This operation is added to the list of pending actions during {@link #commit()}, and
|
||||
* will be executed on the UI thread to run this FragmentTransaction.
|
||||
*
|
||||
* @param records Modified to add this BackStackRecord
|
||||
* @param isRecordPop Modified to add a false (this isn't a pop)
|
||||
* @return true always because the records and isRecordPop will always be changed
|
||||
*/
|
||||
@Override
|
||||
public boolean generateOps(ArrayList<BackStackRecord> records, ArrayList<Boolean> isRecordPop) {
|
||||
if (FragmentManagerImpl.DEBUG) {
|
||||
Log.v(TAG, "Run: " + this);
|
||||
}
|
||||
|
||||
records.add(this);
|
||||
isRecordPop.add(false);
|
||||
if (mAddToBackStack) {
|
||||
mManager.addBackStackState(this);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean interactsWith(int containerId) {
|
||||
final int numOps = mOps.size();
|
||||
for (int opNum = 0; opNum < numOps; opNum++) {
|
||||
final Op op = mOps.get(opNum);
|
||||
final int fragContainer = op.mFragment != null ? op.mFragment.mContainerId : 0;
|
||||
if (fragContainer != 0 && fragContainer == containerId) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean interactsWith(ArrayList<BackStackRecord> records, int startIndex, int endIndex) {
|
||||
if (endIndex == startIndex) {
|
||||
return false;
|
||||
}
|
||||
final int numOps = mOps.size();
|
||||
int lastContainer = -1;
|
||||
for (int opNum = 0; opNum < numOps; opNum++) {
|
||||
final Op op = mOps.get(opNum);
|
||||
final int container = op.mFragment != null ? op.mFragment.mContainerId : 0;
|
||||
if (container != 0 && container != lastContainer) {
|
||||
lastContainer = container;
|
||||
for (int i = startIndex; i < endIndex; i++) {
|
||||
BackStackRecord record = records.get(i);
|
||||
final int numThoseOps = record.mOps.size();
|
||||
for (int thoseOpIndex = 0; thoseOpIndex < numThoseOps; thoseOpIndex++) {
|
||||
final Op thatOp = record.mOps.get(thoseOpIndex);
|
||||
final int thatContainer = thatOp.mFragment != null
|
||||
? thatOp.mFragment.mContainerId : 0;
|
||||
if (thatContainer == container) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the operations contained within this transaction. The Fragment states will only
|
||||
* be modified if optimizations are not allowed.
|
||||
*/
|
||||
void executeOps() {
|
||||
final int numOps = mOps.size();
|
||||
for (int opNum = 0; opNum < numOps; opNum++) {
|
||||
final Op op = mOps.get(opNum);
|
||||
final Fragment f = op.mFragment;
|
||||
if (f != null) {
|
||||
f.setNextTransition(mTransition, mTransitionStyle);
|
||||
}
|
||||
switch (op.mCmd) {
|
||||
case OP_ADD:
|
||||
f.setNextAnim(op.mEnterAnim);
|
||||
mManager.addFragment(f, false);
|
||||
break;
|
||||
case OP_REMOVE:
|
||||
f.setNextAnim(op.mExitAnim);
|
||||
mManager.removeFragment(f);
|
||||
break;
|
||||
case OP_HIDE:
|
||||
f.setNextAnim(op.mExitAnim);
|
||||
mManager.hideFragment(f);
|
||||
break;
|
||||
case OP_SHOW:
|
||||
f.setNextAnim(op.mEnterAnim);
|
||||
mManager.showFragment(f);
|
||||
break;
|
||||
case OP_DETACH:
|
||||
f.setNextAnim(op.mExitAnim);
|
||||
mManager.detachFragment(f);
|
||||
break;
|
||||
case OP_ATTACH:
|
||||
f.setNextAnim(op.mEnterAnim);
|
||||
mManager.attachFragment(f);
|
||||
break;
|
||||
case OP_SET_PRIMARY_NAV:
|
||||
mManager.setPrimaryNavigationFragment(f);
|
||||
break;
|
||||
case OP_UNSET_PRIMARY_NAV:
|
||||
mManager.setPrimaryNavigationFragment(null);
|
||||
break;
|
||||
case OP_SET_MAX_LIFECYCLE:
|
||||
mManager.setMaxLifecycle(f, op.mCurrentMaxState);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown cmd: " + op.mCmd);
|
||||
}
|
||||
if (!mReorderingAllowed && op.mCmd != OP_ADD && f != null) {
|
||||
mManager.moveFragmentToExpectedState(f);
|
||||
}
|
||||
}
|
||||
if (!mReorderingAllowed) {
|
||||
// Added fragments are added at the end to comply with prior behavior.
|
||||
mManager.moveToState(mManager.mCurState, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverses the execution of the operations within this transaction. The Fragment states will
|
||||
* only be modified if reordering is not allowed.
|
||||
*
|
||||
* @param moveToState {@code true} if added fragments should be moved to their final state
|
||||
* in ordered transactions
|
||||
*/
|
||||
void executePopOps(boolean moveToState) {
|
||||
for (int opNum = mOps.size() - 1; opNum >= 0; opNum--) {
|
||||
final Op op = mOps.get(opNum);
|
||||
Fragment f = op.mFragment;
|
||||
if (f != null) {
|
||||
f.setNextTransition(FragmentManagerImpl.reverseTransit(mTransition),
|
||||
mTransitionStyle);
|
||||
}
|
||||
switch (op.mCmd) {
|
||||
case OP_ADD:
|
||||
f.setNextAnim(op.mPopExitAnim);
|
||||
mManager.removeFragment(f);
|
||||
break;
|
||||
case OP_REMOVE:
|
||||
f.setNextAnim(op.mPopEnterAnim);
|
||||
mManager.addFragment(f, false);
|
||||
break;
|
||||
case OP_HIDE:
|
||||
f.setNextAnim(op.mPopEnterAnim);
|
||||
mManager.showFragment(f);
|
||||
break;
|
||||
case OP_SHOW:
|
||||
f.setNextAnim(op.mPopExitAnim);
|
||||
mManager.hideFragment(f);
|
||||
break;
|
||||
case OP_DETACH:
|
||||
f.setNextAnim(op.mPopEnterAnim);
|
||||
mManager.attachFragment(f);
|
||||
break;
|
||||
case OP_ATTACH:
|
||||
f.setNextAnim(op.mPopExitAnim);
|
||||
mManager.detachFragment(f);
|
||||
break;
|
||||
case OP_SET_PRIMARY_NAV:
|
||||
mManager.setPrimaryNavigationFragment(null);
|
||||
break;
|
||||
case OP_UNSET_PRIMARY_NAV:
|
||||
mManager.setPrimaryNavigationFragment(f);
|
||||
break;
|
||||
case OP_SET_MAX_LIFECYCLE:
|
||||
mManager.setMaxLifecycle(f, op.mOldMaxState);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown cmd: " + op.mCmd);
|
||||
}
|
||||
if (!mReorderingAllowed && op.mCmd != OP_REMOVE && f != null) {
|
||||
mManager.moveFragmentToExpectedState(f);
|
||||
}
|
||||
}
|
||||
if (!mReorderingAllowed && moveToState) {
|
||||
mManager.moveToState(mManager.mCurState, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Expands all meta-ops into their more primitive equivalents. This must be called prior to
|
||||
* {@link #executeOps()} or any other call that operations on mOps for forward navigation.
|
||||
* It should not be called for pop/reverse navigation operations.
|
||||
*
|
||||
* <p>Removes all OP_REPLACE ops and replaces them with the proper add and remove
|
||||
* operations that are equivalent to the replace.</p>
|
||||
*
|
||||
* <p>Adds OP_UNSET_PRIMARY_NAV ops to match OP_SET_PRIMARY_NAV, OP_REMOVE and OP_DETACH
|
||||
* ops so that we can restore the old primary nav fragment later. Since callers call this
|
||||
* method in a loop before running ops from several transactions at once, the caller should
|
||||
* pass the return value from this method as the oldPrimaryNav parameter for the next call.
|
||||
* The first call in such a loop should pass the value of
|
||||
* {@link FragmentManager#getPrimaryNavigationFragment()}.</p>
|
||||
*
|
||||
* @param added Initialized to the fragments that are in the mManager.mAdded, this
|
||||
* will be modified to contain the fragments that will be in mAdded
|
||||
* after the execution ({@link #executeOps()}.
|
||||
* @param oldPrimaryNav The tracked primary navigation fragment as of the beginning of
|
||||
* this set of ops
|
||||
* @return the new oldPrimaryNav fragment after this record's ops would be run
|
||||
*/
|
||||
@SuppressWarnings("ReferenceEquality")
|
||||
Fragment expandOps(ArrayList<Fragment> added, Fragment oldPrimaryNav) {
|
||||
for (int opNum = 0; opNum < mOps.size(); opNum++) {
|
||||
final Op op = mOps.get(opNum);
|
||||
switch (op.mCmd) {
|
||||
case OP_ADD:
|
||||
case OP_ATTACH:
|
||||
added.add(op.mFragment);
|
||||
break;
|
||||
case OP_REMOVE:
|
||||
case OP_DETACH: {
|
||||
added.remove(op.mFragment);
|
||||
if (op.mFragment == oldPrimaryNav) {
|
||||
mOps.add(opNum, new Op(OP_UNSET_PRIMARY_NAV, op.mFragment));
|
||||
opNum++;
|
||||
oldPrimaryNav = null;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case OP_REPLACE: {
|
||||
final Fragment f = op.mFragment;
|
||||
final int containerId = f.mContainerId;
|
||||
boolean alreadyAdded = false;
|
||||
for (int i = added.size() - 1; i >= 0; i--) {
|
||||
final Fragment old = added.get(i);
|
||||
if (old.mContainerId == containerId) {
|
||||
if (old == f) {
|
||||
alreadyAdded = true;
|
||||
} else {
|
||||
// This is duplicated from above since we only make
|
||||
// a single pass for expanding ops. Unset any outgoing primary nav.
|
||||
if (old == oldPrimaryNav) {
|
||||
mOps.add(opNum, new Op(OP_UNSET_PRIMARY_NAV, old));
|
||||
opNum++;
|
||||
oldPrimaryNav = null;
|
||||
}
|
||||
final Op removeOp = new Op(OP_REMOVE, old);
|
||||
removeOp.mEnterAnim = op.mEnterAnim;
|
||||
removeOp.mPopEnterAnim = op.mPopEnterAnim;
|
||||
removeOp.mExitAnim = op.mExitAnim;
|
||||
removeOp.mPopExitAnim = op.mPopExitAnim;
|
||||
mOps.add(opNum, removeOp);
|
||||
added.remove(old);
|
||||
opNum++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (alreadyAdded) {
|
||||
mOps.remove(opNum);
|
||||
opNum--;
|
||||
} else {
|
||||
op.mCmd = OP_ADD;
|
||||
added.add(f);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case OP_SET_PRIMARY_NAV: {
|
||||
// It's ok if this is null, that means we will restore to no active
|
||||
// primary navigation fragment on a pop.
|
||||
mOps.add(opNum, new Op(OP_UNSET_PRIMARY_NAV, oldPrimaryNav));
|
||||
opNum++;
|
||||
// Will be set by the OP_SET_PRIMARY_NAV we inserted before when run
|
||||
oldPrimaryNav = op.mFragment;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return oldPrimaryNav;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes fragments that are added or removed during a pop operation.
|
||||
*
|
||||
* @param added Initialized to the fragments that are in the mManager.mAdded, this
|
||||
* will be modified to contain the fragments that will be in mAdded
|
||||
* after the execution ({@link #executeOps()}.
|
||||
* @param oldPrimaryNav The tracked primary navigation fragment as of the beginning of
|
||||
* this set of ops
|
||||
* @return the new oldPrimaryNav fragment after this record's ops would be popped
|
||||
*/
|
||||
Fragment trackAddedFragmentsInPop(ArrayList<Fragment> added, Fragment oldPrimaryNav) {
|
||||
for (int opNum = mOps.size() - 1; opNum >= 0; opNum--) {
|
||||
final Op op = mOps.get(opNum);
|
||||
switch (op.mCmd) {
|
||||
case OP_ADD:
|
||||
case OP_ATTACH:
|
||||
added.remove(op.mFragment);
|
||||
break;
|
||||
case OP_REMOVE:
|
||||
case OP_DETACH:
|
||||
added.add(op.mFragment);
|
||||
break;
|
||||
case OP_UNSET_PRIMARY_NAV:
|
||||
oldPrimaryNav = op.mFragment;
|
||||
break;
|
||||
case OP_SET_PRIMARY_NAV:
|
||||
oldPrimaryNav = null;
|
||||
break;
|
||||
case OP_SET_MAX_LIFECYCLE:
|
||||
op.mCurrentMaxState = op.mOldMaxState;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return oldPrimaryNav;
|
||||
}
|
||||
|
||||
boolean isPostponed() {
|
||||
for (int opNum = 0; opNum < mOps.size(); opNum++) {
|
||||
final Op op = mOps.get(opNum);
|
||||
if (isFragmentPostponed(op)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void setOnStartPostponedListener(Fragment.OnStartEnterTransitionListener listener) {
|
||||
for (int opNum = 0; opNum < mOps.size(); opNum++) {
|
||||
final Op op = mOps.get(opNum);
|
||||
if (isFragmentPostponed(op)) {
|
||||
op.mFragment.setOnStartEnterTransitionListener(listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isFragmentPostponed(Op op) {
|
||||
final Fragment fragment = op.mFragment;
|
||||
return fragment != null && fragment.mAdded && fragment.mView != null && !fragment.mDetached
|
||||
&& !fragment.mHidden && fragment.isPostponed();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public String getName() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return mOps.isEmpty();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
/*
|
||||
* Copyright 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.fragment.app;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.lifecycle.Lifecycle;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
@SuppressLint("BanParcelableUsage")
|
||||
final class BackStackState implements Parcelable {
|
||||
final int[] mOps;
|
||||
final ArrayList<String> mFragmentWhos;
|
||||
final int[] mOldMaxLifecycleStates;
|
||||
final int[] mCurrentMaxLifecycleStates;
|
||||
final int mTransition;
|
||||
final int mTransitionStyle;
|
||||
final String mName;
|
||||
final int mIndex;
|
||||
final int mBreadCrumbTitleRes;
|
||||
final CharSequence mBreadCrumbTitleText;
|
||||
final int mBreadCrumbShortTitleRes;
|
||||
final CharSequence mBreadCrumbShortTitleText;
|
||||
final ArrayList<String> mSharedElementSourceNames;
|
||||
final ArrayList<String> mSharedElementTargetNames;
|
||||
final boolean mReorderingAllowed;
|
||||
|
||||
public BackStackState(BackStackRecord bse) {
|
||||
final int numOps = bse.mOps.size();
|
||||
mOps = new int[numOps * 5];
|
||||
|
||||
if (!bse.mAddToBackStack) {
|
||||
throw new IllegalStateException("Not on back stack");
|
||||
}
|
||||
|
||||
mFragmentWhos = new ArrayList<>(numOps);
|
||||
mOldMaxLifecycleStates = new int[numOps];
|
||||
mCurrentMaxLifecycleStates = new int[numOps];
|
||||
int pos = 0;
|
||||
for (int opNum = 0; opNum < numOps; opNum++) {
|
||||
final BackStackRecord.Op op = bse.mOps.get(opNum);
|
||||
mOps[pos++] = op.mCmd;
|
||||
mFragmentWhos.add(op.mFragment != null ? op.mFragment.mWho : null);
|
||||
mOps[pos++] = op.mEnterAnim;
|
||||
mOps[pos++] = op.mExitAnim;
|
||||
mOps[pos++] = op.mPopEnterAnim;
|
||||
mOps[pos++] = op.mPopExitAnim;
|
||||
mOldMaxLifecycleStates[opNum] = op.mOldMaxState.ordinal();
|
||||
mCurrentMaxLifecycleStates[opNum] = op.mCurrentMaxState.ordinal();
|
||||
}
|
||||
mTransition = bse.mTransition;
|
||||
mTransitionStyle = bse.mTransitionStyle;
|
||||
mName = bse.mName;
|
||||
mIndex = bse.mIndex;
|
||||
mBreadCrumbTitleRes = bse.mBreadCrumbTitleRes;
|
||||
mBreadCrumbTitleText = bse.mBreadCrumbTitleText;
|
||||
mBreadCrumbShortTitleRes = bse.mBreadCrumbShortTitleRes;
|
||||
mBreadCrumbShortTitleText = bse.mBreadCrumbShortTitleText;
|
||||
mSharedElementSourceNames = bse.mSharedElementSourceNames;
|
||||
mSharedElementTargetNames = bse.mSharedElementTargetNames;
|
||||
mReorderingAllowed = bse.mReorderingAllowed;
|
||||
}
|
||||
|
||||
public BackStackState(Parcel in) {
|
||||
mOps = in.createIntArray();
|
||||
mFragmentWhos = in.createStringArrayList();
|
||||
mOldMaxLifecycleStates = in.createIntArray();
|
||||
mCurrentMaxLifecycleStates = in.createIntArray();
|
||||
mTransition = in.readInt();
|
||||
mTransitionStyle = in.readInt();
|
||||
mName = in.readString();
|
||||
mIndex = in.readInt();
|
||||
mBreadCrumbTitleRes = in.readInt();
|
||||
mBreadCrumbTitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
|
||||
mBreadCrumbShortTitleRes = in.readInt();
|
||||
mBreadCrumbShortTitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
|
||||
mSharedElementSourceNames = in.createStringArrayList();
|
||||
mSharedElementTargetNames = in.createStringArrayList();
|
||||
mReorderingAllowed = in.readInt() != 0;
|
||||
}
|
||||
|
||||
public BackStackRecord instantiate(FragmentManagerImpl fm) {
|
||||
BackStackRecord bse = new BackStackRecord(fm);
|
||||
int pos = 0;
|
||||
int num = 0;
|
||||
while (pos < mOps.length) {
|
||||
BackStackRecord.Op op = new BackStackRecord.Op();
|
||||
op.mCmd = mOps[pos++];
|
||||
if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG,
|
||||
"Instantiate " + bse + " op #" + num + " base fragment #" + mOps[pos]);
|
||||
String fWho = mFragmentWhos.get(num);
|
||||
if (fWho != null) {
|
||||
Fragment f = fm.mActive.get(fWho);
|
||||
op.mFragment = f;
|
||||
} else {
|
||||
op.mFragment = null;
|
||||
}
|
||||
op.mOldMaxState = Lifecycle.State.values()[mOldMaxLifecycleStates[num]];
|
||||
op.mCurrentMaxState = Lifecycle.State.values()[mCurrentMaxLifecycleStates[num]];
|
||||
op.mEnterAnim = mOps[pos++];
|
||||
op.mExitAnim = mOps[pos++];
|
||||
op.mPopEnterAnim = mOps[pos++];
|
||||
op.mPopExitAnim = mOps[pos++];
|
||||
bse.mEnterAnim = op.mEnterAnim;
|
||||
bse.mExitAnim = op.mExitAnim;
|
||||
bse.mPopEnterAnim = op.mPopEnterAnim;
|
||||
bse.mPopExitAnim = op.mPopExitAnim;
|
||||
bse.addOp(op);
|
||||
num++;
|
||||
}
|
||||
bse.mTransition = mTransition;
|
||||
bse.mTransitionStyle = mTransitionStyle;
|
||||
bse.mName = mName;
|
||||
bse.mIndex = mIndex;
|
||||
bse.mAddToBackStack = true;
|
||||
bse.mBreadCrumbTitleRes = mBreadCrumbTitleRes;
|
||||
bse.mBreadCrumbTitleText = mBreadCrumbTitleText;
|
||||
bse.mBreadCrumbShortTitleRes = mBreadCrumbShortTitleRes;
|
||||
bse.mBreadCrumbShortTitleText = mBreadCrumbShortTitleText;
|
||||
bse.mSharedElementSourceNames = mSharedElementSourceNames;
|
||||
bse.mSharedElementTargetNames = mSharedElementTargetNames;
|
||||
bse.mReorderingAllowed = mReorderingAllowed;
|
||||
bse.bumpBackStackNesting(1);
|
||||
return bse;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeIntArray(mOps);
|
||||
dest.writeStringList(mFragmentWhos);
|
||||
dest.writeIntArray(mOldMaxLifecycleStates);
|
||||
dest.writeIntArray(mCurrentMaxLifecycleStates);
|
||||
dest.writeInt(mTransition);
|
||||
dest.writeInt(mTransitionStyle);
|
||||
dest.writeString(mName);
|
||||
dest.writeInt(mIndex);
|
||||
dest.writeInt(mBreadCrumbTitleRes);
|
||||
TextUtils.writeToParcel(mBreadCrumbTitleText, dest, 0);
|
||||
dest.writeInt(mBreadCrumbShortTitleRes);
|
||||
TextUtils.writeToParcel(mBreadCrumbShortTitleText, dest, 0);
|
||||
dest.writeStringList(mSharedElementSourceNames);
|
||||
dest.writeStringList(mSharedElementTargetNames);
|
||||
dest.writeInt(mReorderingAllowed ? 1 : 0);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<BackStackState> CREATOR
|
||||
= new Parcelable.Creator<BackStackState>() {
|
||||
@Override
|
||||
public BackStackState createFromParcel(Parcel in) {
|
||||
return new BackStackState(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BackStackState[] newArray(int size) {
|
||||
return new BackStackState[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,548 @@
|
||||
/*
|
||||
* Copyright 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.fragment.app;
|
||||
|
||||
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RestrictTo;
|
||||
import androidx.annotation.StyleRes;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* Static library support version of the framework's {@link android.app.DialogFragment}.
|
||||
* Used to write apps that run on platforms prior to Android 3.0. When running
|
||||
* on Android 3.0 or above, this implementation is still used; it does not try
|
||||
* to switch to the framework's implementation. See the framework SDK
|
||||
* documentation for a class overview.
|
||||
*/
|
||||
public class DialogFragment extends Fragment
|
||||
implements DialogInterface.OnCancelListener, DialogInterface.OnDismissListener {
|
||||
|
||||
/** @hide */
|
||||
@RestrictTo(LIBRARY_GROUP_PREFIX)
|
||||
@IntDef({STYLE_NORMAL, STYLE_NO_TITLE, STYLE_NO_FRAME, STYLE_NO_INPUT})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
private @interface DialogStyle {}
|
||||
|
||||
/**
|
||||
* Style for {@link #setStyle(int, int)}: a basic,
|
||||
* normal dialog.
|
||||
*/
|
||||
public static final int STYLE_NORMAL = 0;
|
||||
|
||||
/**
|
||||
* Style for {@link #setStyle(int, int)}: don't include
|
||||
* a title area.
|
||||
*/
|
||||
public static final int STYLE_NO_TITLE = 1;
|
||||
|
||||
/**
|
||||
* Style for {@link #setStyle(int, int)}: don't draw
|
||||
* any frame at all; the view hierarchy returned by {@link #onCreateView}
|
||||
* is entirely responsible for drawing the dialog.
|
||||
*/
|
||||
public static final int STYLE_NO_FRAME = 2;
|
||||
|
||||
/**
|
||||
* Style for {@link #setStyle(int, int)}: like
|
||||
* {@link #STYLE_NO_FRAME}, but also disables all input to the dialog.
|
||||
* The user can not touch it, and its window will not receive input focus.
|
||||
*/
|
||||
public static final int STYLE_NO_INPUT = 3;
|
||||
|
||||
private static final String SAVED_DIALOG_STATE_TAG = "android:savedDialogState";
|
||||
private static final String SAVED_STYLE = "android:style";
|
||||
private static final String SAVED_THEME = "android:theme";
|
||||
private static final String SAVED_CANCELABLE = "android:cancelable";
|
||||
private static final String SAVED_SHOWS_DIALOG = "android:showsDialog";
|
||||
private static final String SAVED_BACK_STACK_ID = "android:backStackId";
|
||||
|
||||
private Handler mHandler;
|
||||
private Runnable mDismissRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (mDialog != null) {
|
||||
onDismiss(mDialog);
|
||||
}
|
||||
}
|
||||
};
|
||||
int mStyle = STYLE_NORMAL;
|
||||
int mTheme = 0;
|
||||
boolean mCancelable = true;
|
||||
boolean mShowsDialog = true;
|
||||
int mBackStackId = -1;
|
||||
|
||||
@Nullable Dialog mDialog;
|
||||
boolean mViewDestroyed;
|
||||
boolean mDismissed;
|
||||
boolean mShownByMe;
|
||||
|
||||
public DialogFragment() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Call to customize the basic appearance and behavior of the
|
||||
* fragment's dialog. This can be used for some common dialog behaviors,
|
||||
* taking care of selecting flags, theme, and other options for you. The
|
||||
* same effect can be achieve by manually setting Dialog and Window
|
||||
* attributes yourself. Calling this after the fragment's Dialog is
|
||||
* created will have no effect.
|
||||
*
|
||||
* @param style Selects a standard style: may be {@link #STYLE_NORMAL},
|
||||
* {@link #STYLE_NO_TITLE}, {@link #STYLE_NO_FRAME}, or
|
||||
* {@link #STYLE_NO_INPUT}.
|
||||
* @param theme Optional custom theme. If 0, an appropriate theme (based
|
||||
* on the style) will be selected for you.
|
||||
*/
|
||||
public void setStyle(@DialogStyle int style, @StyleRes int theme) {
|
||||
mStyle = style;
|
||||
if (mStyle == STYLE_NO_FRAME || mStyle == STYLE_NO_INPUT) {
|
||||
mTheme = android.R.style.Theme_Panel;
|
||||
}
|
||||
if (theme != 0) {
|
||||
mTheme = theme;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the dialog, adding the fragment to the given FragmentManager. This
|
||||
* is a convenience for explicitly creating a transaction, adding the
|
||||
* fragment to it with the given tag, and {@link FragmentTransaction#commit() committing} it.
|
||||
* This does <em>not</em> add the transaction to the fragment back stack. When the fragment
|
||||
* is dismissed, a new transaction will be executed to remove it from
|
||||
* the activity.
|
||||
* @param manager The FragmentManager this fragment will be added to.
|
||||
* @param tag The tag for this fragment, as per
|
||||
* {@link FragmentTransaction#add(Fragment, String) FragmentTransaction.add}.
|
||||
*/
|
||||
public void show(@NonNull FragmentManager manager, @Nullable String tag) {
|
||||
mDismissed = false;
|
||||
mShownByMe = true;
|
||||
FragmentTransaction ft = manager.beginTransaction();
|
||||
ft.add(this, tag);
|
||||
ft.commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the dialog, adding the fragment using an existing transaction
|
||||
* and then {@link FragmentTransaction#commit() committing} the transaction.
|
||||
* @param transaction An existing transaction in which to add the fragment.
|
||||
* @param tag The tag for this fragment, as per
|
||||
* {@link FragmentTransaction#add(Fragment, String) FragmentTransaction.add}.
|
||||
* @return Returns the identifier of the committed transaction, as per
|
||||
* {@link FragmentTransaction#commit() FragmentTransaction.commit()}.
|
||||
*/
|
||||
public int show(@NonNull FragmentTransaction transaction, @Nullable String tag) {
|
||||
mDismissed = false;
|
||||
mShownByMe = true;
|
||||
transaction.add(this, tag);
|
||||
mViewDestroyed = false;
|
||||
mBackStackId = transaction.commit();
|
||||
return mBackStackId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the dialog, immediately adding the fragment to the given FragmentManager. This
|
||||
* is a convenience for explicitly creating a transaction, adding the
|
||||
* fragment to it with the given tag, and calling {@link FragmentTransaction#commitNow()}.
|
||||
* This does <em>not</em> add the transaction to the fragment back stack. When the fragment
|
||||
* is dismissed, a new transaction will be executed to remove it from
|
||||
* the activity.
|
||||
* @param manager The FragmentManager this fragment will be added to.
|
||||
* @param tag The tag for this fragment, as per
|
||||
* {@link FragmentTransaction#add(Fragment, String) FragmentTransaction.add}.
|
||||
*/
|
||||
public void showNow(@NonNull FragmentManager manager, @Nullable String tag) {
|
||||
mDismissed = false;
|
||||
mShownByMe = true;
|
||||
FragmentTransaction ft = manager.beginTransaction();
|
||||
ft.add(this, tag);
|
||||
ft.commitNow();
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismiss the fragment and its dialog. If the fragment was added to the
|
||||
* back stack, all back stack state up to and including this entry will
|
||||
* be popped. Otherwise, a new transaction will be committed to remove
|
||||
* the fragment.
|
||||
*/
|
||||
public void dismiss() {
|
||||
dismissInternal(false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Version of {@link #dismiss()} that uses
|
||||
* {@link FragmentTransaction#commitAllowingStateLoss()
|
||||
* FragmentTransaction.commitAllowingStateLoss()}. See linked
|
||||
* documentation for further details.
|
||||
*/
|
||||
public void dismissAllowingStateLoss() {
|
||||
dismissInternal(true, false);
|
||||
}
|
||||
|
||||
void dismissInternal(boolean allowStateLoss, boolean fromOnDismiss) {
|
||||
if (mDismissed) {
|
||||
return;
|
||||
}
|
||||
mDismissed = true;
|
||||
mShownByMe = false;
|
||||
if (mDialog != null) {
|
||||
// Instead of waiting for a posted onDismiss(), null out
|
||||
// the listener and call onDismiss() manually to ensure
|
||||
// that the callback happens before onDestroy()
|
||||
mDialog.setOnDismissListener(null);
|
||||
mDialog.dismiss();
|
||||
if (!fromOnDismiss) {
|
||||
// onDismiss() is always called on the main thread, so
|
||||
// we mimic that behavior here. The difference here is that
|
||||
// we don't post the message to ensure that the onDismiss()
|
||||
// callback still happens before onDestroy()
|
||||
if (Looper.myLooper() == mHandler.getLooper()) {
|
||||
onDismiss(mDialog);
|
||||
} else {
|
||||
mHandler.post(mDismissRunnable);
|
||||
}
|
||||
}
|
||||
}
|
||||
mViewDestroyed = true;
|
||||
if (mBackStackId >= 0) {
|
||||
requireFragmentManager().popBackStack(mBackStackId,
|
||||
FragmentManager.POP_BACK_STACK_INCLUSIVE);
|
||||
mBackStackId = -1;
|
||||
} else {
|
||||
FragmentTransaction ft = requireFragmentManager().beginTransaction();
|
||||
ft.remove(this);
|
||||
if (allowStateLoss) {
|
||||
ft.commitAllowingStateLoss();
|
||||
} else {
|
||||
ft.commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link Dialog} this fragment is currently controlling.
|
||||
*
|
||||
* @see #requireDialog()
|
||||
*/
|
||||
@Nullable
|
||||
public Dialog getDialog() {
|
||||
return mDialog;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link Dialog} this fragment is currently controlling.
|
||||
*
|
||||
* @throws IllegalStateException if the Dialog has not yet been created (before
|
||||
* {@link #onCreateDialog(Bundle)}) or has been destroyed (after {@link #onDestroyView()}.
|
||||
* @see #getDialog()
|
||||
*/
|
||||
@NonNull
|
||||
public final Dialog requireDialog() {
|
||||
Dialog dialog = getDialog();
|
||||
if (dialog == null) {
|
||||
throw new IllegalStateException("DialogFragment " + this + " does not have a Dialog.");
|
||||
}
|
||||
return dialog;
|
||||
}
|
||||
|
||||
@StyleRes
|
||||
public int getTheme() {
|
||||
return mTheme;
|
||||
}
|
||||
|
||||
/**
|
||||
* Control whether the shown Dialog is cancelable. Use this instead of
|
||||
* directly calling {@link Dialog#setCancelable(boolean)
|
||||
* Dialog.setCancelable(boolean)}, because DialogFragment needs to change
|
||||
* its behavior based on this.
|
||||
*
|
||||
* @param cancelable If true, the dialog is cancelable. The default
|
||||
* is true.
|
||||
*/
|
||||
public void setCancelable(boolean cancelable) {
|
||||
mCancelable = cancelable;
|
||||
if (mDialog != null) mDialog.setCancelable(cancelable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current value of {@link #setCancelable(boolean)}.
|
||||
*/
|
||||
public boolean isCancelable() {
|
||||
return mCancelable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Controls whether this fragment should be shown in a dialog. If not
|
||||
* set, no Dialog will be created in {@link #onActivityCreated(Bundle)},
|
||||
* and the fragment's view hierarchy will thus not be added to it. This
|
||||
* allows you to instead use it as a normal fragment (embedded inside of
|
||||
* its activity).
|
||||
*
|
||||
* <p>This is normally set for you based on whether the fragment is
|
||||
* associated with a container view ID passed to
|
||||
* {@link FragmentTransaction#add(int, Fragment) FragmentTransaction.add(int, Fragment)}.
|
||||
* If the fragment was added with a container, setShowsDialog will be
|
||||
* initialized to false; otherwise, it will be true.
|
||||
*
|
||||
* @param showsDialog If true, the fragment will be displayed in a Dialog.
|
||||
* If false, no Dialog will be created and the fragment's view hierarchy
|
||||
* left undisturbed.
|
||||
*/
|
||||
public void setShowsDialog(boolean showsDialog) {
|
||||
mShowsDialog = showsDialog;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current value of {@link #setShowsDialog(boolean)}.
|
||||
*/
|
||||
public boolean getShowsDialog() {
|
||||
return mShowsDialog;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(@NonNull Context context) {
|
||||
super.onAttach(context);
|
||||
if (!mShownByMe) {
|
||||
// If not explicitly shown through our API, take this as an
|
||||
// indication that the dialog is no longer dismissed.
|
||||
mDismissed = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetach() {
|
||||
super.onDetach();
|
||||
if (!mShownByMe && !mDismissed) {
|
||||
// The fragment was not shown by a direct call here, it is not
|
||||
// dismissed, and now it is being detached... well, okay, thou
|
||||
// art now dismissed. Have fun.
|
||||
mDismissed = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
// This assumes that onCreate() is being called on the main thread
|
||||
mHandler = new Handler();
|
||||
|
||||
mShowsDialog = mContainerId == 0;
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
mStyle = savedInstanceState.getInt(SAVED_STYLE, STYLE_NORMAL);
|
||||
mTheme = savedInstanceState.getInt(SAVED_THEME, 0);
|
||||
mCancelable = savedInstanceState.getBoolean(SAVED_CANCELABLE, true);
|
||||
mShowsDialog = savedInstanceState.getBoolean(SAVED_SHOWS_DIALOG, mShowsDialog);
|
||||
mBackStackId = savedInstanceState.getInt(SAVED_BACK_STACK_ID, -1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public LayoutInflater onGetLayoutInflater(@Nullable Bundle savedInstanceState) {
|
||||
if (!mShowsDialog) {
|
||||
return super.onGetLayoutInflater(savedInstanceState);
|
||||
}
|
||||
|
||||
mDialog = onCreateDialog(savedInstanceState);
|
||||
|
||||
if (mDialog != null) {
|
||||
setupDialog(mDialog, mStyle);
|
||||
|
||||
return (LayoutInflater) mDialog.getContext().getSystemService(
|
||||
Context.LAYOUT_INFLATER_SERVICE);
|
||||
}
|
||||
return (LayoutInflater) mHost.getContext().getSystemService(
|
||||
Context.LAYOUT_INFLATER_SERVICE);
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@RestrictTo(LIBRARY_GROUP_PREFIX)
|
||||
public void setupDialog(@NonNull Dialog dialog, int style) {
|
||||
switch (style) {
|
||||
case STYLE_NO_INPUT:
|
||||
dialog.getWindow().addFlags(
|
||||
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
|
||||
WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
|
||||
// fall through...
|
||||
case STYLE_NO_FRAME:
|
||||
case STYLE_NO_TITLE:
|
||||
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Override to build your own custom Dialog container. This is typically
|
||||
* used to show an AlertDialog instead of a generic Dialog; when doing so,
|
||||
* {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)} does not need
|
||||
* to be implemented since the AlertDialog takes care of its own content.
|
||||
*
|
||||
* <p>This method will be called after {@link #onCreate(Bundle)} and
|
||||
* before {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}. The
|
||||
* default implementation simply instantiates and returns a {@link Dialog}
|
||||
* class.
|
||||
*
|
||||
* <p><em>Note: DialogFragment own the {@link Dialog#setOnCancelListener
|
||||
* Dialog.setOnCancelListener} and {@link Dialog#setOnDismissListener
|
||||
* Dialog.setOnDismissListener} callbacks. You must not set them yourself.</em>
|
||||
* To find out about these events, override {@link #onCancel(DialogInterface)}
|
||||
* and {@link #onDismiss(DialogInterface)}.</p>
|
||||
*
|
||||
* @param savedInstanceState The last saved instance state of the Fragment,
|
||||
* or null if this is a freshly created Fragment.
|
||||
*
|
||||
* @return Return a new Dialog instance to be displayed by the Fragment.
|
||||
*/
|
||||
@NonNull
|
||||
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
|
||||
return new Dialog(requireContext(), getTheme());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel(@NonNull DialogInterface dialog) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDismiss(@NonNull DialogInterface dialog) {
|
||||
if (!mViewDestroyed) {
|
||||
// Note: we need to use allowStateLoss, because the dialog
|
||||
// dispatches this asynchronously so we can receive the call
|
||||
// after the activity is paused. Worst case, when the user comes
|
||||
// back to the activity they see the dialog again.
|
||||
dismissInternal(true, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
if (!mShowsDialog) {
|
||||
return;
|
||||
}
|
||||
|
||||
View view = getView();
|
||||
if (view != null) {
|
||||
if (view.getParent() != null) {
|
||||
throw new IllegalStateException(
|
||||
"DialogFragment can not be attached to a container view");
|
||||
}
|
||||
mDialog.setContentView(view);
|
||||
}
|
||||
final Activity activity = getActivity();
|
||||
if (activity != null) {
|
||||
mDialog.setOwnerActivity(activity);
|
||||
}
|
||||
mDialog.setCancelable(mCancelable);
|
||||
mDialog.setOnCancelListener(this);
|
||||
mDialog.setOnDismissListener(this);
|
||||
if (savedInstanceState != null) {
|
||||
Bundle dialogState = savedInstanceState.getBundle(SAVED_DIALOG_STATE_TAG);
|
||||
if (dialogState != null) {
|
||||
mDialog.onRestoreInstanceState(dialogState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
|
||||
if (mDialog != null) {
|
||||
mViewDestroyed = false;
|
||||
mDialog.show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
if (mDialog != null) {
|
||||
Bundle dialogState = mDialog.onSaveInstanceState();
|
||||
if (dialogState != null) {
|
||||
outState.putBundle(SAVED_DIALOG_STATE_TAG, dialogState);
|
||||
}
|
||||
}
|
||||
if (mStyle != STYLE_NORMAL) {
|
||||
outState.putInt(SAVED_STYLE, mStyle);
|
||||
}
|
||||
if (mTheme != 0) {
|
||||
outState.putInt(SAVED_THEME, mTheme);
|
||||
}
|
||||
if (!mCancelable) {
|
||||
outState.putBoolean(SAVED_CANCELABLE, mCancelable);
|
||||
}
|
||||
if (!mShowsDialog) {
|
||||
outState.putBoolean(SAVED_SHOWS_DIALOG, mShowsDialog);
|
||||
}
|
||||
if (mBackStackId != -1) {
|
||||
outState.putInt(SAVED_BACK_STACK_ID, mBackStackId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
if (mDialog != null) {
|
||||
mDialog.hide();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove dialog.
|
||||
*/
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
if (mDialog != null) {
|
||||
// Set removed here because this dismissal is just to hide
|
||||
// the dialog -- we don't want this to cause the fragment to
|
||||
// actually be removed.
|
||||
mViewDestroyed = true;
|
||||
// Instead of waiting for a posted onDismiss(), null out
|
||||
// the listener and call onDismiss() manually to ensure
|
||||
// that the callback happens before onDestroy()
|
||||
mDialog.setOnDismissListener(null);
|
||||
mDialog.dismiss();
|
||||
if (!mDismissed) {
|
||||
// Don't send a second onDismiss() callback if we've already
|
||||
// dismissed the dialog manually in dismissInternal()
|
||||
onDismiss(mDialog);
|
||||
}
|
||||
mDialog = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
3041
fragment-1.1.0/src/main/java/androidx/fragment/app/Fragment.java
Normal file
3041
fragment-1.1.0/src/main/java/androidx/fragment/app/Fragment.java
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.fragment.app;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.IdRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
|
||||
/**
|
||||
* Callbacks to a {@link Fragment}'s container.
|
||||
*/
|
||||
public abstract class FragmentContainer {
|
||||
/**
|
||||
* Return the view with the given resource ID. May return {@code null} if the
|
||||
* view is not a child of this container.
|
||||
*/
|
||||
@Nullable
|
||||
public abstract View onFindViewById(@IdRes int id);
|
||||
|
||||
/**
|
||||
* Return {@code true} if the container holds any view.
|
||||
*/
|
||||
public abstract boolean onHasView();
|
||||
|
||||
|
||||
/**
|
||||
* Creates an instance of the specified fragment, can be overridden to construct fragments
|
||||
* with dependencies, or change the fragment being constructed. By default just calls
|
||||
* {@link Fragment#instantiate(Context, String, Bundle)}.
|
||||
* @deprecated Use {@link FragmentManager#setFragmentFactory} to control how Fragments are
|
||||
* instantiated.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
@Deprecated
|
||||
@NonNull
|
||||
public Fragment instantiate(@NonNull Context context, @NonNull String className,
|
||||
@Nullable Bundle arguments) {
|
||||
return Fragment.instantiate(context, className, arguments);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,532 @@
|
||||
/*
|
||||
* Copyright 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.fragment.app;
|
||||
|
||||
import static androidx.core.util.Preconditions.checkNotNull;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Parcelable;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.collection.SimpleArrayMap;
|
||||
import androidx.lifecycle.ViewModelStoreOwner;
|
||||
import androidx.loader.app.LoaderManager;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Provides integration points with a {@link FragmentManager} for a fragment host.
|
||||
* <p>
|
||||
* It is the responsibility of the host to take care of the Fragment's lifecycle.
|
||||
* The methods provided by {@link FragmentController} are for that purpose.
|
||||
*/
|
||||
public class FragmentController {
|
||||
private final FragmentHostCallback<?> mHost;
|
||||
|
||||
/**
|
||||
* Returns a {@link FragmentController}.
|
||||
*/
|
||||
@NonNull
|
||||
public static FragmentController createController(@NonNull FragmentHostCallback<?> callbacks) {
|
||||
return new FragmentController(checkNotNull(callbacks, "callbacks == null"));
|
||||
}
|
||||
|
||||
private FragmentController(FragmentHostCallback<?> callbacks) {
|
||||
mHost = callbacks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link FragmentManager} for this controller.
|
||||
*/
|
||||
@NonNull
|
||||
public FragmentManager getSupportFragmentManager() {
|
||||
return mHost.mFragmentManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link LoaderManager}.
|
||||
*
|
||||
* @deprecated Loaders are managed separately from FragmentController and this now throws an
|
||||
* {@link UnsupportedOperationException}. Use {@link LoaderManager#getInstance} to obtain a
|
||||
* LoaderManager.
|
||||
* @see LoaderManager#getInstance
|
||||
*/
|
||||
@Deprecated
|
||||
@SuppressLint("UnknownNullness")
|
||||
public LoaderManager getSupportLoaderManager() {
|
||||
throw new UnsupportedOperationException("Loaders are managed separately from "
|
||||
+ "FragmentController, use LoaderManager.getInstance() to obtain a LoaderManager.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a fragment with the given identifier.
|
||||
*/
|
||||
@Nullable
|
||||
public Fragment findFragmentByWho(@NonNull String who) {
|
||||
return mHost.mFragmentManager.findFragmentByWho(who);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of active fragments.
|
||||
*/
|
||||
public int getActiveFragmentsCount() {
|
||||
return mHost.mFragmentManager.getActiveFragmentCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of active fragments.
|
||||
*/
|
||||
@NonNull
|
||||
public List<Fragment> getActiveFragments(@SuppressLint("UnknownNullness")
|
||||
List<Fragment> actives) {
|
||||
return mHost.mFragmentManager.getActiveFragments();
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches the host to the FragmentManager for this controller. The host must be
|
||||
* attached before the FragmentManager can be used to manage Fragments.
|
||||
*/
|
||||
public void attachHost(@Nullable Fragment parent) {
|
||||
mHost.mFragmentManager.attachController(
|
||||
mHost, mHost /*container*/, parent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a Fragment's view.
|
||||
*
|
||||
* @param parent The parent that the created view will be placed
|
||||
* in; <em>note that this may be null</em>.
|
||||
* @param name Tag name to be inflated.
|
||||
* @param context The context the view is being created in.
|
||||
* @param attrs Inflation attributes as specified in XML file.
|
||||
*
|
||||
* @return view the newly created view
|
||||
*/
|
||||
@Nullable
|
||||
public View onCreateView(@Nullable View parent, @NonNull String name, @NonNull Context context,
|
||||
@NonNull AttributeSet attrs) {
|
||||
return mHost.mFragmentManager.onCreateView(parent, name, context, attrs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the fragment state as unsaved. This allows for "state loss" detection.
|
||||
*/
|
||||
public void noteStateNotSaved() {
|
||||
mHost.mFragmentManager.noteStateNotSaved();
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the state for all Fragments.
|
||||
*
|
||||
* @see #restoreSaveState(Parcelable)
|
||||
*/
|
||||
@Nullable
|
||||
public Parcelable saveAllState() {
|
||||
return mHost.mFragmentManager.saveAllState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores the saved state for all Fragments. The given Fragment list are Fragment
|
||||
* instances retained across configuration changes.
|
||||
*
|
||||
* @deprecated Have your {@link FragmentHostCallback} implement {@link ViewModelStoreOwner}
|
||||
* to automatically restore the Fragment's non configuration state and use
|
||||
* {@link #restoreSaveState(Parcelable)} to restore the Fragment's save state.
|
||||
*/
|
||||
@Deprecated
|
||||
public void restoreAllState(@Nullable Parcelable state,
|
||||
@Nullable List<Fragment> nonConfigList) {
|
||||
mHost.mFragmentManager.restoreAllState(state,
|
||||
new FragmentManagerNonConfig(nonConfigList, null, null));
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores the saved state for all Fragments. The given FragmentManagerNonConfig are Fragment
|
||||
* instances retained across configuration changes, including nested fragments
|
||||
*
|
||||
* @deprecated Have your {@link FragmentHostCallback} implement {@link ViewModelStoreOwner}
|
||||
* to automatically restore the Fragment's non configuration state and use
|
||||
* {@link #restoreSaveState(Parcelable)} to restore the Fragment's save state.
|
||||
*/
|
||||
@Deprecated
|
||||
public void restoreAllState(@Nullable Parcelable state,
|
||||
@Nullable FragmentManagerNonConfig nonConfig) {
|
||||
mHost.mFragmentManager.restoreAllState(state, nonConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores the saved state for all Fragments.
|
||||
*
|
||||
* @param state the saved state containing the Parcelable returned by {@link #saveAllState()}
|
||||
* @see #saveAllState()
|
||||
*/
|
||||
public void restoreSaveState(@Nullable Parcelable state) {
|
||||
if (!(mHost instanceof ViewModelStoreOwner)) {
|
||||
throw new IllegalStateException("Your FragmentHostCallback must implement "
|
||||
+ "ViewModelStoreOwner to call restoreSaveState(). Call restoreAllState() "
|
||||
+ " if you're still using retainNestedNonConfig().");
|
||||
}
|
||||
mHost.mFragmentManager.restoreSaveState(state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of Fragments that have opted to retain their instance across
|
||||
* configuration changes.
|
||||
*
|
||||
* @deprecated Have your {@link FragmentHostCallback} implement {@link ViewModelStoreOwner}
|
||||
* to automatically retain the Fragment's non configuration state.
|
||||
*/
|
||||
@Deprecated
|
||||
@Nullable
|
||||
public List<Fragment> retainNonConfig() {
|
||||
FragmentManagerNonConfig nonconf = mHost.mFragmentManager.retainNonConfig();
|
||||
return nonconf != null && nonconf.getFragments() != null
|
||||
? new ArrayList<>(nonconf.getFragments())
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a nested tree of Fragments that have opted to retain their instance across
|
||||
* configuration changes.
|
||||
*
|
||||
* @deprecated Have your {@link FragmentHostCallback} implement {@link ViewModelStoreOwner}
|
||||
* to automatically retain the Fragment's non configuration state.
|
||||
*/
|
||||
@Deprecated
|
||||
@Nullable
|
||||
public FragmentManagerNonConfig retainNestedNonConfig() {
|
||||
return mHost.mFragmentManager.retainNonConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves all Fragments managed by the controller's FragmentManager
|
||||
* into the create state.
|
||||
* <p>Call when Fragments should be created.
|
||||
*
|
||||
* @see Fragment#onCreate(Bundle)
|
||||
*/
|
||||
public void dispatchCreate() {
|
||||
mHost.mFragmentManager.dispatchCreate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves all Fragments managed by the controller's FragmentManager
|
||||
* into the activity created state.
|
||||
* <p>Call when Fragments should be informed their host has been created.
|
||||
*
|
||||
* @see Fragment#onActivityCreated(Bundle)
|
||||
*/
|
||||
public void dispatchActivityCreated() {
|
||||
mHost.mFragmentManager.dispatchActivityCreated();
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves all Fragments managed by the controller's FragmentManager
|
||||
* into the start state.
|
||||
* <p>Call when Fragments should be started.
|
||||
*
|
||||
* @see Fragment#onStart()
|
||||
*/
|
||||
public void dispatchStart() {
|
||||
mHost.mFragmentManager.dispatchStart();
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves all Fragments managed by the controller's FragmentManager
|
||||
* into the resume state.
|
||||
* <p>Call when Fragments should be resumed.
|
||||
*
|
||||
* @see Fragment#onResume()
|
||||
*/
|
||||
public void dispatchResume() {
|
||||
mHost.mFragmentManager.dispatchResume();
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves all Fragments managed by the controller's FragmentManager
|
||||
* into the pause state.
|
||||
* <p>Call when Fragments should be paused.
|
||||
*
|
||||
* @see Fragment#onPause()
|
||||
*/
|
||||
public void dispatchPause() {
|
||||
mHost.mFragmentManager.dispatchPause();
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves all Fragments managed by the controller's FragmentManager
|
||||
* into the stop state.
|
||||
* <p>Call when Fragments should be stopped.
|
||||
*
|
||||
* @see Fragment#onStop()
|
||||
*/
|
||||
public void dispatchStop() {
|
||||
mHost.mFragmentManager.dispatchStop();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated This functionality has been rolled into {@link #dispatchStop()}.
|
||||
*/
|
||||
@Deprecated
|
||||
public void dispatchReallyStop() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves all Fragments managed by the controller's FragmentManager
|
||||
* into the destroy view state.
|
||||
* <p>Call when the Fragment's views should be destroyed.
|
||||
*
|
||||
* @see Fragment#onDestroyView()
|
||||
*/
|
||||
public void dispatchDestroyView() {
|
||||
mHost.mFragmentManager.dispatchDestroyView();
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves Fragments managed by the controller's FragmentManager
|
||||
* into the destroy state.
|
||||
* <p>
|
||||
* If the {@link androidx.fragment.app.FragmentHostCallback} is an instance of {@link ViewModelStoreOwner},
|
||||
* then retained Fragments and any other non configuration state such as any
|
||||
* {@link androidx.lifecycle.ViewModel} attached to Fragments will only be destroyed if
|
||||
* {@link androidx.lifecycle.ViewModelStore#clear()} is called prior to this method.
|
||||
* <p>
|
||||
* Otherwise, the FragmentManager will look to see if the
|
||||
* {@link FragmentHostCallback host's} Context is an {@link Activity}
|
||||
* and if {@link Activity#isChangingConfigurations()} returns true. In only that case
|
||||
* will non configuration state be retained.
|
||||
* <p>Call when Fragments should be destroyed.
|
||||
*
|
||||
* @see Fragment#onDestroy()
|
||||
*/
|
||||
public void dispatchDestroy() {
|
||||
mHost.mFragmentManager.dispatchDestroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Lets all Fragments managed by the controller's FragmentManager know the multi-window mode of
|
||||
* the activity changed.
|
||||
* <p>Call when the multi-window mode of the activity changed.
|
||||
*
|
||||
* @see Fragment#onMultiWindowModeChanged
|
||||
*/
|
||||
public void dispatchMultiWindowModeChanged(boolean isInMultiWindowMode) {
|
||||
mHost.mFragmentManager.dispatchMultiWindowModeChanged(isInMultiWindowMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lets all Fragments managed by the controller's FragmentManager know the picture-in-picture
|
||||
* mode of the activity changed.
|
||||
* <p>Call when the picture-in-picture mode of the activity changed.
|
||||
*
|
||||
* @see Fragment#onPictureInPictureModeChanged
|
||||
*/
|
||||
public void dispatchPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
|
||||
mHost.mFragmentManager.dispatchPictureInPictureModeChanged(isInPictureInPictureMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lets all Fragments managed by the controller's FragmentManager
|
||||
* know a configuration change occurred.
|
||||
* <p>Call when there is a configuration change.
|
||||
*
|
||||
* @see Fragment#onConfigurationChanged(Configuration)
|
||||
*/
|
||||
public void dispatchConfigurationChanged(@NonNull Configuration newConfig) {
|
||||
mHost.mFragmentManager.dispatchConfigurationChanged(newConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lets all Fragments managed by the controller's FragmentManager
|
||||
* know the device is in a low memory condition.
|
||||
* <p>Call when the device is low on memory and Fragment's should trim
|
||||
* their memory usage.
|
||||
*
|
||||
* @see Fragment#onLowMemory()
|
||||
*/
|
||||
public void dispatchLowMemory() {
|
||||
mHost.mFragmentManager.dispatchLowMemory();
|
||||
}
|
||||
|
||||
/**
|
||||
* Lets all Fragments managed by the controller's FragmentManager
|
||||
* know they should create an options menu.
|
||||
* <p>Call when the Fragment should create an options menu.
|
||||
*
|
||||
* @return {@code true} if the options menu contains items to display
|
||||
* @see Fragment#onCreateOptionsMenu(Menu, MenuInflater)
|
||||
*/
|
||||
public boolean dispatchCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
|
||||
return mHost.mFragmentManager.dispatchCreateOptionsMenu(menu, inflater);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lets all Fragments managed by the controller's FragmentManager
|
||||
* know they should prepare their options menu for display.
|
||||
* <p>Call immediately before displaying the Fragment's options menu.
|
||||
*
|
||||
* @return {@code true} if the options menu contains items to display
|
||||
* @see Fragment#onPrepareOptionsMenu(Menu)
|
||||
*/
|
||||
public boolean dispatchPrepareOptionsMenu(@NonNull Menu menu) {
|
||||
return mHost.mFragmentManager.dispatchPrepareOptionsMenu(menu);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an option item selection event to the Fragments managed by the
|
||||
* controller's FragmentManager. Once the event has been consumed,
|
||||
* no additional handling will be performed.
|
||||
* <p>Call immediately after an options menu item has been selected
|
||||
*
|
||||
* @return {@code true} if the options menu selection event was consumed
|
||||
* @see Fragment#onOptionsItemSelected(MenuItem)
|
||||
*/
|
||||
public boolean dispatchOptionsItemSelected(@NonNull MenuItem item) {
|
||||
return mHost.mFragmentManager.dispatchOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a context item selection event to the Fragments managed by the
|
||||
* controller's FragmentManager. Once the event has been consumed,
|
||||
* no additional handling will be performed.
|
||||
* <p>Call immediately after an options menu item has been selected
|
||||
*
|
||||
* @return {@code true} if the context menu selection event was consumed
|
||||
* @see Fragment#onContextItemSelected(MenuItem)
|
||||
*/
|
||||
public boolean dispatchContextItemSelected(@NonNull MenuItem item) {
|
||||
return mHost.mFragmentManager.dispatchContextItemSelected(item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lets all Fragments managed by the controller's FragmentManager
|
||||
* know their options menu has closed.
|
||||
* <p>Call immediately after closing the Fragment's options menu.
|
||||
*
|
||||
* @see Fragment#onOptionsMenuClosed(Menu)
|
||||
*/
|
||||
public void dispatchOptionsMenuClosed(@NonNull Menu menu) {
|
||||
mHost.mFragmentManager.dispatchOptionsMenuClosed(menu);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute any pending actions for the Fragments managed by the
|
||||
* controller's FragmentManager.
|
||||
* <p>Call when queued actions can be performed [eg when the
|
||||
* Fragment moves into a start or resume state].
|
||||
* @return {@code true} if queued actions were performed
|
||||
*/
|
||||
public boolean execPendingActions() {
|
||||
return mHost.mFragmentManager.execPendingActions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the loaders.
|
||||
*
|
||||
* @deprecated Loaders are managed separately from FragmentController
|
||||
*/
|
||||
@Deprecated
|
||||
public void doLoaderStart() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the loaders, optionally retaining their state. This is useful for keeping the
|
||||
* loader state across configuration changes.
|
||||
*
|
||||
* @param retain When {@code true}, the loaders aren't stopped, but, their instances
|
||||
* are retained in a started state
|
||||
*
|
||||
* @deprecated Loaders are managed separately from FragmentController
|
||||
*/
|
||||
@Deprecated
|
||||
public void doLoaderStop(boolean retain) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Retains the state of each of the loaders.
|
||||
*
|
||||
* @deprecated Loaders are managed separately from FragmentController
|
||||
*/
|
||||
@Deprecated
|
||||
public void doLoaderRetain() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys the loaders and, if their state is not being retained, removes them.
|
||||
*
|
||||
* @deprecated Loaders are managed separately from FragmentController
|
||||
*/
|
||||
@Deprecated
|
||||
public void doLoaderDestroy() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Lets the loaders know the host is ready to receive notifications.
|
||||
*
|
||||
* @deprecated Loaders are managed separately from FragmentController
|
||||
*/
|
||||
@Deprecated
|
||||
public void reportLoaderStart() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of LoaderManagers that have opted to retain their instance across
|
||||
* configuration changes.
|
||||
*
|
||||
* @deprecated Loaders are managed separately from FragmentController
|
||||
*/
|
||||
@Deprecated
|
||||
@Nullable
|
||||
public SimpleArrayMap<String, LoaderManager> retainLoaderNonConfig() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores the saved state for all LoaderManagers. The given LoaderManager list are
|
||||
* LoaderManager instances retained across configuration changes.
|
||||
*
|
||||
* @see #retainLoaderNonConfig()
|
||||
*
|
||||
* @deprecated Loaders are managed separately from FragmentController
|
||||
*/
|
||||
@Deprecated
|
||||
public void restoreLoaderNonConfig(@SuppressLint("UnknownNullness")
|
||||
SimpleArrayMap<String, LoaderManager> loaderManagers) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Dumps the current state of the loaders.
|
||||
*
|
||||
* @deprecated Loaders are managed separately from FragmentController
|
||||
*/
|
||||
@Deprecated
|
||||
public void dumpLoaders(@NonNull String prefix, @Nullable FileDescriptor fd,
|
||||
@NonNull PrintWriter writer, @Nullable String[] args) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* Copyright 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.fragment.app;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.collection.SimpleArrayMap;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
|
||||
/**
|
||||
* Interface used to control the instantiation of {@link Fragment} instances.
|
||||
* Implementations can be registered with a {@link FragmentManager} via
|
||||
* {@link FragmentManager#setFragmentFactory(FragmentFactory)}.
|
||||
*
|
||||
* @see FragmentManager#setFragmentFactory(FragmentFactory)
|
||||
*/
|
||||
public class FragmentFactory {
|
||||
private static final SimpleArrayMap<String, Class<?>> sClassMap = new SimpleArrayMap<>();
|
||||
|
||||
/**
|
||||
* Determine if the given fragment name is a support library fragment class.
|
||||
*
|
||||
* @param classLoader The default classloader to use for loading the Class
|
||||
* @param className Class name of the fragment to load
|
||||
* @return Returns the parsed Class
|
||||
*/
|
||||
@NonNull
|
||||
private static Class<?> loadClass(@NonNull ClassLoader classLoader,
|
||||
@NonNull String className) throws ClassNotFoundException {
|
||||
Class<?> clazz = sClassMap.get(className);
|
||||
if (clazz == null) {
|
||||
// Class not found in the cache, see if it's real, and try to add it
|
||||
clazz = Class.forName(className, false, classLoader);
|
||||
sClassMap.put(className, clazz);
|
||||
}
|
||||
return clazz;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given fragment name is a valid Fragment class.
|
||||
*
|
||||
* @param classLoader The default classloader to use for loading the Class
|
||||
* @param className Class name of the fragment to test
|
||||
* @return true if <code>className</code> is <code>androidx.fragment.app.Fragment</code>
|
||||
* or a subclass, false otherwise.
|
||||
*/
|
||||
static boolean isFragmentClass(@NonNull ClassLoader classLoader,
|
||||
@NonNull String className) {
|
||||
try {
|
||||
Class<?> clazz = loadClass(classLoader, className);
|
||||
return Fragment.class.isAssignableFrom(clazz);
|
||||
} catch (ClassNotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a Fragment Class from the given class name. The resulting Class is kept in a global
|
||||
* cache, bypassing the {@link Class#forName(String)} calls when passed the same
|
||||
* class name again.
|
||||
*
|
||||
* @param classLoader The default classloader to use for loading the Class
|
||||
* @param className The class name of the fragment to parse.
|
||||
* @return Returns the parsed Fragment Class
|
||||
* @throws Fragment.InstantiationException If there is a failure in parsing
|
||||
* the given fragment class. This is a runtime exception; it is not
|
||||
* normally expected to happen.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@NonNull
|
||||
public static Class<? extends Fragment> loadFragmentClass(@NonNull ClassLoader classLoader,
|
||||
@NonNull String className) {
|
||||
try {
|
||||
Class<?> clazz = loadClass(classLoader, className);
|
||||
return (Class<? extends Fragment>) clazz;
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new Fragment.InstantiationException("Unable to instantiate fragment " + className
|
||||
+ ": make sure class name exists", e);
|
||||
} catch (ClassCastException e) {
|
||||
throw new Fragment.InstantiationException("Unable to instantiate fragment " + className
|
||||
+ ": make sure class is a valid subclass of Fragment", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance of a Fragment with the given class name. This uses
|
||||
* {@link #loadFragmentClass(ClassLoader, String)} and the empty
|
||||
* constructor of the resulting Class by default.
|
||||
*
|
||||
* @param classLoader The default classloader to use for instantiation
|
||||
* @param className The class name of the fragment to instantiate.
|
||||
* @return Returns a new fragment instance.
|
||||
* @throws Fragment.InstantiationException If there is a failure in instantiating
|
||||
* the given fragment class. This is a runtime exception; it is not
|
||||
* normally expected to happen.
|
||||
*/
|
||||
@NonNull
|
||||
public Fragment instantiate(@NonNull ClassLoader classLoader, @NonNull String className) {
|
||||
try {
|
||||
Class<? extends Fragment> cls = loadFragmentClass(classLoader, className);
|
||||
return cls.getConstructor().newInstance();
|
||||
} catch (java.lang.InstantiationException e) {
|
||||
throw new Fragment.InstantiationException("Unable to instantiate fragment " + className
|
||||
+ ": make sure class name exists, is public, and has an"
|
||||
+ " empty constructor that is public", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new Fragment.InstantiationException("Unable to instantiate fragment " + className
|
||||
+ ": make sure class name exists, is public, and has an"
|
||||
+ " empty constructor that is public", e);
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new Fragment.InstantiationException("Unable to instantiate fragment " + className
|
||||
+ ": could not find Fragment constructor", e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new Fragment.InstantiationException("Unable to instantiate fragment " + className
|
||||
+ ": calling Fragment constructor caused an exception", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
* Copyright 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.fragment.app;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentSender;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.util.Preconditions;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
/**
|
||||
* Integration points with the Fragment host.
|
||||
* <p>
|
||||
* Fragments may be hosted by any object; such as an {@link Activity}. In order to
|
||||
* host fragments, implement {@link FragmentHostCallback}, overriding the methods
|
||||
* applicable to the host.
|
||||
*/
|
||||
public abstract class FragmentHostCallback<E> extends FragmentContainer {
|
||||
@Nullable private final Activity mActivity;
|
||||
@NonNull private final Context mContext;
|
||||
@NonNull private final Handler mHandler;
|
||||
private final int mWindowAnimations;
|
||||
final FragmentManagerImpl mFragmentManager = new FragmentManagerImpl();
|
||||
|
||||
public FragmentHostCallback(@NonNull Context context, @NonNull Handler handler,
|
||||
int windowAnimations) {
|
||||
this(context instanceof Activity ? (Activity) context : null, context, handler,
|
||||
windowAnimations);
|
||||
}
|
||||
|
||||
FragmentHostCallback(@NonNull FragmentActivity activity) {
|
||||
this(activity, activity /*context*/, new Handler(), 0 /*windowAnimations*/);
|
||||
}
|
||||
|
||||
FragmentHostCallback(@Nullable Activity activity, @NonNull Context context,
|
||||
@NonNull Handler handler, int windowAnimations) {
|
||||
mActivity = activity;
|
||||
mContext = Preconditions.checkNotNull(context, "context == null");
|
||||
mHandler = Preconditions.checkNotNull(handler, "handler == null");
|
||||
mWindowAnimations = windowAnimations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Print internal state into the given stream.
|
||||
*
|
||||
* @param prefix Desired prefix to prepend at each line of output.
|
||||
* @param fd The raw file descriptor that the dump is being sent to.
|
||||
* @param writer The PrintWriter to which you should dump your state. This will be closed
|
||||
* for you after you return.
|
||||
* @param args additional arguments to the dump request.
|
||||
*/
|
||||
public void onDump(@NonNull String prefix, @Nullable FileDescriptor fd,
|
||||
@NonNull PrintWriter writer, @Nullable String[] args) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return {@code true} if the fragment's state needs to be saved.
|
||||
*/
|
||||
public boolean onShouldSaveFragmentState(@NonNull Fragment fragment) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link LayoutInflater}.
|
||||
* See {@link Activity#getLayoutInflater()}.
|
||||
*/
|
||||
@NonNull
|
||||
public LayoutInflater onGetLayoutInflater() {
|
||||
return LayoutInflater.from(mContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the object that's currently hosting the fragment. If a {@link Fragment}
|
||||
* is hosted by a {@link FragmentActivity}, the object returned here should be
|
||||
* the same object returned from {@link Fragment#getActivity()}.
|
||||
*/
|
||||
@Nullable
|
||||
public abstract E onGetHost();
|
||||
|
||||
/**
|
||||
* Invalidates the activity's options menu.
|
||||
* See {@link FragmentActivity#supportInvalidateOptionsMenu()}
|
||||
*/
|
||||
public void onSupportInvalidateOptionsMenu() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a new {@link Activity} from the given fragment.
|
||||
* See {@link FragmentActivity#startActivityForResult(Intent, int)}.
|
||||
*/
|
||||
public void onStartActivityFromFragment(@NonNull Fragment fragment,
|
||||
@SuppressLint("UnknownNullness") Intent intent, int requestCode) {
|
||||
onStartActivityFromFragment(fragment, intent, requestCode, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a new {@link Activity} from the given fragment.
|
||||
* See {@link FragmentActivity#startActivityForResult(Intent, int, Bundle)}.
|
||||
*/
|
||||
public void onStartActivityFromFragment(
|
||||
@NonNull Fragment fragment, @SuppressLint("UnknownNullness") Intent intent,
|
||||
int requestCode, @Nullable Bundle options) {
|
||||
if (requestCode != -1) {
|
||||
throw new IllegalStateException(
|
||||
"Starting activity with a requestCode requires a FragmentActivity host");
|
||||
}
|
||||
mContext.startActivity(intent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a new {@link IntentSender} from the given fragment.
|
||||
* See {@link Activity#startIntentSender(IntentSender, Intent, int, int, int, Bundle)}.
|
||||
*/
|
||||
public void onStartIntentSenderFromFragment(@NonNull Fragment fragment,
|
||||
@SuppressLint("UnknownNullness") IntentSender intent, int requestCode,
|
||||
@Nullable Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags,
|
||||
@Nullable Bundle options) throws IntentSender.SendIntentException {
|
||||
if (requestCode != -1) {
|
||||
throw new IllegalStateException(
|
||||
"Starting intent sender with a requestCode requires a FragmentActivity host");
|
||||
}
|
||||
ActivityCompat.startIntentSenderForResult(mActivity, intent, requestCode, fillInIntent,
|
||||
flagsMask, flagsValues, extraFlags, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests permissions from the given fragment.
|
||||
* See {@link FragmentActivity#requestPermissions(String[], int)}
|
||||
*/
|
||||
public void onRequestPermissionsFromFragment(@NonNull Fragment fragment,
|
||||
@NonNull String[] permissions, int requestCode) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether to show permission rationale UI from a fragment.
|
||||
* See {@link FragmentActivity#shouldShowRequestPermissionRationale(String)}
|
||||
*/
|
||||
public boolean onShouldShowRequestPermissionRationale(@NonNull String permission) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return {@code true} if there are window animations.
|
||||
*/
|
||||
public boolean onHasWindowAnimations() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the window animations.
|
||||
*/
|
||||
public int onGetWindowAnimations() {
|
||||
return mWindowAnimations;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onFindViewById(int id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onHasView() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
Activity getActivity() {
|
||||
return mActivity;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
Context getContext() {
|
||||
return mContext;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
Handler getHandler() {
|
||||
return mHandler;
|
||||
}
|
||||
|
||||
void onAttachFragment(@NonNull Fragment fragment) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,601 @@
|
||||
/*
|
||||
* Copyright 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.fragment.app;
|
||||
|
||||
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.IdRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RestrictTo;
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Static library support version of the framework's {@link android.app.FragmentManager}.
|
||||
* Used to write apps that run on platforms prior to Android 3.0. When running
|
||||
* on Android 3.0 or above, this implementation is still used; it does not try
|
||||
* to switch to the framework's implementation. See the framework {@link FragmentManager}
|
||||
* documentation for a class overview.
|
||||
*
|
||||
* <p>Your activity must derive from {@link FragmentActivity} to use this. From such an activity,
|
||||
* you can acquire the {@link FragmentManager} by calling
|
||||
* {@link FragmentActivity#getSupportFragmentManager}.
|
||||
*/
|
||||
public abstract class FragmentManager {
|
||||
static final FragmentFactory DEFAULT_FACTORY = new FragmentFactory();
|
||||
|
||||
/**
|
||||
* Representation of an entry on the fragment back stack, as created
|
||||
* with {@link FragmentTransaction#addToBackStack(String)
|
||||
* FragmentTransaction.addToBackStack()}. Entries can later be
|
||||
* retrieved with {@link FragmentManager#getBackStackEntryAt(int)
|
||||
* FragmentManager.getBackStackEntryAt()}.
|
||||
*
|
||||
* <p>Note that you should never hold on to a BackStackEntry object;
|
||||
* the identifier as returned by {@link #getId} is the only thing that
|
||||
* will be persisted across activity instances.
|
||||
*/
|
||||
public interface BackStackEntry {
|
||||
/**
|
||||
* Return the unique identifier for the entry. This is the only
|
||||
* representation of the entry that will persist across activity
|
||||
* instances.
|
||||
*/
|
||||
public int getId();
|
||||
|
||||
/**
|
||||
* Get the name that was supplied to
|
||||
* {@link FragmentTransaction#addToBackStack(String)
|
||||
* FragmentTransaction.addToBackStack(String)} when creating this entry.
|
||||
*/
|
||||
@Nullable
|
||||
public String getName();
|
||||
|
||||
/**
|
||||
* Return the full bread crumb title resource identifier for the entry,
|
||||
* or 0 if it does not have one.
|
||||
*/
|
||||
@StringRes
|
||||
public int getBreadCrumbTitleRes();
|
||||
|
||||
/**
|
||||
* Return the short bread crumb title resource identifier for the entry,
|
||||
* or 0 if it does not have one.
|
||||
*/
|
||||
@StringRes
|
||||
public int getBreadCrumbShortTitleRes();
|
||||
|
||||
/**
|
||||
* Return the full bread crumb title for the entry, or null if it
|
||||
* does not have one.
|
||||
*/
|
||||
@Nullable
|
||||
public CharSequence getBreadCrumbTitle();
|
||||
|
||||
/**
|
||||
* Return the short bread crumb title for the entry, or null if it
|
||||
* does not have one.
|
||||
*/
|
||||
@Nullable
|
||||
public CharSequence getBreadCrumbShortTitle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface to watch for changes to the back stack.
|
||||
*/
|
||||
public interface OnBackStackChangedListener {
|
||||
/**
|
||||
* Called whenever the contents of the back stack change.
|
||||
*/
|
||||
public void onBackStackChanged();
|
||||
}
|
||||
|
||||
private FragmentFactory mFragmentFactory = null;
|
||||
|
||||
/**
|
||||
* Start a series of edit operations on the Fragments associated with
|
||||
* this FragmentManager.
|
||||
*
|
||||
* <p>Note: A fragment transaction can only be created/committed prior
|
||||
* to an activity saving its state. If you try to commit a transaction
|
||||
* after {@link FragmentActivity#onSaveInstanceState FragmentActivity.onSaveInstanceState()}
|
||||
* (and prior to a following {@link FragmentActivity#onStart FragmentActivity.onStart}
|
||||
* or {@link FragmentActivity#onResume FragmentActivity.onResume()}, you will get an error.
|
||||
* This is because the framework takes care of saving your current fragments
|
||||
* in the state, and if changes are made after the state is saved then they
|
||||
* will be lost.</p>
|
||||
*/
|
||||
@NonNull
|
||||
public abstract FragmentTransaction beginTransaction();
|
||||
|
||||
/**
|
||||
* @hide -- remove once prebuilts are in.
|
||||
* @deprecated
|
||||
*/
|
||||
@RestrictTo(LIBRARY_GROUP_PREFIX)
|
||||
@Deprecated
|
||||
@NonNull
|
||||
public FragmentTransaction openTransaction() {
|
||||
return beginTransaction();
|
||||
}
|
||||
|
||||
/**
|
||||
* After a {@link FragmentTransaction} is committed with
|
||||
* {@link FragmentTransaction#commit FragmentTransaction.commit()}, it
|
||||
* is scheduled to be executed asynchronously on the process's main thread.
|
||||
* If you want to immediately executing any such pending operations, you
|
||||
* can call this function (only from the main thread) to do so. Note that
|
||||
* all callbacks and other related behavior will be done from within this
|
||||
* call, so be careful about where this is called from.
|
||||
*
|
||||
* <p>If you are committing a single transaction that does not modify the
|
||||
* fragment back stack, strongly consider using
|
||||
* {@link FragmentTransaction#commitNow()} instead. This can help avoid
|
||||
* unwanted side effects when other code in your app has pending committed
|
||||
* transactions that expect different timing.</p>
|
||||
* <p>
|
||||
* This also forces the start of any postponed Transactions where
|
||||
* {@link Fragment#postponeEnterTransition()} has been called.
|
||||
*
|
||||
* @return Returns true if there were any pending transactions to be
|
||||
* executed.
|
||||
*/
|
||||
public abstract boolean executePendingTransactions();
|
||||
|
||||
/**
|
||||
* Finds a fragment that was identified by the given id either when inflated
|
||||
* from XML or as the container ID when added in a transaction. This first
|
||||
* searches through fragments that are currently added to the manager's
|
||||
* activity; if no such fragment is found, then all fragments currently
|
||||
* on the back stack associated with this ID are searched.
|
||||
* @return The fragment if found or null otherwise.
|
||||
*/
|
||||
@Nullable
|
||||
public abstract Fragment findFragmentById(@IdRes int id);
|
||||
|
||||
/**
|
||||
* Finds a fragment that was identified by the given tag either when inflated
|
||||
* from XML or as supplied when added in a transaction. This first
|
||||
* searches through fragments that are currently added to the manager's
|
||||
* activity; if no such fragment is found, then all fragments currently
|
||||
* on the back stack are searched.
|
||||
* @return The fragment if found or null otherwise.
|
||||
*/
|
||||
@Nullable
|
||||
public abstract Fragment findFragmentByTag(@Nullable String tag);
|
||||
|
||||
/**
|
||||
* Flag for {@link #popBackStack(String, int)}
|
||||
* and {@link #popBackStack(int, int)}: If set, and the name or ID of
|
||||
* a back stack entry has been supplied, then all matching entries will
|
||||
* be consumed until one that doesn't match is found or the bottom of
|
||||
* the stack is reached. Otherwise, all entries up to but not including that entry
|
||||
* will be removed.
|
||||
*/
|
||||
public static final int POP_BACK_STACK_INCLUSIVE = 1<<0;
|
||||
|
||||
/**
|
||||
* Pop the top state off the back stack. This function is asynchronous -- it enqueues the
|
||||
* request to pop, but the action will not be performed until the application
|
||||
* returns to its event loop.
|
||||
*/
|
||||
public abstract void popBackStack();
|
||||
|
||||
/**
|
||||
* Like {@link #popBackStack()}, but performs the operation immediately
|
||||
* inside of the call. This is like calling {@link #executePendingTransactions()}
|
||||
* afterwards without forcing the start of postponed Transactions.
|
||||
* @return Returns true if there was something popped, else false.
|
||||
*/
|
||||
public abstract boolean popBackStackImmediate();
|
||||
|
||||
/**
|
||||
* Pop the last fragment transition from the manager's fragment
|
||||
* back stack.
|
||||
* This function is asynchronous -- it enqueues the
|
||||
* request to pop, but the action will not be performed until the application
|
||||
* returns to its event loop.
|
||||
*
|
||||
* @param name If non-null, this is the name of a previous back state
|
||||
* to look for; if found, all states up to that state will be popped. The
|
||||
* {@link #POP_BACK_STACK_INCLUSIVE} flag can be used to control whether
|
||||
* the named state itself is popped. If null, only the top state is popped.
|
||||
* @param flags Either 0 or {@link #POP_BACK_STACK_INCLUSIVE}.
|
||||
*/
|
||||
public abstract void popBackStack(@Nullable String name, int flags);
|
||||
|
||||
/**
|
||||
* Like {@link #popBackStack(String, int)}, but performs the operation immediately
|
||||
* inside of the call. This is like calling {@link #executePendingTransactions()}
|
||||
* afterwards without forcing the start of postponed Transactions.
|
||||
* @return Returns true if there was something popped, else false.
|
||||
*/
|
||||
public abstract boolean popBackStackImmediate(@Nullable String name, int flags);
|
||||
|
||||
/**
|
||||
* Pop all back stack states up to the one with the given identifier.
|
||||
* This function is asynchronous -- it enqueues the
|
||||
* request to pop, but the action will not be performed until the application
|
||||
* returns to its event loop.
|
||||
*
|
||||
* @param id Identifier of the stated to be popped. If no identifier exists,
|
||||
* false is returned.
|
||||
* The identifier is the number returned by
|
||||
* {@link FragmentTransaction#commit() FragmentTransaction.commit()}. The
|
||||
* {@link #POP_BACK_STACK_INCLUSIVE} flag can be used to control whether
|
||||
* the named state itself is popped.
|
||||
* @param flags Either 0 or {@link #POP_BACK_STACK_INCLUSIVE}.
|
||||
*/
|
||||
public abstract void popBackStack(int id, int flags);
|
||||
|
||||
/**
|
||||
* Like {@link #popBackStack(int, int)}, but performs the operation immediately
|
||||
* inside of the call. This is like calling {@link #executePendingTransactions()}
|
||||
* afterwards without forcing the start of postponed Transactions.
|
||||
* @return Returns true if there was something popped, else false.
|
||||
*/
|
||||
public abstract boolean popBackStackImmediate(int id, int flags);
|
||||
|
||||
/**
|
||||
* Return the number of entries currently in the back stack.
|
||||
*/
|
||||
public abstract int getBackStackEntryCount();
|
||||
|
||||
/**
|
||||
* Return the BackStackEntry at index <var>index</var> in the back stack;
|
||||
* entries start index 0 being the bottom of the stack.
|
||||
*/
|
||||
@NonNull
|
||||
public abstract BackStackEntry getBackStackEntryAt(int index);
|
||||
|
||||
/**
|
||||
* Add a new listener for changes to the fragment back stack.
|
||||
*/
|
||||
public abstract void addOnBackStackChangedListener(
|
||||
@NonNull OnBackStackChangedListener listener);
|
||||
|
||||
/**
|
||||
* Remove a listener that was previously added with
|
||||
* {@link #addOnBackStackChangedListener(OnBackStackChangedListener)}.
|
||||
*/
|
||||
public abstract void removeOnBackStackChangedListener(
|
||||
@NonNull OnBackStackChangedListener listener);
|
||||
|
||||
/**
|
||||
* Put a reference to a fragment in a Bundle. This Bundle can be
|
||||
* persisted as saved state, and when later restoring
|
||||
* {@link #getFragment(Bundle, String)} will return the current
|
||||
* instance of the same fragment.
|
||||
*
|
||||
* @param bundle The bundle in which to put the fragment reference.
|
||||
* @param key The name of the entry in the bundle.
|
||||
* @param fragment The Fragment whose reference is to be stored.
|
||||
*/
|
||||
public abstract void putFragment(@NonNull Bundle bundle, @NonNull String key,
|
||||
@NonNull Fragment fragment);
|
||||
|
||||
/**
|
||||
* Retrieve the current Fragment instance for a reference previously
|
||||
* placed with {@link #putFragment(Bundle, String, Fragment)}.
|
||||
*
|
||||
* @param bundle The bundle from which to retrieve the fragment reference.
|
||||
* @param key The name of the entry in the bundle.
|
||||
* @return Returns the current Fragment instance that is associated with
|
||||
* the given reference.
|
||||
*/
|
||||
@Nullable
|
||||
public abstract Fragment getFragment(@NonNull Bundle bundle, @NonNull String key);
|
||||
|
||||
/**
|
||||
* Get a list of all fragments that are currently added to the FragmentManager.
|
||||
* This may include those that are hidden as well as those that are shown.
|
||||
* This will not include any fragments only in the back stack, or fragments that
|
||||
* are detached or removed.
|
||||
* <p>
|
||||
* The order of the fragments in the list is the order in which they were
|
||||
* added or attached.
|
||||
*
|
||||
* @return A list of all fragments that are added to the FragmentManager.
|
||||
*/
|
||||
@NonNull
|
||||
public abstract List<Fragment> getFragments();
|
||||
|
||||
/**
|
||||
* Save the current instance state of the given Fragment. This can be
|
||||
* used later when creating a new instance of the Fragment and adding
|
||||
* it to the fragment manager, to have it create itself to match the
|
||||
* current state returned here. Note that there are limits on how
|
||||
* this can be used:
|
||||
*
|
||||
* <ul>
|
||||
* <li>The Fragment must currently be attached to the FragmentManager.
|
||||
* <li>A new Fragment created using this saved state must be the same class
|
||||
* type as the Fragment it was created from.
|
||||
* <li>The saved state can not contain dependencies on other fragments --
|
||||
* that is it can't use {@link #putFragment(Bundle, String, Fragment)} to
|
||||
* store a fragment reference because that reference may not be valid when
|
||||
* this saved state is later used. Likewise the Fragment's target and
|
||||
* result code are not included in this state.
|
||||
* </ul>
|
||||
*
|
||||
* @param f The Fragment whose state is to be saved.
|
||||
* @return The generated state. This will be null if there was no
|
||||
* interesting state created by the fragment.
|
||||
*/
|
||||
@Nullable
|
||||
public abstract Fragment.SavedState saveFragmentInstanceState(@NonNull Fragment f);
|
||||
|
||||
/**
|
||||
* Returns true if the final {@link android.app.Activity#onDestroy() Activity.onDestroy()}
|
||||
* call has been made on the FragmentManager's Activity, so this instance is now dead.
|
||||
*/
|
||||
public abstract boolean isDestroyed();
|
||||
|
||||
/**
|
||||
* Registers a {@link FragmentLifecycleCallbacks} to listen to fragment lifecycle events
|
||||
* happening in this FragmentManager. All registered callbacks will be automatically
|
||||
* unregistered when this FragmentManager is destroyed.
|
||||
*
|
||||
* @param cb Callbacks to register
|
||||
* @param recursive true to automatically register this callback for all child FragmentManagers
|
||||
*/
|
||||
public abstract void registerFragmentLifecycleCallbacks(@NonNull FragmentLifecycleCallbacks cb,
|
||||
boolean recursive);
|
||||
|
||||
/**
|
||||
* Unregisters a previously registered {@link FragmentLifecycleCallbacks}. If the callback
|
||||
* was not previously registered this call has no effect. All registered callbacks will be
|
||||
* automatically unregistered when this FragmentManager is destroyed.
|
||||
*
|
||||
* @param cb Callbacks to unregister
|
||||
*/
|
||||
public abstract void unregisterFragmentLifecycleCallbacks(
|
||||
@NonNull FragmentLifecycleCallbacks cb);
|
||||
|
||||
/**
|
||||
* Return the currently active primary navigation fragment for this FragmentManager.
|
||||
* The primary navigation fragment is set by fragment transactions using
|
||||
* {@link FragmentTransaction#setPrimaryNavigationFragment(Fragment)}.
|
||||
*
|
||||
* <p>The primary navigation fragment's
|
||||
* {@link Fragment#getChildFragmentManager() child FragmentManager} will be called first
|
||||
* to process delegated navigation actions such as {@link #popBackStack()} if no ID
|
||||
* or transaction name is provided to pop to.</p>
|
||||
*
|
||||
* @return the fragment designated as the primary navigation fragment
|
||||
*/
|
||||
@Nullable
|
||||
public abstract Fragment getPrimaryNavigationFragment();
|
||||
|
||||
/**
|
||||
* Set a {@link FragmentFactory} for this FragmentManager that will be used
|
||||
* to create new Fragment instances from this point onward.
|
||||
*
|
||||
* @param fragmentFactory the factory to use to create new Fragment instances
|
||||
*/
|
||||
public void setFragmentFactory(@NonNull FragmentFactory fragmentFactory) {
|
||||
mFragmentFactory = fragmentFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current {@link FragmentFactory} used to instantiate new Fragment instances.
|
||||
*
|
||||
* @return the current FragmentFactory
|
||||
*/
|
||||
@NonNull
|
||||
public FragmentFactory getFragmentFactory() {
|
||||
if (mFragmentFactory == null) {
|
||||
mFragmentFactory = DEFAULT_FACTORY;
|
||||
}
|
||||
return mFragmentFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Print the FragmentManager's state into the given stream.
|
||||
*
|
||||
* @param prefix Text to print at the front of each line.
|
||||
* @param fd The raw file descriptor that the dump is being sent to.
|
||||
* @param writer A PrintWriter to which the dump is to be set.
|
||||
* @param args Additional arguments to the dump request.
|
||||
*/
|
||||
public abstract void dump(@NonNull String prefix, @Nullable FileDescriptor fd,
|
||||
@NonNull PrintWriter writer, @Nullable String[] args);
|
||||
|
||||
/**
|
||||
* Control whether the framework's internal fragment manager debugging
|
||||
* logs are turned on. If enabled, you will see output in logcat as
|
||||
* the framework performs fragment operations.
|
||||
*/
|
||||
public static void enableDebugLogging(boolean enabled) {
|
||||
FragmentManagerImpl.DEBUG = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the FragmentManager's state has already been saved
|
||||
* by its host. Any operations that would change saved state should not be performed
|
||||
* if this method returns true. For example, any popBackStack() method, such as
|
||||
* {@link #popBackStackImmediate()} or any FragmentTransaction using
|
||||
* {@link FragmentTransaction#commit()} instead of
|
||||
* {@link FragmentTransaction#commitAllowingStateLoss()} will change
|
||||
* the state and will result in an error.
|
||||
*
|
||||
* @return true if this FragmentManager's state has already been saved by its host
|
||||
*/
|
||||
public abstract boolean isStateSaved();
|
||||
|
||||
/**
|
||||
* Callback interface for listening to fragment state changes that happen
|
||||
* within a given FragmentManager.
|
||||
*/
|
||||
public abstract static class FragmentLifecycleCallbacks {
|
||||
/**
|
||||
* Called right before the fragment's {@link Fragment#onAttach(Context)} method is called.
|
||||
* This is a good time to inject any required dependencies or perform other configuration
|
||||
* for the fragment before any of the fragment's lifecycle methods are invoked.
|
||||
*
|
||||
* @param fm Host FragmentManager
|
||||
* @param f Fragment changing state
|
||||
* @param context Context that the Fragment is being attached to
|
||||
*/
|
||||
public void onFragmentPreAttached(@NonNull FragmentManager fm, @NonNull Fragment f,
|
||||
@NonNull Context context) {}
|
||||
|
||||
/**
|
||||
* Called after the fragment has been attached to its host. Its host will have had
|
||||
* <code>onAttachFragment</code> called before this call happens.
|
||||
*
|
||||
* @param fm Host FragmentManager
|
||||
* @param f Fragment changing state
|
||||
* @param context Context that the Fragment was attached to
|
||||
*/
|
||||
public void onFragmentAttached(@NonNull FragmentManager fm, @NonNull Fragment f,
|
||||
@NonNull Context context) {}
|
||||
|
||||
/**
|
||||
* Called right before the fragment's {@link Fragment#onCreate(Bundle)} method is called.
|
||||
* This is a good time to inject any required dependencies or perform other configuration
|
||||
* for the fragment.
|
||||
*
|
||||
* @param fm Host FragmentManager
|
||||
* @param f Fragment changing state
|
||||
* @param savedInstanceState Saved instance bundle from a previous instance
|
||||
*/
|
||||
public void onFragmentPreCreated(@NonNull FragmentManager fm, @NonNull Fragment f,
|
||||
@Nullable Bundle savedInstanceState) {}
|
||||
|
||||
/**
|
||||
* Called after the fragment has returned from the FragmentManager's call to
|
||||
* {@link Fragment#onCreate(Bundle)}. This will only happen once for any given
|
||||
* fragment instance, though the fragment may be attached and detached multiple times.
|
||||
*
|
||||
* @param fm Host FragmentManager
|
||||
* @param f Fragment changing state
|
||||
* @param savedInstanceState Saved instance bundle from a previous instance
|
||||
*/
|
||||
public void onFragmentCreated(@NonNull FragmentManager fm, @NonNull Fragment f,
|
||||
@Nullable Bundle savedInstanceState) {}
|
||||
|
||||
/**
|
||||
* Called after the fragment has returned from the FragmentManager's call to
|
||||
* {@link Fragment#onActivityCreated(Bundle)}. This will only happen once for any given
|
||||
* fragment instance, though the fragment may be attached and detached multiple times.
|
||||
*
|
||||
* @param fm Host FragmentManager
|
||||
* @param f Fragment changing state
|
||||
* @param savedInstanceState Saved instance bundle from a previous instance
|
||||
*/
|
||||
public void onFragmentActivityCreated(@NonNull FragmentManager fm, @NonNull Fragment f,
|
||||
@Nullable Bundle savedInstanceState) {}
|
||||
|
||||
/**
|
||||
* Called after the fragment has returned a non-null view from the FragmentManager's
|
||||
* request to {@link Fragment#onCreateView(LayoutInflater, ViewGroup, Bundle)}.
|
||||
*
|
||||
* @param fm Host FragmentManager
|
||||
* @param f Fragment that created and owns the view
|
||||
* @param v View returned by the fragment
|
||||
* @param savedInstanceState Saved instance bundle from a previous instance
|
||||
*/
|
||||
public void onFragmentViewCreated(@NonNull FragmentManager fm, @NonNull Fragment f,
|
||||
@NonNull View v, @Nullable Bundle savedInstanceState) {}
|
||||
|
||||
/**
|
||||
* Called after the fragment has returned from the FragmentManager's call to
|
||||
* {@link Fragment#onStart()}.
|
||||
*
|
||||
* @param fm Host FragmentManager
|
||||
* @param f Fragment changing state
|
||||
*/
|
||||
public void onFragmentStarted(@NonNull FragmentManager fm, @NonNull Fragment f) {}
|
||||
|
||||
/**
|
||||
* Called after the fragment has returned from the FragmentManager's call to
|
||||
* {@link Fragment#onResume()}.
|
||||
*
|
||||
* @param fm Host FragmentManager
|
||||
* @param f Fragment changing state
|
||||
*/
|
||||
public void onFragmentResumed(@NonNull FragmentManager fm, @NonNull Fragment f) {}
|
||||
|
||||
/**
|
||||
* Called after the fragment has returned from the FragmentManager's call to
|
||||
* {@link Fragment#onPause()}.
|
||||
*
|
||||
* @param fm Host FragmentManager
|
||||
* @param f Fragment changing state
|
||||
*/
|
||||
public void onFragmentPaused(@NonNull FragmentManager fm, @NonNull Fragment f) {}
|
||||
|
||||
/**
|
||||
* Called after the fragment has returned from the FragmentManager's call to
|
||||
* {@link Fragment#onStop()}.
|
||||
*
|
||||
* @param fm Host FragmentManager
|
||||
* @param f Fragment changing state
|
||||
*/
|
||||
public void onFragmentStopped(@NonNull FragmentManager fm, @NonNull Fragment f) {}
|
||||
|
||||
/**
|
||||
* Called after the fragment has returned from the FragmentManager's call to
|
||||
* {@link Fragment#onSaveInstanceState(Bundle)}.
|
||||
*
|
||||
* @param fm Host FragmentManager
|
||||
* @param f Fragment changing state
|
||||
* @param outState Saved state bundle for the fragment
|
||||
*/
|
||||
public void onFragmentSaveInstanceState(@NonNull FragmentManager fm, @NonNull Fragment f,
|
||||
@NonNull Bundle outState) {}
|
||||
|
||||
/**
|
||||
* Called after the fragment has returned from the FragmentManager's call to
|
||||
* {@link Fragment#onDestroyView()}.
|
||||
*
|
||||
* @param fm Host FragmentManager
|
||||
* @param f Fragment changing state
|
||||
*/
|
||||
public void onFragmentViewDestroyed(@NonNull FragmentManager fm, @NonNull Fragment f) {}
|
||||
|
||||
/**
|
||||
* Called after the fragment has returned from the FragmentManager's call to
|
||||
* {@link Fragment#onDestroy()}.
|
||||
*
|
||||
* @param fm Host FragmentManager
|
||||
* @param f Fragment changing state
|
||||
*/
|
||||
public void onFragmentDestroyed(@NonNull FragmentManager fm, @NonNull Fragment f) {}
|
||||
|
||||
/**
|
||||
* Called after the fragment has returned from the FragmentManager's call to
|
||||
* {@link Fragment#onDetach()}.
|
||||
*
|
||||
* @param fm Host FragmentManager
|
||||
* @param f Fragment changing state
|
||||
*/
|
||||
public void onFragmentDetached(@NonNull FragmentManager fm, @NonNull Fragment f) {}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
package androidx.fragment.app;
|
||||
|
||||
import android.os.Parcelable;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.ViewModelStore;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* FragmentManagerNonConfig stores the retained instance fragments across
|
||||
* activity recreation events.
|
||||
*
|
||||
* <p>Apps should treat objects of this type as opaque, returned by
|
||||
* and passed to the state save and restore process for fragments in
|
||||
* {@link FragmentController#retainNestedNonConfig()} and
|
||||
* {@link FragmentController#restoreAllState(Parcelable, FragmentManagerNonConfig)}.</p>
|
||||
*
|
||||
* @deprecated Have your {@link FragmentHostCallback} implement
|
||||
* {@link androidx.lifecycle.ViewModelStoreOwner} to automatically retain the Fragment's
|
||||
* non configuration state.
|
||||
*/
|
||||
@Deprecated
|
||||
public class FragmentManagerNonConfig {
|
||||
private final @Nullable Collection<Fragment> mFragments;
|
||||
private final @Nullable Map<String, FragmentManagerNonConfig> mChildNonConfigs;
|
||||
private final @Nullable Map<String, ViewModelStore> mViewModelStores;
|
||||
|
||||
FragmentManagerNonConfig(@Nullable Collection<Fragment> fragments,
|
||||
@Nullable Map<String, FragmentManagerNonConfig> childNonConfigs,
|
||||
@Nullable Map<String, ViewModelStore> viewModelStores) {
|
||||
mFragments = fragments;
|
||||
mChildNonConfigs = childNonConfigs;
|
||||
mViewModelStores = viewModelStores;
|
||||
}
|
||||
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
boolean isRetaining(Fragment f) {
|
||||
if (mFragments == null) {
|
||||
return false;
|
||||
}
|
||||
return mFragments.contains(f);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the retained instance fragments returned by a FragmentManager
|
||||
*/
|
||||
@Nullable
|
||||
Collection<Fragment> getFragments() {
|
||||
return mFragments;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the FragmentManagerNonConfigs from any applicable fragment's child FragmentManager
|
||||
*/
|
||||
@Nullable
|
||||
Map<String, FragmentManagerNonConfig> getChildNonConfigs() {
|
||||
return mChildNonConfigs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the ViewModelStores for all fragments associated with the FragmentManager
|
||||
*/
|
||||
@Nullable
|
||||
Map<String, ViewModelStore> getViewModelStores() {
|
||||
return mViewModelStores;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.fragment.app;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
@SuppressLint("BanParcelableUsage")
|
||||
final class FragmentManagerState implements Parcelable {
|
||||
ArrayList<FragmentState> mActive;
|
||||
ArrayList<String> mAdded;
|
||||
BackStackState[] mBackStack;
|
||||
String mPrimaryNavActiveWho = null;
|
||||
int mNextFragmentIndex;
|
||||
|
||||
public FragmentManagerState() {
|
||||
}
|
||||
|
||||
public FragmentManagerState(Parcel in) {
|
||||
mActive = in.createTypedArrayList(FragmentState.CREATOR);
|
||||
mAdded = in.createStringArrayList();
|
||||
mBackStack = in.createTypedArray(BackStackState.CREATOR);
|
||||
mPrimaryNavActiveWho = in.readString();
|
||||
mNextFragmentIndex = in.readInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeTypedList(mActive);
|
||||
dest.writeStringList(mAdded);
|
||||
dest.writeTypedArray(mBackStack, flags);
|
||||
dest.writeString(mPrimaryNavActiveWho);
|
||||
dest.writeInt(mNextFragmentIndex);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<FragmentManagerState> CREATOR
|
||||
= new Parcelable.Creator<FragmentManagerState>() {
|
||||
@Override
|
||||
public FragmentManagerState createFromParcel(Parcel in) {
|
||||
return new FragmentManagerState(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FragmentManagerState[] newArray(int size) {
|
||||
return new FragmentManagerState[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,280 @@
|
||||
/*
|
||||
* Copyright 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.fragment.app;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.lifecycle.ViewModelStore;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* FragmentManagerViewModel is the always up to date view of the Fragment's
|
||||
* non configuration state
|
||||
*/
|
||||
class FragmentManagerViewModel extends ViewModel {
|
||||
|
||||
private static final ViewModelProvider.Factory FACTORY = new ViewModelProvider.Factory() {
|
||||
@NonNull
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
|
||||
FragmentManagerViewModel viewModel = new FragmentManagerViewModel(true);
|
||||
return (T) viewModel;
|
||||
}
|
||||
};
|
||||
|
||||
@NonNull
|
||||
static FragmentManagerViewModel getInstance(ViewModelStore viewModelStore) {
|
||||
ViewModelProvider viewModelProvider = new ViewModelProvider(viewModelStore,
|
||||
FACTORY);
|
||||
return viewModelProvider.get(FragmentManagerViewModel.class);
|
||||
}
|
||||
|
||||
private final HashSet<Fragment> mRetainedFragments = new HashSet<>();
|
||||
private final HashMap<String, FragmentManagerViewModel> mChildNonConfigs = new HashMap<>();
|
||||
private final HashMap<String, ViewModelStore> mViewModelStores = new HashMap<>();
|
||||
|
||||
private final boolean mStateAutomaticallySaved;
|
||||
// Only used when mStateAutomaticallySaved is true
|
||||
private boolean mHasBeenCleared = false;
|
||||
// Only used when mStateAutomaticallySaved is false
|
||||
private boolean mHasSavedSnapshot = false;
|
||||
|
||||
/**
|
||||
* FragmentManagerViewModel simultaneously supports two modes:
|
||||
* <ol>
|
||||
* <li>Automatically saved: in this model, it is assumed that the ViewModel is added to
|
||||
* an appropriate {@link ViewModelStore} that has the same lifecycle as the
|
||||
* FragmentManager and that {@link #onCleared()} indicates that the Fragment's host
|
||||
* is being permanently destroyed.</li>
|
||||
* <li>Not automatically saved: in this model, the FragmentManager is responsible for
|
||||
* calling {@link #getSnapshot()} and later restoring the ViewModel with
|
||||
* {@link #restoreFromSnapshot(FragmentManagerNonConfig)}.</li>
|
||||
* </ol>
|
||||
* These states are mutually exclusive.
|
||||
*
|
||||
* @param stateAutomaticallySaved Whether the ViewModel will be automatically saved.
|
||||
*/
|
||||
FragmentManagerViewModel(boolean stateAutomaticallySaved) {
|
||||
mStateAutomaticallySaved = stateAutomaticallySaved;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCleared() {
|
||||
if (FragmentManagerImpl.DEBUG) {
|
||||
Log.d(FragmentManagerImpl.TAG, "onCleared called for " + this);
|
||||
}
|
||||
mHasBeenCleared = true;
|
||||
}
|
||||
|
||||
boolean isCleared() {
|
||||
return mHasBeenCleared;
|
||||
}
|
||||
|
||||
boolean addRetainedFragment(@NonNull Fragment fragment) {
|
||||
return mRetainedFragments.add(fragment);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
Collection<Fragment> getRetainedFragments() {
|
||||
return mRetainedFragments;
|
||||
}
|
||||
|
||||
boolean shouldDestroy(@NonNull Fragment fragment) {
|
||||
if (!mRetainedFragments.contains(fragment)) {
|
||||
// Always destroy non-retained Fragments
|
||||
return true;
|
||||
}
|
||||
if (mStateAutomaticallySaved) {
|
||||
// If we automatically save our state, then only
|
||||
// destroy a retained Fragment when we've been cleared
|
||||
return mHasBeenCleared;
|
||||
} else {
|
||||
// Else, only destroy retained Fragments if they've
|
||||
// been reaped before the state has been saved
|
||||
return !mHasSavedSnapshot;
|
||||
}
|
||||
}
|
||||
|
||||
boolean removeRetainedFragment(@NonNull Fragment fragment) {
|
||||
return mRetainedFragments.remove(fragment);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
FragmentManagerViewModel getChildNonConfig(@NonNull Fragment f) {
|
||||
FragmentManagerViewModel childNonConfig = mChildNonConfigs.get(f.mWho);
|
||||
if (childNonConfig == null) {
|
||||
childNonConfig = new FragmentManagerViewModel(mStateAutomaticallySaved);
|
||||
mChildNonConfigs.put(f.mWho, childNonConfig);
|
||||
}
|
||||
return childNonConfig;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
ViewModelStore getViewModelStore(@NonNull Fragment f) {
|
||||
ViewModelStore viewModelStore = mViewModelStores.get(f.mWho);
|
||||
if (viewModelStore == null) {
|
||||
viewModelStore = new ViewModelStore();
|
||||
mViewModelStores.put(f.mWho, viewModelStore);
|
||||
}
|
||||
return viewModelStore;
|
||||
}
|
||||
|
||||
void clearNonConfigState(@NonNull Fragment f) {
|
||||
if (FragmentManagerImpl.DEBUG) {
|
||||
Log.d(FragmentManagerImpl.TAG, "Clearing non-config state for " + f);
|
||||
}
|
||||
// Clear and remove the Fragment's child non config state
|
||||
FragmentManagerViewModel childNonConfig = mChildNonConfigs.get(f.mWho);
|
||||
if (childNonConfig != null) {
|
||||
childNonConfig.onCleared();
|
||||
mChildNonConfigs.remove(f.mWho);
|
||||
}
|
||||
// Clear and remove the Fragment's ViewModelStore
|
||||
ViewModelStore viewModelStore = mViewModelStores.get(f.mWho);
|
||||
if (viewModelStore != null) {
|
||||
viewModelStore.clear();
|
||||
mViewModelStores.remove(f.mWho);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Ideally, we only support mStateAutomaticallySaved = true and remove this
|
||||
* code, alongside
|
||||
* {@link FragmentController#restoreAllState(android.os.Parcelable, FragmentManagerNonConfig)}.
|
||||
*/
|
||||
@Deprecated
|
||||
void restoreFromSnapshot(@Nullable FragmentManagerNonConfig nonConfig) {
|
||||
mRetainedFragments.clear();
|
||||
mChildNonConfigs.clear();
|
||||
mViewModelStores.clear();
|
||||
if (nonConfig != null) {
|
||||
Collection<Fragment> fragments = nonConfig.getFragments();
|
||||
if (fragments != null) {
|
||||
mRetainedFragments.addAll(fragments);
|
||||
}
|
||||
Map<String, FragmentManagerNonConfig> childNonConfigs = nonConfig.getChildNonConfigs();
|
||||
if (childNonConfigs != null) {
|
||||
for (Map.Entry<String, FragmentManagerNonConfig> entry :
|
||||
childNonConfigs.entrySet()) {
|
||||
FragmentManagerViewModel childViewModel =
|
||||
new FragmentManagerViewModel(mStateAutomaticallySaved);
|
||||
childViewModel.restoreFromSnapshot(entry.getValue());
|
||||
mChildNonConfigs.put(entry.getKey(), childViewModel);
|
||||
}
|
||||
}
|
||||
Map<String, ViewModelStore> viewModelStores = nonConfig.getViewModelStores();
|
||||
if (viewModelStores != null) {
|
||||
mViewModelStores.putAll(viewModelStores);
|
||||
}
|
||||
}
|
||||
mHasSavedSnapshot = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Ideally, we only support mStateAutomaticallySaved = true and remove this
|
||||
* code, alongside {@link FragmentController#retainNestedNonConfig()}.
|
||||
*/
|
||||
@Deprecated
|
||||
@Nullable
|
||||
FragmentManagerNonConfig getSnapshot() {
|
||||
if (mRetainedFragments.isEmpty() && mChildNonConfigs.isEmpty()
|
||||
&& mViewModelStores.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
HashMap<String, FragmentManagerNonConfig> childNonConfigs = new HashMap<>();
|
||||
for (Map.Entry<String, FragmentManagerViewModel> entry : mChildNonConfigs.entrySet()) {
|
||||
FragmentManagerNonConfig childNonConfig = entry.getValue().getSnapshot();
|
||||
if (childNonConfig != null) {
|
||||
childNonConfigs.put(entry.getKey(), childNonConfig);
|
||||
}
|
||||
}
|
||||
|
||||
mHasSavedSnapshot = true;
|
||||
if (mRetainedFragments.isEmpty() && childNonConfigs.isEmpty()
|
||||
&& mViewModelStores.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return new FragmentManagerNonConfig(
|
||||
new ArrayList<>(mRetainedFragments),
|
||||
childNonConfigs,
|
||||
new HashMap<>(mViewModelStores));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
FragmentManagerViewModel that = (FragmentManagerViewModel) o;
|
||||
|
||||
return mRetainedFragments.equals(that.mRetainedFragments)
|
||||
&& mChildNonConfigs.equals(that.mChildNonConfigs)
|
||||
&& mViewModelStores.equals(that.mViewModelStores);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = mRetainedFragments.hashCode();
|
||||
result = 31 * result + mChildNonConfigs.hashCode();
|
||||
result = 31 * result + mViewModelStores.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder("FragmentManagerViewModel{");
|
||||
sb.append(Integer.toHexString(System.identityHashCode(this)));
|
||||
sb.append("} Fragments (");
|
||||
Iterator<Fragment> fragmentIterator = mRetainedFragments.iterator();
|
||||
while (fragmentIterator.hasNext()) {
|
||||
sb.append(fragmentIterator.next());
|
||||
if (fragmentIterator.hasNext()) {
|
||||
sb.append(", ");
|
||||
}
|
||||
}
|
||||
sb.append(") Child Non Config (");
|
||||
Iterator<String> childNonConfigIterator = mChildNonConfigs.keySet().iterator();
|
||||
while (childNonConfigIterator.hasNext()) {
|
||||
sb.append(childNonConfigIterator.next());
|
||||
if (childNonConfigIterator.hasNext()) {
|
||||
sb.append(", ");
|
||||
}
|
||||
}
|
||||
sb.append(") ViewModelStores (");
|
||||
Iterator<String> viewModelStoreIterator = mViewModelStores.keySet().iterator();
|
||||
while (viewModelStoreIterator.hasNext()) {
|
||||
sb.append(viewModelStoreIterator.next());
|
||||
if (viewModelStoreIterator.hasNext()) {
|
||||
sb.append(", ");
|
||||
}
|
||||
}
|
||||
sb.append(')');
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
/*
|
||||
* Copyright 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.fragment.app;
|
||||
|
||||
import android.os.Parcelable;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.Lifecycle;
|
||||
import androidx.viewpager.widget.PagerAdapter;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* Implementation of {@link PagerAdapter} that
|
||||
* represents each page as a {@link Fragment} that is persistently
|
||||
* kept in the fragment manager as long as the user can return to the page.
|
||||
*
|
||||
* <p>This version of the pager is best for use when there are a handful of
|
||||
* typically more static fragments to be paged through, such as a set of tabs.
|
||||
* The fragment of each page the user visits will be kept in memory, though its
|
||||
* view hierarchy may be destroyed when not visible. This can result in using
|
||||
* a significant amount of memory since fragment instances can hold on to an
|
||||
* arbitrary amount of state. For larger sets of pages, consider
|
||||
* {@link FragmentStatePagerAdapter}.
|
||||
*
|
||||
* <p>When using FragmentPagerAdapter the host ViewPager must have a
|
||||
* valid ID set.</p>
|
||||
*
|
||||
* <p>Subclasses only need to implement {@link #getItem(int)}
|
||||
* and {@link #getCount()} to have a working adapter.
|
||||
*
|
||||
* <p>Here is an example implementation of a pager containing fragments of
|
||||
* lists:
|
||||
*
|
||||
* {@sample frameworks/support/samples/Support4Demos/src/main/java/com/example/android/supportv4/app/FragmentPagerSupport.java
|
||||
* complete}
|
||||
*
|
||||
* <p>The <code>R.layout.fragment_pager</code> resource of the top-level fragment is:
|
||||
*
|
||||
* {@sample frameworks/support/samples/Support4Demos/src/main/res/layout/fragment_pager.xml
|
||||
* complete}
|
||||
*
|
||||
* <p>The <code>R.layout.fragment_pager_list</code> resource containing each
|
||||
* individual fragment's layout is:
|
||||
*
|
||||
* {@sample frameworks/support/samples/Support4Demos/src/main/res/layout/fragment_pager_list.xml
|
||||
* complete}
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public abstract class FragmentPagerAdapter extends PagerAdapter {
|
||||
private static final String TAG = "FragmentPagerAdapter";
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({BEHAVIOR_SET_USER_VISIBLE_HINT, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT})
|
||||
private @interface Behavior { }
|
||||
|
||||
/**
|
||||
* Indicates that {@link Fragment#setUserVisibleHint(boolean)} will be called when the current
|
||||
* fragment changes.
|
||||
*
|
||||
* @deprecated This behavior relies on the deprecated
|
||||
* {@link Fragment#setUserVisibleHint(boolean)} API. Use
|
||||
* {@link #BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT} to switch to its replacement,
|
||||
* {@link FragmentTransaction#setMaxLifecycle}.
|
||||
* @see #FragmentPagerAdapter(FragmentManager, int)
|
||||
*/
|
||||
@Deprecated
|
||||
public static final int BEHAVIOR_SET_USER_VISIBLE_HINT = 0;
|
||||
|
||||
/**
|
||||
* Indicates that only the current fragment will be in the {@link Lifecycle.State#RESUMED}
|
||||
* state. All other Fragments are capped at {@link Lifecycle.State#STARTED}.
|
||||
*
|
||||
* @see #FragmentPagerAdapter(FragmentManager, int)
|
||||
*/
|
||||
public static final int BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT = 1;
|
||||
|
||||
private final FragmentManager mFragmentManager;
|
||||
private final int mBehavior;
|
||||
private FragmentTransaction mCurTransaction = null;
|
||||
private Fragment mCurrentPrimaryItem = null;
|
||||
|
||||
/**
|
||||
* Constructor for {@link FragmentPagerAdapter} that sets the fragment manager for the adapter.
|
||||
* This is the equivalent of calling {@link #FragmentPagerAdapter(FragmentManager, int)} and
|
||||
* passing in {@link #BEHAVIOR_SET_USER_VISIBLE_HINT}.
|
||||
*
|
||||
* <p>Fragments will have {@link Fragment#setUserVisibleHint(boolean)} called whenever the
|
||||
* current Fragment changes.</p>
|
||||
*
|
||||
* @param fm fragment manager that will interact with this adapter
|
||||
* @deprecated use {@link #FragmentPagerAdapter(FragmentManager, int)} with
|
||||
* {@link #BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT}
|
||||
*/
|
||||
@Deprecated
|
||||
public FragmentPagerAdapter(@NonNull FragmentManager fm) {
|
||||
this(fm, BEHAVIOR_SET_USER_VISIBLE_HINT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for {@link FragmentPagerAdapter}.
|
||||
*
|
||||
* If {@link #BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT} is passed in, then only the current
|
||||
* Fragment is in the {@link Lifecycle.State#RESUMED} state. All other fragments are capped at
|
||||
* {@link Lifecycle.State#STARTED}. If {@link #BEHAVIOR_SET_USER_VISIBLE_HINT} is passed, all
|
||||
* fragments are in the {@link Lifecycle.State#RESUMED} state and there will be callbacks to
|
||||
* {@link Fragment#setUserVisibleHint(boolean)}.
|
||||
*
|
||||
* @param fm fragment manager that will interact with this adapter
|
||||
* @param behavior determines if only current fragments are in a resumed state
|
||||
*/
|
||||
public FragmentPagerAdapter(@NonNull FragmentManager fm,
|
||||
@Behavior int behavior) {
|
||||
mFragmentManager = fm;
|
||||
mBehavior = behavior;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Fragment associated with a specified position.
|
||||
*/
|
||||
@NonNull
|
||||
public abstract Fragment getItem(int position);
|
||||
|
||||
@Override
|
||||
public void startUpdate(@NonNull ViewGroup container) {
|
||||
if (container.getId() == View.NO_ID) {
|
||||
throw new IllegalStateException("ViewPager with adapter " + this
|
||||
+ " requires a view id");
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"ReferenceEquality", "deprecation"})
|
||||
@NonNull
|
||||
@Override
|
||||
public Object instantiateItem(@NonNull ViewGroup container, int position) {
|
||||
if (mCurTransaction == null) {
|
||||
mCurTransaction = mFragmentManager.beginTransaction();
|
||||
}
|
||||
|
||||
final long itemId = getItemId(position);
|
||||
|
||||
// Do we already have this fragment?
|
||||
String name = makeFragmentName(container.getId(), itemId);
|
||||
Fragment fragment = mFragmentManager.findFragmentByTag(name);
|
||||
if (fragment != null) {
|
||||
if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
|
||||
mCurTransaction.attach(fragment);
|
||||
} else {
|
||||
fragment = getItem(position);
|
||||
if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
|
||||
mCurTransaction.add(container.getId(), fragment,
|
||||
makeFragmentName(container.getId(), itemId));
|
||||
}
|
||||
if (fragment != mCurrentPrimaryItem) {
|
||||
fragment.setMenuVisibility(false);
|
||||
if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
|
||||
mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED);
|
||||
} else {
|
||||
fragment.setUserVisibleHint(false);
|
||||
}
|
||||
}
|
||||
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
|
||||
Fragment fragment = (Fragment) object;
|
||||
|
||||
if (mCurTransaction == null) {
|
||||
mCurTransaction = mFragmentManager.beginTransaction();
|
||||
}
|
||||
if (DEBUG) Log.v(TAG, "Detaching item #" + getItemId(position) + ": f=" + object
|
||||
+ " v=" + (fragment.getView()));
|
||||
mCurTransaction.detach(fragment);
|
||||
if (fragment == mCurrentPrimaryItem) {
|
||||
mCurrentPrimaryItem = null;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"ReferenceEquality", "deprecation"})
|
||||
@Override
|
||||
public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
|
||||
Fragment fragment = (Fragment)object;
|
||||
if (fragment != mCurrentPrimaryItem) {
|
||||
if (mCurrentPrimaryItem != null) {
|
||||
mCurrentPrimaryItem.setMenuVisibility(false);
|
||||
if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
|
||||
if (mCurTransaction == null) {
|
||||
mCurTransaction = mFragmentManager.beginTransaction();
|
||||
}
|
||||
mCurTransaction.setMaxLifecycle(mCurrentPrimaryItem, Lifecycle.State.STARTED);
|
||||
} else {
|
||||
mCurrentPrimaryItem.setUserVisibleHint(false);
|
||||
}
|
||||
}
|
||||
fragment.setMenuVisibility(true);
|
||||
if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
|
||||
if (mCurTransaction == null) {
|
||||
mCurTransaction = mFragmentManager.beginTransaction();
|
||||
}
|
||||
mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.RESUMED);
|
||||
} else {
|
||||
fragment.setUserVisibleHint(true);
|
||||
}
|
||||
|
||||
mCurrentPrimaryItem = fragment;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finishUpdate(@NonNull ViewGroup container) {
|
||||
if (mCurTransaction != null) {
|
||||
mCurTransaction.commitNowAllowingStateLoss();
|
||||
mCurTransaction = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
|
||||
return ((Fragment)object).getView() == view;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Parcelable saveState() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void restoreState(@Nullable Parcelable state, @Nullable ClassLoader loader) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a unique identifier for the item at the given position.
|
||||
*
|
||||
* <p>The default implementation returns the given position.
|
||||
* Subclasses should override this method if the positions of items can change.</p>
|
||||
*
|
||||
* @param position Position within this adapter
|
||||
* @return Unique identifier for the item at position
|
||||
*/
|
||||
public long getItemId(int position) {
|
||||
return position;
|
||||
}
|
||||
|
||||
private static String makeFragmentName(int viewId, long id) {
|
||||
return "android:switcher:" + viewId + ":" + id;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* Copyright 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.fragment.app;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.Lifecycle;
|
||||
|
||||
@SuppressLint("BanParcelableUsage")
|
||||
final class FragmentState implements Parcelable {
|
||||
final String mClassName;
|
||||
final String mWho;
|
||||
final boolean mFromLayout;
|
||||
final int mFragmentId;
|
||||
final int mContainerId;
|
||||
final String mTag;
|
||||
final boolean mRetainInstance;
|
||||
final boolean mRemoving;
|
||||
final boolean mDetached;
|
||||
final Bundle mArguments;
|
||||
final boolean mHidden;
|
||||
final int mMaxLifecycleState;
|
||||
|
||||
Bundle mSavedFragmentState;
|
||||
|
||||
Fragment mInstance;
|
||||
|
||||
FragmentState(Fragment frag) {
|
||||
mClassName = frag.getClass().getName();
|
||||
mWho = frag.mWho;
|
||||
mFromLayout = frag.mFromLayout;
|
||||
mFragmentId = frag.mFragmentId;
|
||||
mContainerId = frag.mContainerId;
|
||||
mTag = frag.mTag;
|
||||
mRetainInstance = frag.mRetainInstance;
|
||||
mRemoving = frag.mRemoving;
|
||||
mDetached = frag.mDetached;
|
||||
mArguments = frag.mArguments;
|
||||
mHidden = frag.mHidden;
|
||||
mMaxLifecycleState = frag.mMaxState.ordinal();
|
||||
}
|
||||
|
||||
FragmentState(Parcel in) {
|
||||
mClassName = in.readString();
|
||||
mWho = in.readString();
|
||||
mFromLayout = in.readInt() != 0;
|
||||
mFragmentId = in.readInt();
|
||||
mContainerId = in.readInt();
|
||||
mTag = in.readString();
|
||||
mRetainInstance = in.readInt() != 0;
|
||||
mRemoving = in.readInt() != 0;
|
||||
mDetached = in.readInt() != 0;
|
||||
mArguments = in.readBundle();
|
||||
mHidden = in.readInt() != 0;
|
||||
mSavedFragmentState = in.readBundle();
|
||||
mMaxLifecycleState = in.readInt();
|
||||
}
|
||||
|
||||
public Fragment instantiate(@NonNull ClassLoader classLoader,
|
||||
@NonNull FragmentFactory factory) {
|
||||
if (mInstance == null) {
|
||||
if (mArguments != null) {
|
||||
mArguments.setClassLoader(classLoader);
|
||||
}
|
||||
|
||||
mInstance = factory.instantiate(classLoader, mClassName);
|
||||
mInstance.setArguments(mArguments);
|
||||
|
||||
if (mSavedFragmentState != null) {
|
||||
mSavedFragmentState.setClassLoader(classLoader);
|
||||
mInstance.mSavedFragmentState = mSavedFragmentState;
|
||||
} else {
|
||||
// When restoring a Fragment, always ensure we have a
|
||||
// non-null Bundle so that developers have a signal for
|
||||
// when the Fragment is being restored
|
||||
mInstance.mSavedFragmentState = new Bundle();
|
||||
}
|
||||
mInstance.mWho = mWho;
|
||||
mInstance.mFromLayout = mFromLayout;
|
||||
mInstance.mRestored = true;
|
||||
mInstance.mFragmentId = mFragmentId;
|
||||
mInstance.mContainerId = mContainerId;
|
||||
mInstance.mTag = mTag;
|
||||
mInstance.mRetainInstance = mRetainInstance;
|
||||
mInstance.mRemoving = mRemoving;
|
||||
mInstance.mDetached = mDetached;
|
||||
mInstance.mHidden = mHidden;
|
||||
mInstance.mMaxState = Lifecycle.State.values()[mMaxLifecycleState];
|
||||
|
||||
if (FragmentManagerImpl.DEBUG) {
|
||||
Log.v(FragmentManagerImpl.TAG, "Instantiated fragment " + mInstance);
|
||||
}
|
||||
}
|
||||
return mInstance;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder(128);
|
||||
sb.append("FragmentState{");
|
||||
sb.append(mClassName);
|
||||
sb.append(" (");
|
||||
sb.append(mWho);
|
||||
sb.append(")}:");
|
||||
if (mFromLayout) {
|
||||
sb.append(" fromLayout");
|
||||
}
|
||||
if (mContainerId != 0) {
|
||||
sb.append(" id=0x");
|
||||
sb.append(Integer.toHexString(mContainerId));
|
||||
}
|
||||
if (mTag != null && !mTag.isEmpty()) {
|
||||
sb.append(" tag=");
|
||||
sb.append(mTag);
|
||||
}
|
||||
if (mRetainInstance) {
|
||||
sb.append(" retainInstance");
|
||||
}
|
||||
if (mRemoving) {
|
||||
sb.append(" removing");
|
||||
}
|
||||
if (mDetached) {
|
||||
sb.append(" detached");
|
||||
}
|
||||
if (mHidden) {
|
||||
sb.append(" hidden");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(mClassName);
|
||||
dest.writeString(mWho);
|
||||
dest.writeInt(mFromLayout ? 1 : 0);
|
||||
dest.writeInt(mFragmentId);
|
||||
dest.writeInt(mContainerId);
|
||||
dest.writeString(mTag);
|
||||
dest.writeInt(mRetainInstance ? 1 : 0);
|
||||
dest.writeInt(mRemoving ? 1 : 0);
|
||||
dest.writeInt(mDetached ? 1 : 0);
|
||||
dest.writeBundle(mArguments);
|
||||
dest.writeInt(mHidden ? 1 : 0);
|
||||
dest.writeBundle(mSavedFragmentState);
|
||||
dest.writeInt(mMaxLifecycleState);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<FragmentState> CREATOR =
|
||||
new Parcelable.Creator<FragmentState>() {
|
||||
@Override
|
||||
public FragmentState createFromParcel(Parcel in) {
|
||||
return new FragmentState(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FragmentState[] newArray(int size) {
|
||||
return new FragmentState[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
/*
|
||||
* Copyright 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.fragment.app;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.Lifecycle;
|
||||
import androidx.viewpager.widget.PagerAdapter;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Implementation of {@link PagerAdapter} that
|
||||
* uses a {@link Fragment} to manage each page. This class also handles
|
||||
* saving and restoring of fragment's state.
|
||||
*
|
||||
* <p>This version of the pager is more useful when there are a large number
|
||||
* of pages, working more like a list view. When pages are not visible to
|
||||
* the user, their entire fragment may be destroyed, only keeping the saved
|
||||
* state of that fragment. This allows the pager to hold on to much less
|
||||
* memory associated with each visited page as compared to
|
||||
* {@link FragmentPagerAdapter} at the cost of potentially more overhead when
|
||||
* switching between pages.
|
||||
*
|
||||
* <p>When using FragmentPagerAdapter the host ViewPager must have a
|
||||
* valid ID set.</p>
|
||||
*
|
||||
* <p>Subclasses only need to implement {@link #getItem(int)}
|
||||
* and {@link #getCount()} to have a working adapter.
|
||||
*
|
||||
* <p>Here is an example implementation of a pager containing fragments of
|
||||
* lists:
|
||||
*
|
||||
* {@sample frameworks/support/samples/Support4Demos/src/main/java/com/example/android/supportv4/app/FragmentStatePagerSupport.java
|
||||
* complete}
|
||||
*
|
||||
* <p>The <code>R.layout.fragment_pager</code> resource of the top-level fragment is:
|
||||
*
|
||||
* {@sample frameworks/support/samples/Support4Demos/src/main/res/layout/fragment_pager.xml
|
||||
* complete}
|
||||
*
|
||||
* <p>The <code>R.layout.fragment_pager_list</code> resource containing each
|
||||
* individual fragment's layout is:
|
||||
*
|
||||
* {@sample frameworks/support/samples/Support4Demos/src/main/res/layout/fragment_pager_list.xml
|
||||
* complete}
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public abstract class FragmentStatePagerAdapter extends PagerAdapter {
|
||||
private static final String TAG = "FragmentStatePagerAdapt";
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({BEHAVIOR_SET_USER_VISIBLE_HINT, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT})
|
||||
private @interface Behavior { }
|
||||
|
||||
/**
|
||||
* Indicates that {@link Fragment#setUserVisibleHint(boolean)} will be called when the current
|
||||
* fragment changes.
|
||||
*
|
||||
* @deprecated This behavior relies on the deprecated
|
||||
* {@link Fragment#setUserVisibleHint(boolean)} API. Use
|
||||
* {@link #BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT} to switch to its replacement,
|
||||
* {@link FragmentTransaction#setMaxLifecycle}.
|
||||
* @see #FragmentStatePagerAdapter(FragmentManager, int)
|
||||
*/
|
||||
@Deprecated
|
||||
public static final int BEHAVIOR_SET_USER_VISIBLE_HINT = 0;
|
||||
|
||||
/**
|
||||
* Indicates that only the current fragment will be in the {@link Lifecycle.State#RESUMED}
|
||||
* state. All other Fragments are capped at {@link Lifecycle.State#STARTED}.
|
||||
*
|
||||
* @see #FragmentStatePagerAdapter(FragmentManager, int)
|
||||
*/
|
||||
public static final int BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT = 1;
|
||||
|
||||
private final FragmentManager mFragmentManager;
|
||||
private final int mBehavior;
|
||||
private FragmentTransaction mCurTransaction = null;
|
||||
|
||||
private ArrayList<Fragment.SavedState> mSavedState = new ArrayList<Fragment.SavedState>();
|
||||
private ArrayList<Fragment> mFragments = new ArrayList<Fragment>();
|
||||
private Fragment mCurrentPrimaryItem = null;
|
||||
|
||||
/**
|
||||
* Constructor for {@link FragmentStatePagerAdapter} that sets the fragment manager for the
|
||||
* adapter. This is the equivalent of calling
|
||||
* {@link #FragmentStatePagerAdapter(FragmentManager, int)} and passing in
|
||||
* {@link #BEHAVIOR_SET_USER_VISIBLE_HINT}.
|
||||
*
|
||||
* <p>Fragments will have {@link Fragment#setUserVisibleHint(boolean)} called whenever the
|
||||
* current Fragment changes.</p>
|
||||
*
|
||||
* @param fm fragment manager that will interact with this adapter
|
||||
* @deprecated use {@link #FragmentStatePagerAdapter(FragmentManager, int)} with
|
||||
* {@link #BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT}
|
||||
*/
|
||||
@Deprecated
|
||||
public FragmentStatePagerAdapter(@NonNull FragmentManager fm) {
|
||||
this(fm, BEHAVIOR_SET_USER_VISIBLE_HINT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for {@link FragmentStatePagerAdapter}.
|
||||
*
|
||||
* If {@link #BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT} is passed in, then only the current
|
||||
* Fragment is in the {@link Lifecycle.State#RESUMED} state, while all other fragments are
|
||||
* capped at {@link Lifecycle.State#STARTED}. If {@link #BEHAVIOR_SET_USER_VISIBLE_HINT} is
|
||||
* passed, all fragments are in the {@link Lifecycle.State#RESUMED} state and there will be
|
||||
* callbacks to {@link Fragment#setUserVisibleHint(boolean)}.
|
||||
*
|
||||
* @param fm fragment manager that will interact with this adapter
|
||||
* @param behavior determines if only current fragments are in a resumed state
|
||||
*/
|
||||
public FragmentStatePagerAdapter(@NonNull FragmentManager fm,
|
||||
@Behavior int behavior) {
|
||||
mFragmentManager = fm;
|
||||
mBehavior = behavior;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Fragment associated with a specified position.
|
||||
*/
|
||||
@NonNull
|
||||
public abstract Fragment getItem(int position);
|
||||
|
||||
@Override
|
||||
public void startUpdate(@NonNull ViewGroup container) {
|
||||
if (container.getId() == View.NO_ID) {
|
||||
throw new IllegalStateException("ViewPager with adapter " + this
|
||||
+ " requires a view id");
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@NonNull
|
||||
@Override
|
||||
public Object instantiateItem(@NonNull ViewGroup container, int position) {
|
||||
// If we already have this item instantiated, there is nothing
|
||||
// to do. This can happen when we are restoring the entire pager
|
||||
// from its saved state, where the fragment manager has already
|
||||
// taken care of restoring the fragments we previously had instantiated.
|
||||
if (mFragments.size() > position) {
|
||||
Fragment f = mFragments.get(position);
|
||||
if (f != null) {
|
||||
return f;
|
||||
}
|
||||
}
|
||||
|
||||
if (mCurTransaction == null) {
|
||||
mCurTransaction = mFragmentManager.beginTransaction();
|
||||
}
|
||||
|
||||
Fragment fragment = getItem(position);
|
||||
if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
|
||||
if (mSavedState.size() > position) {
|
||||
Fragment.SavedState fss = mSavedState.get(position);
|
||||
if (fss != null) {
|
||||
fragment.setInitialSavedState(fss);
|
||||
}
|
||||
}
|
||||
while (mFragments.size() <= position) {
|
||||
mFragments.add(null);
|
||||
}
|
||||
fragment.setMenuVisibility(false);
|
||||
if (mBehavior == BEHAVIOR_SET_USER_VISIBLE_HINT) {
|
||||
fragment.setUserVisibleHint(false);
|
||||
}
|
||||
|
||||
mFragments.set(position, fragment);
|
||||
mCurTransaction.add(container.getId(), fragment);
|
||||
|
||||
if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
|
||||
mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED);
|
||||
}
|
||||
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
|
||||
Fragment fragment = (Fragment) object;
|
||||
|
||||
if (mCurTransaction == null) {
|
||||
mCurTransaction = mFragmentManager.beginTransaction();
|
||||
}
|
||||
if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object
|
||||
+ " v=" + ((Fragment)object).getView());
|
||||
while (mSavedState.size() <= position) {
|
||||
mSavedState.add(null);
|
||||
}
|
||||
mSavedState.set(position, fragment.isAdded()
|
||||
? mFragmentManager.saveFragmentInstanceState(fragment) : null);
|
||||
mFragments.set(position, null);
|
||||
|
||||
mCurTransaction.remove(fragment);
|
||||
if (fragment == mCurrentPrimaryItem) {
|
||||
mCurrentPrimaryItem = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings({"ReferenceEquality", "deprecation"})
|
||||
public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
|
||||
Fragment fragment = (Fragment)object;
|
||||
if (fragment != mCurrentPrimaryItem) {
|
||||
if (mCurrentPrimaryItem != null) {
|
||||
mCurrentPrimaryItem.setMenuVisibility(false);
|
||||
if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
|
||||
if (mCurTransaction == null) {
|
||||
mCurTransaction = mFragmentManager.beginTransaction();
|
||||
}
|
||||
mCurTransaction.setMaxLifecycle(mCurrentPrimaryItem, Lifecycle.State.STARTED);
|
||||
} else {
|
||||
mCurrentPrimaryItem.setUserVisibleHint(false);
|
||||
}
|
||||
}
|
||||
fragment.setMenuVisibility(true);
|
||||
if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
|
||||
if (mCurTransaction == null) {
|
||||
mCurTransaction = mFragmentManager.beginTransaction();
|
||||
}
|
||||
mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.RESUMED);
|
||||
} else {
|
||||
fragment.setUserVisibleHint(true);
|
||||
}
|
||||
|
||||
mCurrentPrimaryItem = fragment;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finishUpdate(@NonNull ViewGroup container) {
|
||||
if (mCurTransaction != null) {
|
||||
mCurTransaction.commitNowAllowingStateLoss();
|
||||
mCurTransaction = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
|
||||
return ((Fragment)object).getView() == view;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Parcelable saveState() {
|
||||
Bundle state = null;
|
||||
if (mSavedState.size() > 0) {
|
||||
state = new Bundle();
|
||||
Fragment.SavedState[] fss = new Fragment.SavedState[mSavedState.size()];
|
||||
mSavedState.toArray(fss);
|
||||
state.putParcelableArray("states", fss);
|
||||
}
|
||||
for (int i=0; i<mFragments.size(); i++) {
|
||||
Fragment f = mFragments.get(i);
|
||||
if (f != null && f.isAdded()) {
|
||||
if (state == null) {
|
||||
state = new Bundle();
|
||||
}
|
||||
String key = "f" + i;
|
||||
mFragmentManager.putFragment(state, key, f);
|
||||
}
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void restoreState(@Nullable Parcelable state, @Nullable ClassLoader loader) {
|
||||
if (state != null) {
|
||||
Bundle bundle = (Bundle)state;
|
||||
bundle.setClassLoader(loader);
|
||||
Parcelable[] fss = bundle.getParcelableArray("states");
|
||||
mSavedState.clear();
|
||||
mFragments.clear();
|
||||
if (fss != null) {
|
||||
for (int i=0; i<fss.length; i++) {
|
||||
mSavedState.add((Fragment.SavedState)fss[i]);
|
||||
}
|
||||
}
|
||||
Iterable<String> keys = bundle.keySet();
|
||||
for (String key: keys) {
|
||||
if (key.startsWith("f")) {
|
||||
int index = Integer.parseInt(key.substring(1));
|
||||
Fragment f = mFragmentManager.getFragment(bundle, key);
|
||||
if (f != null) {
|
||||
while (mFragments.size() <= index) {
|
||||
mFragments.add(null);
|
||||
}
|
||||
f.setMenuVisibility(false);
|
||||
mFragments.set(index, f);
|
||||
} else {
|
||||
Log.w(TAG, "Bad fragment at key " + key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,441 @@
|
||||
/*
|
||||
* Copyright 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.fragment.app;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TabHost;
|
||||
import android.widget.TabWidget;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Special TabHost that allows the use of {@link Fragment} objects for
|
||||
* its tab content. When placing this in a view hierarchy, after inflating
|
||||
* the hierarchy you must call {@link #setup(Context, FragmentManager, int)}
|
||||
* to complete the initialization of the tab host.
|
||||
*
|
||||
* @deprecated Use <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
|
||||
* TabLayout and ViewPager</a> instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public class FragmentTabHost extends TabHost
|
||||
implements TabHost.OnTabChangeListener {
|
||||
private final ArrayList<TabInfo> mTabs = new ArrayList<>();
|
||||
|
||||
private FrameLayout mRealTabContent;
|
||||
private Context mContext;
|
||||
private FragmentManager mFragmentManager;
|
||||
private int mContainerId;
|
||||
private TabHost.OnTabChangeListener mOnTabChangeListener;
|
||||
private TabInfo mLastTab;
|
||||
private boolean mAttached;
|
||||
|
||||
static final class TabInfo {
|
||||
final @NonNull String tag;
|
||||
final @NonNull Class<?> clss;
|
||||
final @Nullable Bundle args;
|
||||
Fragment fragment;
|
||||
|
||||
TabInfo(@NonNull String _tag, @NonNull Class<?> _class, @Nullable Bundle _args) {
|
||||
tag = _tag;
|
||||
clss = _class;
|
||||
args = _args;
|
||||
}
|
||||
}
|
||||
|
||||
static class DummyTabFactory implements TabHost.TabContentFactory {
|
||||
private final Context mContext;
|
||||
|
||||
public DummyTabFactory(Context context) {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View createTabContent(String tag) {
|
||||
View v = new View(mContext);
|
||||
v.setMinimumWidth(0);
|
||||
v.setMinimumHeight(0);
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
static class SavedState extends BaseSavedState {
|
||||
String curTab;
|
||||
|
||||
SavedState(Parcelable superState) {
|
||||
super(superState);
|
||||
}
|
||||
|
||||
SavedState(Parcel in) {
|
||||
super(in);
|
||||
curTab = in.readString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel out, int flags) {
|
||||
super.writeToParcel(out, flags);
|
||||
out.writeString(curTab);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "FragmentTabHost.SavedState{"
|
||||
+ Integer.toHexString(System.identityHashCode(this))
|
||||
+ " curTab=" + curTab + "}";
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<SavedState> CREATOR
|
||||
= new Parcelable.Creator<SavedState>() {
|
||||
@Override
|
||||
public SavedState createFromParcel(Parcel in) {
|
||||
return new SavedState(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SavedState[] newArray(int size) {
|
||||
return new SavedState[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use
|
||||
* <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
|
||||
* TabLayout and ViewPager</a> instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public FragmentTabHost(@NonNull Context context) {
|
||||
// Note that we call through to the version that takes an AttributeSet,
|
||||
// because the simple Context construct can result in a broken object!
|
||||
super(context, null);
|
||||
initFragmentTabHost(context, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use
|
||||
* <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
|
||||
* TabLayout and ViewPager</a> instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public FragmentTabHost(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
initFragmentTabHost(context, attrs);
|
||||
}
|
||||
|
||||
private void initFragmentTabHost(Context context, AttributeSet attrs) {
|
||||
final TypedArray a = context.obtainStyledAttributes(attrs,
|
||||
new int[] { android.R.attr.inflatedId }, 0, 0);
|
||||
mContainerId = a.getResourceId(0, 0);
|
||||
a.recycle();
|
||||
|
||||
super.setOnTabChangedListener(this);
|
||||
}
|
||||
|
||||
private void ensureHierarchy(Context context) {
|
||||
// If owner hasn't made its own view hierarchy, then as a convenience
|
||||
// we will construct a standard one here.
|
||||
if (findViewById(android.R.id.tabs) == null) {
|
||||
LinearLayout ll = new LinearLayout(context);
|
||||
ll.setOrientation(LinearLayout.VERTICAL);
|
||||
addView(ll, new FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
|
||||
TabWidget tw = new TabWidget(context);
|
||||
tw.setId(android.R.id.tabs);
|
||||
tw.setOrientation(TabWidget.HORIZONTAL);
|
||||
ll.addView(tw, new LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT, 0));
|
||||
|
||||
FrameLayout fl = new FrameLayout(context);
|
||||
fl.setId(android.R.id.tabcontent);
|
||||
ll.addView(fl, new LinearLayout.LayoutParams(0, 0, 0));
|
||||
|
||||
mRealTabContent = fl = new FrameLayout(context);
|
||||
mRealTabContent.setId(mContainerId);
|
||||
ll.addView(fl, new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT, 0, 1));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use
|
||||
* <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
|
||||
* TabLayout and ViewPager</a> instead.
|
||||
*/
|
||||
@Override @Deprecated
|
||||
public void setup() {
|
||||
throw new IllegalStateException(
|
||||
"Must call setup() that takes a Context and FragmentManager");
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the FragmentTabHost to use the given FragmentManager
|
||||
*
|
||||
* @deprecated Use
|
||||
* <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
|
||||
* TabLayout and ViewPager</a> instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public void setup(@NonNull Context context, @NonNull FragmentManager manager) {
|
||||
ensureHierarchy(context); // Ensure views required by super.setup()
|
||||
super.setup();
|
||||
mContext = context;
|
||||
mFragmentManager = manager;
|
||||
ensureContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the FragmentTabHost to use the given FragmentManager
|
||||
*
|
||||
* @deprecated Use
|
||||
* <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
|
||||
* TabLayout and ViewPager</a> instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public void setup(@NonNull Context context, @NonNull FragmentManager manager,
|
||||
int containerId) {
|
||||
ensureHierarchy(context); // Ensure views required by super.setup()
|
||||
super.setup();
|
||||
mContext = context;
|
||||
mFragmentManager = manager;
|
||||
mContainerId = containerId;
|
||||
ensureContent();
|
||||
mRealTabContent.setId(containerId);
|
||||
|
||||
// We must have an ID to be able to save/restore our state. If
|
||||
// the owner hasn't set one at this point, we will set it ourselves.
|
||||
if (getId() == View.NO_ID) {
|
||||
setId(android.R.id.tabhost);
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureContent() {
|
||||
if (mRealTabContent == null) {
|
||||
mRealTabContent = (FrameLayout)findViewById(mContainerId);
|
||||
if (mRealTabContent == null) {
|
||||
throw new IllegalStateException(
|
||||
"No tab content FrameLayout found for id " + mContainerId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use
|
||||
* <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
|
||||
* TabLayout and ViewPager</a> instead.
|
||||
*/
|
||||
@Deprecated
|
||||
@Override
|
||||
public void setOnTabChangedListener(@Nullable OnTabChangeListener l) {
|
||||
mOnTabChangeListener = l;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use
|
||||
* <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
|
||||
* TabLayout and ViewPager</a> instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public void addTab(@NonNull TabHost.TabSpec tabSpec, @NonNull Class<?> clss,
|
||||
@Nullable Bundle args) {
|
||||
tabSpec.setContent(new DummyTabFactory(mContext));
|
||||
|
||||
final String tag = tabSpec.getTag();
|
||||
final TabInfo info = new TabInfo(tag, clss, args);
|
||||
|
||||
if (mAttached) {
|
||||
// If we are already attached to the window, then check to make
|
||||
// sure this tab's fragment is inactive if it exists. This shouldn't
|
||||
// normally happen.
|
||||
info.fragment = mFragmentManager.findFragmentByTag(tag);
|
||||
if (info.fragment != null && !info.fragment.isDetached()) {
|
||||
final FragmentTransaction ft = mFragmentManager.beginTransaction();
|
||||
ft.detach(info.fragment);
|
||||
ft.commit();
|
||||
}
|
||||
}
|
||||
|
||||
mTabs.add(info);
|
||||
addTab(tabSpec);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use
|
||||
* <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
|
||||
* TabLayout and ViewPager</a> instead.
|
||||
*/
|
||||
@Deprecated
|
||||
@Override
|
||||
protected void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
|
||||
final String currentTag = getCurrentTabTag();
|
||||
|
||||
// Go through all tabs and make sure their fragments match
|
||||
// the correct state.
|
||||
FragmentTransaction ft = null;
|
||||
for (int i = 0, count = mTabs.size(); i < count; i++) {
|
||||
final TabInfo tab = mTabs.get(i);
|
||||
tab.fragment = mFragmentManager.findFragmentByTag(tab.tag);
|
||||
if (tab.fragment != null && !tab.fragment.isDetached()) {
|
||||
if (tab.tag.equals(currentTag)) {
|
||||
// The fragment for this tab is already there and
|
||||
// active, and it is what we really want to have
|
||||
// as the current tab. Nothing to do.
|
||||
mLastTab = tab;
|
||||
} else {
|
||||
// This fragment was restored in the active state,
|
||||
// but is not the current tab. Deactivate it.
|
||||
if (ft == null) {
|
||||
ft = mFragmentManager.beginTransaction();
|
||||
}
|
||||
ft.detach(tab.fragment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We are now ready to go. Make sure we are switched to the
|
||||
// correct tab.
|
||||
mAttached = true;
|
||||
ft = doTabChanged(currentTag, ft);
|
||||
if (ft != null) {
|
||||
ft.commit();
|
||||
mFragmentManager.executePendingTransactions();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use
|
||||
* <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
|
||||
* TabLayout and ViewPager</a> instead.
|
||||
*/
|
||||
@Deprecated
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
mAttached = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use
|
||||
* <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
|
||||
* TabLayout and ViewPager</a> instead.
|
||||
*/
|
||||
@Deprecated
|
||||
@Override
|
||||
@NonNull
|
||||
protected Parcelable onSaveInstanceState() {
|
||||
Parcelable superState = super.onSaveInstanceState();
|
||||
SavedState ss = new SavedState(superState);
|
||||
ss.curTab = getCurrentTabTag();
|
||||
return ss;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use
|
||||
* <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
|
||||
* TabLayout and ViewPager</a> instead.
|
||||
*/
|
||||
@Deprecated
|
||||
@Override
|
||||
protected void onRestoreInstanceState(@SuppressLint("UnknownNullness") Parcelable state) {
|
||||
if (!(state instanceof SavedState)) {
|
||||
super.onRestoreInstanceState(state);
|
||||
return;
|
||||
}
|
||||
SavedState ss = (SavedState) state;
|
||||
super.onRestoreInstanceState(ss.getSuperState());
|
||||
setCurrentTabByTag(ss.curTab);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use
|
||||
* <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
|
||||
* TabLayout and ViewPager</a> instead.
|
||||
*/
|
||||
@Deprecated
|
||||
@Override
|
||||
public void onTabChanged(@Nullable String tabId) {
|
||||
if (mAttached) {
|
||||
final FragmentTransaction ft = doTabChanged(tabId, null);
|
||||
if (ft != null) {
|
||||
ft.commit();
|
||||
}
|
||||
}
|
||||
if (mOnTabChangeListener != null) {
|
||||
mOnTabChangeListener.onTabChanged(tabId);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private FragmentTransaction doTabChanged(@Nullable String tag,
|
||||
@Nullable FragmentTransaction ft) {
|
||||
final TabInfo newTab = getTabInfoForTag(tag);
|
||||
if (mLastTab != newTab) {
|
||||
if (ft == null) {
|
||||
ft = mFragmentManager.beginTransaction();
|
||||
}
|
||||
|
||||
if (mLastTab != null) {
|
||||
if (mLastTab.fragment != null) {
|
||||
ft.detach(mLastTab.fragment);
|
||||
}
|
||||
}
|
||||
|
||||
if (newTab != null) {
|
||||
if (newTab.fragment == null) {
|
||||
newTab.fragment = mFragmentManager.getFragmentFactory().instantiate(
|
||||
mContext.getClassLoader(), newTab.clss.getName());
|
||||
newTab.fragment.setArguments(newTab.args);
|
||||
ft.add(mContainerId, newTab.fragment, newTab.tag);
|
||||
} else {
|
||||
ft.attach(newTab.fragment);
|
||||
}
|
||||
}
|
||||
|
||||
mLastTab = newTab;
|
||||
}
|
||||
|
||||
return ft;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private TabInfo getTabInfoForTag(String tabId) {
|
||||
for (int i = 0, count = mTabs.size(); i < count; i++) {
|
||||
final TabInfo tab = mTabs.get(i);
|
||||
if (tab.tag.equals(tabId)) {
|
||||
return tab;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,731 @@
|
||||
/*
|
||||
* Copyright 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.fragment.app;
|
||||
|
||||
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.AnimRes;
|
||||
import androidx.annotation.AnimatorRes;
|
||||
import androidx.annotation.IdRes;
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RestrictTo;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.annotation.StyleRes;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.lifecycle.Lifecycle;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Static library support version of the framework's {@link android.app.FragmentTransaction}.
|
||||
* Used to write apps that run on platforms prior to Android 3.0. When running
|
||||
* on Android 3.0 or above, this implementation is still used; it does not try
|
||||
* to switch to the framework's implementation. See the framework SDK
|
||||
* documentation for a class overview.
|
||||
*/
|
||||
public abstract class FragmentTransaction {
|
||||
|
||||
static final int OP_NULL = 0;
|
||||
static final int OP_ADD = 1;
|
||||
static final int OP_REPLACE = 2;
|
||||
static final int OP_REMOVE = 3;
|
||||
static final int OP_HIDE = 4;
|
||||
static final int OP_SHOW = 5;
|
||||
static final int OP_DETACH = 6;
|
||||
static final int OP_ATTACH = 7;
|
||||
static final int OP_SET_PRIMARY_NAV = 8;
|
||||
static final int OP_UNSET_PRIMARY_NAV = 9;
|
||||
static final int OP_SET_MAX_LIFECYCLE = 10;
|
||||
|
||||
static final class Op {
|
||||
int mCmd;
|
||||
Fragment mFragment;
|
||||
int mEnterAnim;
|
||||
int mExitAnim;
|
||||
int mPopEnterAnim;
|
||||
int mPopExitAnim;
|
||||
Lifecycle.State mOldMaxState;
|
||||
Lifecycle.State mCurrentMaxState;
|
||||
|
||||
Op() {
|
||||
}
|
||||
|
||||
Op(int cmd, Fragment fragment) {
|
||||
this.mCmd = cmd;
|
||||
this.mFragment = fragment;
|
||||
this.mOldMaxState = Lifecycle.State.RESUMED;
|
||||
this.mCurrentMaxState = Lifecycle.State.RESUMED;
|
||||
}
|
||||
|
||||
Op(int cmd, @NonNull Fragment fragment, Lifecycle.State state) {
|
||||
this.mCmd = cmd;
|
||||
this.mFragment = fragment;
|
||||
this.mOldMaxState = fragment.mMaxState;
|
||||
this.mCurrentMaxState = state;
|
||||
}
|
||||
}
|
||||
|
||||
ArrayList<Op> mOps = new ArrayList<>();
|
||||
int mEnterAnim;
|
||||
int mExitAnim;
|
||||
int mPopEnterAnim;
|
||||
int mPopExitAnim;
|
||||
int mTransition;
|
||||
int mTransitionStyle;
|
||||
boolean mAddToBackStack;
|
||||
boolean mAllowAddToBackStack = true;
|
||||
@Nullable String mName;
|
||||
|
||||
int mBreadCrumbTitleRes;
|
||||
CharSequence mBreadCrumbTitleText;
|
||||
int mBreadCrumbShortTitleRes;
|
||||
CharSequence mBreadCrumbShortTitleText;
|
||||
|
||||
ArrayList<String> mSharedElementSourceNames;
|
||||
ArrayList<String> mSharedElementTargetNames;
|
||||
boolean mReorderingAllowed = false;
|
||||
|
||||
ArrayList<Runnable> mCommitRunnables;
|
||||
|
||||
void addOp(Op op) {
|
||||
mOps.add(op);
|
||||
op.mEnterAnim = mEnterAnim;
|
||||
op.mExitAnim = mExitAnim;
|
||||
op.mPopEnterAnim = mPopEnterAnim;
|
||||
op.mPopExitAnim = mPopExitAnim;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link #add(int, Fragment, String)} with a 0 containerViewId.
|
||||
*/
|
||||
@NonNull
|
||||
public FragmentTransaction add(@NonNull Fragment fragment, @Nullable String tag) {
|
||||
doAddOp(0, fragment, tag, OP_ADD);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link #add(int, Fragment, String)} with a null tag.
|
||||
*/
|
||||
@NonNull
|
||||
public FragmentTransaction add(@IdRes int containerViewId, @NonNull Fragment fragment) {
|
||||
doAddOp(containerViewId, fragment, null, OP_ADD);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a fragment to the activity state. This fragment may optionally
|
||||
* also have its view (if {@link Fragment#onCreateView Fragment.onCreateView}
|
||||
* returns non-null) into a container view of the activity.
|
||||
*
|
||||
* @param containerViewId Optional identifier of the container this fragment is
|
||||
* to be placed in. If 0, it will not be placed in a container.
|
||||
* @param fragment The fragment to be added. This fragment must not already
|
||||
* be added to the activity.
|
||||
* @param tag Optional tag name for the fragment, to later retrieve the
|
||||
* fragment with {@link FragmentManager#findFragmentByTag(String)
|
||||
* FragmentManager.findFragmentByTag(String)}.
|
||||
*
|
||||
* @return Returns the same FragmentTransaction instance.
|
||||
*/
|
||||
@NonNull
|
||||
public FragmentTransaction add(@IdRes int containerViewId, @NonNull Fragment fragment,
|
||||
@Nullable String tag) {
|
||||
doAddOp(containerViewId, fragment, tag, OP_ADD);
|
||||
return this;
|
||||
}
|
||||
|
||||
void doAddOp(int containerViewId, Fragment fragment, @Nullable String tag, int opcmd) {
|
||||
final Class<?> fragmentClass = fragment.getClass();
|
||||
final int modifiers = fragmentClass.getModifiers();
|
||||
if (fragmentClass.isAnonymousClass() || !Modifier.isPublic(modifiers)
|
||||
|| (fragmentClass.isMemberClass() && !Modifier.isStatic(modifiers))) {
|
||||
throw new IllegalStateException("Fragment " + fragmentClass.getCanonicalName()
|
||||
+ " must be a public static class to be properly recreated from"
|
||||
+ " instance state.");
|
||||
}
|
||||
|
||||
if (tag != null) {
|
||||
if (fragment.mTag != null && !tag.equals(fragment.mTag)) {
|
||||
throw new IllegalStateException("Can't change tag of fragment "
|
||||
+ fragment + ": was " + fragment.mTag
|
||||
+ " now " + tag);
|
||||
}
|
||||
fragment.mTag = tag;
|
||||
}
|
||||
|
||||
if (containerViewId != 0) {
|
||||
if (containerViewId == View.NO_ID) {
|
||||
throw new IllegalArgumentException("Can't add fragment "
|
||||
+ fragment + " with tag " + tag + " to container view with no id");
|
||||
}
|
||||
if (fragment.mFragmentId != 0 && fragment.mFragmentId != containerViewId) {
|
||||
throw new IllegalStateException("Can't change container ID of fragment "
|
||||
+ fragment + ": was " + fragment.mFragmentId
|
||||
+ " now " + containerViewId);
|
||||
}
|
||||
fragment.mContainerId = fragment.mFragmentId = containerViewId;
|
||||
}
|
||||
|
||||
addOp(new Op(opcmd, fragment));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link #replace(int, Fragment, String)} with a null tag.
|
||||
*/
|
||||
@NonNull
|
||||
public FragmentTransaction replace(@IdRes int containerViewId, @NonNull Fragment fragment) {
|
||||
return replace(containerViewId, fragment, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace an existing fragment that was added to a container. This is
|
||||
* essentially the same as calling {@link #remove(Fragment)} for all
|
||||
* currently added fragments that were added with the same containerViewId
|
||||
* and then {@link #add(int, Fragment, String)} with the same arguments
|
||||
* given here.
|
||||
*
|
||||
* @param containerViewId Identifier of the container whose fragment(s) are
|
||||
* to be replaced.
|
||||
* @param fragment The new fragment to place in the container.
|
||||
* @param tag Optional tag name for the fragment, to later retrieve the
|
||||
* fragment with {@link FragmentManager#findFragmentByTag(String)
|
||||
* FragmentManager.findFragmentByTag(String)}.
|
||||
*
|
||||
* @return Returns the same FragmentTransaction instance.
|
||||
*/
|
||||
@NonNull
|
||||
public FragmentTransaction replace(@IdRes int containerViewId, @NonNull Fragment fragment,
|
||||
@Nullable String tag) {
|
||||
if (containerViewId == 0) {
|
||||
throw new IllegalArgumentException("Must use non-zero containerViewId");
|
||||
}
|
||||
doAddOp(containerViewId, fragment, tag, OP_REPLACE);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an existing fragment. If it was added to a container, its view
|
||||
* is also removed from that container.
|
||||
*
|
||||
* @param fragment The fragment to be removed.
|
||||
*
|
||||
* @return Returns the same FragmentTransaction instance.
|
||||
*/
|
||||
@NonNull
|
||||
public FragmentTransaction remove(@NonNull Fragment fragment) {
|
||||
addOp(new Op(OP_REMOVE, fragment));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides an existing fragment. This is only relevant for fragments whose
|
||||
* views have been added to a container, as this will cause the view to
|
||||
* be hidden.
|
||||
*
|
||||
* @param fragment The fragment to be hidden.
|
||||
*
|
||||
* @return Returns the same FragmentTransaction instance.
|
||||
*/
|
||||
@NonNull
|
||||
public FragmentTransaction hide(@NonNull Fragment fragment) {
|
||||
addOp(new Op(OP_HIDE, fragment));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a previously hidden fragment. This is only relevant for fragments whose
|
||||
* views have been added to a container, as this will cause the view to
|
||||
* be shown.
|
||||
*
|
||||
* @param fragment The fragment to be shown.
|
||||
*
|
||||
* @return Returns the same FragmentTransaction instance.
|
||||
*/
|
||||
@NonNull
|
||||
public FragmentTransaction show(@NonNull Fragment fragment) {
|
||||
addOp(new Op(OP_SHOW, fragment));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detach the given fragment from the UI. This is the same state as
|
||||
* when it is put on the back stack: the fragment is removed from
|
||||
* the UI, however its state is still being actively managed by the
|
||||
* fragment manager. When going into this state its view hierarchy
|
||||
* is destroyed.
|
||||
*
|
||||
* @param fragment The fragment to be detached.
|
||||
*
|
||||
* @return Returns the same FragmentTransaction instance.
|
||||
*/
|
||||
@NonNull
|
||||
public FragmentTransaction detach(@NonNull Fragment fragment) {
|
||||
addOp(new Op(OP_DETACH, fragment));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-attach a fragment after it had previously been detached from
|
||||
* the UI with {@link #detach(Fragment)}. This
|
||||
* causes its view hierarchy to be re-created, attached to the UI,
|
||||
* and displayed.
|
||||
*
|
||||
* @param fragment The fragment to be attached.
|
||||
*
|
||||
* @return Returns the same FragmentTransaction instance.
|
||||
*/
|
||||
@NonNull
|
||||
public FragmentTransaction attach(@NonNull Fragment fragment) {
|
||||
addOp(new Op(OP_ATTACH, fragment));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a currently active fragment in this FragmentManager as the primary navigation fragment.
|
||||
*
|
||||
* <p>The primary navigation fragment's
|
||||
* {@link Fragment#getChildFragmentManager() child FragmentManager} will be called first
|
||||
* to process delegated navigation actions such as {@link FragmentManager#popBackStack()}
|
||||
* if no ID or transaction name is provided to pop to. Navigation operations outside of the
|
||||
* fragment system may choose to delegate those actions to the primary navigation fragment
|
||||
* as returned by {@link FragmentManager#getPrimaryNavigationFragment()}.</p>
|
||||
*
|
||||
* <p>The fragment provided must currently be added to the FragmentManager to be set as
|
||||
* a primary navigation fragment, or previously added as part of this transaction.</p>
|
||||
*
|
||||
* @param fragment the fragment to set as the primary navigation fragment
|
||||
* @return the same FragmentTransaction instance
|
||||
*/
|
||||
@NonNull
|
||||
public FragmentTransaction setPrimaryNavigationFragment(@Nullable Fragment fragment) {
|
||||
addOp(new Op(OP_SET_PRIMARY_NAV, fragment));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a ceiling for the state of an active fragment in this FragmentManager. If fragment is
|
||||
* already above the received state, it will be forced down to the correct state.
|
||||
*
|
||||
* <p>The fragment provided must currently be added to the FragmentManager to have it's
|
||||
* Lifecycle state capped, or previously added as part of this transaction. The
|
||||
* {@link Lifecycle.State} passed in must at least be {@link Lifecycle.State#CREATED}, otherwise
|
||||
* an {@link IllegalArgumentException} will be thrown.</p>
|
||||
*
|
||||
* @param fragment the fragment to have it's state capped.
|
||||
* @param state the ceiling state for the fragment.
|
||||
* @return the same FragmentTransaction instance
|
||||
*/
|
||||
@NonNull
|
||||
public FragmentTransaction setMaxLifecycle(@NonNull Fragment fragment,
|
||||
@NonNull Lifecycle.State state) {
|
||||
addOp(new Op(OP_SET_MAX_LIFECYCLE, fragment, state));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return <code>true</code> if this transaction contains no operations,
|
||||
* <code>false</code> otherwise.
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return mOps.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Bit mask that is set for all enter transitions.
|
||||
*/
|
||||
public static final int TRANSIT_ENTER_MASK = 0x1000;
|
||||
|
||||
/**
|
||||
* Bit mask that is set for all exit transitions.
|
||||
*/
|
||||
public static final int TRANSIT_EXIT_MASK = 0x2000;
|
||||
|
||||
/** @hide */
|
||||
@RestrictTo(LIBRARY_GROUP_PREFIX)
|
||||
@IntDef({TRANSIT_NONE, TRANSIT_FRAGMENT_OPEN, TRANSIT_FRAGMENT_CLOSE, TRANSIT_FRAGMENT_FADE})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
private @interface Transit {}
|
||||
|
||||
/** Not set up for a transition. */
|
||||
public static final int TRANSIT_UNSET = -1;
|
||||
/** No animation for transition. */
|
||||
public static final int TRANSIT_NONE = 0;
|
||||
/** Fragment is being added onto the stack */
|
||||
public static final int TRANSIT_FRAGMENT_OPEN = 1 | TRANSIT_ENTER_MASK;
|
||||
/** Fragment is being removed from the stack */
|
||||
public static final int TRANSIT_FRAGMENT_CLOSE = 2 | TRANSIT_EXIT_MASK;
|
||||
/** Fragment should simply fade in or out; that is, no strong navigation associated
|
||||
* with it except that it is appearing or disappearing for some reason. */
|
||||
public static final int TRANSIT_FRAGMENT_FADE = 3 | TRANSIT_ENTER_MASK;
|
||||
|
||||
/**
|
||||
* Set specific animation resources to run for the fragments that are
|
||||
* entering and exiting in this transaction. These animations will not be
|
||||
* played when popping the back stack.
|
||||
*
|
||||
* @param enter An animation or animator resource ID used for the enter animation on the
|
||||
* view of the fragment being added or attached.
|
||||
* @param exit An animation or animator resource ID used for the exit animation on the
|
||||
* view of the fragment being removed or detached.
|
||||
*/
|
||||
@NonNull
|
||||
public FragmentTransaction setCustomAnimations(@AnimatorRes @AnimRes int enter,
|
||||
@AnimatorRes @AnimRes int exit) {
|
||||
return setCustomAnimations(enter, exit, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set specific animation resources to run for the fragments that are
|
||||
* entering and exiting in this transaction. The <code>popEnter</code>
|
||||
* and <code>popExit</code> animations will be played for enter/exit
|
||||
* operations specifically when popping the back stack.
|
||||
*
|
||||
* @param enter An animation or animator resource ID used for the enter animation on the
|
||||
* view of the fragment being added or attached.
|
||||
* @param exit An animation or animator resource ID used for the exit animation on the
|
||||
* view of the fragment being removed or detached.
|
||||
* @param popEnter An animation or animator resource ID used for the enter animation on the
|
||||
* view of the fragment being readded or reattached caused by
|
||||
* {@link FragmentManager#popBackStack()} or similar methods.
|
||||
* @param popExit An animation or animator resource ID used for the enter animation on the
|
||||
* view of the fragment being removed or detached caused by
|
||||
* {@link FragmentManager#popBackStack()} or similar methods.
|
||||
*/
|
||||
@NonNull
|
||||
public FragmentTransaction setCustomAnimations(@AnimatorRes @AnimRes int enter,
|
||||
@AnimatorRes @AnimRes int exit, @AnimatorRes @AnimRes int popEnter,
|
||||
@AnimatorRes @AnimRes int popExit) {
|
||||
mEnterAnim = enter;
|
||||
mExitAnim = exit;
|
||||
mPopEnterAnim = popEnter;
|
||||
mPopExitAnim = popExit;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used with custom Transitions to map a View from a removed or hidden
|
||||
* Fragment to a View from a shown or added Fragment.
|
||||
* <var>sharedElement</var> must have a unique transitionName in the View hierarchy.
|
||||
*
|
||||
* @param sharedElement A View in a disappearing Fragment to match with a View in an
|
||||
* appearing Fragment.
|
||||
* @param name The transitionName for a View in an appearing Fragment to match to the shared
|
||||
* element.
|
||||
* @see Fragment#setSharedElementReturnTransition(Object)
|
||||
* @see Fragment#setSharedElementEnterTransition(Object)
|
||||
*/
|
||||
@NonNull
|
||||
public FragmentTransaction addSharedElement(@NonNull View sharedElement, @NonNull String name) {
|
||||
if (FragmentTransition.supportsTransition()) {
|
||||
String transitionName = ViewCompat.getTransitionName(sharedElement);
|
||||
if (transitionName == null) {
|
||||
throw new IllegalArgumentException("Unique transitionNames are required for all"
|
||||
+ " sharedElements");
|
||||
}
|
||||
if (mSharedElementSourceNames == null) {
|
||||
mSharedElementSourceNames = new ArrayList<String>();
|
||||
mSharedElementTargetNames = new ArrayList<String>();
|
||||
} else if (mSharedElementTargetNames.contains(name)) {
|
||||
throw new IllegalArgumentException("A shared element with the target name '"
|
||||
+ name + "' has already been added to the transaction.");
|
||||
} else if (mSharedElementSourceNames.contains(transitionName)) {
|
||||
throw new IllegalArgumentException("A shared element with the source name '"
|
||||
+ transitionName + "' has already been added to the transaction.");
|
||||
}
|
||||
|
||||
mSharedElementSourceNames.add(transitionName);
|
||||
mSharedElementTargetNames.add(name);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Select a standard transition animation for this transaction. May be
|
||||
* one of {@link #TRANSIT_NONE}, {@link #TRANSIT_FRAGMENT_OPEN},
|
||||
* {@link #TRANSIT_FRAGMENT_CLOSE}, or {@link #TRANSIT_FRAGMENT_FADE}.
|
||||
*/
|
||||
@NonNull
|
||||
public FragmentTransaction setTransition(@Transit int transition) {
|
||||
mTransition = transition;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a custom style resource that will be used for resolving transit
|
||||
* animations.
|
||||
*/
|
||||
@NonNull
|
||||
public FragmentTransaction setTransitionStyle(@StyleRes int styleRes) {
|
||||
mTransitionStyle = styleRes;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add this transaction to the back stack. This means that the transaction
|
||||
* will be remembered after it is committed, and will reverse its operation
|
||||
* when later popped off the stack.
|
||||
* <p>
|
||||
* {@link #setReorderingAllowed(boolean)} must be set to <code>true</code>
|
||||
* in the same transaction as addToBackStack() to allow the pop of that
|
||||
* transaction to be reordered.
|
||||
*
|
||||
* @param name An optional name for this back stack state, or null.
|
||||
*/
|
||||
@NonNull
|
||||
public FragmentTransaction addToBackStack(@Nullable String name) {
|
||||
if (!mAllowAddToBackStack) {
|
||||
throw new IllegalStateException(
|
||||
"This FragmentTransaction is not allowed to be added to the back stack.");
|
||||
}
|
||||
mAddToBackStack = true;
|
||||
mName = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this FragmentTransaction is allowed to be added to the back
|
||||
* stack. If this method would return false, {@link #addToBackStack(String)}
|
||||
* will throw {@link IllegalStateException}.
|
||||
*
|
||||
* @return True if {@link #addToBackStack(String)} is permitted on this transaction.
|
||||
*/
|
||||
public boolean isAddToBackStackAllowed() {
|
||||
return mAllowAddToBackStack;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disallow calls to {@link #addToBackStack(String)}. Any future calls to
|
||||
* addToBackStack will throw {@link IllegalStateException}. If addToBackStack
|
||||
* has already been called, this method will throw IllegalStateException.
|
||||
*/
|
||||
@NonNull
|
||||
public FragmentTransaction disallowAddToBackStack() {
|
||||
if (mAddToBackStack) {
|
||||
throw new IllegalStateException(
|
||||
"This transaction is already being added to the back stack");
|
||||
}
|
||||
mAllowAddToBackStack = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the full title to show as a bread crumb when this transaction
|
||||
* is on the back stack.
|
||||
*
|
||||
* @param res A string resource containing the title.
|
||||
*/
|
||||
@NonNull
|
||||
public FragmentTransaction setBreadCrumbTitle(@StringRes int res) {
|
||||
mBreadCrumbTitleRes = res;
|
||||
mBreadCrumbTitleText = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Like {@link #setBreadCrumbTitle(int)} but taking a raw string; this
|
||||
* method is <em>not</em> recommended, as the string can not be changed
|
||||
* later if the locale changes.
|
||||
*/
|
||||
@NonNull
|
||||
public FragmentTransaction setBreadCrumbTitle(@Nullable CharSequence text) {
|
||||
mBreadCrumbTitleRes = 0;
|
||||
mBreadCrumbTitleText = text;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the short title to show as a bread crumb when this transaction
|
||||
* is on the back stack.
|
||||
*
|
||||
* @param res A string resource containing the title.
|
||||
*/
|
||||
@NonNull
|
||||
public FragmentTransaction setBreadCrumbShortTitle(@StringRes int res) {
|
||||
mBreadCrumbShortTitleRes = res;
|
||||
mBreadCrumbShortTitleText = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Like {@link #setBreadCrumbShortTitle(int)} but taking a raw string; this
|
||||
* method is <em>not</em> recommended, as the string can not be changed
|
||||
* later if the locale changes.
|
||||
*/
|
||||
@NonNull
|
||||
public FragmentTransaction setBreadCrumbShortTitle(@Nullable CharSequence text) {
|
||||
mBreadCrumbShortTitleRes = 0;
|
||||
mBreadCrumbShortTitleText = text;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether or not to allow optimizing operations within and across
|
||||
* transactions. This will remove redundant operations, eliminating
|
||||
* operations that cancel. For example, if two transactions are executed
|
||||
* together, one that adds a fragment A and the next replaces it with fragment B,
|
||||
* the operations will cancel and only fragment B will be added. That means that
|
||||
* fragment A may not go through the creation/destruction lifecycle.
|
||||
* <p>
|
||||
* The side effect of removing redundant operations is that fragments may have state changes
|
||||
* out of the expected order. For example, one transaction adds fragment A,
|
||||
* a second adds fragment B, then a third removes fragment A. Without removing the redundant
|
||||
* operations, fragment B could expect that while it is being created, fragment A will also
|
||||
* exist because fragment A will be removed after fragment B was added.
|
||||
* With removing redundant operations, fragment B cannot expect fragment A to exist when
|
||||
* it has been created because fragment A's add/remove will be optimized out.
|
||||
* <p>
|
||||
* It can also reorder the state changes of Fragments to allow for better Transitions.
|
||||
* Added Fragments may have {@link Fragment#onCreate(Bundle)} called before replaced
|
||||
* Fragments have {@link Fragment#onDestroy()} called.
|
||||
* <p>
|
||||
* {@link Fragment#postponeEnterTransition()} requires {@code setReorderingAllowed(true)}.
|
||||
* <p>
|
||||
* The default is {@code false}.
|
||||
*
|
||||
* @param reorderingAllowed {@code true} to enable optimizing out redundant operations
|
||||
* or {@code false} to disable optimizing out redundant
|
||||
* operations on this transaction.
|
||||
*/
|
||||
@NonNull
|
||||
public FragmentTransaction setReorderingAllowed(boolean reorderingAllowed) {
|
||||
mReorderingAllowed = reorderingAllowed;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated This has been renamed {@link #setReorderingAllowed(boolean)}.
|
||||
*/
|
||||
@Deprecated
|
||||
@NonNull
|
||||
public FragmentTransaction setAllowOptimization(boolean allowOptimization) {
|
||||
return setReorderingAllowed(allowOptimization);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a Runnable to this transaction that will be run after this transaction has
|
||||
* been committed. If fragment transactions are {@link #setReorderingAllowed(boolean) optimized}
|
||||
* this may be after other subsequent fragment operations have also taken place, or operations
|
||||
* in this transaction may have been optimized out due to the presence of a subsequent
|
||||
* fragment transaction in the batch.
|
||||
*
|
||||
* <p>If a transaction is committed using {@link #commitAllowingStateLoss()} this runnable
|
||||
* may be executed when the FragmentManager is in a state where new transactions may not
|
||||
* be committed without allowing state loss.</p>
|
||||
*
|
||||
* <p><code>runOnCommit</code> may not be used with transactions
|
||||
* {@link #addToBackStack(String) added to the back stack} as Runnables cannot be persisted
|
||||
* with back stack state. {@link IllegalStateException} will be thrown if
|
||||
* {@link #addToBackStack(String)} has been previously called for this transaction
|
||||
* or if it is called after a call to <code>runOnCommit</code>.</p>
|
||||
*
|
||||
* @param runnable Runnable to add
|
||||
* @return this FragmentTransaction
|
||||
* @throws IllegalStateException if {@link #addToBackStack(String)} has been called
|
||||
*/
|
||||
@NonNull
|
||||
public FragmentTransaction runOnCommit(@NonNull Runnable runnable) {
|
||||
disallowAddToBackStack();
|
||||
if (mCommitRunnables == null) {
|
||||
mCommitRunnables = new ArrayList<>();
|
||||
}
|
||||
mCommitRunnables.add(runnable);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules a commit of this transaction. The commit does
|
||||
* not happen immediately; it will be scheduled as work on the main thread
|
||||
* to be done the next time that thread is ready.
|
||||
*
|
||||
* <p class="note">A transaction can only be committed with this method
|
||||
* prior to its containing activity saving its state. If the commit is
|
||||
* attempted after that point, an exception will be thrown. This is
|
||||
* because the state after the commit can be lost if the activity needs to
|
||||
* be restored from its state. See {@link #commitAllowingStateLoss()} for
|
||||
* situations where it may be okay to lose the commit.</p>
|
||||
*
|
||||
* @return Returns the identifier of this transaction's back stack entry,
|
||||
* if {@link #addToBackStack(String)} had been called. Otherwise, returns
|
||||
* a negative number.
|
||||
*/
|
||||
public abstract int commit();
|
||||
|
||||
/**
|
||||
* Like {@link #commit} but allows the commit to be executed after an
|
||||
* activity's state is saved. This is dangerous because the commit can
|
||||
* be lost if the activity needs to later be restored from its state, so
|
||||
* this should only be used for cases where it is okay for the UI state
|
||||
* to change unexpectedly on the user.
|
||||
*/
|
||||
public abstract int commitAllowingStateLoss();
|
||||
|
||||
/**
|
||||
* Commits this transaction synchronously. Any added fragments will be
|
||||
* initialized and brought completely to the lifecycle state of their host
|
||||
* and any removed fragments will be torn down accordingly before this
|
||||
* call returns. Committing a transaction in this way allows fragments
|
||||
* to be added as dedicated, encapsulated components that monitor the
|
||||
* lifecycle state of their host while providing firmer ordering guarantees
|
||||
* around when those fragments are fully initialized and ready. Fragments
|
||||
* that manage views will have those views created and attached.
|
||||
*
|
||||
* <p>Calling <code>commitNow</code> is preferable to calling
|
||||
* {@link #commit()} followed by {@link FragmentManager#executePendingTransactions()}
|
||||
* as the latter will have the side effect of attempting to commit <em>all</em>
|
||||
* currently pending transactions whether that is the desired behavior
|
||||
* or not.</p>
|
||||
*
|
||||
* <p>Transactions committed in this way may not be added to the
|
||||
* FragmentManager's back stack, as doing so would break other expected
|
||||
* ordering guarantees for other asynchronously committed transactions.
|
||||
* This method will throw {@link IllegalStateException} if the transaction
|
||||
* previously requested to be added to the back stack with
|
||||
* {@link #addToBackStack(String)}.</p>
|
||||
*
|
||||
* <p class="note">A transaction can only be committed with this method
|
||||
* prior to its containing activity saving its state. If the commit is
|
||||
* attempted after that point, an exception will be thrown. This is
|
||||
* because the state after the commit can be lost if the activity needs to
|
||||
* be restored from its state. See {@link #commitAllowingStateLoss()} for
|
||||
* situations where it may be okay to lose the commit.</p>
|
||||
*/
|
||||
public abstract void commitNow();
|
||||
|
||||
/**
|
||||
* Like {@link #commitNow} but allows the commit to be executed after an
|
||||
* activity's state is saved. This is dangerous because the commit can
|
||||
* be lost if the activity needs to later be restored from its state, so
|
||||
* this should only be used for cases where it is okay for the UI state
|
||||
* to change unexpectedly on the user.
|
||||
*/
|
||||
public abstract void commitNowAllowingStateLoss();
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,318 @@
|
||||
/*
|
||||
* Copyright 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.fragment.app;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.transition.Transition;
|
||||
import android.transition.TransitionManager;
|
||||
import android.transition.TransitionSet;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@RequiresApi(21)
|
||||
class FragmentTransitionCompat21 extends FragmentTransitionImpl {
|
||||
|
||||
@Override
|
||||
public boolean canHandle(Object transition) {
|
||||
return transition instanceof Transition;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object cloneTransition(Object transition) {
|
||||
Transition copy = null;
|
||||
if (transition != null) {
|
||||
copy = ((Transition) transition).clone();
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object wrapTransitionInSet(Object transition) {
|
||||
if (transition == null) {
|
||||
return null;
|
||||
}
|
||||
TransitionSet transitionSet = new TransitionSet();
|
||||
transitionSet.addTransition((Transition) transition);
|
||||
return transitionSet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSharedElementTargets(Object transitionObj,
|
||||
View nonExistentView, ArrayList<View> sharedViews) {
|
||||
TransitionSet transition = (TransitionSet) transitionObj;
|
||||
final List<View> views = transition.getTargets();
|
||||
views.clear();
|
||||
final int count = sharedViews.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
final View view = sharedViews.get(i);
|
||||
bfsAddViewChildren(views, view);
|
||||
}
|
||||
views.add(nonExistentView);
|
||||
sharedViews.add(nonExistentView);
|
||||
addTargets(transition, sharedViews);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEpicenter(Object transitionObj, View view) {
|
||||
if (view != null) {
|
||||
Transition transition = (Transition) transitionObj;
|
||||
final Rect epicenter = new Rect();
|
||||
getBoundsOnScreen(view, epicenter);
|
||||
|
||||
transition.setEpicenterCallback(new Transition.EpicenterCallback() {
|
||||
@Override
|
||||
public Rect onGetEpicenter(Transition transition) {
|
||||
return epicenter;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTargets(Object transitionObj, ArrayList<View> views) {
|
||||
Transition transition = (Transition) transitionObj;
|
||||
if (transition == null) {
|
||||
return;
|
||||
}
|
||||
if (transition instanceof TransitionSet) {
|
||||
TransitionSet set = (TransitionSet) transition;
|
||||
int numTransitions = set.getTransitionCount();
|
||||
for (int i = 0; i < numTransitions; i++) {
|
||||
Transition child = set.getTransitionAt(i);
|
||||
addTargets(child, views);
|
||||
}
|
||||
} else if (!hasSimpleTarget(transition)) {
|
||||
List<View> targets = transition.getTargets();
|
||||
if (isNullOrEmpty(targets)) {
|
||||
// We can just add the target views
|
||||
int numViews = views.size();
|
||||
for (int i = 0; i < numViews; i++) {
|
||||
transition.addTarget(views.get(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if there are any targets based on ID, transition or type.
|
||||
*/
|
||||
private static boolean hasSimpleTarget(Transition transition) {
|
||||
return !isNullOrEmpty(transition.getTargetIds())
|
||||
|| !isNullOrEmpty(transition.getTargetNames())
|
||||
|| !isNullOrEmpty(transition.getTargetTypes());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object mergeTransitionsTogether(Object transition1, Object transition2,
|
||||
Object transition3) {
|
||||
TransitionSet transitionSet = new TransitionSet();
|
||||
if (transition1 != null) {
|
||||
transitionSet.addTransition((Transition) transition1);
|
||||
}
|
||||
if (transition2 != null) {
|
||||
transitionSet.addTransition((Transition) transition2);
|
||||
}
|
||||
if (transition3 != null) {
|
||||
transitionSet.addTransition((Transition) transition3);
|
||||
}
|
||||
return transitionSet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scheduleHideFragmentView(Object exitTransitionObj, final View fragmentView,
|
||||
final ArrayList<View> exitingViews) {
|
||||
Transition exitTransition = (Transition) exitTransitionObj;
|
||||
exitTransition.addListener(new Transition.TransitionListener() {
|
||||
@Override
|
||||
public void onTransitionStart(Transition transition) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransitionEnd(Transition transition) {
|
||||
transition.removeListener(this);
|
||||
fragmentView.setVisibility(View.GONE);
|
||||
final int numViews = exitingViews.size();
|
||||
for (int i = 0; i < numViews; i++) {
|
||||
exitingViews.get(i).setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransitionCancel(Transition transition) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransitionPause(Transition transition) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransitionResume(Transition transition) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object mergeTransitionsInSequence(Object exitTransitionObj,
|
||||
Object enterTransitionObj, Object sharedElementTransitionObj) {
|
||||
// First do exit, then enter, but allow shared element transition to happen
|
||||
// during both.
|
||||
Transition staggered = null;
|
||||
final Transition exitTransition = (Transition) exitTransitionObj;
|
||||
final Transition enterTransition = (Transition) enterTransitionObj;
|
||||
final Transition sharedElementTransition = (Transition) sharedElementTransitionObj;
|
||||
if (exitTransition != null && enterTransition != null) {
|
||||
staggered = new TransitionSet()
|
||||
.addTransition(exitTransition)
|
||||
.addTransition(enterTransition)
|
||||
.setOrdering(TransitionSet.ORDERING_SEQUENTIAL);
|
||||
} else if (exitTransition != null) {
|
||||
staggered = exitTransition;
|
||||
} else if (enterTransition != null) {
|
||||
staggered = enterTransition;
|
||||
}
|
||||
if (sharedElementTransition != null) {
|
||||
TransitionSet together = new TransitionSet();
|
||||
if (staggered != null) {
|
||||
together.addTransition(staggered);
|
||||
}
|
||||
together.addTransition(sharedElementTransition);
|
||||
return together;
|
||||
} else {
|
||||
return staggered;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beginDelayedTransition(ViewGroup sceneRoot, Object transition) {
|
||||
TransitionManager.beginDelayedTransition(sceneRoot, (Transition) transition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scheduleRemoveTargets(final Object overallTransitionObj,
|
||||
final Object enterTransition, final ArrayList<View> enteringViews,
|
||||
final Object exitTransition, final ArrayList<View> exitingViews,
|
||||
final Object sharedElementTransition, final ArrayList<View> sharedElementsIn) {
|
||||
final Transition overallTransition = (Transition) overallTransitionObj;
|
||||
overallTransition.addListener(new Transition.TransitionListener() {
|
||||
@Override
|
||||
public void onTransitionStart(Transition transition) {
|
||||
if (enterTransition != null) {
|
||||
replaceTargets(enterTransition, enteringViews, null);
|
||||
}
|
||||
if (exitTransition != null) {
|
||||
replaceTargets(exitTransition, exitingViews, null);
|
||||
}
|
||||
if (sharedElementTransition != null) {
|
||||
replaceTargets(sharedElementTransition, sharedElementsIn, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransitionEnd(Transition transition) {
|
||||
transition.removeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransitionCancel(Transition transition) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransitionPause(Transition transition) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransitionResume(Transition transition) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void swapSharedElementTargets(Object sharedElementTransitionObj,
|
||||
ArrayList<View> sharedElementsOut, ArrayList<View> sharedElementsIn) {
|
||||
TransitionSet sharedElementTransition = (TransitionSet) sharedElementTransitionObj;
|
||||
if (sharedElementTransition != null) {
|
||||
sharedElementTransition.getTargets().clear();
|
||||
sharedElementTransition.getTargets().addAll(sharedElementsIn);
|
||||
replaceTargets(sharedElementTransition, sharedElementsOut, sharedElementsIn);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void replaceTargets(Object transitionObj, ArrayList<View> oldTargets,
|
||||
ArrayList<View> newTargets) {
|
||||
Transition transition = (Transition) transitionObj;
|
||||
if (transition instanceof TransitionSet) {
|
||||
TransitionSet set = (TransitionSet) transition;
|
||||
int numTransitions = set.getTransitionCount();
|
||||
for (int i = 0; i < numTransitions; i++) {
|
||||
Transition child = set.getTransitionAt(i);
|
||||
replaceTargets(child, oldTargets, newTargets);
|
||||
}
|
||||
} else if (!hasSimpleTarget(transition)) {
|
||||
List<View> targets = transition.getTargets();
|
||||
if (targets != null && targets.size() == oldTargets.size()
|
||||
&& targets.containsAll(oldTargets)) {
|
||||
// We have an exact match. We must have added these earlier in addTargets
|
||||
final int targetCount = newTargets == null ? 0 : newTargets.size();
|
||||
for (int i = 0; i < targetCount; i++) {
|
||||
transition.addTarget(newTargets.get(i));
|
||||
}
|
||||
for (int i = oldTargets.size() - 1; i >= 0; i--) {
|
||||
transition.removeTarget(oldTargets.get(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTarget(Object transitionObj, View view) {
|
||||
if (transitionObj != null) {
|
||||
Transition transition = (Transition) transitionObj;
|
||||
transition.addTarget(view);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeTarget(Object transitionObj, View view) {
|
||||
if (transitionObj != null) {
|
||||
Transition transition = (Transition) transitionObj;
|
||||
transition.removeTarget(view);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEpicenter(Object transitionObj, final Rect epicenter) {
|
||||
if (transitionObj != null) {
|
||||
Transition transition = (Transition) transitionObj;
|
||||
transition.setEpicenterCallback(new Transition.EpicenterCallback() {
|
||||
@Override
|
||||
public Rect onGetEpicenter(Transition transition) {
|
||||
if (epicenter == null || epicenter.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return epicenter;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,368 @@
|
||||
/*
|
||||
* Copyright 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.fragment.app;
|
||||
|
||||
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.graphics.Rect;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.RestrictTo;
|
||||
import androidx.core.view.OneShotPreDrawListener;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.core.view.ViewGroupCompat;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@RestrictTo(LIBRARY_GROUP_PREFIX)
|
||||
@SuppressLint("UnknownNullness")
|
||||
public abstract class FragmentTransitionImpl {
|
||||
|
||||
/**
|
||||
* Returns {@code true} if this implementation can handle the specified {@link transition}.
|
||||
*/
|
||||
public abstract boolean canHandle(Object transition);
|
||||
|
||||
/**
|
||||
* Returns a clone of a transition or null if it is null
|
||||
*/
|
||||
public abstract Object cloneTransition(Object transition);
|
||||
|
||||
/**
|
||||
* Wraps a transition in a TransitionSet and returns the set. If transition is null, null is
|
||||
* returned.
|
||||
*/
|
||||
public abstract Object wrapTransitionInSet(Object transition);
|
||||
|
||||
/**
|
||||
* Finds all children of the shared elements and sets the wrapping TransitionSet
|
||||
* targets to point to those. It also limits transitions that have no targets to the
|
||||
* specific shared elements. This allows developers to target child views of the
|
||||
* shared elements specifically, but this doesn't happen by default.
|
||||
*/
|
||||
public abstract void setSharedElementTargets(Object transitionObj,
|
||||
View nonExistentView, ArrayList<View> sharedViews);
|
||||
|
||||
/**
|
||||
* Sets a transition epicenter to the rectangle of a given View.
|
||||
*/
|
||||
public abstract void setEpicenter(Object transitionObj, View view);
|
||||
|
||||
/**
|
||||
* Replacement for view.getBoundsOnScreen because that is not public. This returns a rect
|
||||
* containing the bounds relative to the screen that the view is in.
|
||||
*/
|
||||
protected void getBoundsOnScreen(View view, Rect epicenter) {
|
||||
int[] loc = new int[2];
|
||||
view.getLocationOnScreen(loc);
|
||||
epicenter.set(loc[0], loc[1], loc[0] + view.getWidth(), loc[1] + view.getHeight());
|
||||
}
|
||||
|
||||
/**
|
||||
* This method adds views as targets to the transition, but only if the transition
|
||||
* doesn't already have a target. It is best for views to contain one View object
|
||||
* that does not exist in the view hierarchy (state.nonExistentView) so that
|
||||
* when they are removed later, a list match will suffice to remove the targets.
|
||||
* Otherwise, if you happened to have targeted the exact views for the transition,
|
||||
* the replaceTargets call will remove them unexpectedly.
|
||||
*/
|
||||
public abstract void addTargets(Object transitionObj, ArrayList<View> views);
|
||||
|
||||
/**
|
||||
* Creates a TransitionSet that plays all passed transitions together. Any null
|
||||
* transitions passed will not be added to the set. If all are null, then an empty
|
||||
* TransitionSet will be returned.
|
||||
*/
|
||||
public abstract Object mergeTransitionsTogether(Object transition1, Object transition2,
|
||||
Object transition3);
|
||||
|
||||
/**
|
||||
* After the transition completes, the fragment's view is set to GONE and the exiting
|
||||
* views are set to VISIBLE.
|
||||
*/
|
||||
public abstract void scheduleHideFragmentView(Object exitTransitionObj, View fragmentView,
|
||||
ArrayList<View> exitingViews);
|
||||
|
||||
/**
|
||||
* Combines enter, exit, and shared element transition so that they play in the proper
|
||||
* sequence. First the exit transition plays along with the shared element transition.
|
||||
* When the exit transition completes, the enter transition starts. The shared element
|
||||
* transition can continue running while the enter transition plays.
|
||||
*
|
||||
* @return A TransitionSet with all of enter, exit, and shared element transitions in
|
||||
* it (modulo null values), ordered such that they play in the proper sequence.
|
||||
*/
|
||||
public abstract Object mergeTransitionsInSequence(Object exitTransitionObj,
|
||||
Object enterTransitionObj, Object sharedElementTransitionObj);
|
||||
|
||||
/**
|
||||
* Calls {@code TransitionManager#beginDelayedTransition(ViewGroup, Transition)}.
|
||||
*/
|
||||
public abstract void beginDelayedTransition(ViewGroup sceneRoot, Object transition);
|
||||
|
||||
/**
|
||||
* Prepares for setting the shared element names by gathering the names of the incoming
|
||||
* shared elements and clearing them. {@link #setNameOverridesReordered(View, ArrayList,
|
||||
* ArrayList, ArrayList, Map)} must be called after this to complete setting the shared element
|
||||
* name overrides. This must be called before
|
||||
* {@link #beginDelayedTransition(ViewGroup, Object)}.
|
||||
*/
|
||||
ArrayList<String> prepareSetNameOverridesReordered(ArrayList<View> sharedElementsIn) {
|
||||
final ArrayList<String> names = new ArrayList<>();
|
||||
final int numSharedElements = sharedElementsIn.size();
|
||||
for (int i = 0; i < numSharedElements; i++) {
|
||||
final View view = sharedElementsIn.get(i);
|
||||
names.add(ViewCompat.getTransitionName(view));
|
||||
ViewCompat.setTransitionName(view, null);
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the shared element names for the incoming shared elements to match those of the
|
||||
* outgoing shared elements. This also temporarily clears the shared element names of the
|
||||
* outgoing shared elements. Must be called after
|
||||
* {@link #beginDelayedTransition(ViewGroup, Object)}.
|
||||
*/
|
||||
void setNameOverridesReordered(final View sceneRoot,
|
||||
final ArrayList<View> sharedElementsOut, final ArrayList<View> sharedElementsIn,
|
||||
final ArrayList<String> inNames, final Map<String, String> nameOverrides) {
|
||||
final int numSharedElements = sharedElementsIn.size();
|
||||
final ArrayList<String> outNames = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < numSharedElements; i++) {
|
||||
final View view = sharedElementsOut.get(i);
|
||||
final String name = ViewCompat.getTransitionName(view);
|
||||
outNames.add(name);
|
||||
if (name == null) {
|
||||
continue;
|
||||
}
|
||||
ViewCompat.setTransitionName(view, null);
|
||||
final String inName = nameOverrides.get(name);
|
||||
for (int j = 0; j < numSharedElements; j++) {
|
||||
if (inName.equals(inNames.get(j))) {
|
||||
ViewCompat.setTransitionName(sharedElementsIn.get(j), name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OneShotPreDrawListener.add(sceneRoot, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (int i = 0; i < numSharedElements; i++) {
|
||||
ViewCompat.setTransitionName(sharedElementsIn.get(i), inNames.get(i));
|
||||
ViewCompat.setTransitionName(sharedElementsOut.get(i), outNames.get(i));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Views in the hierarchy affected by entering and exiting Activity Scene transitions.
|
||||
*
|
||||
* @param transitioningViews This View will be added to transitioningViews if it is VISIBLE and
|
||||
* a normal View or a ViewGroup with
|
||||
* {@link android.view.ViewGroup#isTransitionGroup()} true.
|
||||
* @param view The base of the view hierarchy to look in.
|
||||
*/
|
||||
void captureTransitioningViews(ArrayList<View> transitioningViews, View view) {
|
||||
if (view.getVisibility() == View.VISIBLE) {
|
||||
if (view instanceof ViewGroup) {
|
||||
ViewGroup viewGroup = (ViewGroup) view;
|
||||
if (ViewGroupCompat.isTransitionGroup(viewGroup)) {
|
||||
transitioningViews.add(viewGroup);
|
||||
} else {
|
||||
int count = viewGroup.getChildCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
View child = viewGroup.getChildAt(i);
|
||||
captureTransitioningViews(transitioningViews, child);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
transitioningViews.add(view);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all views that have transition names in the hierarchy under the given view and
|
||||
* stores them in {@code namedViews} map with the name as the key.
|
||||
*/
|
||||
void findNamedViews(Map<String, View> namedViews, @NonNull View view) {
|
||||
if (view.getVisibility() == View.VISIBLE) {
|
||||
String transitionName = ViewCompat.getTransitionName(view);
|
||||
if (transitionName != null) {
|
||||
namedViews.put(transitionName, view);
|
||||
}
|
||||
if (view instanceof ViewGroup) {
|
||||
ViewGroup viewGroup = (ViewGroup) view;
|
||||
int count = viewGroup.getChildCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
View child = viewGroup.getChildAt(i);
|
||||
findNamedViews(namedViews, child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*Applies the prepared {@code nameOverrides} to the view hierarchy.
|
||||
*/
|
||||
void setNameOverridesOrdered(final View sceneRoot,
|
||||
final ArrayList<View> sharedElementsIn, final Map<String, String> nameOverrides) {
|
||||
OneShotPreDrawListener.add(sceneRoot, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final int numSharedElements = sharedElementsIn.size();
|
||||
for (int i = 0; i < numSharedElements; i++) {
|
||||
View view = sharedElementsIn.get(i);
|
||||
String name = ViewCompat.getTransitionName(view);
|
||||
if (name != null) {
|
||||
String inName = findKeyForValue(nameOverrides, name);
|
||||
ViewCompat.setTransitionName(view, inName);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* After the transition has started, remove all targets that we added to the transitions
|
||||
* so that the transitions are left in a clean state.
|
||||
*/
|
||||
public abstract void scheduleRemoveTargets(Object overallTransitionObj,
|
||||
Object enterTransition, ArrayList<View> enteringViews,
|
||||
Object exitTransition, ArrayList<View> exitingViews,
|
||||
Object sharedElementTransition, ArrayList<View> sharedElementsIn);
|
||||
|
||||
/**
|
||||
* Swap the targets for the shared element transition from those Views in sharedElementsOut
|
||||
* to those in sharedElementsIn
|
||||
*/
|
||||
public abstract void swapSharedElementTargets(Object sharedElementTransitionObj,
|
||||
ArrayList<View> sharedElementsOut, ArrayList<View> sharedElementsIn);
|
||||
|
||||
/**
|
||||
* This method removes the views from transitions that target ONLY those views and
|
||||
* replaces them with the new targets list.
|
||||
* The views list should match those added in addTargets and should contain
|
||||
* one view that is not in the view hierarchy (state.nonExistentView).
|
||||
*/
|
||||
public abstract void replaceTargets(Object transitionObj, ArrayList<View> oldTargets,
|
||||
ArrayList<View> newTargets);
|
||||
|
||||
/**
|
||||
* Adds a View target to a transition. If transitionObj is null, nothing is done.
|
||||
*/
|
||||
public abstract void addTarget(Object transitionObj, View view);
|
||||
|
||||
/**
|
||||
* Remove a View target to a transition. If transitionObj is null, nothing is done.
|
||||
*/
|
||||
public abstract void removeTarget(Object transitionObj, View view);
|
||||
|
||||
/**
|
||||
* Sets the epicenter of a transition to a rect object. The object can be modified until
|
||||
* the transition runs.
|
||||
*/
|
||||
public abstract void setEpicenter(Object transitionObj, Rect epicenter);
|
||||
|
||||
void scheduleNameReset(final ViewGroup sceneRoot,
|
||||
final ArrayList<View> sharedElementsIn, final Map<String, String> nameOverrides) {
|
||||
OneShotPreDrawListener.add(sceneRoot, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final int numSharedElements = sharedElementsIn.size();
|
||||
for (int i = 0; i < numSharedElements; i++) {
|
||||
final View view = sharedElementsIn.get(i);
|
||||
final String name = ViewCompat.getTransitionName(view);
|
||||
final String inName = nameOverrides.get(name);
|
||||
ViewCompat.setTransitionName(view, inName);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses a breadth-first scheme to add startView and all of its children to views.
|
||||
* It won't add a child if it is already in views.
|
||||
*/
|
||||
protected static void bfsAddViewChildren(final List<View> views, final View startView) {
|
||||
final int startIndex = views.size();
|
||||
if (containedBeforeIndex(views, startView, startIndex)) {
|
||||
return; // This child is already in the list, so all its children are also.
|
||||
}
|
||||
views.add(startView);
|
||||
for (int index = startIndex; index < views.size(); index++) {
|
||||
final View view = views.get(index);
|
||||
if (view instanceof ViewGroup) {
|
||||
ViewGroup viewGroup = (ViewGroup) view;
|
||||
final int childCount = viewGroup.getChildCount();
|
||||
for (int childIndex = 0; childIndex < childCount; childIndex++) {
|
||||
final View child = viewGroup.getChildAt(childIndex);
|
||||
if (!containedBeforeIndex(views, child, startIndex)) {
|
||||
views.add(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Does a linear search through views for view, limited to maxIndex.
|
||||
*/
|
||||
private static boolean containedBeforeIndex(final List<View> views, final View view,
|
||||
final int maxIndex) {
|
||||
for (int i = 0; i < maxIndex; i++) {
|
||||
if (views.get(i) == view) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple utility to detect if a list is null or has no elements.
|
||||
*/
|
||||
protected static boolean isNullOrEmpty(List list) {
|
||||
return list == null || list.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility to find the String key in {@code map} that maps to {@code value}.
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
static String findKeyForValue(Map<String, String> map, String value) {
|
||||
for (Map.Entry<String, String> entry : map.entrySet()) {
|
||||
if (value.equals(entry.getValue())) {
|
||||
return entry.getKey();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.fragment.app;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.Lifecycle;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.lifecycle.LifecycleRegistry;
|
||||
|
||||
class FragmentViewLifecycleOwner implements LifecycleOwner {
|
||||
private LifecycleRegistry mLifecycleRegistry = null;
|
||||
|
||||
/**
|
||||
* Initializes the underlying Lifecycle if it hasn't already been created.
|
||||
*/
|
||||
void initialize() {
|
||||
if (mLifecycleRegistry == null) {
|
||||
mLifecycleRegistry = new LifecycleRegistry(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return True if the Lifecycle has been initialized.
|
||||
*/
|
||||
boolean isInitialized() {
|
||||
return mLifecycleRegistry != null;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Lifecycle getLifecycle() {
|
||||
initialize();
|
||||
return mLifecycleRegistry;
|
||||
}
|
||||
|
||||
void handleLifecycleEvent(@NonNull Lifecycle.Event event) {
|
||||
mLifecycleRegistry.handleLifecycleEvent(event);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,401 @@
|
||||
/*
|
||||
* Copyright 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.fragment.app;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ListAdapter;
|
||||
import android.widget.ListView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Static library support version of the framework's {@link android.app.ListFragment}.
|
||||
* Used to write apps that run on platforms prior to Android 3.0. When running
|
||||
* on Android 3.0 or above, this implementation is still used; it does not try
|
||||
* to switch to the framework's implementation. See the framework SDK
|
||||
* documentation for a class overview.
|
||||
*/
|
||||
public class ListFragment extends Fragment {
|
||||
static final int INTERNAL_EMPTY_ID = 0x00ff0001;
|
||||
static final int INTERNAL_PROGRESS_CONTAINER_ID = 0x00ff0002;
|
||||
static final int INTERNAL_LIST_CONTAINER_ID = 0x00ff0003;
|
||||
|
||||
final private Handler mHandler = new Handler();
|
||||
|
||||
final private Runnable mRequestFocus = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mList.focusableViewAvailable(mList);
|
||||
}
|
||||
};
|
||||
|
||||
final private AdapterView.OnItemClickListener mOnClickListener
|
||||
= new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
|
||||
onListItemClick((ListView)parent, v, position, id);
|
||||
}
|
||||
};
|
||||
|
||||
ListAdapter mAdapter;
|
||||
ListView mList;
|
||||
View mEmptyView;
|
||||
TextView mStandardEmptyView;
|
||||
View mProgressContainer;
|
||||
View mListContainer;
|
||||
CharSequence mEmptyText;
|
||||
boolean mListShown;
|
||||
|
||||
public ListFragment() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide default implementation to return a simple list view. Subclasses
|
||||
* can override to replace with their own layout. If doing so, the
|
||||
* returned view hierarchy <em>must</em> have a ListView whose id
|
||||
* is {@link android.R.id#list android.R.id.list} and can optionally
|
||||
* have a sibling view id {@link android.R.id#empty android.R.id.empty}
|
||||
* that is to be shown when the list is empty.
|
||||
*
|
||||
* <p>If you are overriding this method with your own custom content,
|
||||
* consider including the standard layout {@link android.R.layout#list_content}
|
||||
* in your layout file, so that you continue to retain all of the standard
|
||||
* behavior of ListFragment. In particular, this is currently the only
|
||||
* way to have the built-in indeterminant progress state be shown.
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
final Context context = requireContext();
|
||||
|
||||
FrameLayout root = new FrameLayout(context);
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
LinearLayout pframe = new LinearLayout(context);
|
||||
pframe.setId(INTERNAL_PROGRESS_CONTAINER_ID);
|
||||
pframe.setOrientation(LinearLayout.VERTICAL);
|
||||
pframe.setVisibility(View.GONE);
|
||||
pframe.setGravity(Gravity.CENTER);
|
||||
|
||||
ProgressBar progress = new ProgressBar(context, null,
|
||||
android.R.attr.progressBarStyleLarge);
|
||||
pframe.addView(progress, new FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
|
||||
root.addView(pframe, new FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
FrameLayout lframe = new FrameLayout(context);
|
||||
lframe.setId(INTERNAL_LIST_CONTAINER_ID);
|
||||
|
||||
TextView tv = new TextView(context);
|
||||
tv.setId(INTERNAL_EMPTY_ID);
|
||||
tv.setGravity(Gravity.CENTER);
|
||||
lframe.addView(tv, new FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
|
||||
ListView lv = new ListView(context);
|
||||
lv.setId(android.R.id.list);
|
||||
lv.setDrawSelectorOnTop(false);
|
||||
lframe.addView(lv, new FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
|
||||
root.addView(lframe, new FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
root.setLayoutParams(new FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach to list view once the view hierarchy has been created.
|
||||
*/
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
ensureList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Detach from list view.
|
||||
*/
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
mHandler.removeCallbacks(mRequestFocus);
|
||||
mList = null;
|
||||
mListShown = false;
|
||||
mEmptyView = mProgressContainer = mListContainer = null;
|
||||
mStandardEmptyView = null;
|
||||
super.onDestroyView();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will be called when an item in the list is selected.
|
||||
* Subclasses should override. Subclasses can call
|
||||
* getListView().getItemAtPosition(position) if they need to access the
|
||||
* data associated with the selected item.
|
||||
*
|
||||
* @param l The ListView where the click happened
|
||||
* @param v The view that was clicked within the ListView
|
||||
* @param position The position of the view in the list
|
||||
* @param id The row id of the item that was clicked
|
||||
*/
|
||||
public void onListItemClick(@NonNull ListView l, @NonNull View v, int position, long id) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide the cursor for the list view.
|
||||
*/
|
||||
public void setListAdapter(@Nullable ListAdapter adapter) {
|
||||
boolean hadAdapter = mAdapter != null;
|
||||
mAdapter = adapter;
|
||||
if (mList != null) {
|
||||
mList.setAdapter(adapter);
|
||||
if (!mListShown && !hadAdapter) {
|
||||
// The list was hidden, and previously didn't have an
|
||||
// adapter. It is now time to show it.
|
||||
setListShown(true, requireView().getWindowToken() != null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the currently selected list item to the specified
|
||||
* position with the adapter's data
|
||||
*
|
||||
* @param position
|
||||
*/
|
||||
public void setSelection(int position) {
|
||||
ensureList();
|
||||
mList.setSelection(position);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the position of the currently selected list item.
|
||||
*/
|
||||
public int getSelectedItemPosition() {
|
||||
ensureList();
|
||||
return mList.getSelectedItemPosition();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cursor row ID of the currently selected list item.
|
||||
*/
|
||||
public long getSelectedItemId() {
|
||||
ensureList();
|
||||
return mList.getSelectedItemId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fragment's list view widget.
|
||||
*/
|
||||
@NonNull
|
||||
public ListView getListView() {
|
||||
ensureList();
|
||||
return mList;
|
||||
}
|
||||
|
||||
/**
|
||||
* The default content for a ListFragment has a TextView that can
|
||||
* be shown when the list is empty. If you would like to have it
|
||||
* shown, call this method to supply the text it should use.
|
||||
*/
|
||||
public void setEmptyText(@Nullable CharSequence text) {
|
||||
ensureList();
|
||||
if (mStandardEmptyView == null) {
|
||||
throw new IllegalStateException("Can't be used with a custom content view");
|
||||
}
|
||||
mStandardEmptyView.setText(text);
|
||||
if (mEmptyText == null) {
|
||||
mList.setEmptyView(mStandardEmptyView);
|
||||
}
|
||||
mEmptyText = text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Control whether the list is being displayed. You can make it not
|
||||
* displayed if you are waiting for the initial data to show in it. During
|
||||
* this time an indeterminant progress indicator will be shown instead.
|
||||
*
|
||||
* <p>Applications do not normally need to use this themselves. The default
|
||||
* behavior of ListFragment is to start with the list not being shown, only
|
||||
* showing it once an adapter is given with {@link #setListAdapter(ListAdapter)}.
|
||||
* If the list at that point had not been shown, when it does get shown
|
||||
* it will be do without the user ever seeing the hidden state.
|
||||
*
|
||||
* @param shown If true, the list view is shown; if false, the progress
|
||||
* indicator. The initial value is true.
|
||||
*/
|
||||
public void setListShown(boolean shown) {
|
||||
setListShown(shown, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Like {@link #setListShown(boolean)}, but no animation is used when
|
||||
* transitioning from the previous state.
|
||||
*/
|
||||
public void setListShownNoAnimation(boolean shown) {
|
||||
setListShown(shown, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Control whether the list is being displayed. You can make it not
|
||||
* displayed if you are waiting for the initial data to show in it. During
|
||||
* this time an indeterminant progress indicator will be shown instead.
|
||||
*
|
||||
* @param shown If true, the list view is shown; if false, the progress
|
||||
* indicator. The initial value is true.
|
||||
* @param animate If true, an animation will be used to transition to the
|
||||
* new state.
|
||||
*/
|
||||
private void setListShown(boolean shown, boolean animate) {
|
||||
ensureList();
|
||||
if (mProgressContainer == null) {
|
||||
throw new IllegalStateException("Can't be used with a custom content view");
|
||||
}
|
||||
if (mListShown == shown) {
|
||||
return;
|
||||
}
|
||||
mListShown = shown;
|
||||
if (shown) {
|
||||
if (animate) {
|
||||
mProgressContainer.startAnimation(AnimationUtils.loadAnimation(
|
||||
getContext(), android.R.anim.fade_out));
|
||||
mListContainer.startAnimation(AnimationUtils.loadAnimation(
|
||||
getContext(), android.R.anim.fade_in));
|
||||
} else {
|
||||
mProgressContainer.clearAnimation();
|
||||
mListContainer.clearAnimation();
|
||||
}
|
||||
mProgressContainer.setVisibility(View.GONE);
|
||||
mListContainer.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
if (animate) {
|
||||
mProgressContainer.startAnimation(AnimationUtils.loadAnimation(
|
||||
getContext(), android.R.anim.fade_in));
|
||||
mListContainer.startAnimation(AnimationUtils.loadAnimation(
|
||||
getContext(), android.R.anim.fade_out));
|
||||
} else {
|
||||
mProgressContainer.clearAnimation();
|
||||
mListContainer.clearAnimation();
|
||||
}
|
||||
mProgressContainer.setVisibility(View.VISIBLE);
|
||||
mListContainer.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ListAdapter associated with this fragment's ListView.
|
||||
*
|
||||
* @see #requireListAdapter()
|
||||
*/
|
||||
@Nullable
|
||||
public ListAdapter getListAdapter() {
|
||||
return mAdapter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ListAdapter associated with this fragment's ListView.
|
||||
*
|
||||
* @throws IllegalStateException if no ListAdapter has been set.
|
||||
* @see #getListAdapter()
|
||||
*/
|
||||
@NonNull
|
||||
public final ListAdapter requireListAdapter() {
|
||||
ListAdapter listAdapter = getListAdapter();
|
||||
if (listAdapter == null) {
|
||||
throw new IllegalStateException("ListFragment " + this
|
||||
+ " does not have a ListAdapter.");
|
||||
}
|
||||
return listAdapter;
|
||||
}
|
||||
|
||||
private void ensureList() {
|
||||
if (mList != null) {
|
||||
return;
|
||||
}
|
||||
View root = getView();
|
||||
if (root == null) {
|
||||
throw new IllegalStateException("Content view not yet created");
|
||||
}
|
||||
if (root instanceof ListView) {
|
||||
mList = (ListView)root;
|
||||
} else {
|
||||
mStandardEmptyView = (TextView)root.findViewById(INTERNAL_EMPTY_ID);
|
||||
if (mStandardEmptyView == null) {
|
||||
mEmptyView = root.findViewById(android.R.id.empty);
|
||||
} else {
|
||||
mStandardEmptyView.setVisibility(View.GONE);
|
||||
}
|
||||
mProgressContainer = root.findViewById(INTERNAL_PROGRESS_CONTAINER_ID);
|
||||
mListContainer = root.findViewById(INTERNAL_LIST_CONTAINER_ID);
|
||||
View rawListView = root.findViewById(android.R.id.list);
|
||||
if (!(rawListView instanceof ListView)) {
|
||||
if (rawListView == null) {
|
||||
throw new RuntimeException(
|
||||
"Your content must have a ListView whose id attribute is " +
|
||||
"'android.R.id.list'");
|
||||
}
|
||||
throw new RuntimeException(
|
||||
"Content has view with id attribute 'android.R.id.list' "
|
||||
+ "that is not a ListView class");
|
||||
}
|
||||
mList = (ListView)rawListView;
|
||||
if (mEmptyView != null) {
|
||||
mList.setEmptyView(mEmptyView);
|
||||
} else if (mEmptyText != null) {
|
||||
mStandardEmptyView.setText(mEmptyText);
|
||||
mList.setEmptyView(mStandardEmptyView);
|
||||
}
|
||||
}
|
||||
mListShown = true;
|
||||
mList.setOnItemClickListener(mOnClickListener);
|
||||
if (mAdapter != null) {
|
||||
ListAdapter adapter = mAdapter;
|
||||
mAdapter = null;
|
||||
setListAdapter(adapter);
|
||||
} else {
|
||||
// We are starting without an adapter, so assume we won't
|
||||
// have our data right away and start with the progress indicator.
|
||||
if (mProgressContainer != null) {
|
||||
setListShown(false, false);
|
||||
}
|
||||
}
|
||||
mHandler.post(mRequestFocus);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.fragment.app;
|
||||
|
||||
import android.util.AndroidRuntimeException;
|
||||
|
||||
final class SuperNotCalledException extends AndroidRuntimeException {
|
||||
public SuperNotCalledException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
@@ -34,11 +34,12 @@ dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
|
||||
implementation 'androidx.annotation:annotation:' + annotationXLibraryVersion
|
||||
implementation 'androidx.core:core:1.6.0'
|
||||
implementation 'androidx.interpolator:interpolator:1.0.0'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.0.0'
|
||||
implementation 'androidx.fragment:fragment:1.1.0'
|
||||
implementation 'androidx.media:media:1.2.0'
|
||||
implementation 'androidx.core:core:' + kotlinCoreVersion
|
||||
implementation 'androidx.recyclerview:recyclerview:' + recyclerviewXLibraryVersion
|
||||
implementation 'androidx.fragment:fragment:' + annotationXLibraryVersion
|
||||
implementation 'androidx.media:media:' + mediaXLibraryVersion
|
||||
implementation 'androidx.interpolator:interpolator:' + leanbackXLibraryVersion
|
||||
|
||||
implementation project(':fragment-1.1.0')
|
||||
implementation project(':sharedutils')
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
include ':smarttubetv', ':common', ':chatkit', ':leanbackassistant', ':leanback-1.0.0'
|
||||
include ':smarttubetv', ':common', ':chatkit', ':leanbackassistant', ':leanback-1.0.0', ':fragment-1.1.0'
|
||||
|
||||
def rootDir = settingsDir
|
||||
|
||||
|
||||
@@ -206,6 +206,7 @@ dependencies {
|
||||
androidTestImplementation 'androidx.test:rules:' + testXSupportLibraryVersion
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:' + espressoVersion
|
||||
|
||||
implementation project(':fragment-1.1.0')
|
||||
implementation project(':leanback-1.0.0')
|
||||
implementation project(':common')
|
||||
implementation project(':sharedutils')
|
||||
|
||||
Reference in New Issue
Block a user