178 lines
5.7 KiB
Java
178 lines
5.7 KiB
Java
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();
|
||
}
|
||
}
|
||
} |