美文网首页
ViewPager中切换RecyclerView/ListVie

ViewPager中切换RecyclerView/ListVie

作者: 鹈鹕醍醐 | 来源:发表于2018-06-29 19:29 被阅读60次

在项目开发中我们经常会碰到这样的需求: 一个左右滑动切换的Viewpager,page中是一个列表。这时大家一般都会采用ViewpagerFragment\ 自定义View的方式去实现,每个页面复用Adapter,当page.size > 2的时候,会出现白屏问题Adapter设置或刷新不生效的问题

Example:
总共有3页,前两页正常加载,到第三页加载完以后左滑回第一页,这时会发现列表丢失了,变成空白页面了,无论是listViewAdapter.notifyDatasetChanged还是RecyclerViewnotifyXxxx都没有生效。

排查原因:

    1. 首先发现直接用匿名类方式使用Adapter每次直接new对象是不会出这种问题的,但是这种实现方法很明显太浪费内存了
    1. 执行过 PagerAdapter#destroyItem()方法的页面出现这个问题,我们以FragmentPagerAdapter的源码来看:
 @Override
    public Object instantiateItem(ViewGroup container, int position) {
        final long itemId = getItemId(position);
        // Do we already have this fragment?
        通过name作为tag标记在事务中缓存了Fragment
        String name = makeFragmentName(container.getId(), itemId);
        Fragment fragment = mFragmentManager.findFragmentByTag(name);
        if (fragment != null) {
            复用的时候直接执行attach()方法
            mCurTransaction.attach(fragment);
        } else {
            fragment = getItem(position);
            mCurTransaction.add(container.getId(), fragment,
                    makeFragmentName(container.getId(), itemId));
        }
         ······
        return fragment;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        ······
        在viewpager销毁页面的时候执行了detach方法,回收了页面
        mCurTransaction.detach((Fragment)object);
    }

FragmentPageAdapterdestoryItem()detach掉了需要销毁的fragment,在instantiateItem()方法中获取Fragment重新加载,在复用时直接调用FragmentTransaction.attach()
理一下Fragment的状态顺序,以index=0fragment为例,

  • 初次实例化的时候通过FragmentTransaction.add()添加到窗口时执行顺序为 Fragment.onAttach()Activity.onAttachFragment(Fragment)Fragment.performCreate()Fragment.onCreate()Fragment.performCreateView()Fragment.onCreateView()Fragment.onViewCreated()
  • detach()时顺序为 Fragment.performDestroyView()Fragment.onDestroyView()Fragment.mContainer.removeView(Fragment.mView)
  • 重新attach()时顺序为
    Fragment.onCreateView()Fragment.onViewCreated()

onViewCreated()执行的前提是onCreateView()的返回view不为空,
因此onViewCreated()中的view一定是非空的

public class RedFragment extends BaseFragment {

    private MyAdapter mAdapter;

    public static RedBagFragment getInstance(String type){
        ······return fragment;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle state) {  
        return inflater.inflate(R.layout.fragment_red, container, false);
    }
    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        mListView = (RecyclerView) view.findViewById(R.id.rv_data);······
    }

复用时重新调用了fragmentonCreateView(),重新创建了一个新的view,因此RecyclerView\ListView已经是一个全新的对象了,而Adapter仍然对应的是旧的RecyclerView\ListView。代码自然就不会生效了,而是会展示一片空白

if(mAdapter ==null){
    mAdapter =new MyRedBagAdapter(mData);
    mListView.setAdapter(mAdapter);
}else {
    mAdapter.notifyDataSetChanged();
}

解决方法:

  • 1,复用FragmentinflaterView(推荐)
private View mInflated;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
    if(mInflated == null){
        mInflated = inflater.inflate(R.layout.fragment_red, container, false);
    }
    return mInflated;
}
  • 2,在onDestoryView时释放adapter(不推荐,内存浪费)
@Override
public void onDestroyView() {
    super.onDestroyView();
    mAdapter = null;
}
  • 3, adapter中自定义关联方法,使得adapterListView/RecyclerView双向关联(不推荐,内存浪费 画蛇添足)
在adapter中,定义attach
public void setAttachedView(View listView){
    mListView = listView;
}
public View getAttachedView() {
    return mListView;
}
在fragment中
void setAdapter(){
    if(mAdapter == null || mAdapter.getAttachedView() != mListView){
        mAdapter = new MyAdapter(mData);
        mAdapter.setAttachedView(mListView);
        mListView.setAdapter(mAdapter);
     } else {
        mAdapter.notifyDataSetChanged();
     }
}

2 3两个方案比 1 差多了,在对象销毁前旧的View和adapter都是内存垃圾处于泄漏状态,而且多执行了inflatefindViewById操作,消耗更多的计算时间。

相关文章

网友评论

      本文标题:ViewPager中切换RecyclerView/ListVie

      本文链接:https://www.haomeiwen.com/subject/kjfzyftx.html