Files
BirdsProject/app/src/main/java/com/juhabi/birds/custom/CustomRefreshHeader.java
2026-09-19 08:41:53 +08:00

178 lines
5.7 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.juhabi.birds.custom;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.LinearInterpolator;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import com.juhabi.birds.R;
import com.scwang.smart.refresh.layout.api.RefreshHeader;
import com.scwang.smart.refresh.layout.api.RefreshKernel;
import com.scwang.smart.refresh.layout.api.RefreshLayout;
import com.scwang.smart.refresh.layout.constant.RefreshState;
import com.scwang.smart.refresh.layout.constant.SpinnerStyle;
/**
* @author fengxiaoyang
* @date 2025/7/11
* @description TODO: 描述该类的作用
*/
public class CustomRefreshHeader extends RelativeLayout implements RefreshHeader {
private ImageView ivRefresh;
private ObjectAnimator rotateAnimator;
// 用于 onMoving 中的动态效果
private float mLastPercent = 0f;
// 记录当前缩放值,避免重复设置
private float mCurrentScale = 1f;
public CustomRefreshHeader(Context context) {
super(context);
init(context);
}
public CustomRefreshHeader(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
private void init(Context context) {
LayoutInflater.from(context).inflate(R.layout.view_refresh_header, this);
ivRefresh = findViewById(R.id.ivRefresh);
// 开启硬件加速,提升动画性能
setLayerType(LAYER_TYPE_HARDWARE, null);
// 初始化 ObjectAnimator
rotateAnimator = ObjectAnimator.ofFloat(ivRefresh, "rotation", 0f, 360f);
rotateAnimator.setDuration(1500);
rotateAnimator.setRepeatCount(ValueAnimator.INFINITE);
rotateAnimator.setInterpolator(new LinearInterpolator());
}
@NonNull
@Override
public View getView() {
return this;
}
@SuppressLint("RestrictedApi")
@Override
public void onStartAnimator(@NonNull RefreshLayout layout, int height, int maxDragHeight) {
if (rotateAnimator != null && !rotateAnimator.isRunning()) {
rotateAnimator.start();
}
}
@SuppressLint("RestrictedApi")
@Override
public int onFinish(@NonNull RefreshLayout layout, boolean success) {
if (rotateAnimator != null && rotateAnimator.isRunning()) {
rotateAnimator.setInterpolator(new DecelerateInterpolator());
postDelayed(() -> {
if (rotateAnimator != null) {
rotateAnimator.cancel();
rotateAnimator.setInterpolator(new LinearInterpolator());
}
}, 300);
}
// 动画过渡回正常大小
animateToNormalScale();
return 500;
}
/**
* 平滑动画过渡回正常大小
*/
private void animateToNormalScale() {
if (ivRefresh == null) return;
float startScale = mCurrentScale;
ValueAnimator scaleAnimator = ValueAnimator.ofFloat(startScale, 1f);
scaleAnimator.setDuration(300);
scaleAnimator.setInterpolator(new DecelerateInterpolator());
scaleAnimator.addUpdateListener(animation -> {
float value = (float) animation.getAnimatedValue();
ivRefresh.setScaleX(value);
ivRefresh.setScaleY(value);
mCurrentScale = value;
});
scaleAnimator.start();
}
// --- 必须实现的方法 ---
@NonNull
@Override
public SpinnerStyle getSpinnerStyle() {
return SpinnerStyle.Translate; // Header 跟随内容滑动
}
@Override
public boolean isSupportHorizontalDrag() {
return false;
}
@SuppressLint("RestrictedApi")
@Override
public void setPrimaryColors(@ColorInt int... colors) {}
@SuppressLint("RestrictedApi")
@Override
public void onInitialized(@NonNull RefreshKernel kernel, int height, int maxDragHeight) {}
@SuppressLint("RestrictedApi")
@Override
public void onMoving(boolean isDragging, float percent, int offset, int height, int maxDragHeight) {
// 只在拖动过程中处理,避免过度计算
if (percent != mLastPercent) {
mLastPercent = percent;
// 1. 动态缩放效果:从 0.5 放大到 1.2
// 使用 clamp 限制范围:percent 0~1 -> scale 0.5~1.2
float targetScale = 0.5f + (0.7f * Math.min(percent, 1.0f));
if (Math.abs(targetScale - mCurrentScale) > 0.01f) {
mCurrentScale = targetScale;
ivRefresh.setScaleX(targetScale);
ivRefresh.setScaleY(targetScale);
}
// 2. 透明度渐变效果(可选)
// ivRefresh.setAlpha(Math.min(percent * 1.5f, 1.0f));
}
}
@SuppressLint("RestrictedApi")
@Override
public void onReleased(@NonNull RefreshLayout layout, int height, int maxDragHeight) {}
@SuppressLint("RestrictedApi")
@Override
public void onHorizontalDrag(float percentX, int offsetX, int offsetMax) {}
@Override
public boolean autoOpen(int duration, float dragRate, boolean animationOnly) {
return false;
}
@SuppressLint("RestrictedApi")
@Override
public void onStateChanged(@NonNull RefreshLayout refreshLayout, @NonNull RefreshState oldState, @NonNull RefreshState newState) {
// 状态切换时平滑恢复
if (newState == RefreshState.PullDownCanceled) {
// 用户取消下拉,恢复到初始状态
animateToNormalScale();
}
}
}