如何向一個Fragment傳遞引數---setArguments方法的介紹
在我們平常開發中經常會用到Fragment,當我們使用Fragment時一般是通過new Fragment的構造方法來實現,如果我問你怎麼向一個Fragment傳遞引數,你是不是會首先想到通過構造方法,當面試被問到這個問題的時候我也是這麼想的,後來發現自己錯了,現在給大家講一下究竟該怎麼做。
首先我們看構造方法這種方式為什麼不行,根據Android文件說明,當一個fragment重新建立的時候,系統會再次呼叫 Fragment中的預設建構函式。 注意這裡:是 預設建構函式。
這句話更直白的意思就是:當你小心翼翼的建立了一個帶有重要引數的Fragment的之後,一旦由於什麼原因(橫豎屏切換)導致你的Fragment重新建立。——-很遺憾的告訴你,你之前傳遞的引數都不見了,因為recreate你的Fragment的時候,呼叫的是預設建構函式。
首先我們通過建構函式來傳遞引數,程式碼如下
public class MainActivity extends FragmentActivity {
private FragmentManager manager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
manager = getSupportFragmentManager();
/*
* 這裡為什麼要進行空判斷,因為在螢幕旋轉的時候,系統會執行onSaveInstanceState
* 方法去儲存當前activity的狀態,然後activity會重建,執行onCreate方法,如果我們不判斷
* savedInstanceState是否為空,那麼每次就會執行下面的commit操作,向Fragmnet傳遞引數,
* 這樣引數的卻會保留下來,但是我們不應該每次都去傳遞引數。當進行了空判斷時,當Activity重建
* 的時候,會呼叫Fragment的預設建構函式,所以我們傳遞過去的引數不能保留了。
*/
if(savedInstanceState == null){
manager.beginTransaction().replace(R.id.fl_main, new FragmentOne("params")).commit();
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
System.out.println("=========savedInstanceState ");
super .onSaveInstanceState(outState);
}
}
public class FragmentOne extends Fragment {
private TextView textView;
private String params = "default";
public FragmentOne(){
System.out.println("===========default constructor");
}
/**
* 通過構造方法接收傳遞過來的引數
* @param content
*/
public FragmentOne(String content){
params = content;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment1, container,false);
textView = (TextView) view.findViewById(R.id.textview);
textView.setText(params);
return view;
}
}
可以看到此時傳遞過來的引數已經不見了,說明通過構造方法傳遞引數是不行的。
我們看看控制檯的列印
注意:
這裡我們必須寫出預設的建構函式,因為Fragment重建的時候,會呼叫預設的建構函式,也就是空引數的建構函式,如果我們只是給出了帶引數的建構函式,系統是不會為我們建立空引數的建構函式的,如果你不寫,在Fragment重建的時候就會發生下面的錯誤。
接下來看看官方推薦的setArguments方法:
public class MainActivity extends FragmentActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.replace(R.id.fl_main, FragmentOne.newInstance("params"))
.commit();
}
}
}
public class FragmentOne extends Fragment{
private TextView textView;
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_one, null);
textView = (TextView) view.findViewById(R.id.textview);
if(getArguments()!=null){
//取出儲存的值
textView.setText(getArguments().getString("name"));
}
return view;
}
public static FragmentOne newInstance(String text){
FragmentOne fragmentOne = new FragmentOne();
Bundle bundle = new Bundle();
bundle.putString("name", text);
//fragment儲存引數,傳入一個Bundle物件
fragmentOne.setArguments(bundle);
return fragmentOne;
}
}
可以看到,螢幕旋轉以後引數也保留下來了。
接下來我們通過原始碼看看Bundle這個引數到底如何保留下來的,
點進去Fragment的setArguments方法:
public void setArguments(Bundle args) {
if (mIndex >= 0) {
throw new IllegalStateException("Fragment already active");
}
mArguments = args;
}
首先將當前的bundle物件賦值給一個全域性的mArguments物件,mArguments時FragmentState物件的一個屬性,FragmentState時Fragment的一個內部類,代表著Fragment的狀態。
final class FragmentState implements Parcelable {
final String mClassName;
final int mIndex;
final boolean mFromLayout;
final int mFragmentId;
final int mContainerId;
final String mTag;
final boolean mRetainInstance;
final boolean mDetached;
final Bundle mArguments;
final boolean mHidden;
Bundle mSavedFragmentState;
Fragment mInstance;
public FragmentState(Fragment frag) {
mClassName = frag.getClass().getName();
mIndex = frag.mIndex;
mFromLayout = frag.mFromLayout;
mFragmentId = frag.mFragmentId;
mContainerId = frag.mContainerId;
mTag = frag.mTag;
mRetainInstance = frag.mRetainInstance;
mDetached = frag.mDetached;
mArguments = frag.mArguments;
mHidden = frag.mHidden;
}
然後Activity儲存狀態的時候會呼叫onSaveInstanceState方法
protected void onSaveInstanceState(Bundle outState) {
outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());
outState.putInt(LAST_AUTOFILL_ID, mLastAutofillId);
// 呼叫saveAllState方法儲存Fragment狀態
Parcelable p = mFragments.saveAllState();
if (p != null) {
// 將結果儲存到Bundle中
outState.putParcelable(FRAGMENTS_TAG, p);
}
if (mAutoFillResetNeeded) {
outState.putBoolean(AUTOFILL_RESET_NEEDED, true);
getAutofillManager().onSaveInstanceState(outState);
}
getApplication().dispatchActivitySaveInstanceState(this, outState);
}
saveAllState方法最終呼叫到FragmentManager的saveAllState方法中
Parcelable saveAllState() {
// 找到所有的存活的Fragment
int N = mActive.size();
// 代表Fragment狀態的陣列
FragmentState[] active = new FragmentState[N];
boolean haveFragments = false;
for (int i=0; i<N; i++) {
Fragment f = mActive.valueAt(i);
if (f != null) {
if (f.mIndex < 0) {
throwException(new IllegalStateException(
"Failure saving state: active " + f
+ " has cleared index: " + f.mIndex));
}
haveFragments = true;
// 找到所有的Fragment,為FragmentState陣列初始化
FragmentState fs = new FragmentState(f);
active[i] = fs;
// 保證Fragment已經建立了並且沒有引數儲存過
if (f.mState > Fragment.INITIALIZING && fs.mSavedFragmentState == null) {
// 儲存Fragment的狀態
fs.mSavedFragmentState = saveFragmentBasicState(f);
FragmentManagerState fms = new FragmentManagerState();
// active是上面代表Fragment狀態的陣列,至此,fragment的狀態就被儲存到
了FragmentManagerState中
fms.mActive = active;
fms.mAdded = added;
fms.mBackStack = backStack;
fms.mNextFragmentIndex = mNextFragmentIndex;
if (mPrimaryNav != null) {
fms.mPrimaryNavActiveIndex = mPrimaryNav.mIndex;
}
saveNonConfig();
return fms;
}
接下來我們看看恢復資料的流程
在Activity的onCreate中有下面的程式碼
// 取出之前儲存的資料
Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
mFragments.restoreAllState(p, mLastNonConfigurationInstances != null
? mLastNonConfigurationInstances.fragments : null);
最終呼叫到了FragmentManager的restoreAllState方法
void restoreAllState(Parcelable state, FragmentManagerNonConfig nonConfig) {
if (state == null) return;
// 取出儲存的資料
FragmentManagerState fms = (FragmentManagerState)state;
if (fms.mActive == null) return;
// ... 省去部分程式碼
// Build the full list of active fragments, instantiating them from
// their saved state.
// 根據之前儲存的狀態初始化新的Fragment
mActive = new SparseArray<>(fms.mActive.length);
for (int i=0; i<fms.mActive.length; i++) {
FragmentState fs = fms.mActive[i];
if (fs != null) {
FragmentManagerNonConfig childNonConfig = null;
if (childNonConfigs != null && i < childNonConfigs.size()) {
childNonConfig = childNonConfigs.get(i);
}
// 呼叫instantiate方法建立Fragment
Fragment f = fs.instantiate(mHost, mContainer, mParent, childNonConfig);
if (DEBUG) Log.v(TAG, "restoreAllState: active #" + i + ": " + f);
mActive.put(f.mIndex, f);
// Now that the fragment is instantiated (or came from being
// retained above), clear mInstance in case we end up re-restoring
// from this FragmentState again.
fs.mInstance = null;
}
}
public Fragment instantiate(FragmentHostCallback host, FragmentContainer container,
Fragment parent, FragmentManagerNonConfig childNonConfig) {
if (mInstance == null) {
final Context context = host.getContext();
if (mArguments != null) {
mArguments.setClassLoader(context.getClassLoader());
}
// 初始化Fragment,把之前儲存的引數傳過去
if (container != null) {
mInstance = container.instantiate(context, mClassName, mArguments);
} else {
mInstance = Fragment.instantiate(context, mClassName, mArguments);
}
if (mSavedFragmentState != null) {
mSavedFragmentState.setClassLoader(context.getClassLoader());
mInstance.mSavedFragmentState = mSavedFragmentState;
}
mInstance.setIndex(mIndex, parent);
mInstance.mFromLayout = mFromLayout;
mInstance.mRestored = true;
mInstance.mFragmentId = mFragmentId;
mInstance.mContainerId = mContainerId;
mInstance.mTag = mTag;
mInstance.mRetainInstance = mRetainInstance;
mInstance.mDetached = mDetached;
mInstance.mHidden = mHidden;
mInstance.mFragmentManager = host.mFragmentManager;
if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG,
"Instantiated fragment " + mInstance);
}
mInstance.mChildNonConfig = childNonConfig;
return mInstance;
}
// 初始化的方法
public static Fragment instantiate(Context context, String fname, @Nullable Bundle args) {
try {
Class<?> clazz = sClassMap.get(fname);
if (clazz == null) {
// Class not found in the cache, see if it's real, and try to add it
clazz = context.getClassLoader().loadClass(fname);
if (!Fragment.class.isAssignableFrom(clazz)) {
throw new InstantiationException("Trying to instantiate a class " + fname
+ " that is not a Fragment", new ClassCastException());
}
sClassMap.put(fname, clazz);
}
// 呼叫Fragment無引數的建構函式
Fragment f = (Fragment) clazz.getConstructor().newInstance();
if (args != null) {
args.setClassLoader(f.getClass().getClassLoader());
// 設定引數,然後我們就可以用getArgument方法獲取了
f.setArguments(args);
}
return f;
注意:
setArguments方法的呼叫必須要在Fragment與Activity關聯之前。
這句話可以這樣理解,setArgument方法的使用必須要在FragmentTransaction 的commit之前使用。
相關推薦
如何向一個Fragment傳遞引數---setArguments方法的介紹
在我們平常開發中經常會用到Fragment,當我們使用Fragment時一般是通過new Fragment的構造方法來實現,如果我問你怎麼向一個Fragment傳遞引數,你是不是會首先想到通過構造方法,當面試被問到這個問題的時候我也是這麼想的,後來發現自己錯了,
向python指令碼傳遞引數的方法
需要模組:sys 引數個數:len(sys.argv) 指令碼名: sys.argv[0] 引數1: sys.argv[1] 引數2: sys.argv[2] 引數列表:sys.argv[1:] 下面通過示例程式碼及操作來說明引數傳遞的具體使用。示例1
從java層向jni中傳遞GLSurfaceView的方法
bool 很多 code http codec android class ble extern 從java朝jni中傳遞各種數據,是在android開發中經常需要面對的事情。對於一些典型的數據類型,網上已經有很多文章介紹,這裏列出一些數據類型: 對於GLSurfa
H5頁面向小程式傳遞引數
H5頁面 js; <script src="https://res.wx.qq.com/open/js/jweixin-1.3.2.js"></script> $(function () { //小程式傳送資訊 wx.miniProg
ansible命令列傳遞引數的方法
在命令列裡面傳值得的方法: ansible-playbook testyml --extra-vars "hosts=vm-1 user=root" 還可以用json格式傳遞引數: ansible-playbook test.yml
Java中eclipse與命令列向main函式傳遞引數
我們知道main函式是java程式的入口,main函式的引數型別是String[]。 1.Eclipse中向main方法傳遞引數 例如: public class Mytest { public static void main(String[] args) {
微信小程式點選事件傳遞引數的方法
小程式在元件上繫結事件後,傳遞引數的方式不同於前端開發其他場景中直接加引數的方式,小程式在引數的傳遞時,採用事件物件的自定義屬性的方式,具體實現如下: wxml: <view bindtap="passQuery" data-index="1">點選事件傳參</view&g
從一個Controller傳遞引數到另一個Controller(addFlashAttribute)
搞了半小時才成功 /** * 線上支付:整理支付單資訊 * @param request * @param response * @param attr * @return paymentbill */ @RequestMapping(value = "paymentbill
關於Mybatis的@Param註解 及 mybatis Mapper中各種傳遞引數的方法
原文:https://blog.csdn.net/mrqiang9001/article/details/79520436 關於Mybatis的@Param註解 Mybatis 作為一個輕量級的資料持久化框架,目前(2018)的應用非常廣泛,基本可以取代Hiberna
父元件向子元件傳遞引數的demo (元件通過區域性定義)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head&
在VS中向命令列新增引數的方法
在VS中向命令列新增引數,即向main()函式傳遞引數的方法: 右鍵單擊要 新增引數的工程-->屬性-->配置屬性-->除錯,在右側“命令引數”欄輸入要新增的引數,各引數間用空格分離。例如: "-hide_banner" "Z:/media/subti
[SSM]Spring MVC3在controller和檢視之間傳遞引數的方法
Spring MVC3在controller和檢視之間傳遞引數的方法: 一, 從controller往檢視傳遞值, controller—->檢視 1)簡單型別,如int, String,直接寫在controller方法的引數裡,是無法傳遞到檢視頁面上
如何向 Docker 容器傳遞引數
我們在執行 docker 映象時希望能用下面的命令向容器傳遞命令列引數 docker run <image-name> <command> arg1 arg2 docker run <image-name> arg1 arg2
四種傳遞引數的方法
主程式在呼叫子程式時,往往要向子程式傳遞一些引數;同樣,子程式執行後也經常要把一些結果引數傳回給主程式。主程式與子程式之間的這種資訊傳遞稱為引數傳遞。 引數傳遞有四種方法:暫存器引數傳遞,約定儲存單元引數傳遞,利用CALL後續區進行引數傳遞,利用堆疊進行引數傳遞。 一、
Vue2.0中子元件向父元件傳遞資料的方法,以完整demo演示
子元件child.vue原始碼:<template> <div class="child"> <button @click="sendData">點擊向父元件傳資料</button> </div> &
Java程式碼:呼叫外部介面(使用Json格式傳遞引數)的方法
程式碼如下: String url="所給外部介面的url"; //建立連線物件 HttpClient httpClient = new HttpClient(); //建立請求
shell呼叫python指令碼,並且向python指令碼傳遞引數
shell中: python test.py $para1 $para2 python中: import sys def main($canshu1, $canshu2) ..... ma
JSP中四種傳遞引數的方法
今天老師講了jsp中四種傳遞引數的方法,我覺得總結一下,挺好的,以備後用! 1、form表單 2、request.setAttribute();和request.getAttribute(); 3、超連結:<a herf="index.jsp"?a=a&b=b
Spring MVC3在controller和檢視之間傳遞引數的方法:
一, 從controller往檢視傳遞值,controller—->檢視 1)簡單型別,如int, String,直接寫在controller方法的引數裡,是無法傳遞到檢視頁面上的(經測試) 2)可以用Map,其鍵值可以在頁面上用EL表示式${鍵值名}
給Fragment傳遞引數 —— FragmentArgumentsSupport
/** * Demonstrates a fragment that can be configured through both Bundle arguments * and layout attributes. */ //展示了一個可以通過屬性和budle來配置f