众所周知,多用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 --
水平有限,难免纰漏,如有错误,欢迎指正。
诸君共勉:)