*本篇文章已授權(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();
}
}
