
眾所周知,多用Fragment能打造更靈活的程序。
本文通過一個淺顯的例子,來闡釋fragment之間基于Argument的數(shù)據(jù)交流。
簡單說一下要實現(xiàn)的目標(biāo):
本項目包含兩個活動和分別依附于這兩個活動的兩個Fragment。
簡單起見,這里分別為他們起名為:FirstActivity、FirstFragment、SecondActivity、SecondFragment。
他們之間的關(guān)系是:
兩個活動只負(fù)責(zé)容納(或者說托管)其對應(yīng)的兩個Fragment。而具體的顯示和與用戶交互則由Fragment負(fù)責(zé)。
為了突出重點,這里只實現(xiàn)最簡單的功能:
- 在
FirstFragment中顯示一個ListView,這個ListView顯示一串編程語言的名稱。 - 當(dāng)用戶點擊其中的
item時,會跳轉(zhuǎn)到SecondActivity。 - 這時
SecondActivity的onCreate()方法啟動,在其中加載SecondFragment。 - 最后
SecondFragment的TextView控件根據(jù)傳過來的信息顯示相應(yīng)的編程語言的名字。
如圖:

在代碼中實現(xiàn)時,FirstActivity和SecondActivity甚至都不需要對應(yīng)的Layout資源文件。因為它們唯一的作用只是為Fragment提供容器,所以這里只需要在java代碼中為兩個Activity設(shè)置contentView即可:
setContentView(R.layout.common_fragment_container);
這個名為common_fragment_container的布局文件提供了一個FrameLayout來作為Fragment的容器:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
根據(jù)我們的構(gòu)想,當(dāng)用戶點擊FirstFragment中的ListView的item時,應(yīng)該跳轉(zhuǎn)到SecondActivity。
為此,我們在SecondActivity中定義靜態(tài)方法:
private final static String
EXTRA_LANGUAGE_PICKED = "language_picked"; //鍵
//靜態(tài)方法,提供從別的活動跳轉(zhuǎn)到SecondActivity
public static Intent newIntent(Context packageContext, String languagePicked) {
Intent intent = new Intent(packageContext, SecondActivity.class);
intent.putExtra(EXTRA_LANGUAGE_PICKED, languagePicked);
return intent;
}
FirstFragment中ListView item的點擊回調(diào):
public class FirstFragment extends Fragment {
ListView mList;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_first, container, false);
mList = v.findViewById(R.id.list);
//點擊回調(diào)
mList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Resources resources = getResources();
//得到資源文件中定義的字符串?dāng)?shù)組
String[] languages =
resources.getStringArray(R.array.languages);
String str = languages[position];
Intent intent = SecondActivity.newIntent(
getActivity(), str);
//啟動SecondActivity
startActivity(intent);
}
});
return v;
}
}
當(dāng)FirstFragment通過startActivity(intent)啟動SecondActivity之后。
SecondActivity并不直接與用戶交互。
它要做的是:
- 將傳入的
intent中的用戶點擊的編程語言名稱取出來; - 然后傳給
SecondFragment。由SecondFragment將它顯示出來。
而SecondFragment想從SecondActivity那兒取到數(shù)據(jù)有兩種方式:
第一種比較直接:
SecondFragment簡單粗暴地通過getActivity()方法得到托管自己的SecondActivity;
然后通過getIntent()方法得到從FirstFragment中傳過來的Intent對象;
最后得到其中的extra信息。
這種方式雖然簡單,但也有代價。那就是破壞了封裝。使得SecondFragment不能被復(fù)用。因為此時它還承擔(dān)了取的工作。
第二種方式比較復(fù)雜,但也更靈活:附加argument給Fragment:
要附加argument給Fragment,需要調(diào)用Fragment.setArguments(Bundle)方法。而且必須是在fragment創(chuàng)建后,添加給Activity之前。
因此,一般的慣用做法是在Fragment類中添加newInstance()靜態(tài)方法。
通過這個方法完成fragment實例以及Bundle對象的創(chuàng)建,
最后再把a(bǔ)rgument放入bundle對象中,并附加給fragment:
//SecondFragment
public class SecondFragment extends Fragment {
private static final String
ARG_LANGUAGE_PICKED = "arg_language_picked";
TextView mText;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_second, container, false);
mText = v.findViewById(R.id.language_picked);
String languagePicked
= getArguments().getString(ARG_LANGUAGE_PICKED);
mText.setText(languagePicked);
return v;
}
//newInstance()方法
public static Fragment newInstance(String languagePicked) {
Bundle bundle = new Bundle();
bundle.putSerializable(ARG_LANGUAGE_PICKED, languagePicked);
Fragment SecondFragmentInstance = new SecondFragment();
SecondFragmentInstance.setArguments(bundle);
return SecondFragmentInstance;
}
}
現(xiàn)在我們有了這個方法,又得到了FirstFragment傳入的Intent對象中的extra信息languagePicked;
我們只需要在SecondActivity的onCreate()方法中,將languagePicked作為參數(shù)傳入SecondFragment.newInstance()方法;
即可實現(xiàn),在SecondFragment創(chuàng)建之后,被添加給SecondActivity之前;
為SecondFragment裝載argument:
//SecondActivity
public class SecondActivity extends AppCompatActivity {
private final static String
EXTRA_LANGUAGE_PICKED = "language_picked";
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//使用通用的Fragment容器,
setContentView(R.layout.common_fragment_container);
//因為目前兩個Activity的布局中
//其實都只需要一個用于容納Fragment的frameLayout
//要想在Activity中創(chuàng)建Fragment,先要得到FragmentManager
FragmentManager fragmentManager = getSupportFragmentManager();
Fragment fragment = fragmentManager.findFragmentById(R.id.fragment_container);
if (fragment == null) {
//在firstActivity中通過Intent跳轉(zhuǎn)到secondActivity,
//SecondActivity創(chuàng)建之后,從傳入的Intent中得到extra信息,
//然后根據(jù)這個信息來創(chuàng)建secondFragment實例,
//得到的信息將用來作為參數(shù),傳入secondFragment的newInstance()方法
String languagePicked =
getIntent().getStringExtra(EXTRA_LANGUAGE_PICKED);
//SecondFragment.newInstance()方法
fragment = SecondFragment.newInstance(languagePicked);
fragmentManager.beginTransaction()
.add(R.id.fragment_container, fragment)
.commit();
}
}
//靜態(tài)方法,提供從別的活動跳轉(zhuǎn)到自身的Intent
public static Intent newIntent(Context packageContext, String languagePicked) {
Intent intent = new Intent(packageContext, SecondActivity.class);
intent.putExtra(EXTRA_LANGUAGE_PICKED, languagePicked);
return intent;
}
}
這一做法的靈活之處就在于:
SecondFragment雖然需要得到數(shù)據(jù),但是它不再親自去取,
而是由托管它的Activity(此處是SecondActivity)來負(fù)責(zé)提供數(shù)據(jù)。
如此一來,就實現(xiàn)了SecondActivity的復(fù)用。
倘若現(xiàn)在有一個ThirdActivity也想要托管SecondFragment,那它只要能提供數(shù)據(jù)(類似于SecondActivity提供的languagePicked),那它就一樣可以其onCreate()方法中作出類似的實現(xiàn)。
-- end --
水平有限,難免紕漏,如有錯誤,歡迎指正。
諸君共勉:)