Android RecyclerView遇到notifyDataSetChanged無效時的解決方案


一、簡述

不管AbsListView(ListView、GridView)或是新出的RecyclerView,在使用notifyDataSetChanged方法更新列表數據時,一定要保證數據為同個對象(即hashCode要一致)。對於這個問題的論證,可以去看官方源代碼,或是看我之前寫的一篇博文“解決ListViews適配器notifyDataSetChanged()無效問題”,相信可以幫到你。但是,這個不是本文的重點,本文重點講解在Fragment中,RecyclerView遇到notifyDataSetChanged無效的問題。如果你趕時間,可以直接看第三部分(”總結”)。

二、探索

1、查看數據(mData)是否是同個對象

*tip:java中可以通過打印hashCode的方式判斷mData是否為同個對象。

注意:initData方法在onActivityCreated()中被調用。

public void initData() {
    if (mData == null) {
        mData = new ArrayList<>();
    }
    mData.clear();
    ...
    數據填充
    ...
    if (mAdapter == null) {
        mAdapter = new LQRAdapterForRecyclerView<String>(getActivity(), mData, R.layout.item_senior) {
            @Override
            public void convert(LQRViewHolderForRecyclerView helper, String item, int position) {
                ...
                視圖填充
                ...
            }
        };
        mRvList.setAdapter(mAdapter);
        LogUtils.sf("setAdapter時mData地址:" + mData.hashCode());
    } else {
        mAdapter.notifyDataSetChanged();
        LogUtils.sf("setAdapter時mData地址:" + mData.hashCode());
    }
}

2、操作與結果

*tip:常規對Fragment的使用,會對其進行緩存,也可能使用單例模式,反正就是短時間內不會重新創建。

①操作一:

打開Activity后,切換Fragment(第一次初始化Fragment)。顯示效果如下:

這里寫圖片描述

②操作二:

切換別的Fragment后,再切回剛才的Fragment(此前該Fragment已經在存在,所以不會再次創建)。顯示效果如下:

這里寫圖片描述

③看控制台:

這里寫圖片描述

可以看到數據對象地址一樣,即為同一個。

3、查看RecyclerView是否是同個對象

說實話,這個是踩坑經驗豐富的網友在群里說的,如果不是他說出來,打死我也沒想到,居然還有這么一個坑。從上面的結果可以看出,adapter中是有數據的沒錯,而且數據地址沒變,所以理應notifyDataSetChanged()方法會生效。但是為什么會這樣呢,這里先賣個關子,先看下面的操作。

①改下上面的代碼,打印RecyclerView的地址。

代碼如下:

public void initData() {
    if (mData == null) {
        mData = new ArrayList<>();
    }
    mData.clear();
    ...
    數據填充
    ...
    if (mAdapter == null) {
        mAdapter = new LQRAdapterForRecyclerView<String>(getActivity(), mData, R.layout.item_senior) {
            @Override
            public void convert(LQRViewHolderForRecyclerView helper, String item, int position) {
                ...
                視圖填充
                ...
            }
        };
        mRvList.setAdapter(mAdapter);
        LogUtils.sf("setAdapter時Rv:" + mRvList.hashCode());
    } else {
        mAdapter.notifyDataSetChanged();
        LogUtils.sf("notify時Rv:" + mRvList.hashCode());
    }
}

②同上述操作一致。

對同一個Fragment來回切換,看控制台輸出。

這里寫圖片描述

果然不一樣!!!

三、總結

為什么在Fragment中RecyclerView的地址會發生變化呢?我們先理清一下Fragment生命周期會陸續調用的幾個方法:

onCreate() -> onCreateView() -> onActivityCreated() -> onDestroy()

中間少了幾個方法,請不用在意,下面貼下我的BaseFragment代碼:

public abstract class BaseFragment extends Fragment {
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        init();
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        //子類不再需要設置布局ID,也不再需要使用ButterKnife.bind()
        View rootView = inflater.inflate(provideContentViewId(), container, false);
        ButterKnife.bind(this, rootView);
        initView(rootView);
        return rootView;
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        initData();
        initListener();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }
}

當一個Fragment在來回切換時,分別調用的方法如下:

第一次顯示:

onCreate() -> onCreateView() -> onActivityCreated()

第二次顯示:

onCreateView() -> onActivityCreated()

這里不難理解,因為Fragment一般使用的時候會被緩存,所以,當第二次顯示的時候,不會調用onCreate()。只會調用onCreateView()和onActivityCreated(),這也就是RecyclerView地址不一樣的原因所在,因為控件獲取操作是在initView()中進行的,即RecyclerView的獲取操作在onCreateView()中,而Fragment的每次顯示都會調用onCreateView(),所以RecyclerView控件會被再次獲取,即重新創建一個對象(此時hashCode就變化了)。

1、結論:

所以,在Fragment中使用RecyclerView或AbsListView控件的notifyDataSetChanged()方法時,除了保證數據(mData對象)不能變以外,控件本身一樣也不能變。

2、解決方案:

1)方案一:

因為Fragment的onCreateView()和onActivityCreated()方法在每次Fragment顯示的時候會被調用,控件會被重新創建一次,所以,解決方法只能是在這兩個方法中重新對RecyclerView設置適配器,而不要使用notifyDataSetChanged(),故代碼改為如下:

public void initData() {
    if (mData == null) {
        mData = new ArrayList<>();
    }
    mData.clear();
    ...
    數據填充
    ...
    if (mAdapter == null) {
        mAdapter = new LQRAdapterForRecyclerView<String>(getActivity(), mData, R.layout.item_senior) {
            @Override
            public void convert(LQRViewHolderForRecyclerView helper, String item, int position) {
                ...
                視圖填充
                ...
            }
        };
    } 
     mRvList.setAdapter(mAdapter);
}

注:只是建議不要在上述兩個生命周期方法中使用notifyDataSetChanged()而已,只要在保證RecyclerView等列表控件設置完適配器后,可以在任意地方繼續使用notifyDataSetChanged()。

2)方案二:

讓rootView作為全局變量,在回調onCreateView()時不再重新創建。

public abstract class BaseFragment extends Fragment {

   View rootView;

  @Override
  public void onCreate(@Nullable Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      init();
  }

  @Nullable
  @Override
  public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle  savedInstanceState) {
      //子類不再需要設置布局ID,也不再需要使用ButterKnife.bind()
      if(rootView == null){
          rootView = inflater.inflate(provideContentViewId(), container, false);
          ButterKnife.bind(this, rootView);
          initView(rootView);
      }
      return rootView;
  }

  @Override
  public void onActivityCreated(@Nullable Bundle savedInstanceState) {
      super.onActivityCreated(savedInstanceState);
      initData();
      initListener();
  }

   @Override
  public void onDestroy() {
      super.onDestroy();
  }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM