Files
yusheng-android/BaseModule/src/main/java/com/xscm/moduleutil/widget/CenterScrollHelper.java
2025-11-07 09:22:39 +08:00

272 lines
9.5 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
}
}