浅谈android layer-list之自定义scrollbar二

122 阅读3分钟

recycleview的scrollbar的背景

最近遇到了一个问题:需要将一张图片设置为recycleview的scrollbar的背景,直接设置

 android:scrollbarThumbVertical="@drawable/icon_scrollbar"

这里的icon_scrollbar为一张切图,但实际上呈现的效果是如下图:
file
可以看到图片被拉伸了 而当这张图片放在android项目drawable下,用layer-list进行包装,

  android:scrollbarThumbVertical="@drawable/shape_scrollbar_thumb"

shape_scrollbar_thumb 内容为:

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
   <item
       android:height="82dp"
       android:drawable="@drawable/icon_scrollbar"
       />
</layer-list>

呈现的效果就满足需求:
file

就这一现象,引发出以下几个知识点?

1、drawble是什么?
Android把可绘制的对象抽象为Drawable,不同的图形图像资源就代表着不同的drawable类型。Android FrameWork提供了一些具体的Drawable实现,通常在代码中都不会直接接触Drawable的实现类。         在实际的开发过程中,会把使用到的资源都放置在res/drawable目录,剩下的工作交给Android SDK 就行了,当需要使用图片资源的时候,可以使用@drawable标志在xml中引用drawable资源就行,也可以在代码中使用id引用这些drawable资源。

2、从layer-list获取drawble对象和直接从图片获取drawble的区别?
当你直接从drwable获取图片时,返回的是BitmapDrawable,recycleview会根据item的多少改变高度,所以图片会被拉伸。而用layer-list包装一层,返回的layerDrawable,同样拉伸时,整个drawable数组会被拉伸,但此时图片是item里面的一个子元素已经被指定了高度,图片则不会被拉伸。简单来说即:layer-list返回的是个整体的viewGroup,把layerlist看成一个关于图片的framelayout。每一子项都可以控制位置,间距,以及大小,如果里面的子控件自身指定了宽高等特性就由自身决定,如果没有指定,则取决于外部。 3、RecycleView源码,默认情况下滑动时,thumb的高度或者宽度是根据整个列表的长度决定的,如果不自定义scrollbar,则可能会出现拉伸

新的问题又出来了

虽然背景是不会拉伸了,但会发现scrollbar是不能滑动到底部和顶部的。如果需要是可以动态改变Scrollbar的高度,以及滑动的位置。
`file

   @Override
   public int computeVerticalScrollOffset() {
       if (mLayout == null) {
           return 0;
       }
       return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollOffset(mState) : 0;
   }

file

     @Override
    public int computeVerticalScrollExtent() {
        if (mLayout == null) {
            return 0;
        }
        return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollExtent(mState) : 0;
    }

file

    @Override
    public int computeVerticalScrollRange() {
        if (mLayout == null) {
            return 0;
        }
        return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollRange(mState) : 0;
    }

![] 计算scrollbar滑动位置的原理如图 scrollbar相关源码的三个变量需要注意:offset、extent、range

   /**
    * 计算竖直方向总的滑动区域
    */
   override fun computeVerticalScrollRange(): Int {
       if (trackLength == -1) {
           trackLength = this.measuredHeight
       }
       return trackLength
   }

   /**
    * 计算当前这次竖直方向滑动的位移
    */
   override fun computeVerticalScrollOffset(): Int {
       getHeights()
       val highestVisiblePixel = super.computeVerticalScrollOffset()

       return computeScrollOffset(highestVisiblePixel)
   }
   
   private fun computeScrollOffset(highestVisiblePixel: Int): Int {
       /**
        * 不可见区域的列表(可以滑动的总长度)
        */
       val invisiblePartOfRecyclerView: Int = totalLength - trackLength

       /**
        * 剩余需要滑动的长度
        */
       val scrollAmountRemaining = invisiblePartOfRecyclerView - highestVisiblePixel
       return when {
           invisiblePartOfRecyclerView == scrollAmountRemaining -> {
               0
           }
           scrollAmountRemaining > 0 -> {
               ((trackLength - thumbLength) / (invisiblePartOfRecyclerView.toFloat() / highestVisiblePixel)).roundToInt()
           }
           else -> {
               trackLength - thumbLength
           }
       }
   }

   /**
    * thumb的长度,当前固定为thumbLength
    */
   override fun computeVerticalScrollExtent(): Int {
       return thumbLength
   }

部分源码可参考:github.com/yuanting201…