以前面试的时候吧,能把Fragment的生命周期从头到尾背一遍,然后懒加载也是知道怎么实现,但是呢,没有写过demo具体研究过,于是就准备写篇博客就当笔记了。
先附上一张面试时候常考的一张Fragment生命周期图:
测试的布局activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.design.widget.TabLayout
android:id="@+id/id_tab"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<android.support.v4.view.ViewPager
android:id="@+id/id_viewpager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="@android:color/white" />
</LinearLayout>
测试的Fragment:
/**
* @author EX_YINQINGYANG
* @version [Android PABank C01, @2016-09-21]
* @date 2016-09-21
* @description
*/
public class MyFragment extends Fragment {
private static final String PAGE_NO = "PAGE_NO";
private static final String TAG = "MyFragment";
private int pageNo;
public static Fragment newInstance(int pageNo){
MyFragment fragment=new MyFragment();
Bundle bundle=new Bundle();
bundle.putInt(PAGE_NO,pageNo);
fragment.setArguments(bundle);
return fragment;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
pageNo=getArguments().getInt(PAGE_NO,0);
Log.e(TAG+" "+pageNo,"---->onAttach");
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.e(TAG+" "+pageNo,"---->onCreate");
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
Log.e(TAG+" "+pageNo,"---->onCreateView");
TextView view=new TextView(getActivity());
view.setGravity(Gravity.CENTER);
view.setTextColor(Color.BLACK);
view.setTextSize(TypedValue.COMPLEX_UNIT_DIP,18f);
view.setText("Fragment"+pageNo);
return view;
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Log.e(TAG+" "+pageNo,"---->onActivityCreated");
}
@Override
public void onResume() {
super.onResume();
Log.e(TAG+" "+pageNo,"---->onResume");
}
@Override
public void onPause() {
super.onPause();
Log.e(TAG+" "+pageNo,"---->onPause");
}
@Override
public void onStop() {
super.onStop();
Log.e(TAG+" "+pageNo,"---->onStop");
}
@Override
public void onDestroyView() {
super.onDestroyView();
Log.e(TAG+" "+pageNo,"---->onDestroyView");
}
@Override
public void onDestroy() {
super.onDestroy();
Log.e(TAG+" "+pageNo,"---->onDestroy");
}
@Override
public void onDetach() {
super.onDetach();
Log.e(TAG+" "+pageNo,"---->onDetach");
}
}
测试的MainActivity:
public class MainActivity extends AppCompatActivity {
private TabLayout mTab;
private ViewPager mPager;
private List<Fragment>fragments;
private String[]titles={"page1","page2","page3"};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTab= (TabLayout) findViewById(R.id.id_tab);
mPager= (ViewPager) findViewById(R.id.id_viewpager);
mPager.setOffscreenPageLimit(0);
initDatas();
}
private void initDatas() {
fragments=new ArrayList<>(3);
fragments.add(MyFragment.newInstance(1));
fragments.add(MyFragment.newInstance(2));
fragments.add(MyFragment.newInstance(3));
mPager.setAdapter(new FragmentPagerAdapter(getSupportFragmentManager()) {
@Override
public Fragment getItem(int position) {
return fragments.get(position);
}
@Override
public int getCount() {
return fragments.size();
}
@Override
public CharSequence getPageTitle(int position) {
return titles[position];
}
});
mTab.setTabMode(TabLayout.MODE_FIXED);
mTab.setupWithViewPager(mPager);
mTab.setTabTextColors(Color.GRAY,Color.RED);
}
}
运行一下代码如图:
相信大家走到这一步都是so easy的啦,我就不具体解释代码了,
首先我们看一下打印的log:
是不是跟我们一开始贴的那张图期待的是一样的?是的! 然后我们注意这么一段代码
mPager.setOffscreenPageLimit(0);
我们改为:
mPager.setOffscreenPageLimit(1);
运行看到log:
结果跟我们上面设置为0是一样的效果,也就是说即使我设置了0,ViewPager任然缓存了1个Fragment,我们看看ViewPager源码。
public void setOffscreenPageLimit(int limit) {
if (limit < DEFAULT_OFFSCREEN_PAGES) {
Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to " +
DEFAULT_OFFSCREEN_PAGES);
limit = DEFAULT_OFFSCREEN_PAGES;
}
if (limit != mOffscreenPageLimit) {
mOffscreenPageLimit = limit;
populate();
}
}
我们看看DEFAULT_OFFSCREEN_PAGES这个属性默认是多少?我们可以看到DEFAULT_OFFSCREEN_PAGES 默认是1,跟我们猜测是一样的,也就是说ViewPager默认缓存了一个item。
private static final int DEFAULT_OFFSCREEN_PAGES = 1;
那么如果不想让ViewPager做缓存怎么办呢? 只需要自己创一个ViewPager叫MyViewPager,然后把ViewPager的所有源码导入进去,修改下
private static final int DEFAULT_OFFSCREEN_PAGES = 0;
即可!!,是不是soeasy呢? 这也算是一小小技巧吧,我们接着往下走……
下面我们来说说Fragment的懒加载,使用场景ViewPager+Fragment的时候,Fragment对用户可见的时候去加载数据,以时间换空间,加载到哪个Fragment的时候才去加载数据,而不是一出来就加载所有数据,上网一搜一大堆实现方式,但是主要的还是利用重写Fragment的setUserVisibleHint方法实现。
下面我们来看看为什么通过重写这个方法就可以实现了,我们看看我们的FragmentPagerAdapter的源码。
public abstract class FragmentPagerAdapter extends PagerAdapter {
private static final String TAG = "FragmentPagerAdapter";
private static final boolean DEBUG = false;
private final FragmentManager mFragmentManager;
private FragmentTransaction mCurTransaction = null;
private Fragment mCurrentPrimaryItem = null;
public FragmentPagerAdapter(FragmentManager fm) {
mFragmentManager = fm;
}
/**
* Return the Fragment associated with a specified position.
*/
public abstract Fragment getItem(int position);
@Override
public void startUpdate(ViewGroup container) {
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
final long itemId = getItemId(position);
// Do we already have this fragment?
String name = makeFragmentName(container.getId(), itemId);
Fragment fragment = mFragmentManager.findFragmentByTag(name);
if (fragment != null) {
if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
mCurTransaction.attach(fragment);
} else {
fragment = getItem(position);
if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
mCurTransaction.add(container.getId(), fragment,
makeFragmentName(container.getId(), itemId));
}
if (fragment != mCurrentPrimaryItem) {
fragment.setMenuVisibility(false);
fragment.setUserVisibleHint(false);
}
return fragment;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
if (DEBUG) Log.v(TAG, "Detaching item #" + getItemId(position) + ": f=" + object
+ " v=" + ((Fragment)object).getView());
mCurTransaction.detach((Fragment)object);
}
@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
Fragment fragment = (Fragment)object;
if (fragment != mCurrentPrimaryItem) {
if (mCurrentPrimaryItem != null) {
mCurrentPrimaryItem.setMenuVisibility(false);
mCurrentPrimaryItem.setUserVisibleHint(false);
}
if (fragment != null) {
fragment.setMenuVisibility(true);
fragment.setUserVisibleHint(true);
}
mCurrentPrimaryItem = fragment;
}
}
@Override
public void finishUpdate(ViewGroup container) {
if (mCurTransaction != null) {
mCurTransaction.commitAllowingStateLoss();
mCurTransaction = null;
mFragmentManager.executePendingTransactions();
}
}
@Override
public boolean isViewFromObject(View view, Object object) {
return ((Fragment)object).getView() == view;
}
@Override
public Parcelable saveState() {
return null;
}
@Override
public void restoreState(Parcelable state, ClassLoader loader) {
}
/**
* Return a unique identifier for the item at the given position.
*
* <p>The default implementation returns the given position.
* Subclasses should override this method if the positions of items can change.</p>
*
* @param position Position within this adapter
* @return Unique identifier for the item at position
*/
public long getItemId(int position) {
return position;
}
private static String makeFragmentName(int viewId, long id) {
return "android:switcher:" + viewId + ":" + id;
}
其实呢也就是继承了一个PagerAdapter,跟我们平时的用法一样,我们看看代码会发现在这个类中setUserVisibleHint方法多次被调用,我们简单撸一撸源码,
@Override
public Object instantiateItem(ViewGroup container, int position) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
final long itemId = getItemId(position);
// Do we already have this fragment?
String name = makeFragmentName(container.getId(), itemId);
Fragment fragment = mFragmentManager.findFragmentByTag(name);
if (fragment != null) {
if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
mCurTransaction.attach(fragment);
} else {
fragment = getItem(position);
if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
mCurTransaction.add(container.getId(), fragment,
makeFragmentName(container.getId(), itemId));
}
if (fragment != mCurrentPrimaryItem) {
fragment.setMenuVisibility(false);
fragment.setUserVisibleHint(false);
}
return fragment;
}
在Adapter的instantiateItem方法中,将即将展现的Fragment的fragment.setUserVisibleHint(false);isVisible赋值为了false,也就是此时Fragment还不可见。
我们紧接着看看FragmentPagerAdapter中有一个setPrimaryItem的方法,将setUserVisibleHint赋值为了true,也就是说此时的Fragment状态变为了对用户可见,是的!setPrimaryItem方法是在Fragment即将对用户可见的时候调用。
@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
Fragment fragment = (Fragment)object;
if (fragment != mCurrentPrimaryItem) {
if (mCurrentPrimaryItem != null) {
mCurrentPrimaryItem.setMenuVisibility(false);
mCurrentPrimaryItem.setUserVisibleHint(false);
}
if (fragment != null) {
fragment.setMenuVisibility(true);
fragment.setUserVisibleHint(true);
}
mCurrentPrimaryItem = fragment;
}
}
到了这里,想必大家对Fragment懒加载的实现有了一点小小的思路了咯。
我们把我们代码改改(重写下setUserVisibleHint方法):
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
Log.e(TAG+" "+pageNo,"---->setUserVisibleHint-->"+isVisibleToUser);
}
我们来运行下代码看看log:
我们看见当调用完第一个Fragment的onCreate方法的时候调用了setUserVisibleHint–>true此时第一个Fragment对用户可见。同时也发现,setUserVisibleHint先于onCreate方法执行,并且执行了两次,但是此时的Fragment对用户不可见。
我们从1—–>2,然后看看log:
第一个Fragment的setUserVisibleHint设为了false,即将出现的第二个Fragment的setUserVisibleHint设置为了true。
我们从2—–>1,然后看看log:
第一个Fragment的setUserVisibleHint设为了true,即将消失的第二个Fragment的setUserVisibleHint设置为了false。
好了,到了这里相信你一定很有思路了。。。。。。
那我们就一起来实现一下Fragment的懒加载。
首先创建一个BaseFragment:
**
* Author:Yqy
* Date:2016-09-21
* Desc:
* Company:cisetech
*/
public abstract class BaseFragment extends Fragment {
/**
* 是否对用户可见标记
*/
protected boolean isVisible;
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if(isVisibleToUser){
isVisible = true;
onVisible();
}else{
isVisible = false;
onInvisible();
}
}
/**
* 不可见的时候操作
*/
protected void onVisible(){
lazyLoad();
}
/**
* 抽象lazyLoad方法,让子类实现。
*/
protected abstract void lazyLoad();
protected void onInvisible(){}
}
然后让MyFragment继承BaseFragment:
**
* @author EX_YINQINGYANG
* @version [Android PABank C01, @2016-09-21]
* @date 2016-09-21
* @description
*/
public class MyFragment extends BaseFragment {
/**
* 初始化View完毕的标记
*/
private boolean isPrepared;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
TextView view=new TextView(getActivity());
view.setGravity(Gravity.CENTER);
view.setTextColor(Color.BLACK);
view.setTextSize(TypedValue.COMPLEX_UNIT_DIP,18f);
isPrepared=true;
lazyLoad();
return view;
}
private ProgressDialog dialog;
@Override
protected void lazyLoad() {
if(!isPrepared || !isVisible) {
return;
}
dialog=ProgressDialog.show(getActivity(),"","加载中,请稍后...");
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
Toast.makeText(getActivity(),"加载完毕!",Toast.LENGTH_SHORT).show();
((TextView)getView()).setText(""+getArguments().getInt("PAGE_NO"));
dialog.dismiss();
}
},3000);
//填充各控件的数据
}
public static Fragment newInstance(int i) {
MyFragment fragment=new MyFragment();
Bundle bundle=new Bundle();
bundle.putInt("PAGE_NO",i);
fragment.setArguments(bundle);
return fragment;
}
}
因为setUserVisibleHint会先于onCreateView执行,如果这个时候加载数据的话,然后赋值会报各种空指针错误,所以当执行完onCreateView方法后,此时的isVisible也为true,所以再去执行一次lazyLoad();方法。
Fragment就暂时先研究到这里了,3Q!!!
最后给出demo的github链接: