package com.xscm.moduleutil.widget; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearSmoothScroller; import androidx.recyclerview.widget.RecyclerView; /** * @Author lxj$ * @Time $ 2025-9-4 21:04:34$ * @Description 自定义滚动辅助类$ */ public class CenterScrollHelper { private RecyclerView recyclerView; public CenterScrollHelper(RecyclerView recyclerView) { this.recyclerView = recyclerView; } /** * 循环滚动指定圈数后停在目标位置 * @param targetPosition 目标位置 * @param circles 滚动圈数 * @param durationPerItem 每个item滚动的持续时间(控制速度) * @param adapterSize 适配器总大小 */ public void scrollWithCircles(int targetPosition, int circles, int durationPerItem, int adapterSize) { scrollWithCircles(targetPosition, circles, durationPerItem, adapterSize, null); } /** * 循环滚动指定圈数后停在目标位置(带回调) * @param targetPosition 目标位置 * @param circles 滚动圈数 * @param durationPerItem 每个item滚动的持续时间(控制速度) * @param adapterSize 适配器总大小 * @param onComplete 滚动完成回调 */ public void scrollWithCircles(int targetPosition, int circles, int durationPerItem, int adapterSize, Runnable onComplete) { if (recyclerView.getLayoutManager() == null) { if (onComplete != null) onComplete.run(); return; } // 计算总滚动位置(多圈+目标位置) int totalItems = circles * adapterSize + targetPosition; // 使用LinearSmoothScroller进行平滑滚动 LinearSmoothScroller smoothScroller = new LinearSmoothScroller(recyclerView.getContext()) { private static final float ACCELERATION = 0.5f; // 加速度 private static final float DECELERATION = 0.8f; // 减速度 @Override protected int calculateTimeForScrolling(int dx) { // 使用缓动函数计算时间,实现从慢到快再到慢的效果 // return calculateEasingTime(dx, durationPerItem); // // 简单线性时间计算,确保滚动速度一致 // int screenWidth = recyclerView.getWidth(); // if (screenWidth <= 0) { // return durationPerItem * 3; // } // int itemWidth = screenWidth / 3; // int items = Math.max(1, dx / itemWidth); // return durationPerItem * items; // 使用缓动函数计算时间,实现从慢到快再到慢的效果 return calculateEasingTime(dx, durationPerItem); } @Override protected void onStop() { super.onStop(); // 滚动停止后确保目标位置居中 // scrollToCenter(targetPosition); // 执行完成回调 if (onComplete != null) { recyclerView.post(onComplete); } } }; smoothScroller.setTargetPosition(totalItems); recyclerView.getLayoutManager().startSmoothScroll(smoothScroller); } // 在 CenterScrollHelper 类中添加 public void reset() { // 清理可能存在的回调或状态 if (recyclerView != null) { recyclerView.stopScroll(); recyclerView.getHandler().removeCallbacksAndMessages(null); } } /** * 使用缓动函数计算滚动时间,实现加速减速效果 * @param dx 滚动距离 * @param baseDurationPerItem 基础时间 * @return 计算后的时间 */ private int calculateEasingTime(int dx, int baseDurationPerItem) { if (dx <= 0) return 0; // 获取屏幕宽度作为参考 int screenWidth = recyclerView.getWidth(); if (screenWidth <= 0) { return baseDurationPerItem * 3; // 默认值 } // 计算item宽度(假设每屏3个item) int itemWidth = screenWidth / 3; // 计算滚动的item数量 int items = Math.max(1, dx / itemWidth); // 使用easeInOutQuad缓动函数实现先慢中快后慢的效果 double progress = Math.min(1.0, (double) items / 100); // 假设100个item为完整过程 // easeInOutQuad缓动函数 double easeProgress; if (progress < 0.5) { easeProgress = 2 * progress * progress; // 先慢后快 } else { easeProgress = 1 - Math.pow(-2 * progress + 2, 2) / 2; // 后慢 } // 计算时间:开始慢(500ms),后来快(50ms) int minDuration = 1000; // 最快速度 int maxDuration = 2000; // 最慢速度 int calculatedTime = (int) (maxDuration - (maxDuration - minDuration) * easeProgress); return Math.max(minDuration, calculatedTime); } /** * 将指定位置的item精确居中显示 * @param position 需要居中的位置(在循环列表中的实际位置) * @param originalSize 原始数据大小 */ public void scrollToCenter(int position, int originalSize) { if (recyclerView.getLayoutManager() == null) return; int screenWidth = recyclerView.getWidth(); if (screenWidth <= 0) return; // 计算item宽度(假设每个item等宽) int itemWidth = screenWidth / 3; // 每屏显示3个item // 计算使item居中需要滚动的总距离 int targetScrollX = position * itemWidth - (screenWidth - itemWidth) / 2; // 获取当前滚动位置 int currentScrollX = recyclerView.computeHorizontalScrollOffset(); // 计算需要滚动的距离 int scrollDistance = targetScrollX - currentScrollX; // 执行滚动 recyclerView.smoothScrollBy(scrollDistance, 0); } /** * 将指定位置的item精确居中显示(简化版) * @param position 需要居中的位置 */ public void scrollToCenter(int position) { if (recyclerView.getLayoutManager() == null) return; int screenWidth = recyclerView.getWidth(); if (screenWidth <= 0) return; // 计算item宽度 int itemWidth = screenWidth / 3; // 计算目标位置的左边缘 int targetLeft = position * itemWidth; // 计算使item居中需要滚动到的位置 int targetScrollX = targetLeft - (screenWidth - itemWidth) / 2; // 获取当前滚动位置 int currentScrollX = recyclerView.computeHorizontalScrollOffset(); // 计算需要滚动的距离 int scrollDistance = targetScrollX - currentScrollX; // 如果距离很小,就不需要滚动了 if (Math.abs(scrollDistance) > 5) { // 执行滚动 recyclerView.smoothScrollBy(scrollDistance, 0); } } /** * 将指定位置的item居中显示(支持循环) */ public void centerItem(int targetPosition, int adapterSize) { if (recyclerView.getLayoutManager() == null) return; LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager(); int screenWidth = recyclerView.getWidth(); if (screenWidth <= 0) return; int itemWidth = screenWidth / 3; // 每个item占屏幕1/3 // 计算需要滚动的距离使item居中 // 这里需要根据实际的item宽度和屏幕宽度来计算居中位置 int targetScrollX = targetPosition * itemWidth - (screenWidth - itemWidth) / 2; int currentScrollX = recyclerView.computeHorizontalScrollOffset(); int scrollDistance = targetScrollX - currentScrollX; // 使用smoothScrollBy确保平滑滚动到居中位置 recyclerView.smoothScrollBy(scrollDistance, 0); } /** * 使用更复杂的缓动函数计算滚动时间 * @param dx 滚动距离 * @param baseDurationPerItem 基础时间 * @return 计算后的时间 */ private int calculateAdvancedEasingTime(int dx, int baseDurationPerItem) { if (dx <= 0) return 0; int screenWidth = recyclerView.getWidth(); if (screenWidth <= 0) { return baseDurationPerItem * 3; } int itemWidth = screenWidth / 3; int totalItems = dx / itemWidth; // 分段处理:开始慢,中间快,结束慢 int totalTime = 0; for (int i = 0; i < totalItems; i++) { // 计算当前位置的进度 (0-1) double progress = (double) i / Math.max(1, totalItems); // 使用贝塞尔缓动函数:慢-快-慢 double easedProgress = bezierEasing(progress, 0.25, 0.1, 0.25, 1.0); // 根据进度调整时间 int minTime = 30; // 最快时间 int maxTime = baseDurationPerItem * 2; // 最慢时间 int itemTime = (int) (maxTime - (maxTime - minTime) * easedProgress); totalTime += itemTime; } return Math.max(100, totalTime); // 至少100ms } /** * 贝塞尔缓动函数 * @param t 时间进度 (0-1) * @param x1 控制点1 x * @param y1 控制点1 y * @param x2 控制点2 x * @param y2 控制点2 y * @return 缓动后的值 */ private double bezierEasing(double t, double x1, double y1, double x2, double y2) { // 简化的贝塞尔曲线计算 // 这里使用一个近似算法 double a = 1 - t; return 3 * a * a * t * y1 + 3 * a * t * t * y2 + t * t * t; } }