|
|
|
|
@ -1,5 +1,6 @@
|
|
|
|
|
//定义类所在的包路径,表示该文件属于com.monke.monkeybook.widget包
|
|
|
|
|
package com.monke.monkeybook.widget;
|
|
|
|
|
|
|
|
|
|
// 导入必要的类
|
|
|
|
|
import android.animation.Animator;
|
|
|
|
|
import android.animation.ObjectAnimator;
|
|
|
|
|
import android.annotation.TargetApi;
|
|
|
|
|
@ -24,215 +25,246 @@ import com.monke.monkeybook.R;
|
|
|
|
|
import com.monke.monkeybook.utils.DensityUtil;
|
|
|
|
|
|
|
|
|
|
public class RecyclerViewBar extends LinearLayout {
|
|
|
|
|
// 定义滑动动画的时间常量
|
|
|
|
|
public static long SLIDE_ANIM_TIME = 800;
|
|
|
|
|
|
|
|
|
|
// 定义滑块的 ImageView
|
|
|
|
|
private ImageView ivSlider;
|
|
|
|
|
// 定义滑块的高度,默认为 35dp
|
|
|
|
|
private int sliderHeight = DensityUtil.dp2px(getContext(), 35f);
|
|
|
|
|
|
|
|
|
|
// 定义 RecyclerView
|
|
|
|
|
private RecyclerView recyclerView;
|
|
|
|
|
|
|
|
|
|
// 定义滑块显示和隐藏的动画对象
|
|
|
|
|
private Animator slideIn;
|
|
|
|
|
private Animator slideOut;
|
|
|
|
|
|
|
|
|
|
// 构造方法,初始化
|
|
|
|
|
public RecyclerViewBar(Context context) {
|
|
|
|
|
this(context, null);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 带有属性集的构造方法
|
|
|
|
|
public RecyclerViewBar(Context context, @Nullable AttributeSet attrs) {
|
|
|
|
|
this(context, attrs, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 带有样式属性的构造方法
|
|
|
|
|
public RecyclerViewBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
|
|
|
|
super(context, attrs, defStyleAttr);
|
|
|
|
|
init(attrs);
|
|
|
|
|
init(attrs);// 调用初始化方法
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 针对 Lollipop 及以上版本的构造方法
|
|
|
|
|
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
|
|
|
|
public RecyclerViewBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
|
|
|
|
super(context, attrs, defStyleAttr, defStyleRes);
|
|
|
|
|
init(attrs);
|
|
|
|
|
init(attrs);// 调用初始化方法
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 初始化方法
|
|
|
|
|
private void init(AttributeSet attrs) {
|
|
|
|
|
setOrientation(VERTICAL);
|
|
|
|
|
setOrientation(VERTICAL);// 设置垂直布局
|
|
|
|
|
// 获取自定义属性
|
|
|
|
|
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.RecyclerViewBar);
|
|
|
|
|
// 设置滑块高度
|
|
|
|
|
sliderHeight = a.getDimensionPixelSize(R.styleable.RecyclerViewBar_slider_height, sliderHeight);
|
|
|
|
|
// 设置滑块的左右内边距
|
|
|
|
|
int paddingLeft = a.getDimensionPixelSize(R.styleable.RecyclerViewBar_slider_paddingLeft, 0);
|
|
|
|
|
int paddingRight = a.getDimensionPixelSize(R.styleable.RecyclerViewBar_slider_paddingRight, 0);
|
|
|
|
|
// 创建滑块 ImageView
|
|
|
|
|
ivSlider = new ImageView(getContext());
|
|
|
|
|
ivSlider.setPadding(paddingLeft, 0, paddingRight, 0);
|
|
|
|
|
ivSlider.setAlpha(0f);
|
|
|
|
|
ivSlider.setClickable(true);
|
|
|
|
|
addView(ivSlider);
|
|
|
|
|
ivSlider.setPadding(paddingLeft, 0, paddingRight, 0);// 设置滑块内边距
|
|
|
|
|
ivSlider.setAlpha(0f);// 初始透明度为 0
|
|
|
|
|
ivSlider.setClickable(true);// 设置为可点击
|
|
|
|
|
addView(ivSlider);// 将 ImageView 添加到布局中
|
|
|
|
|
// 设置滑块的布局参数
|
|
|
|
|
LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, sliderHeight);
|
|
|
|
|
ivSlider.setLayoutParams(layoutParams);
|
|
|
|
|
ivSlider.setImageResource(R.drawable.icon_slider);
|
|
|
|
|
ivSlider.setScaleType(ImageView.ScaleType.FIT_XY);
|
|
|
|
|
ivSlider.setLayoutParams(layoutParams);// 应用布局参数
|
|
|
|
|
ivSlider.setImageResource(R.drawable.icon_slider);// 设置滑块图标
|
|
|
|
|
ivSlider.setScaleType(ImageView.ScaleType.FIT_XY);// 设置图像缩放模式
|
|
|
|
|
|
|
|
|
|
initIvSlider();
|
|
|
|
|
initIvSlider();// 初始化滑块的触摸事件
|
|
|
|
|
|
|
|
|
|
// 添加全局布局监听
|
|
|
|
|
RecyclerViewBar.this.getViewTreeObserver().addOnGlobalLayoutListener(layoutInitListener);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 用于记录触摸的 final Y 坐标
|
|
|
|
|
private float finalY = -10000;
|
|
|
|
|
|
|
|
|
|
// 初始化滑块的触摸事件监听
|
|
|
|
|
private void initIvSlider() {
|
|
|
|
|
// 设置触摸事件监听器
|
|
|
|
|
ivSlider.setOnTouchListener(new OnTouchListener() {
|
|
|
|
|
@Override
|
|
|
|
|
public boolean onTouch(View v, MotionEvent event) {
|
|
|
|
|
int action = event.getAction();
|
|
|
|
|
int action = event.getAction();// 获取触摸事件类型
|
|
|
|
|
switch (action) {
|
|
|
|
|
case MotionEvent.ACTION_DOWN:
|
|
|
|
|
finalY = event.getY();
|
|
|
|
|
return true;
|
|
|
|
|
finalY = event.getY();// 记录初始 Y 坐标
|
|
|
|
|
return true;// 消耗事件
|
|
|
|
|
case MotionEvent.ACTION_MOVE:
|
|
|
|
|
if (finalY >= 0) {
|
|
|
|
|
float tempY = event.getY();
|
|
|
|
|
float durY = tempY - finalY;
|
|
|
|
|
updateSlider(durY);
|
|
|
|
|
if (finalY >= 0) {// 检查是否为合法的 Y 坐标
|
|
|
|
|
float tempY = event.getY();// 获取当前 Y 坐标
|
|
|
|
|
float durY = tempY - finalY;// 计算移动的距离
|
|
|
|
|
updateSlider(durY);// 更新滑块位置
|
|
|
|
|
|
|
|
|
|
showSlide();
|
|
|
|
|
showSlide(); // 显示滑块
|
|
|
|
|
} else {
|
|
|
|
|
finalY = event.getY();
|
|
|
|
|
finalY = event.getY();// 更新 finalY
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
return true;// 消耗事件
|
|
|
|
|
case MotionEvent.ACTION_UP:
|
|
|
|
|
if (finalY >= 0) {
|
|
|
|
|
finalY = -10000;
|
|
|
|
|
timeCountDown.cancel();
|
|
|
|
|
timeCountDown.start();
|
|
|
|
|
return true;
|
|
|
|
|
if (finalY >= 0) {// 检查是否为合法的 Y 坐标
|
|
|
|
|
finalY = -10000;// 重置 finalY
|
|
|
|
|
timeCountDown.cancel();// 取消计时器
|
|
|
|
|
timeCountDown.start();// 启动计时器
|
|
|
|
|
return true;// 消耗事件
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
if (finalY >= 0) {
|
|
|
|
|
finalY = -10000;
|
|
|
|
|
return true;
|
|
|
|
|
finalY = -10000;// 重置 finalY
|
|
|
|
|
return true;// 消耗事件
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
return false;// 未消费事件
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 更新滑块位置
|
|
|
|
|
private void updateSlider(float durY) {
|
|
|
|
|
LayoutParams l = (LayoutParams) ivSlider.getLayoutParams();
|
|
|
|
|
float finalMarginTop = l.topMargin + durY;
|
|
|
|
|
LayoutParams l = (LayoutParams) ivSlider.getLayoutParams();// 获取滑块的布局参数
|
|
|
|
|
float finalMarginTop = l.topMargin + durY;// 计算新的顶部边距
|
|
|
|
|
// 限制滑块的位置
|
|
|
|
|
if (finalMarginTop < 0) {
|
|
|
|
|
finalMarginTop = 0;
|
|
|
|
|
} else if (finalMarginTop > getHeight() - sliderHeight) {
|
|
|
|
|
finalMarginTop = getHeight() - sliderHeight;
|
|
|
|
|
}
|
|
|
|
|
// 如果 RecyclerView 不为空
|
|
|
|
|
if (recyclerView != null) {
|
|
|
|
|
// 计算 RecyclerView 的滚动位置
|
|
|
|
|
int position = Math.round(finalMarginTop / (getHeight() - sliderHeight) * (recyclerView.getAdapter().getItemCount() - 1));
|
|
|
|
|
((LinearLayoutManager) recyclerView.getLayoutManager()).scrollToPositionWithOffset(position, 0);
|
|
|
|
|
((LinearLayoutManager) recyclerView.getLayoutManager()).scrollToPositionWithOffset(position, 0);// 滚动到指定位置
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
l.topMargin = Math.round(finalMarginTop);
|
|
|
|
|
ivSlider.setLayoutParams(l);
|
|
|
|
|
l.topMargin = Math.round(finalMarginTop);// 更新滑块的顶部边距
|
|
|
|
|
ivSlider.setLayoutParams(l);// 应用新的布局参数
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 设置 RecyclerView
|
|
|
|
|
public void setRecyclerView(RecyclerView recyclerView) {
|
|
|
|
|
this.recyclerView = recyclerView;
|
|
|
|
|
this.recyclerView = recyclerView;// 保存 RecyclerView 的引用
|
|
|
|
|
if (this.recyclerView != null) {
|
|
|
|
|
// 添加滚动监听
|
|
|
|
|
this.recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
|
|
|
|
@Override
|
|
|
|
|
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
|
|
|
|
|
// 如果滚动状态不为停止,则显示滑块
|
|
|
|
|
super.onScrollStateChanged(recyclerView, newState);
|
|
|
|
|
if (newState != 0) {
|
|
|
|
|
showSlide();
|
|
|
|
|
} else {
|
|
|
|
|
timeCountDown.cancel();
|
|
|
|
|
timeCountDown.start();
|
|
|
|
|
timeCountDown.cancel();// 停止计时器
|
|
|
|
|
timeCountDown.start();// 启动计时器
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
|
|
|
|
|
super.onScrolled(recyclerView, dx, dy);
|
|
|
|
|
// 将滑块滚动到当前可见项目的位置
|
|
|
|
|
scrollToPositionWithOffset(((LinearLayoutManager) recyclerView.getLayoutManager()).findFirstVisibleItemPosition());
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 根据位置滚动并更新滑块位置
|
|
|
|
|
public void scrollToPositionWithOffset(int position) {
|
|
|
|
|
// 如果 RecyclerView 存在且位置合法
|
|
|
|
|
if (recyclerView != null && position < recyclerView.getAdapter().getItemCount()) {
|
|
|
|
|
float temp = position * 1.0f / recyclerView.getAdapter().getItemCount();
|
|
|
|
|
LayoutParams l = (LayoutParams) ivSlider.getLayoutParams();
|
|
|
|
|
l.topMargin = Math.round(((getHeight() - sliderHeight) * temp));
|
|
|
|
|
ivSlider.setLayoutParams(l);
|
|
|
|
|
LayoutParams l = (LayoutParams) ivSlider.getLayoutParams();// 获取滑块的布局参数
|
|
|
|
|
l.topMargin = Math.round(((getHeight() - sliderHeight) * temp));// 计算新的顶部边距
|
|
|
|
|
ivSlider.setLayoutParams(l);// 应用新的布局参数
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 显示滑块
|
|
|
|
|
private void showSlide() {
|
|
|
|
|
if (ivSlider.getAlpha() < 1) {
|
|
|
|
|
if (ivSlider.getAlpha() < 1) {// 如果透明度小于1
|
|
|
|
|
if (slideOut != null && slideOut.isRunning()) {
|
|
|
|
|
slideOut.cancel();
|
|
|
|
|
slideOut.cancel();// 如果滑出动画正在执行,取消它
|
|
|
|
|
}
|
|
|
|
|
if (slideIn == null) {
|
|
|
|
|
slideIn = ObjectAnimator.ofFloat(ivSlider, "alpha", ivSlider.getAlpha(), 1f);
|
|
|
|
|
slideIn.setDuration((long) (SLIDE_ANIM_TIME * (1f - ivSlider.getAlpha())));
|
|
|
|
|
slideIn = ObjectAnimator.ofFloat(ivSlider, "alpha", ivSlider.getAlpha(), 1f);// 创建滑入动画
|
|
|
|
|
slideIn.setDuration((long) (SLIDE_ANIM_TIME * (1f - ivSlider.getAlpha())));// 设置动画持续时间
|
|
|
|
|
}
|
|
|
|
|
if (!slideIn.isRunning()) {
|
|
|
|
|
slideIn.start();
|
|
|
|
|
slideIn.start();// 启动滑入动画
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 隐藏滑块
|
|
|
|
|
private void hideSlide() {
|
|
|
|
|
if (ivSlider.getAlpha() > 0) {
|
|
|
|
|
if (ivSlider.getAlpha() > 0) {// 如果透明度大于0
|
|
|
|
|
if (slideIn != null && slideIn.isRunning()) {
|
|
|
|
|
slideIn.cancel();
|
|
|
|
|
slideIn.cancel();// 如果滑入动画正在执行,取消它
|
|
|
|
|
}
|
|
|
|
|
if (slideOut == null) {
|
|
|
|
|
slideOut = ObjectAnimator.ofFloat(ivSlider, "alpha", ivSlider.getAlpha(), 0f);
|
|
|
|
|
slideOut.setDuration((long) (SLIDE_ANIM_TIME * ivSlider.getAlpha()));
|
|
|
|
|
slideOut = ObjectAnimator.ofFloat(ivSlider, "alpha", ivSlider.getAlpha(), 0f);// 创建滑出动画
|
|
|
|
|
slideOut.setDuration((long) (SLIDE_ANIM_TIME * ivSlider.getAlpha()));// 设置动画持续时间
|
|
|
|
|
}
|
|
|
|
|
if (!slideOut.isRunning()) {
|
|
|
|
|
slideOut.start();
|
|
|
|
|
slideOut.start();// 启动滑出动画
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 创建计时器,用于滑块的自动隐藏
|
|
|
|
|
private TimeCountDown timeCountDown = new TimeCountDown();
|
|
|
|
|
|
|
|
|
|
// 定义倒计时类
|
|
|
|
|
class TimeCountDown extends CountDownTimer {
|
|
|
|
|
|
|
|
|
|
// 构造方法,设置计时器
|
|
|
|
|
public TimeCountDown() {
|
|
|
|
|
this(1000, 1000);
|
|
|
|
|
this(1000, 1000);// 1000 毫秒后完成,间隔 1000 毫秒
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public TimeCountDown(long millisInFuture, long countDownInterval) {
|
|
|
|
|
super(millisInFuture, countDownInterval);
|
|
|
|
|
super(millisInFuture, countDownInterval);// 调用父类构造方法
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void onTick(long millisUntilFinished) {
|
|
|
|
|
|
|
|
|
|
// 每次计时器滴答时调用(可选实现,暂时为空)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void onFinish() {
|
|
|
|
|
hideSlide();
|
|
|
|
|
hideSlide();// 计时结束时隐藏滑块
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 用于存储视图的高度
|
|
|
|
|
private int height = 0;
|
|
|
|
|
// 全局布局监听器
|
|
|
|
|
private ViewTreeObserver.OnGlobalLayoutListener layoutInitListener = new ViewTreeObserver.OnGlobalLayoutListener() {
|
|
|
|
|
@Override
|
|
|
|
|
public void onGlobalLayout() {
|
|
|
|
|
if(getHeight()>0){
|
|
|
|
|
if(getHeight()>0){// 确保当前高度大于0
|
|
|
|
|
if (height == 0) {
|
|
|
|
|
height = getHeight();
|
|
|
|
|
height = getHeight();// 保存初始高度
|
|
|
|
|
} else {
|
|
|
|
|
int diff = height - getHeight();
|
|
|
|
|
int diff = height - getHeight();// 计算高度差
|
|
|
|
|
if (diff != 0) {
|
|
|
|
|
LayoutParams l = (LayoutParams) ivSlider.getLayoutParams();
|
|
|
|
|
LayoutParams l = (LayoutParams) ivSlider.getLayoutParams();// 获取滑块的布局参数
|
|
|
|
|
l.topMargin = (int) ((l.topMargin*1.0f/(height-sliderHeight))*(getHeight()-sliderHeight));
|
|
|
|
|
ivSlider.setLayoutParams(l);
|
|
|
|
|
height = getHeight();
|
|
|
|
|
ivSlider.setLayoutParams(l);// 更新滑块的顶部边距
|
|
|
|
|
height = getHeight();// 更新高度
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|