转载请注明:
http://blog.csdn.net/sinat_30276961/article/details/48466719
上一篇Android fragment全面深入解析二讲解了fragment的一些系统自带的子类,FragmentManager和FragmentTransaction,并通过实例讲解了事务的堆栈和过渡动画。本篇将继续讲解fragment里的特性。
在fragment执行完onAttach之后,该fragment就和activity已经建立了关系。我们可以通过在fragment里调用getActivity()轻松获取到宿主activity。
而在activity那边,可以通过上一篇讲到的FragmentManager.findFragmentByXXX方法获取到fragment。
这个就用一般的方式,在fragment里写个方法。然后只要activity那边获取到那个fragment对象(通过FragmentManager.findFragmentByXXX),再调用那个方法就行了。
那么,在宿主activity最初创建fragment时,怎么收到其他activity或者宿主activity需要传递的数据呢?
Android studio的用户在这方面很方便,因为在创建fragment时,会有选项让你选择:
上图中,红框框起来的两个选项,前者是用来自动添加fragment创建代码,后者用来添加与activity交互的接口,方便吧。我们来看看那些代码:
public class BlankFragment extends Fragment {
// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private static final String ARG_PARAM1 = "param1";
private static final String ARG_PARAM2 = "param2";
private OnFragmentInteractionListener mListener;
// TODO: Rename and change types and number of parameters
public static BlankFragment newInstance(String param1, String param2) {
BlankFragment fragment = new BlankFragment();
Bundle args = new Bundle();
args.putString(ARG_PARAM1, param1);
args.putString(ARG_PARAM2, param2);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mParam1 = getArguments().getString(ARG_PARAM1);
mParam2 = getArguments().getString(ARG_PARAM2);
}
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mListener = (OnFragmentInteractionListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement OnFragmentInteractionListener");
}
}
@Override
public void onDetach() {
super.onDetach();
mListener = null;
}
public interface OnFragmentInteractionListener {
// TODO: Update argument type and name
public void onFragmentInteraction(Uri uri);
}
我只贴了我们现在关注的代码,可以看到,它用静态的newInstance来创建fragment,并通过setArguments给fragment传递数据。然后在onCreate()接收bundle数据。
setArguments这种方案是官方推荐的,所以以后就按这种方法写吧。如果是Android studio用户,就在它创建的代码基础上修改一下就行了。
当然,如果你的fragment是直接添加在布局里的,那么要接收其他activity启动宿主activity时传递过来的数据,就用getActivity().getIntent().getStringExtra这类方式好了,注意加好判空。
那么怎么实现fragment通知activity?比方说,fragment那边有收到用户的事件,然后fragment除了自己处理一些事情,还需要通知到activity。
估计会有很多童鞋立马会想到EventBus。没错,那个是很方便啦。只要在activity那里复写一个onEventMainThread(),然后在fragment那边post一个事件就行了。不过,在这里,我们还是用最原始的方法。
这部分代码其实在上面自动生动代码里有了,不过我再讲下。
很简单,直接上代码:
public class CallFragment extends Fragment {
......
public interface OnFragmentInteractionListener {
// TODO: Update argument type and name
public void onFragmentInteraction();
}
上述代码,先是在fragment里写个接口。
然后activity自然要实现这个接口:
public class CallbackToActivity extends AppCompatActivity implements CallFragment.OnFragmentInteractionListener{
......
@Override
public void onFragmentInteraction() {
Toast.makeText(this, "get Callback from fragment", Toast.LENGTH_SHORT).show();
}
}
那么在哪里关联呢,你可以通过传统的回调设置方式,在fragment写个setOnFragmentInteractionListener(OnFragmentInteractionListener listener),然后在activity那里获取fragment对象,并调用一下,fragment.setOnFragmentInteractionListener(this)。
当然,还有更简单的方法,通过使用fragment能获取到activity的特性,在fragment里把activity对象直接赋给listener就行了:
public class CallFragment extends Fragment {
private OnFragmentInteractionListener mListener;
......
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mListener = (OnFragmentInteractionListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement OnFragmentInteractionListener");
}
}
@Override
public void onDetach() {
super.onDetach();
mListener = null;
}
讲完了activity与fragment之间的通信,接着讲讲同一个宿主下的fragment之间的通信。
有几个方案供你选择:
1.getActivity().getSupportFragmentManager().findFragmentByXXX
通过上面代码,我可以很轻松的找到我要交流的fragment。
2.getTargetFragment()。
这个只能找到一个fragment,且需要先setTargetFragment。
3.通过宿主activity中转,这个方案不推荐,太麻烦。
在fragment里也是可以添加menu的,这种设定大大方便了我们动态管理menu。
可以看到,在fragment里关于menu的操作几乎和activity里的一样:
上图有两个关键的api没有显示,onOptionsItemSelected和onContextItemSelected。
OK,既然都差不多,那么调用方式也差不多吧。对的,如果activity里已经解析了一个menu,那么fragment就可以直接添加menu item:
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
menu.add("Menu "+index).setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
}
再贴一下上一篇的动画效果,你仔细看右上角,可以看到,不同的fragment显示不同的menu。
需要注意的是,如果要在fragment收到某个menu item的反馈,宿主activity里的那个menu item监听要返回return super.onOptionsItemSelected(item);
在fragment也是有onActivityResult这个api的。这个和activity里的onActivityResult一样,唯一的区别在于在fragment里是没有setResult的,你需要通过getActivity().setResult(REQUEST, intent)。
在上一篇,有说到,fragment可以是没有ui的,也就是说,这个fragment不提供view给activity,只提供劳动力。
因为fragment的生命周期和activity的生命周期息息相关,所以管理起来也很简单。并且它的开销很小,相比较service来说,明显划算。但它的弱点也很明显,就是只能存活于一个activity。
我通过一个简单的例子来讲解一下没有ui的fragment。
public class NoUIFragmentTest extends AppCompatActivity {
private Button mButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_no_uifragment_test);
mButton = (Button) findViewById(R.id.restart);
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
NoUIFragment fragment =
(NoUIFragment) getSupportFragmentManager().findFragmentByTag("fragment");
fragment.restart();
}
});
if (savedInstanceState == null) {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.add(new NoUIFragment(), "fragment");
ft.commit();
}
}
上述代码,R.layout.activity_no_uifragment_test包含一个progressbar和button。
之所以加上if (savedInstanceState == null)的判断,是因为我要做横竖屏切换。横竖屏切换的时候,会调到onSaveInstanceState()。在那里,会把当前activity已经创建的所有fragment,事务backstack和fragment的state全部保存下来。然后在activity下次执行onCreate()时,全部重新创建fragments并还原它们的state。这部分如果有疑问,可以查看源码,你可以跟进去看下activity的onSaveInstanceState()和onCreate()。篇幅有限,就不展开讲了。
上面指的fragment的state不是指的fragment的状态,而是fragment里onSaveInstanceState的bundle数据。
这里,又体现了fragment的一个好处。就是当应用遇到意外情况(如:内存不足)由系统销毁一个Activity或者横竖屏切换等其他情况时,onSaveInstanceState() 被调用起来了。此时,已经创建的fragment和事务堆栈以及state会被保存下来,然后在下次activity启动时会自动还原。
所以说,上述代码,如果不加个if (savedInstanceState == null)判断,那么每次切屏,都会多创建一个fragment。虽然这个fragment没有ui,但这可不是个好想法,会导致内存泄露,所以一定要注意加判断。
ok,接着往下看代码:
public static class NoUIFragment extends Fragment {
int position = 0;
boolean ready = false;
boolean quiting = false;
ProgressBar progressBar;
final Runnable mRunnable = new Runnable() {
@Override
public void run() {
while (true) {
synchronized (this) {
while (!ready || position >= 500) {
if (quiting)
return;
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
position++;
progressBar.setProgress(position);
}
synchronized (this) {
try {
wait(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
};
static NoUIFragment newInstance() {
NoUIFragment fragment = new NoUIFragment();
return fragment;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
new Thread(mRunnable).start();
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
position = savedInstanceState.getInt("position", 0);
}
}
public void restart() {
synchronized (mRunnable) {
position = 0;
mRunnable.notify();
}
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
progressBar = (ProgressBar) getActivity().findViewById(R.id.progress_horizontal);
synchronized (mRunnable) {
ready = true;
mRunnable.notify();
}
}
@Override
public void onDetach() {
super.onDetach();
synchronized (mRunnable) {
progressBar = null;
ready = false;
mRunnable.notify();
}
}
@Override
public void onDestroy() {
super.onDestroy();
synchronized (mRunnable) {
ready = false;
quiting = true;
mRunnable.notify();
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("position", position);
}
}
代码有点长,不过逻辑不难,我稍微讲下。
在这个fragment里,我定义了一个Runnable的匿名内部类。这个Runnable就是用来不断增加进度条的。我在fragment的onCreate起一个thread执行它,然后它会在第一个同步块的wait()阻塞。然后,我在onActivityCreated里先获取progressbar对象,并唤醒阻塞。此时,progressbar就以每0.05秒加1格的速度增加。
这里,我在fragment里复写了onSaveInstanceState,然后就可以看到,在横竖屏切换时,progressbar不会从0开始,而是继续开始。
OK,来看下效果,横竖屏切换的话,只能你自己去测试,我这边不方便贴上来。
我们再来看下FragmentTransaction.hide()。
这个hide,顾名思义就是隐藏。它的特性有点类似View.INVISIBLE,也就是说,这个view是存在的,只是不可见。
那这个API干嘛用,直接用View.setVisible()不就行了?试着再回忆一下前面几篇提到的fragment事务堆栈,想起了吧。再和这个关联起来,想到了吧。对的,可见不可见可以通过事务堆栈去控制了!
直接上代码看看吧:
先在activity里动态加入一个HideFragment
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_hide_fragment);
if (savedInstanceState == null) {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.add(R.id.container, new HideFragment(), "HideFragment");
ft.commit();
}
}
然后在menu里加上响应代码,执行隐藏和保存事务到堆栈:
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if ("Next".equals(item.getTitle())) {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.hide(getSupportFragmentManager().findFragmentByTag("HideFragment"));
ft.replace(R.id.container, new CallFragment());
ft.addToBackStack(null);
ft.commit();
return true;
}else if ("Hide".equals(item.getTitle())) {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.hide(getSupportFragmentManager().findFragmentByTag("HideFragment"));
ft.addToBackStack(null);
ft.commit();
return true;
}
“Next”的那个菜单可以先忽略。可以看到,在”Hide”菜单响应里,我先隐藏那个fragment,然后把这个事务加到堆栈里。
我们看下效果:
在点击了”HIDE”菜单之后,HideFragment里的一个EditText就不可见了。然后点击BACK之后,EditText又可见了。
OK,在上一篇里有说到过:
如果你在事务里执行了remove动作,然后没有调用addBackToStack,那么remove的那个fragment就会在commit时被destroy掉了,用户就回退不到原先的fragment。但是如果你调用了addBackToStack,那么那个fragment只是被stop了,等用户按BACK就会变回resumed状态。
事实上,这句话没说全。我们再来看看这个例子。
if ("Next".equals(item.getTitle())) {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.hide(getSupportFragmentManager().findFragmentByTag("HideFragment"));
ft.replace(R.id.container, new CallFragment());
// ft.addToBackStack(null);
ft.commit();
return true;
}
现在我们来看下”Next”菜单里的代码,这里,我把ft.addToBackStack(null)给注释掉了,我们来看看HideFragment那边打印的log:
可以看到,HideFragment直接被destroy并且onDetach了。
然后把ft.addToBackStack(null)取消注释,再看log。
可以看到,HideFragment执行到onDestroyView,后面onDestroy和onDetach这两个没有执行。也就是说加了addToBackStack之后,执行remove动作,那个fragment不会被销毁,但是view被销毁了。出栈的时候是重新去创建view的。
有些朋友就会有疑问,上面的代码里没有remove动作啊?事实上是有的,replace动作其实是remove+add这两个动作的结合。
这样一来,上述代码执行的结果就是,在HideFragment的EditText里输入了一些内容,然后点击Next,再点BACK,会发现内容没有了。那有什么办法可以不让它内容消失呢?
那就别做remove动作呗。把replace动作改成add动作就行了。当然还有另一个方案,在HideFragment的onCreateView里获取到Edittext,然后添加一个SaveEnable属性,这样就会保存下来。
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Log.d(TAG, "onCreateView");
View view = inflater.inflate(R.layout.fragment_hide, container, false);
TextView textView = (TextView) view.findViewById(R.id.edit_text);
textView.setSaveEnabled(true);
return view;
}
效果如下:
ok,关于fragment的深入解析就讲到这里。
代码入口