272 lines
9.5 KiB
Java
272 lines
9.5 KiB
Java
|
|
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;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|