RecyclerView不显示滚动条问题分析

4,077 阅读3分钟

记录工作中的点滴,如有不正确的地方,还望大家不吝赐教.

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带来的影响。