RecyclerView遇到notifyDataSetChanged無效時(shí)的解決方案

*本篇文章已授權(quán)微信公眾號(hào) guolin_blog (郭霖)獨(dú)家發(fā)布

一、簡(jiǎn)述

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

二、探索

1、查看數(shù)據(jù)(mData)是否是同個(gè)對(duì)象

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

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

public void initData() {
    if (mData == null) {
        mData = new ArrayList<>();
    }
    mData.clear();
    ...
    數(shù)據(jù)填充
    ...
    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時(shí)mData地址:" + mData.hashCode());
    } else {
        mAdapter.notifyDataSetChanged();
        LogUtils.sf("setAdapter時(shí)mData地址:" + mData.hashCode());
    }
}

2、操作與結(jié)果

*tip:常規(guī)對(duì)Fragment的使用,會(huì)對(duì)其進(jìn)行緩存,也可能使用單例模式,反正就是短時(shí)間內(nèi)不會(huì)重新創(chuàng)建。

①操作一:

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

②操作二:

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

③看控制臺(tái):

可以看到數(shù)據(jù)對(duì)象地址一樣,即為同一個(gè)。

3、查看RecyclerView是否是同個(gè)對(duì)象

說實(shí)話,這個(gè)是踩坑經(jīng)驗(yàn)豐富的網(wǎng)友在群里說的,如果不是他說出來,打死我也沒想到,居然還有這么一個(gè)坑。從上面的結(jié)果可以看出,adapter中是有數(shù)據(jù)的沒錯(cuò),而且數(shù)據(jù)地址沒變,所以理應(yīng)notifyDataSetChanged()方法會(huì)生效。但是為什么會(huì)這樣呢,這里先賣個(gè)關(guān)子,先看下面的操作。

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

代碼如下:

public void initData() {
    if (mData == null) {
        mData = new ArrayList<>();
    }
    mData.clear();
    ...
    數(shù)據(jù)填充
    ...
    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時(shí)Rv:" + mRvList.hashCode());
    } else {
        mAdapter.notifyDataSetChanged();
        LogUtils.sf("notify時(shí)Rv:" + mRvList.hashCode());
    }
}

②同上述操作一致。

對(duì)同一個(gè)Fragment來回切換,看控制臺(tái)輸出。

果然不一樣?。。?/p>

三、總結(jié)

為什么在Fragment中RecyclerView的地址會(huì)發(fā)生變化呢?我們先理清一下Fragment生命周期會(huì)陸續(xù)調(diào)用的幾個(gè)方法:

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

中間少了幾個(gè)方法,請(qǐng)不用在意,下面貼下我的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) {
        //子類不再需要設(shè)置布局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();
    }
}

當(dāng)一個(gè)Fragment在來回切換時(shí),分別調(diào)用的方法如下:

第一次顯示:

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

第二次顯示:

onCreateView() -> onActivityCreated()

這里不難理解,因?yàn)镕ragment一般使用的時(shí)候會(huì)被緩存,所以,當(dāng)?shù)诙物@示的時(shí)候,不會(huì)調(diào)用onCreate()。只會(huì)調(diào)用onCreateView()和onActivityCreated(),這也就是RecyclerView地址不一樣的原因所在,因?yàn)榭丶@取操作是在initView()中進(jìn)行的,即RecyclerView的獲取操作在onCreateView()中,而Fragment的每次顯示都會(huì)調(diào)用onCreateView(),所以RecyclerView控件會(huì)被再次獲取,即重新創(chuàng)建一個(gè)對(duì)象(此時(shí)hashCode就變化了)。

1、結(jié)論:

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

2、解決方案:

1)方案一:

因?yàn)镕ragment的onCreateView()和onActivityCreated()方法在每次Fragment顯示的時(shí)候會(huì)被調(diào)用,控件會(huì)被重新創(chuàng)建一次,所以,解決方法只能是在這兩個(gè)方法中重新對(duì)RecyclerView設(shè)置適配器,而不要使用notifyDataSetChanged(),故代碼改為如下:

public void initData() {
    if (mData == null) {
        mData = new ArrayList<>();
    }
    mData.clear();
    ...
    數(shù)據(jù)填充
    ...
    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);
}

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

2)方案二:

讓rootView作為全局變量,在回調(diào)onCreateView()時(shí)不再重新創(chuàng)建。

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) {
      //子類不再需要設(shè)置布局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();
  }
}
歡迎關(guān)注微信公眾號(hào):全棧行動(dòng)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容