眾所周知,多用Fragment能打造更靈活的程序。
本文通過一個淺顯的例子,來闡釋fragment
之間基于Argument
的數據交流。
簡單說一下要實現的目標:
本項目包含兩個活動和分別依附于這兩個活動的兩個Fragment。
簡單起見,這里分別為他們起名為:FirstActivity
、FirstFragment
、SecondActivity
、SecondFragment
。
他們之間的關系是:
兩個活動只負責容納(或者說托管)其對應的兩個Fragment。而具體的顯示和與用戶交互則由Fragment負責。
為了突出重點,這里只實現最簡單的功能:
- 在
FirstFragment
中顯示一個ListView,這個ListView顯示一串編程語言的名稱。 - 當用戶點擊其中的
item
時,會跳轉到SecondActivity
。 - 這時
SecondActivity
的onCreate()
方法啟動,在其中加載SecondFragment
。 - 最后
SecondFragment
的TextView
控件根據傳過來的信息顯示相應的編程語言的名字。
如圖:
在代碼中實現時,FirstActivity
和SecondActivity
甚至都不需要對應的Layout
資源文件。因為它們唯一的作用只是為Fragment提供容器,所以這里只需要在java代碼中為兩個Activity設置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" />
根據我們的構想,當用戶點擊FirstFragment
中的ListView的item時,應該跳轉到SecondActivity
。
為此,我們在SecondActivity
中定義靜態方法:
private final static String
EXTRA_LANGUAGE_PICKED = "language_picked"; //鍵
//靜態方法,提供從別的活動跳轉到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的點擊回調:
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);
//點擊回調
mList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Resources resources = getResources();
//得到資源文件中定義的字符串數組
String[] languages =
resources.getStringArray(R.array.languages);
String str = languages[position];
Intent intent = SecondActivity.newIntent(
getActivity(), str);
//啟動SecondActivity
startActivity(intent);
}
});
return v;
}
}
當FirstFragment
通過startActivity(intent)
啟動SecondActivity
之后。
SecondActivity
并不直接與用戶交互。
它要做的是:
- 將傳入的
intent
中的用戶點擊的編程語言名稱
取出來; - 然后傳給
SecondFragment
。由SecondFragment
將它顯示出來。
而SecondFragment
想從SecondActivity
那兒取到數據有兩種方式:
第一種比較直接:
SecondFragment
簡單粗暴地通過getActivity()
方法得到托管自己的SecondActivity
;
然后通過getIntent()
方法得到從FirstFragment
中傳過來的Intent對象;
最后得到其中的extra
信息。
這種方式雖然簡單,但也有代價。那就是破壞了封裝。使得SecondFragment
不能被復用。因為此時它還承擔了取
的工作。
第二種方式比較復雜,但也更靈活:附加argument給Fragment:
要附加argument給Fragment,需要調用Fragment.setArguments(Bundle)
方法。而且必須是在fragment創建后,添加給Activity之前。
因此,一般的慣用做法是在Fragment類中添加newInstance()
靜態方法。
通過這個方法完成fragment實例以及Bundle對象的創建,
最后再把argument放入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;
}
}
現在我們有了這個方法,又得到了FirstFragment
傳入的Intent對象中的extra信息languagePicked
;
我們只需要在SecondActivity
的onCreate()
方法中,將languagePicked
作為參數傳入SecondFragment.newInstance()
方法;
即可實現,在SecondFragment
創建之后,被添加給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中創建Fragment,先要得到FragmentManager
FragmentManager fragmentManager = getSupportFragmentManager();
Fragment fragment = fragmentManager.findFragmentById(R.id.fragment_container);
if (fragment == null) {
//在firstActivity中通過Intent跳轉到secondActivity,
//SecondActivity創建之后,從傳入的Intent中得到extra信息,
//然后根據這個信息來創建secondFragment實例,
//得到的信息將用來作為參數,傳入secondFragment的newInstance()方法
String languagePicked =
getIntent().getStringExtra(EXTRA_LANGUAGE_PICKED);
//SecondFragment.newInstance()方法
fragment = SecondFragment.newInstance(languagePicked);
fragmentManager.beginTransaction()
.add(R.id.fragment_container, fragment)
.commit();
}
}
//靜態方法,提供從別的活動跳轉到自身的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
雖然需要得到數據,但是它不再親自去取
,
而是由托管它的Activity(此處是SecondActivity)
來負責提供數據。
如此一來,就實現了SecondActivity
的復用。
倘若現在有一個ThirdActivity
也想要托管SecondFragment
,那它只要能提供數據(類似于SecondActivity
提供的languagePicked
),那它就一樣可以其onCreate()方法
中作出類似的實現。
-- end --
水平有限,難免紕漏,如有錯誤,歡迎指正。
諸君共勉:)