问题描述:

RecyclerView自带快速滚动无法控制滚动条的长度唯一,也就是说随着item的增多,滚动条的长度会越变越小。

解决问题:

通过自定义RecyclerView来实现滚动条的长度不会因为item的增多而发生长度变化。

具体实现:

package com.emsm.app.widget;import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.MotionEvent;import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;import com.emsm.app.util.LogHelps;/*** @Author emsm* @Time 2022/9/5 19:08* @Description 参考;RecyclerView中的initFastScroller实现* <p>* 专注美妆(香水口红护肤)批发代发-供淘宝/天猫/京东/微商/代购/闲鱼等* 主做欧美大牌:迪奥/阿玛尼/祖马龙/香奈儿/古驰/TF/MAC/圣罗兰等等* 只做高品质产品!(送朋友亲人客户公司活动以及自用或泡妞等等)* +V:em-smart-99999*/
public class FastScrollerRecyclerView extends RecyclerView {private FastScroller mFastScroller;public FastScrollerRecyclerView(Context context) {super(context);}public FastScrollerRecyclerView(Context context, AttributeSet attrs) {super(context, attrs);init(context, attrs);}public FastScrollerRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init(context, attrs);}private void init(Context context, AttributeSet attrs) {mFastScroller = new FastScroller(context, attrs);}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);int mMeasureWidth = MeasureSpec.getSize(widthMeasureSpec);int mMeasureHeight = MeasureSpec.getSize(heightMeasureSpec);setMeasuredDimension(mMeasureWidth, mMeasureHeight);LogHelps.w("setMeasuredDimension ");if (mFastScroller != null) {mFastScroller.onMeasure(mMeasureWidth, mMeasureHeight);}}private class FastScroller extends RecyclerView.ItemDecoration implements RecyclerView.OnItemTouchListener {private ScrollerDraw mScrollerDraw;private ScrollerEvent mScrollerEvent;FastScroller(Context context, AttributeSet attrs) {init(context, attrs);}private void init(Context context, AttributeSet attrs) {mScrollerDraw = new ScrollerDraw();mScrollerDraw.init(context, attrs);mScrollerDraw.setView(FastScrollerRecyclerView.this);if (isEnabled()) {addItemDecoration(this);addOnItemTouchListener(this);mScrollerEvent = new ScrollerEvent();mScrollerEvent.attachRecyclerView(FastScrollerRecyclerView.this, ratio -> {setRatio(ratio);});}}private void onMeasure(int measureWidth, int measureHeight) {if (isEnabled()) {mScrollerDraw.onMeasure(measureWidth, measureHeight, measureWidth);}}private boolean isEnabled() {return (mScrollerDraw != null && mScrollerDraw.isEnabled());}@Overridepublic void onDrawOver(@NonNull Canvas canvas, @NonNull RecyclerView parent, @NonNull State state) {super.onDrawOver(canvas, parent, state);try {if (!ScrollerEvent.isRecyclerScrollable(FastScrollerRecyclerView.this)) {LogHelps.w("item数量不足一屏 不支持展示滚动条");return;}if (!isEnabled()) {return;}if (!mScrollerDraw.isAlwaysShow()) {mScrollerDraw.mH.removeMessages(1);if (mScrollerDraw.getAnimatorWhat() == 1) {mScrollerDraw.mH.sendEmptyMessage(1);mScrollerDraw.setAnimatorWhat(2);}}mScrollerDraw.onDraw(canvas);if (!mScrollerDraw.isAlwaysShow()) {mScrollerDraw.mH.removeMessages(2);if (mScrollerDraw.getAnimatorWhat() == 2) {mScrollerDraw.mH.sendEmptyMessageDelayed(2, 1000);}}} catch (Exception e) {LogHelps.e("Exception-IllegalArgumentException :" + e.getMessage());}}@Overridepublic boolean onInterceptTouchEvent(@NonNull RecyclerView recyclerView, @NonNull MotionEvent ev) {// 判断是不是在滚动条范围之内 在就拦截item滚动boolean insideVerticalThumb = isPointInsideVerticalThumb(ev.getX(), ev.getY());if (ev.getAction() == MotionEvent.ACTION_DOWN && (insideVerticalThumb)) {return true;}return false;}private boolean isPointInsideVerticalThumb(float x, float y) {if (!isEnabled()) {return false;}// SeekbarRecyclerViewCopy.this.getPaddingRight() 获取的值相当于布局文件中 android:paddingRight="30dp"// LogHelps.i(SeekbarRecyclerViewCopy.this.getPaddingRight() + " |getPaddingRight " + SizeUtil.px2dip(mContext, 30));return x > mScrollerDraw.getThumbBitmapC() - FastScrollerRecyclerView.this.getPaddingRight();}@Overridepublic void onTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent event) {if (mScrollerEvent != null && isEnabled()) {mScrollerEvent.onTouchEvent(event, getHeight());}}@Overridepublic void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {}public void setRatio(float ratio) {if (isEnabled()) {mScrollerDraw.setAnimatorWhat(1);mScrollerDraw.setRatio(ratio);postInvalidate();}}}
}
package com.emsm.app.widget;import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.drawable.BitmapDrawable;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.AttributeSet;
import android.view.View;import androidx.annotation.NonNull;import com.emsm.app.R;
import com.emsm.app.util.BitmapUtils;
import com.emsm.app.util.LogHelps;/*** @Author chentao 0000668668* @Time 2023/2/10* @Description*  * 专注美妆(香水口红护肤)批发代发-供淘宝/天猫/京东/微商/代购/闲鱼等*  * 主做欧美大牌:迪奥/阿玛尼/祖马龙/香奈儿/古驰/TF/MAC/圣罗兰等等*  * 只做高品质产品!(送朋友亲人客户公司活动以及自用或泡妞等等)*  * +V:em-smart-99999*/
public class ScrollerDraw {private Paint mPaint;private Matrix matrix;private Bitmap mBgBitmap;private Bitmap mPgBitmap;private Bitmap mThumbBitmap;private boolean mEnabled = true;// true  说明 一直显示 false 说明 滑动显示不滑动隐藏private boolean mAlwaysShow = false;// 滚动的比例值 0-1private float mRatio = 0;private int mDrawHeight;private int mThumbBitmapW, mThumbBitmapH, mThumbBitmapC;private int mBgBitmapW, mBgBitmapH, mBgBitmapC;private int mPgBitmapW, mPgBitmapH, mPgBitmapC;private final ValueAnimator mShowHideAnimator = ValueAnimator.ofFloat(0, 1);public boolean isAlwaysShow() {return mAlwaysShow;}public boolean isEnabled() {return mEnabled;}public void setRatio(float ratio) {this.mRatio = ratio;}public void init(Context context, AttributeSet attrs) {TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.ScrollBar);mEnabled = array.getBoolean(R.styleable.ScrollBar_ScrollBarEnabled, true);if (!mEnabled) {LogHelps.w(" init-快速滚动设置为不可用!!!");array.recycle();return;}mAlwaysShow = array.getBoolean(R.styleable.ScrollBar_ScrollBarAlwaysShow, false);BitmapDrawable drawableBg = (BitmapDrawable) array.getDrawable(R.styleable.ScrollBar_ScrollBarBackground);if (drawableBg != null) {mBgBitmap = drawableBg.getBitmap();}BitmapDrawable drawablePr = (BitmapDrawable) array.getDrawable(R.styleable.ScrollBar_ScrollBarProgress);if (drawablePr != null) {mPgBitmap = drawablePr.getBitmap();}BitmapDrawable drawableTb = (BitmapDrawable) array.getDrawable(R.styleable.ScrollBar_ScrollBarThumb);if (drawableTb != null) {mThumbBitmap = drawableTb.getBitmap();}mRatio = array.getInt(R.styleable.ScrollBar_ScrollBarCurrentProgress, 0);array.recycle();mPaint = new Paint();mPaint.setAntiAlias(true);if (!mAlwaysShow) {mShowHideAnimator.addUpdateListener(new AnimatorUpdater());mH.sendEmptyMessage(1);}}private int mAnimatorWhat = 2;private static final int SCROLLBAR_FULL_OPAQUE = 255;private View mView;public void setView(View view) {this.mView = view;}public int getAnimatorWhat() {return mAnimatorWhat;}public void setAnimatorWhat(int mAnimatorWhat) {this.mAnimatorWhat = mAnimatorWhat;}public final Handler mH = new Handler(Looper.getMainLooper()) {@Overridepublic void handleMessage(@NonNull Message msg) {super.handleMessage(msg);switch (msg.what) {case 1:  // 显示LogHelps.i(" handleMessage show");mPaint.setAlpha(225);if (false) {mShowHideAnimator.setFloatValues((float) mShowHideAnimator.getAnimatedValue(), 1);mShowHideAnimator.setDuration(100);//  mShowHideAnimator.setStartDelay(0);mShowHideAnimator.start();}break;case 2: // 隐藏LogHelps.i(" handleMessage hide");if (mShowHideAnimator.isRunning()) {mShowHideAnimator.cancel();}mShowHideAnimator.setFloatValues((float) mShowHideAnimator.getAnimatedValue(), 0);mShowHideAnimator.setDuration(100);mShowHideAnimator.start();break;}}};private class AnimatorUpdater implements ValueAnimator.AnimatorUpdateListener {AnimatorUpdater() {}@Overridepublic void onAnimationUpdate(ValueAnimator valueAnimator) {int alpha = (int) (SCROLLBAR_FULL_OPAQUE * ((float) valueAnimator.getAnimatedValue()));// 隐藏之后把数字修改未其他的值 避免再次执行 显示和隐藏的逻辑mAnimatorWhat = 3;mPaint.setAlpha(alpha);if (mView != null) {mView.postInvalidate();}}}public void onMeasure(int measureWidth, int measureHeight,int left) {if (!mEnabled) {return;}if (mBgBitmap != null) {mBgBitmap = BitmapUtils.alterBitmapSize(mBgBitmap, mBgBitmap.getWidth(), measureHeight);}if (mPgBitmap != null) {mPgBitmap = BitmapUtils.alterBitmapSize(mPgBitmap, mPgBitmap.getWidth(), measureHeight);}mThumbBitmapW = mThumbBitmap.getWidth();mThumbBitmapH = mThumbBitmap.getHeight();mBgBitmapW = mBgBitmap.getWidth();mBgBitmapH = mBgBitmap.getHeight();mPgBitmapW = mPgBitmap.getWidth();mPgBitmapH = mPgBitmap.getHeight();if (measureHeight > mPgBitmapH) {measureHeight = mPgBitmapH;}this.mDrawHeight = measureHeight - mThumbBitmapH;mThumbBitmapC = left - mThumbBitmapW / 2;mBgBitmapC = left - mBgBitmapW / 2;mPgBitmapC = left - mPgBitmapW / 2;}public int getThumbBitmapC() {return mThumbBitmapC;}public void onDraw(Canvas canvas) {if (!mEnabled) {return;}try {float thumbBitmapTop = 0;if (mRatio > 0) {thumbBitmapTop = mRatio * mDrawHeight;}if (mThumbBitmap != null && mThumbBitmapW > 0 && mThumbBitmapH > 0) {canvas.drawBitmap(mThumbBitmap, mThumbBitmapC, thumbBitmapTop, mPaint);}if (mBgBitmap != null && mBgBitmapW > 0 && mBgBitmapH > 0) {canvas.drawBitmap(mBgBitmap, mBgBitmapC, thumbBitmapTop + mThumbBitmapH, mPaint);}if (mPgBitmap != null && mPgBitmapW > 0 && mPgBitmapH > 0 && mDrawHeight > 0 && mRatio > 0) {matrix = new Matrix();matrix.setScale(1, mRatio);Bitmap bitmap = Bitmap.createBitmap(mPgBitmap, 0, 0, mPgBitmapW, mDrawHeight, matrix, true);canvas.drawBitmap(bitmap, mPgBitmapC, 0, mPaint);}} catch (Exception e) {LogHelps.e("Exception-IllegalArgumentException :" + e.getMessage());}}}
package com.emsm.app.widget;import android.view.MotionEvent;import androidx.annotation.NonNull;
import androidx.core.view.ViewCompat;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;import com.emsm.app.util.LogHelps;/*** @Author chentao 0000668668* @Time 2023/2/10* @Description*  * 专注美妆(香水口红护肤)批发代发-供淘宝/天猫/京东/微商/代购/闲鱼等*  * 主做欧美大牌:迪奥/阿玛尼/祖马龙/香奈儿/古驰/TF/MAC/圣罗兰等等*  * 只做高品质产品!(送朋友亲人客户公司活动以及自用或泡妞等等)*  * +V:em-smart-99999*/
public class ScrollerEvent {private RecyclerView mRecyclerView;private float mInitialBarHeight;private float mLastPressedYAdjustedToInitial;private int mLastAppBarLayoutOffset;public void attachRecyclerView(RecyclerView recyclerView, CallBack call) {this.mRecyclerView = recyclerView;this.mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {@Overridepublic void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {super.onScrollStateChanged(recyclerView, newState);LogHelps.i("");}@Overridepublic void onScrolled(@NonNull RecyclerView parent, int dx, int dy) {super.onScrolled(parent, dx, dy);if (call == null) {return;}// 滚动条拇指的垂直范围float extent = parent.computeVerticalScrollExtent();//  可滚动的区域大小float range = parent.computeVerticalScrollRange();// 当前偏移量(当前滚动的距离)float offset = parent.computeVerticalScrollOffset();// 最大偏移量(最大可滚动的距离)float maxOffset = range - extent;// 可以滑动时,在绘制if (maxOffset > 0) {// float offsetY = ratio * mMeasureHeight;float ratio = offset / maxOffset;LogHelps.i("dx:" + dx +" dy:" + dy +" extent:" + extent +" range:" + range +" offset:" + offset +" maxOffset:" + maxOffset +" ratio:" + ratio);call.onScrolled(ratio);}}});}public boolean onTouchEvent(MotionEvent event, int viewHeight) {if (mRecyclerView == null || event == null) {return true;}if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {mRecyclerView.stopScroll();int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE;nestedScrollAxis |= ViewCompat.SCROLL_AXIS_VERTICAL;mRecyclerView.startNestedScroll(nestedScrollAxis);mInitialBarHeight = viewHeight;mLastPressedYAdjustedToInitial = event.getY() + 0;} else if (event.getActionMasked() == MotionEvent.ACTION_MOVE) {float newHandlePressedY = event.getY() + 0;int barHeight = viewHeight;float newHandlePressedYAdjustedToInitial = newHandlePressedY + (mInitialBarHeight - barHeight);float deltaPressedYFromLastAdjustedToInitial = newHandlePressedYAdjustedToInitial - mLastPressedYAdjustedToInitial;int dY = (int) ((deltaPressedYFromLastAdjustedToInitial / mInitialBarHeight) * (mRecyclerView.computeVerticalScrollRange() + 0));updateRvScroll(dY + mLastAppBarLayoutOffset);mLastPressedYAdjustedToInitial = newHandlePressedYAdjustedToInitial;} else if (event.getActionMasked() == MotionEvent.ACTION_UP) {mLastPressedYAdjustedToInitial = -1;mRecyclerView.stopNestedScroll();}return true;}public void updateRvScroll(int dY) {if (mRecyclerView == null) {return;}try {mRecyclerView.scrollBy(0, dY);} catch (Exception t) {t.printStackTrace();}}interface CallBack {// 滚动的比例值 0-1void onScrolled(float ratio);}/*** 判断是否可以滚动* @param recyclerView* @return*/public static boolean isRecyclerScrollable(RecyclerView recyclerView) {if (recyclerView == null) {return false;}float range = recyclerView.computeVerticalScrollRange();float height = recyclerView.getHeight();// LogHelps.i("recyclerView的滚动范围 " + range + " | RecyclerView的高度 " + height);// 滚动范围大于RecyclerView的高度 说明是可以滚动的if (true) {return range > height;}boolean h = false;if (recyclerView.getLayoutManager() instanceof LinearLayoutManager) {LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();RecyclerView.Adapter adapter = recyclerView.getAdapter();if (layoutManager == null || adapter == null) {h = false;} else {h = layoutManager.findLastCompletelyVisibleItemPosition() < adapter.getItemCount() - 1;}} else if (recyclerView.getLayoutManager() instanceof GridLayoutManager) {GridLayoutManager layoutManager = (GridLayoutManager) recyclerView.getLayoutManager();RecyclerView.Adapter adapter = recyclerView.getAdapter();if (layoutManager == null || adapter == null) {h = false;} else {h = layoutManager.findLastCompletelyVisibleItemPosition() < adapter.getItemCount() - 1;}} else if (recyclerView.getLayoutManager() instanceof StaggeredGridLayoutManager) {StaggeredGridLayoutManager layoutManager = (StaggeredGridLayoutManager) recyclerView.getLayoutManager();RecyclerView.Adapter adapter = recyclerView.getAdapter();if (layoutManager == null || adapter == null) {h = false;} else {h = layoutManager.findLastCompletelyVisibleItemPositions(null)[(layoutManager.getSpanCount() - 1)] < adapter.getItemCount() - 1;}}return h;}}
    <com.emsm.app.widget.FastScrollerRecyclerViewandroid:id="@+id/recycler"android:layout_width="0dp"android:layout_height="match_parent"android:layout_marginRight="100dp"android:background="@color/E3E3E3E3"android:paddingRight="30dp"app:ScrollBarAlwaysShow="true"app:ScrollBarBackground="@mipmap/ad3_bt_contacts_slider_n"app:ScrollBarBottom="20dp"app:ScrollBarCurrentProgress="0"app:ScrollBarEnabled="true"app:ScrollBarProgress="@mipmap/ad3_bt_contacts_slider_n"app:ScrollBarThumb="@mipmap/ad3_bt_contacts_slider_d"app:ScrollBarTop="20dp"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent" />

具体效果:

附带:使用到的图片

自定义RecyclerView支持快速滚动相关推荐

  1. Android Touch事件总结 二 (双指实现RecyclerView的快速滚动)

    说明:本博客为原创,转载请注明出处 CSDN-ANDROID笔记栈 由于作者水平有限,错误在所难免,请见谅,可以留言,本人会及时改正 索引 MotionEvent Demo 在 Android Tou ...

  2. qq模板图片asqq_重要更新 电脑编辑规则、快速滚动、富文本图片、规则模板等十多项更新!...

    新方圆小棉袄,传说中的贴心小棉袄,宇宙无敌超级厉害. 记住我们的付费群(大佬众多):978260150,免费群:1101359539 1.方圆和海阔的规则导入和支付宝口令类似,将口令全部复制打开最新版 ...

  3. Android RecyclerView 实现快速滑动

    前言: 使用RecyclerView时,调用smoothScrollToPostion()方法滑动到指定位置,但是条目很多时滑动的很慢,本篇文章就是实现RecyclerView的快速滑动. 先介绍如何 ...

  4. 自定义高效支持点击监听的RecyclerView

    自定义高效支持点击监听的RecyclerView 效果图 Demo 地址:GitHub 传统做法 在Adapter内部直接对View添加点击事件 因为这种方式虽然也可以解决点击监听问题,但是效率不高, ...

  5. 由旋转画廊,看自定义RecyclerView.LayoutManager

    一.简介 前段时间需要一个旋转木马效果用于展示图片,于是第一时间在github上找了一圈,找了一个还不错的控件,但是使用起来有点麻烦,始终觉得很不爽,所以寻思着自己做一个轮子.想起旋转画廊的效果不是和 ...

  6. recyclerview 软键盘_android-打开键盘时,Recyclerview不会滚动到结尾

    android-打开键盘时,Recyclerview不会滚动到结尾 我在应用程序中使用recylerview,并且每当将新元素添加到recyclerview时,它都会通过使用滚动到最后一个元素 rec ...

  7. Android支持横行滚动的ListView控件

    原帖地址:http://www.cnblogs.com/over140/archive/2011/12/07/2275207.html 声明 欢迎转载,但请保留文章原始出处:) 博客园:http:// ...

  8. android listview 横向滚动,Android支持水平滚动的ListView控件

    前言 ListView是一个纵向滚动的列表视图,也有朋友嵌套HorizontalScrollView来实现,比如这里,但在ListView的API中明确指明了两者不可同时使用.本文分享一种办法,以方便 ...

  9. Android图片控件,跟随列表(recyclerView)的上下滚动而同步平移。

    一个用于放置在RecycleView中的图片控件,其主要功能是跟随列表的上下滚动而上下平移,使得呈现出一种图像相对列表静止的感觉. Overview ScrollingImageView 提供以下特性 ...

最新文章

  1. 0、为什么推荐学习PowerShell?
  2. web.xml中filter,servlet和listener区别
  3. Linux下网络流量实时监控工具 大全
  4. C语言整数加法器,【菜鸟解析】C++大数加法器的实现
  5. 洛谷P2280 [HNOI2003]激光炸弹
  6. php ios 判断字符串长度,iOStextfield 限制输入字符长度和过滤表情符号
  7. 如何针对数据进行分析
  8. 视频教程-【孙伟】网页设计(切图)视频教程-UI
  9. 如何用C语言打印出ASCII码表
  10. PCL ——最小包围盒
  11. 央行降准对股市的影响利好还是利空?
  12. 1078: 字符串加密
  13. fast虚拟服务器ip地址设置,迅捷路由器静态ip怎么设置_迅捷固定IP地址怎么设置?-192路由网...
  14. cad绘制正八边形_软件CAD | 各种“线”工具
  15. 前端三刺客----HTML
  16. 深度解析 ORA-01555 原因及解决方法
  17. 中位数应用-货仓选址-纸牌均分-糖果传递-七夕祭
  18. 初级网络工程师这30道面试题一定得会,建议小白收藏!
  19. 百趣代谢组学文献分享:大麦盐胁迫响应机制的组学分析
  20. Faster R CNN

热门文章

  1. PHP Switch 语句之学习笔记
  2. HashMap keySet与entrySet遍历分析
  3. linux下目录挂接命令是,玩转 Linux 之:磁盘分区、挂载知多少?
  4. #双11故事联播#揭秘篇|白条团队双11备战实录
  5. 店宝宝:怎么给店铺带来自然流量
  6. 32位浮点数表示方法
  7. 太有才了!街头创意涂鸦手绘图片欣赏【上篇】
  8. RHCE(6)tuend\stratis\vdo总结和课堂案例
  9. kswapd0内存过高排查经历
  10. mysql命令(一)进入数据库