import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import androidx.annotation.NonNull;
import com.google.android.material.appbar.AppBarLayout;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.view.NestedScrollingChild;
import androidx.core.view.ViewCompat;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.widget.OverScroller;
import com.eastmoney.android.util.log.EMLog;
import java.lang.reflect.Field;
public class AppBarFlingBehavior extends AppBarLayout.Behavior {
private static final String TAG = "AppBarFlingBehavior";
private View mNestedScrollingChild;
private OverScroller mScroller;
//处理AppBarLayout中含有水平滑动控件
private boolean mIsOnHorizontalMoving;
private final int mTouchSlop;
private float startY;
private float startX;
private final Handler mHandler = new Handler(Looper.getMainLooper());
private View mTargetView;
public AppBarFlingBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
injectMyScroller(context);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
initDragCallback();
}
/**
* 处理某些情况下在AppBarLayout区域无法上下滑动,在viewpager区域才能滑动的问题
*/
private void initDragCallback() {
setDragCallback(new DragCallback() {
@Override
public boolean canDrag(@NonNull AppBarLayout appBarLayout) {
if (mNestedScrollingChild != null) {
//如果标签没有滑动到顶部,可以被拖动
if (Math.abs(getTopAndBottomOffset()) < appBarLayout.getTotalScrollRange()) {
return true;
}
//到顶部的话,看recyclerView是否可以向上滑动
return !ifChildCanScrollUp();
} else {
return true;
}
}
});
}
/**
* 自定义Scroller处理AppBarLayout在滑动过程中触碰,不自动停止的问题
*/
private void injectMyScroller(Context context) {
mScroller = new OverScroller(context);
//获取HeaderBehavior class
Class<?> clzHeaderBehavior = getTargetSuperClass(getClass(), "HeaderBehavior");
if (clzHeaderBehavior == null) return;
try {
injectField(clzHeaderBehavior, "scroller");
} catch (Exception e) {
e.printStackTrace();
try {
injectField(clzHeaderBehavior, "mScroller");
} catch (Exception e1) {
e1.printStackTrace();
}
}
}
@Override
public boolean onInterceptTouchEvent(CoordinatorLayout parent, AppBarLayout child, MotionEvent ev) {
switch (ev.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
// 记录手指按下的位置
startY = ev.getY();
startX = ev.getX();
log("onInterceptTouchEvent MotionEvent.ACTION_DOWN");
//手指按下后,如果mScroller仍在滑动,强制停止
if (mScroller.computeScrollOffset()) {
mScroller.forceFinished(true);
}
//从mNestedScrollingChild向上Fling过程中,新的Down事件发生在AppBarLayout区域,需要停止嵌套滚动
if (mNestedScrollingChild != null && parent.isPointInChildBounds(child, (int) startX, (int) startY)) {
ViewCompat.stopNestedScroll(mNestedScrollingChild, ViewCompat.TYPE_NON_TOUCH);
}
// 初始化标记
mIsOnHorizontalMoving = false;
break;
case MotionEvent.ACTION_MOVE:
log("onInterceptTouchEvent MotionEvent.ACTION_MOVE");
// 如果viewpager正在拖拽中,那么不拦截它的事件,直接return false;
if (mIsOnHorizontalMoving) {
return false;
}
// 获取当前手指位置
float endY = ev.getY();
float endX = ev.getX();
float distanceX = Math.abs(endX - startX);
float distanceY = Math.abs(endY - startY);
// 如果X轴位移大于Y轴位移,不拦截事件
if (distanceX > mTouchSlop && distanceX > distanceY) {
mIsOnHorizontalMoving = true;
return false;
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
log("onInterceptTouchEvent MotionEvent.ACTION_CANCEL/ACTION_UP");
// 初始化标记
mIsOnHorizontalMoving = false;
break;
}
boolean ret = super.onInterceptTouchEvent(parent, child, ev);
log("ev.getAction():" + ev.getAction() + " result:" + ret);
return ret;
}
@Override
public boolean onTouchEvent(CoordinatorLayout parent, AppBarLayout child, MotionEvent ev) {
switch (ev.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_MOVE:
log("onTouchEvent MotionEvent.ACTION_MOVE");
if (mOnAppBarMoveListener != null) {
mOnAppBarMoveListener.onAppBarMove(startX, startY, ev.getX(), ev.getY());
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
log("onTouchEvent MotionEvent.ACTION_CANCEL/ACTION_UP");
if (mOnAppBarMoveCancelListener != null) {
mOnAppBarMoveCancelListener.onAppBarMoveCancel(ev.getX(), ev.getY());
}
break;
}
return super.onTouchEvent(parent, child, ev);
}
@Override
public boolean onNestedFling(@NonNull CoordinatorLayout coordinatorLayout, @NonNull AppBarLayout child
, @NonNull View target, float velocityX, float velocityY, boolean consumed) {
log("onNestedFling:target" + target.getClass().getSimpleName() + " velocityY:" + velocityY + " consumed:" + consumed);
return super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, consumed);
}
@Override
public boolean onNestedPreFling(@NonNull CoordinatorLayout coordinatorLayout, @NonNull AppBarLayout child
, @NonNull View target, float velocityX, float velocityY) {
mNestedScrollingChild = target;
log("onNestedPreFling:target" + target.getClass().getSimpleName() + " velocityY:" + velocityY);
return super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY);
}
@Override
public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child, View directTargetChild
, View target, int nestedScrollAxes, int type) {
log("onStartedNestedScroll");
mTargetView = target;
if (mOnStartNestedScrollListener != null) {
mOnStartNestedScrollListener.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes, type);
}
return super.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes, type);
}
@Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target
, int dx, int dy, int[] consumed, int type) {
mNestedScrollingChild = target;
log("onNestedPreScroll:target " + target.getClass().getSimpleName() + " consumedX:" + consumed[0] + " consumedY:" + consumed[1]);
log("onNestedPreScroll: dx " + dx + " dy " + dy + " type" + type);
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type);
stopNestedScrollIfNeeded(dy, child, target, type);
}
@Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target
, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) {
log("onNestedScroll:target" + target.getClass().getSimpleName() + " dyConsumed:" + dyConsumed + " dyUnconsumed:" + dyUnconsumed);
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type);
}
@Override
public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout abl, View target, int type) {
log("onStopNestedScroll"+type);
super.onStopNestedScroll(coordinatorLayout, abl, target, type);
if (target != null) {
mScrollStopChecker.preScrollY = target.getScrollY();
mHandler.removeCallbacks(mScrollStopChecker);
mHandler.postDelayed(mScrollStopChecker, 500);
}
if (mOnStopNestedScrollListener != null) {
mOnStopNestedScrollListener.onStopNestedScroll(coordinatorLayout, abl, target, type);
}
}
public void onDestroy() {
mHandler.removeCallbacksAndMessages(null);
}
/**
* 处理滑动到顶部回弹,及滑动到child底部之后短时间内item点击无效的问题
*/
private void stopNestedScrollIfNeeded(int dy, AppBarLayout child, View target, int type) {
if (type == ViewCompat.TYPE_NON_TOUCH) {
final int currOffset = getTopAndBottomOffset();
if ((dy < 0 && currOffset == 0)
|| (dy > 0 && currOffset == -child.getTotalScrollRange())) {
log("stopNestedScrollIfNeeded");
ViewCompat.stopNestedScroll(target, ViewCompat.TYPE_NON_TOUCH);
}
}
}
public boolean nestedChildScrollToTop() {
return mNestedScrollingChild == null
|| !mNestedScrollingChild.canScrollVertically(-1);
}
public boolean ifChildCanScrollUp() {
return mNestedScrollingChild != null
&& ViewCompat.canScrollVertically((View) mNestedScrollingChild, -1);
}
public View getNestedScrollingChild() {
return mNestedScrollingChild;
}
/**
* 找到子view中的第一个ScrollView
*
* @param view
*/
private void findFirstNestedScrollChild(View view) {
if (mNestedScrollingChild != null || view == null || !(view instanceof ViewGroup)) {
return;
}
if (view instanceof NestedScrollingChild && view.isShown()) {
mNestedScrollingChild = view;
return;
}
ViewGroup viewGroup = (ViewGroup) view;
for (int i = 0; i < viewGroup.getChildCount(); i++) {
View child = viewGroup.getChildAt(i);
if (mNestedScrollingChild == null) {
findFirstNestedScrollChild(child);
}
}
}
public void updateCurrentScrollChild(View root) {
mNestedScrollingChild = null;
findFirstNestedScrollChild(root);
}
private void log(String msg) {
EMLog.d(TAG, msg);
}
private OnStartNestedScrollListener mOnStartNestedScrollListener;
public void setOnStartNestedScrollListener(OnStartNestedScrollListener onStartNestedScrollListener) {
this.mOnStartNestedScrollListener = onStartNestedScrollListener;
}
public interface OnStartNestedScrollListener {
void onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child, View directTargetChild, View target, int nestedScrollAxes, int type);
}
private OnStopNestedScrollListener mOnStopNestedScrollListener;
public void setOnStopNestedScrollListener(OnStopNestedScrollListener onStopNestedScrollListener) {
this.mOnStopNestedScrollListener = onStopNestedScrollListener;
}
public interface OnStopNestedScrollListener {
void onStopNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout abl, View target, int type);
}
private OnRealStopNestedScrollListener mOnRealStopNestedScrollListener;
public void setOnRealStopNestedScrollListener(OnRealStopNestedScrollListener onRealStopNestedScrollListener) {
this.mOnRealStopNestedScrollListener = onRealStopNestedScrollListener;
}
public interface OnRealStopNestedScrollListener {
void onRealStopNestedScroll(View target);
}
private OnAppBarMoveListener mOnAppBarMoveListener;
public void setOnAppBarMoveListener(OnAppBarMoveListener onAppBarMoveListener) {
this.mOnAppBarMoveListener = onAppBarMoveListener;
}
public interface OnAppBarMoveListener {
void onAppBarMove(float startX, float startY, float endX, float endY);
}
private OnAppBarMoveCancelListener mOnAppBarMoveCancelListener;
public void setOnAppBarMoveCancelListener(OnAppBarMoveCancelListener onAppBarMoveCancelListener) {
this.mOnAppBarMoveCancelListener = onAppBarMoveCancelListener;
}
public interface OnAppBarMoveCancelListener {
void onAppBarMoveCancel(float x, float y);
}
private void injectField(Class<?> clzHeaderBehavior, String scroller) throws NoSuchFieldException, IllegalAccessException {
Field fieldScroller = clzHeaderBehavior.getDeclaredField(scroller);
fieldScroller.setAccessible(true);
fieldScroller.set(this, mScroller);
}
private Class<?> getTargetSuperClass(@NonNull Class<?> currentClass, String name) {
Class<?> superClass = currentClass.getSuperclass();
if (superClass == null) {
return null;
}
if (superClass.getSimpleName().equals(name)) {
return superClass;
} else {
return getTargetSuperClass(superClass, name);
}
}
private final ScrollStopChecker mScrollStopChecker = new ScrollStopChecker();
public class ScrollStopChecker implements Runnable {
public int preScrollY = 0;
@Override
public void run() {
View target = mTargetView;
if (target == null) {
if (mOnRealStopNestedScrollListener != null) {
mOnRealStopNestedScrollListener.onRealStopNestedScroll(null);
}
return;
}
int scrollY = target.getScrollY();
if (scrollY == preScrollY) {
if (mOnRealStopNestedScrollListener != null) {
mOnRealStopNestedScrollListener.onRealStopNestedScroll(target);
}
} else {
preScrollY = scrollY;
mHandler.removeCallbacks(mScrollStopChecker);
mHandler.postDelayed(mScrollStopChecker, 500);
}
}
}
}
网友评论