记录工作中的点滴,如有不正确的地方,还望大家不吝赐教.
1.问题来源
在项目中由于UI设计师要求:1、不要顶部和底部的over scroll显示;2、要求显示右侧滚动条
对于此要求很快的写下了如下的布局:
用android:overScrollMode="never"来设定不显示over scroll,android:scrollbars="vertical"来设定显示滚动条
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:overScrollMode="never"
android:paddingEnd="30dp"
android:scrollbars="vertical" />
</LinearLayout>
运行后发现效果为不显示over scroll同时也不显示右侧滚动条了,郁闷...
网上简单搜索查询后发现解决不显示滚动条的方法有下面两种:
1、给RecyclerView增加background属性
2、去掉RecyclerView的overscrollMode属性
使用1方法后是可以解决问题的,使用方法2后滚动条是可以显示滚动条了,但是同时也显示出来over scroll了。
2.问题分析
使用方法1虽然解决了问题,但是我们还是要知道为什么它可以解决问题,这个时候最好的办法就是去查看源码了。
首先来查看View.java中的draw方法如下:
// View.java
public void draw(Canvas canvas) {
...
/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
* 1. Draw the background
* 2. If necessary, save the canvas' layers to prepare for fading
* 3. Draw view's content
* 4. Draw children
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance)
*/
...
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
...
}
draw方法中的注释已经很清楚的描述了绘制流程,背景和滚动条的绘制都是在本方法中触发的,对于上面的方法1增加background属性后滚动条也可以显示,而不设置背景滚动条也不显示,那么不显示的时候很可能是该draw方法没有被调用到。
此时突然想到了之前看view绘制流程相关文章中的提到的一个知识点,对于ViewGroup类型默认情况下会开启WILL_NOT_DRAW标记,表示自身不绘制任何内容,便于系统优化。那么我们的问题中是不是也是因为这个原因呢。
// ViewGroup.java
public ViewGroup(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initViewGroup();
initFromAttributes(context, attrs, defStyleAttr, defStyleRes);
}
private void initViewGroup() {
// ViewGroup doesn't draw by default
if (!debugDraw()) {
setFlags(WILL_NOT_DRAW, DRAW_MASK);
}
...
}
再来查看RecyclerView.java,果然在设置为over scrool never后会开启系统优化,不绘制本身,那么也就不会调用draw方法去绘制滚动条了。
// RecyclerView.java
public RecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
...
setWillNotDraw(getOverScrollMode() == View.OVER_SCROLL_NEVER);
...
}
那还有一个疑问,为什么设置了背景又可以显示滚动条了呢,继续看这个setWillNotSDraw的方法
// View.java
public void setWillNotDraw(boolean willNotDraw) {
setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}
void setFlags(int flags, int mask) {
...
if ((changed & DRAW_MASK) != 0) {
if ((mViewFlags & WILL_NOT_DRAW) != 0) {
// 这里还要判断背景、焦点高亮、前景是否为空,只要一个不为空就还是不能忽略绘制
if (mBackground != null
|| mDefaultFocusHighlight != null
|| (mForegroundInfo != null && mForegroundInfo.mDrawable != null)) {
mPrivateFlags &= ~PFLAG_SKIP_DRAW;
} else {
mPrivateFlags |= PFLAG_SKIP_DRAW;
}
} else {
mPrivateFlags &= ~PFLAG_SKIP_DRAW;
}
requestLayout();
invalidate(true);
}
...
}
setWillNotDraw方法仅仅是继续调用了setFlags方法,在这个setFlags方法中还会继续判断背景、焦点高亮、前景是否为空,都为空才会设置忽略绘制的flag。
那这个忽略绘制的flag又是在什么时候使用的呢?在代码中可以看到是在updateDisplayListIfDirty方法中使用的见下面代码所示:
// View.java
public RenderNode updateDisplayListIfDirty() {
...
// Fast path for layouts with no backgrounds
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
// 直接绘制子view
dispatchDraw(canvas);
drawAutofilledHighlight(canvas);
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().draw(canvas);
}
if (debugDraw()) {
debugDrawFocus(canvas);
}
} else {
// 绘制本身和子view
draw(canvas);
}
...
}
至此问题产生的原因已经了解清楚了,那么除了设置背景外我们还可以通过在代码中调用setWillNotDraw(false);来解除overScrollMode为never带来的影响。