android fragment的一个简单写法是:
public class WebViewFragment extends Fragment { private WebView mWebView; private View mRootView; private String mUrl; @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); mUrl = getArguments().getString("url"); setUrl(); } @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); } @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { mRootView = inflater.inflate(R.layout.tp_webview_main,null); mWebView = (WebView) mRootView.findViewById(R.id.webview); return mRootView; } }
Fragment有很多可以复写的方法,其中最常用的就是onCreateView(),该方法返回Fragment的UI布局,需要注意的是inflate()的第三个参数是false,因为在Fragment内部实现中,会把该布局添加到container中,如果设为true,那么就会重复做两次添加,则会抛如下异常:
Caused by: java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
如果在创建Fragment时要传入参数,必须要通过setArguments(Bundle bundle)方式添加,而不建议通过为Fragment添加带参数的构造函数,因为通过setArguments()方式添加,在由于内存紧张导致Fragment被系统杀掉并恢复(re-instantiate)时能保留这些数据
我们可以在Fragment的onAttach()
中通过getArguments()
获得传进来的参数,并在之后使用这些参数。如果要获取Activity对象,不建议调用getActivity()
,而是在onAttach()
中将Context对象强转为Activity对象。
创建完Fragment后,接下来就是把Fragment添加到Activity中。在Activity中添加Fragment的方式有两种:
静态添加:通过xml的方式添加,缺点是一旦添加就不能在运行时删除。
动态添加:运行时添加,这种方式比较灵活,因此建议使用这种方式。
这里只给出动态添加的方式。首先Activity需要有一个容器存放Fragment,一般是FrameLayout,因此在Activity的布局文件中加入FrameLayout:
<FrameLayout android:id="@+id/container" android:layout_width="match_parent" android:layout_height="match_parent"/>
然后在onCreate()
中,通过以下代码将Fragment添加进Activity中。
if (bundle == null) { getSupportFragmentManager().beginTransaction() .add(R.id.container, Fragment1.newInstance("hello world"), "f1") //.addToBackStack("fname") .commit(); }
这里需要注意几点:
因为我们使用了support库的Fragment,因此需要使用
getSupportFragmentManager()
获取FragmentManager。add()
是对Fragment众多操作中的一种,还有remove()
,replace()
等,第一个参数是根容器的id(FrameLayout的id,即”@id/container”),第二个参数是Fragment对象,第三个参数是fragment的tag名,指定tag的好处是后续我们可以通过Fragment1 frag = getSupportFragmentManager().findFragmentByTag("f1")
从FragmentManager中查找Fragment对象。在一次事务中,可以做多个操作,比如同时做
add().remove().replace()
。commit()
操作是异步的,内部通过mManager.enqueueAction()
加入处理队列。对应的同步方法为commitNow()
,commit()
内部会有checkStateLoss()
操作,如果开发人员使用不当(比如commit()
操作在onSaveInstanceState()
之后),可能会抛出异常,而commitAllowingStateLoss()
方法则是不会抛出异常版本的commit()
方法,但是尽量使用commit()
,而不要使用commitAllowingStateLoss()
。addToBackStack("fname")
是可选的。FragmentManager拥有回退栈(BackStack),类似于Activity的任务栈,如果添加了该语句,就把该事务加入回退栈,当用户点击返回按钮,会回退该事务(回退指的是如果事务是add(frag1)
,那么回退操作就是remove(frag1)
);如果没添加该语句,用户点击返回按钮会直接销毁Activity。Fragment有一个常见的问题,即Fragment重叠问题,这是由于Fragment被系统杀掉,并重新初始化时再次将fragment加入activity,因此通过在外围加if语句能判断此时是否是被系统杀掉并重新初始化的情况。
Fragment有个常见的异常:
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1341) at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1352) at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:595) at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:574)
该异常出现的原因是:commit()
在onSaveInstanceState()
后调用。首先,onSaveInstanceState()
在onPause()
之后,onStop()
之前调用。onRestoreInstanceState()
在onStart()
之后,onResume()
之前。
因此避免出现该异常的方案有:
不要把Fragment事务放在异步线程的回调中,比如不要把Fragment事务放在AsyncTask的
onPostExecute()
,因此onPostExecute()
可能会在onSaveInstanceState()
之后执行。逼不得已时使用
commitAllowingStateLoss()
。
Fragment的生命周期如下:
onAttach():Fragment和Activity相关联时调用。可以通过该方法获取Activity引用,还可以通过getArguments()获取参数。
onCreate():Fragment被创建时调用。
onCreateView():创建Fragment的布局。
onActivityCreated():当Activity完成onCreate()时调用。
onStart():当Fragment可见时调用。
onResume():当Fragment可见且可交互时调用。
onPause():当Fragment不可交互但可见时调用。
onStop():当Fragment不可见时调用。
onDestroyView():当Fragment的UI从视图结构中移除时调用。
onDestroy():销毁Fragment时调用。
onDetach():当Fragment和Activity解除关联时调用。
上面的方法中,只有onCreateView()在重写时不用写super方法,其他都需要。
常见的Fragment面试题:
Fragment与view有啥区别?
Fragment是有生命周期的
2. Fragment特点:
Fragment可以作为Activity界面的一部分组成出现;
可以在一个Activity中同时出现多个Fragment,并且一个Fragment也可以在多个Activity中使用;
在Activity运行过程中,可以添加、移除或者替换Fragment;
Fragment可以响应自己的输入事件,并且有自己的生命周期,它们的生命周期会受宿主Activity的生命周期影响。
3. 为什么引入Fragment?
Android在api11中加入了Fragment,主要是给大屏设备上进行动态灵活的UI设计提供支持,更多的情况下我们把Fragment作为一个可重复利用的模块化组件,利用它自身的生命周期来对功能模块进行分离。
4. FragmentPagerAdapter与FragmentStatePagerAdapter的区别?
FragmentPagerAdapter一般用于少量界面的ViewPager,划过的Fragment会一直保存在内存中不会被销毁。而FragmentStatePagerAdapter适用于界面较多的ViewPager,它会保存当前的界面以及下一个界面和上一个界面,最多保存三个,其他的会在destroyItem()方法中被销毁调,可以节省内存占用。
5. Fragment通信
在Fragment中调用Activity中的方法
通过getActivity()方法获取所附属的Activity后调用当中的方法
在Activity中调用Fragment中的方法
采用接口回调的形式
在Fragment中调用Fragment中的方法
findFragmentById()
6. 无法在activity调用了onSaveInstanceState之后再执行commit进行添加Fragment
Activity被系统回收(界面已经不存在了),为了能在下次打开的时候恢复原来的样子,系统为我们保存界面的所有状态,这个时候再去修改界面理论上肯定是不被允许的,为了避免这种异常可以使用:
transaction.commitAllowingStateLoss();
来提交添加Fragment到Activity的事务,与commit()不同的是使用这种方法允许丢失界面的状态和信息。
7. ViewPager与Fragment结合使用时的懒加载问题
所谓的 “懒加载” 就是数据只有在Fragment对于用户可见的时才进行加载。因为ViewPager会帮我们预先初始化Fragment。由于这个特性,我们不能把数据的加载放到onCreateView方法或者onCreate方法中。
因此,我们需要判定Fragment在什么时候是处于可见的状态。一般我们通常是通过Fragment中的生命周期方法onResume来判断Fragment是否可见,但是由于ViewPager预加载的特性,Fragment即便不可见也会执行onResume方法,因此使用这个方法进行可见性的判断就行不通了。这个时候我们需要用到下面的这个方法来进行Fragment可见性的判断:
setUserVisibleHint()方法:
什么时候被调用?
当fragment被创建的时,setUserVisibleHint(boolean isVisibleToUser)方法会被调用,且传入参数值为false。
当fragment可见时,setUserVisibleHint(boolean isVisibleToUser)方法会被调用,且传入参数值为true。
当fragment由 可见 -> 不可见 时,setUserVisibleHint(boolean isVisibleToUser)方法会被调用,且传入参数值为false。
了解了这个方法之后,我们应该比较清楚懒加载该如何实现了吧,只需要当setUserVisibleHint(boolean isVisibleToUser)方法中的 isVisibleToUser 参数的值为true的时候我们才开始进行数据的加载。但是有一点需要需要注意的是 setUserVisibleHint(boolean isVisibleToUser)方法在Fragment的生命周期方法onCreate 之前调用的,也就是说他并不在Fragment的生命周期中。既然是在 onCreate 方法之前被调用,这样就存在许多不确定因素,如果Fragmnet的View还没有完成初始化之前,就在setUserVisibleHint()方法中进行UI的操作,这样显然会导致空指针的出现。因此我们需要对Fragment创建的View进行缓存,确保缓存的View不为空的情况下我们才可以在setUserVisibleHint方法中进行UI操作。
共同学习,写下你的评论
评论加载中...
作者其他优质文章