1.问题引入
做项目的时候,AppBarLayout里面嵌套RecycleView。当没有数据的时候就提示请求出错的图片。当Fragment互相切换切回这个Fragment的时候(切的时候因为需求原因,有的时候需要沉浸式,有的时候又不需要,所以设置了Padding),重新请求数据,发现请求出错的图片往下挪了点位置。后来经过定位,是在设置Visiblity的时候引起的,而且获取同一个控件宽高的时候发现位置和padding还有点关系,注释掉padding就没有这个问题。(我看了一下setPadding源码,发现它会重绘整个view)很明显,setVisiblity和重绘有关联,需要看源码
2.源码解析
void setFlags(int flags, int mask) {
......
//保存当前视图状态为old
int old = mViewFlags;
//更新视图状态为将要更改后的属性。
mViewFlags = (mViewFlags & ~mask) | (flags & mask);
//获取其更改的属性位
int changed = mViewFlags ^ old;
//记录当前视图的逻辑属性。
int privateFlags = mPrivateFlags;
final int newVisibility = flags & VISIBILITY_MASK;
if (newVisibility == VISIBLE) {
if ((changed & VISIBILITY_MASK) != 0) {
/*
* If this view is becoming visible, invalidate it in case it changed while
* it was not visible. Marking it drawn ensures that the invalidation will
* go through.
*/
mPrivateFlags |= PFLAG_DRAWN;
invalidate(true);
//invalidate自己,child
needGlobalAttributesUpdate(true);
......
}
}
//如果视图的属性要设置为GONE
if ((changed & GONE) != 0) {
//需要全局属性更新,因为GONE属性设置其视图不见了,其他视图的位置也会受到影响。
needGlobalAttributesUpdate(false);
//申请重新布局
requestLayout();
if (((mViewFlags & VISIBILITY_MASK) == GONE)) {
......
if (mParent instanceof View) {
// GONE views noop invalidation, so invalidate the parent
((View) mParent).invalidate(true);
}
mPrivateFlags |= PFLAG_DRAWN;
//requestLayout,invalidate parent,然后设置PFLAG_DRAWN以便下次invalidate
}
......
}
//如果视图将要设置为INVISIBLE了
if ((changed & INVISIBLE) != 0) {
//不需要全局属性更新
needGlobalAttributesUpdate(false);
/*
* If this view is becoming invisible, set the DRAWN flag so that
* the next invalidate() will not be skipped.
*/
mPrivateFlags |= PFLAG_DRAWN;
//改变标记位PFLAG_DRAWN,以便下次invalidate()
......
}
if ((changed & VISIBILITY_MASK) != 0) {
......
if (mParent instanceof ViewGroup) {
((ViewGroup) mParent).onChildVisibilityChanged(this,
(changed & VISIBILITY_MASK), newVisibility);
((View) mParent).invalidate(true);
} else if (mParent != null) {
mParent.invalidateChild(this, null);
}
......
}
......
}
可以做如下总结
setVisibility=View.VISIBLE
------invalidate自己,parent,child
setVisibility=View.INVISIBLE
------改变标记位PFLAG_DRAWN,以便下次invalidate()
setVisibility=View.GONE
------requestLayout,invalidate parent,然后设置PFLAG_DRAWN以便下次invalidate
那么,可以看出GONE的时候会requestLayout,并且全局属性更新。如果从VISIBLE切换到GONE的时候是不会有什么问题的,但是从GONE切换到VISIBLE的时候,会抢占焦点
3.问题解决
setPadding导致整个view重绘,使得原本处于屏幕中间的图不再处于正中间,网络请求结束以后,先对整个内容区域的所有控件设置GONE,在对请求出错的图片设置VISIBLE导致自身重绘到内容区域的最中间,所以会往下挪。而且往下挪的位置正好是padding的距离
4.总结
对于那些可滑动性的控件(ListView,RecycleView,ScrollView)而言,当内部控件设置为GONE和VISIBLE的时候,一定要注意重绘的问题,很可能因为重绘问题导致移位。
扩展:除了重绘的问题需要注意,同时重绘还可能导致焦点抢占的问题也需要注意,可能会出现抢占焦点导致整个RecycleView的内容往上滑动。这个问题很好解决,添加descendantFocusability属性就行
网友评论