大家好,又见面了,我是你们的朋友全栈君。
在很久以前,总觉得ListView的notifyDataSetChanged之类的方法很神奇,数据更新后,调用一下,视图就变了…
不过自从知道观察者模式以后就没感觉那么神奇了,反而对View的绘制测量一系列精细的计算叹为观止—虽然会生出另一种感觉~~某种程度上来说,Android的源代码其实挺臃肿的。
后面推出了RecyclerView,但其实更新机制并无不同。
就如调用notifyDataSetChanged方法:
//RecyclerView.java
public abstract static class Adapter<VH extends RecyclerView.ViewHolder> {
private final RecyclerView.AdapterDataObservable mObservable = new RecyclerView.AdapterDataObservable();
public final void notifyDataSetChanged() {
this.mObservable.notifyChanged();
}
}
从这种意义上来讲,开发者构造的Adpater就是被观察者,而最终的视图RecyclerView就是观察者,当Adpater数据变动时,RecyclerView会被通知到并根据数据变动视图。
而setAdapter就是两者的订阅关系的建立。
public void setAdapter(@Nullable RecyclerView.Adapter adapter) {
this.setLayoutFrozen(false);
this.setAdapterInternal(adapter, false, true);
this.processDataSetCompletelyChanged(false);
this.requestLayout();
}
其实从这里就可以开始正当地怀疑了,requestLayout方法简直就是明目张胆地说—“视图变化就是我干的!”
我们知道,setAdpater视图确实是有所变化的;我们也知道,requestLayout方法和invalidate方法有所不同,invalidate只会调用onDraw,而requestLayout则会onMeasure、onLayout、onDraw都调用。
至于问为什么?和ViewRootImpl有关。requestLayout和invalidate都会调用父类视图的同名方法,最终到达ViewRootImpl中的同名方法,而ViewRootImpl会根据一些标记来决定是否执行measure/layout/draw流程。
罪魁祸首具体是不是requestLayout,我们承接上面notifyDataSetChanged方法的流程,看一下AdapterDataObservable吧:
static class AdapterDataObservable extends Observable<RecyclerView.AdapterDataObserver> {
AdapterDataObservable() {
}
public void notifyChanged() {
for(int i = this.mObservers.size() - 1; i >= 0; --i) {
((RecyclerView.AdapterDataObserver)this.mObservers.get(i)).onChanged();
}
}
}
这里明显是在找所有订阅了此Adpater的观察者,并逐个通知。
这些个观察者是谁?在构造方法中找到:
public RecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
this.mObserver = new RecyclerView.RecyclerViewDataObserver();
}
是RecyclerViewDataObserver。
看一下这个类的onChanged方法:
private class RecyclerViewDataObserver extends RecyclerView.AdapterDataObserver {
RecyclerViewDataObserver() {
}
public void onChanged() {
RecyclerView.this.assertNotInLayoutOrScroll((String)null);
RecyclerView.this.mState.mStructureChanged = true;
RecyclerView.this.processDataSetCompletelyChanged(true);
if (!RecyclerView.this.mAdapterHelper.hasPendingUpdates()) {
RecyclerView.this.requestLayout();
}
}
}
//AdapterHelper.java
final ArrayList<AdapterHelper.UpdateOp> mPendingUpdates;
boolean hasPendingUpdates() {
return this.mPendingUpdates.size() > 0;
}
果然,看到了requestLayout。至于hasPendingUpdates,可以理解为添加删除等操作的标记数量,默认情况下是为0的。
所以requestLayout是得以顺利执行的,那么视图变化就变得顺理成章了。
另外例如notifyItemRangeRemoved、notifyItemRangeInserted之类的方法,会与上面的逻辑有所不同。
比如删除某个Item:
private class RecyclerViewDataObserver extends RecyclerView.AdapterDataObserver {
public void onItemRangeRemoved(int positionStart, int itemCount) {
RecyclerView.this.assertNotInLayoutOrScroll((String)null);
if (RecyclerView.this.mAdapterHelper.onItemRangeRemoved(positionStart, itemCount)) {
this.triggerUpdateProcessor();
}
}
}
这里明显就没有那么直接了,并且条件就变成了判断onItemRangeRemoved方法:
//AdapterHelper.java
boolean onItemRangeRemoved(int positionStart, int itemCount) {
if (itemCount < 1) {
return false;
} else {
this.mPendingUpdates.add(this.obtainUpdateOp(2, positionStart, itemCount, (Object)null));
this.mExistingUpdateTypes |= 2;
return this.mPendingUpdates.size() == 1;
}
}
可以看到,在这种情况下,上面提到的mPendingUpdates的数量是有所变化的,变成了1,那么条件满足,会执行triggerUpdateProcessor方法:
//RecyclerView.java
void triggerUpdateProcessor() {
if (RecyclerView.POST_UPDATES_ON_ANIMATION && RecyclerView.this.mHasFixedSize && RecyclerView.this.mIsAttached) {
ViewCompat.postOnAnimation(RecyclerView.this, RecyclerView.this.mUpdateChildViewsRunnable);
} else {
RecyclerView.this.mAdapterUpdateDuringMeasure = true;
RecyclerView.this.requestLayout();
}
}
}
方法中的第一行判断是否成立?
先说结论:在不设置mHasFixedSize时是无法成立的,
POST_UPDATES_ON_ANIMATION = VERSION.SDK_INT >= 16;
POST_UPDATES_ON_ANIMATION标记是指安卓系统版本大于4.0即为true;
mIsAttached标记是指当前RecyclerView是否已经依附于Window,在已经需要更新数据的场景下,非首次绘制肯定也是为true了;
只有mHasFixedSize这个标记需要手动设置,并且影响较大:
//RecyclerView.java
public void setHasFixedSize(boolean hasFixedSize) {
this.mHasFixedSize = hasFixedSize;
}
public boolean hasFixedSize() {
return this.mHasFixedSize;
}
主要是影响什么呢?
影响大小的测量,也就是视图宽高的绘制。
//RecyclerView.java
RecyclerView.LayoutManager mLayout;
protected void onMeasure(int widthSpec, int heightSpec) {
if (this.mLayout == null) {
this.defaultOnMeasure(widthSpec, heightSpec);
} else {
if (!this.mLayout.isAutoMeasureEnabled()) {
if (this.mHasFixedSize) {
this.mLayout.onMeasure(this.mRecycler, this.mState, widthSpec, heightSpec);
return;
}
//省略一些测量代码
}
//省略一些测量代码
}
//省略一些测量代码
}
public abstract static class LayoutManager {
public void onMeasure(@NonNull RecyclerView.Recycler recycler, @NonNull RecyclerView.State state, int widthSpec, int heightSpec) {
this.mRecyclerView.defaultOnMeasure(widthSpec, heightSpec);
}
}
isAutoMeasureEnabled默认是为false的,所以会进一步判断mHasFixedSize。
如果mHasFixedSize为true,代表着不必再测量宽高,直接使用默认的宽高或者说之前已经测量好的宽高就可以;如果为false,那么进行其他的测量流程。
也就是说,只有在不影响宽高的情况下,我们设置mHasFixedSize为true。
话说回来,也就是在triggerUpdateProcessor方法中,没有意外情况的话,仍然会执行requestLayout方法。
其他诸如notifyItemRangeInserted之类的方法同样如此。
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/148019.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...