直接上代码
/**
* 开发人员:ZHT
* 开发时间:2022/1/10
* 文档说明:自定义回弹ScrollView
*/
public class ReboundScrollView extends NestedScrollView {
private Rect mNormal = new Rect();//用于记录临界状态的左、上、右、下
private boolean mIsFinishAnimation = true;//是否动画结束
private float mLastY;//上一次y轴方向操作的坐标位置
private int mDownY;
private View mChildView;
private boolean mChildCanClick = true;
public ReboundScrollView(@NonNull Context context) {
super(context);
}
public ReboundScrollView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public ReboundScrollView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
/**
* @param childCanClick 是否拦截子View的Touch事件
*/
public void setChildCanClick(boolean childCanClick) {
mChildCanClick = childCanClick;
}
//获取子视图
@Override
protected void onFinishInflate() {
super.onFinishInflate();
if (getChildCount() > 0) {
mChildView = getChildAt(0);
}
}
//拦截:实现父视图对子视图的拦截
//是否拦截成功,取决于方法的返回值。返回值true:拦截成功。反之,拦截失败
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (!mChildCanClick) return true;
boolean isIntercept = false;
int eventY = (int) ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mLastY = mDownY = eventY;
break;
case MotionEvent.ACTION_MOVE:
//获取垂直方向的移动距离
int absY = Math.abs(eventY - mDownY);
if (absY >= 10) {
isIntercept = true;//执行拦截
}
mLastY = eventY;
break;
}
return isIntercept;
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (mChildView == null || !mIsFinishAnimation) {
return super.onTouchEvent(ev);
}
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mLastY = ev.getY();
break;
case MotionEvent.ACTION_MOVE:
int dy = (int) (ev.getY() - mLastY);//微小的移动量
if (isNeedMove(dy)) {
if (mNormal.isEmpty()) {
//记录了mChildView的临界状态的左、上、右、下
mNormal.set(mChildView.getLeft(), mChildView.getTop(), mChildView.getRight(), mChildView.getBottom());
}
//重新布局
mChildView.layout(mChildView.getLeft(), mChildView.getTop() + dy / 2, mChildView.getRight(), mChildView.getBottom() + dy / 2);
}
mLastY = ev.getY();//重新赋值
break;
case MotionEvent.ACTION_UP:
if (isNeedAnimation()) {
//使用平移动画
int translateY = mChildView.getBottom() - mNormal.bottom;
TranslateAnimation translateAnimation = new TranslateAnimation(0, 0, 0, -translateY);
translateAnimation.setDuration(200);
translateAnimation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
mIsFinishAnimation = false;
}
@Override
public void onAnimationEnd(Animation animation) {
mIsFinishAnimation = true;
mChildView.clearAnimation();//清除动画
//重新布局
mChildView.layout(mNormal.left, mNormal.top, mNormal.right, mNormal.bottom);
//清除normal的数据
mNormal.setEmpty();
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
//启动动画
mChildView.startAnimation(translateAnimation);
}
break;
}
return super.onTouchEvent(ev);
}
//判断是否需要执行平移动画
private boolean isNeedAnimation() {
return !mNormal.isEmpty();
}
//用户在y轴方向上的偏移量 (上 + 下 -)
private boolean isNeedMove(float scaleY) {
int childMeasuredHeight = mChildView.getMeasuredHeight();//获取子视图的高度
int scrollViewMeasuredHeight = this.getMeasuredHeight();//获取布局的高度
int dy = childMeasuredHeight - scrollViewMeasuredHeight;//dy >= 0
//按照我们自定义的ScrollView的方式处理,其他处在临界范围内的,返回false。即表示,仍按照ScrollView的方式处理
return scaleY <= 0 || scaleY >= dy;
}
}
整体父布局NestedScrollView
红色Banner
蓝色ReboundScrollView?
问题如下:
ReboundScrollView?和Banner重叠部分是RecyclerView,此区域点击不会触发拦截,从而不会下拉回弹
setChildCanClick为true。拦截后一切正常
?
?
|