自定义AppBarLayout.Behavior实现惯性(fling)传递
FlingBehavior
AppBarLayout 惯性传递是指在滚动 AppBarLayout 时,AppBarLayout 会将惯性传递给其子 View,从而实现子 View 的滚动效果。具体来说,当用户在滚动 AppBarLayout 时,AppBarLayout 会根据滚动方向和速度计算出一个惯性滚动值,并将该值传递给其子 View。子 View 可以根据该惯性滚动值进行相应的滚动操作,从而实现与 AppBarLayout 的联动效果。
例如,在一个包含 RecyclerView 和 CollapsingToolbarLayout 的布局中,当用户向下滚动 RecyclerView 时,AppBarLayout 会根据滚动速度和方向计算出一个惯性滚动值,并将该值传递给 CollapsingToolbarLayout。CollapsingToolbarLayout 可以根据该惯性滚动值进行相应的折叠操作,从而实现与 RecyclerView 的联动效果。
总的来说,AppBarLayout 惯性传递是实现 AppBarLayout 与其子 View 真正联动的关键。通过惯性传递,AppBarLayout 可以将自身的滚动效果传递给子 View,从而实现整体联动的效果。
实现说明:
在 onInterceptTouchEvent 方法中,用于拦截触摸事件,判断是否需要进行拖拽,它会判断当前触摸事件是否在 AppBarLayout 区域内,并记录下当前触摸点的坐标和 id。然后根据触摸点的移动距离和 touchSlop 值判断是否开始拖拽,并记录下 isBeingDragged 标志位。如果当前触摸事件需要分发给下层 View,则将事件传递给与 AppBarLayout 关联的可滚动 View,并返回 true。
在 onTouchEvent 方法中,用于处理拖拽事件,计算滑动距离并将事件分发给下层 View。它会根据 isBeingDragged 标志位判断是否需要将触摸事件传递给可滚动 View。如果需要,则将事件进行偏移,以保证传递给可滚动 View 后,事件的坐标正确。然后将事件传递给可滚动 View,并返回 true。
在 onLayoutChild 方法中,它会查找与 AppBarLayout 关联的可滚动 View,并记录下来。这样,在后续的处理中,它就可以将触摸事件传递给该 View 了。
拿走即用,如果帮你解决了问题,记得点个赞呦!
在代码中有详细说明:
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;import androidx.annotation.NonNull;
import androidx.coordinatorlayout.widget.CoordinatorLayout;import com.google.android.material.appbar.AppBarLayout;/*** 自定义的 Behavior 类,用于实现 AppBarLayout 的 fling 效果。* 在滑动 AppBarLayout 时,它会将滑动事件传递给与 AppBarLayout 关联的可滚动 View,以实现 fling 效果** @author HuKui* @date 2022/11/24 下午 03:02*/
public class FlingBehavior extends AppBarLayout.Behavior {/*** 表示无效的触摸点**/private static final int INVALID_POINTER = -1;/*** 是否正在被拖拽**/private boolean isBeingDragged;/*** 当前活动的触摸点的 id**/private int activePointerId = INVALID_POINTER;/*** 上一次触摸事件的Y坐标**/private int laseMotionY;/*** 上一次触摸事件的X坐标**/private int laseMotionX;/*** 触摸的最小滑动距离**/private final int touchSlop;/*** 当前触摸事件**/private MotionEvent currentMotionEvent;/*** 是否需要将事件分发给下层 View**/private boolean needDispatchDown;/*** 与 AppBarLayout 关联的可滚动 View**/private View scrollViewBehaviorView;public FlingBehavior(Context context, AttributeSet attrs) {super(context, attrs);touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();}/*** 这段代码的作用是判断用户是否正在拖拽 AppBarLayout。当用户按下触摸点时,记录下触摸点的坐标和触摸点的 ID;* 当用户移动触摸点时,计算触摸点移动的距离,并判断是否大于 touchSlop,* 如果是则认为正在拖拽;当用户取消或抬起触摸点时,清除拖拽状态并将事件设置为 ACTION_CANCEL* 。如果有关联的子 View(如 NestedScrollView),则将事件发送给子 View 处理。最后返回是否正在拖拽的状态。**/@Overridepublic boolean onInterceptTouchEvent(@NonNull CoordinatorLayout parent, @NonNull AppBarLayout child, @NonNull MotionEvent ev) {switch (ev.getActionMasked()) {case MotionEvent.ACTION_DOWN:isBeingDragged = false;needDispatchDown = true;// 获取触摸点的坐标final int x = (int) ev.getX();final int y = (int) ev.getY();// 检查触摸点是否在 AppBarLayout 中if (parent.isPointInChildBounds(child, x, y)) {// 如果是,则记录下坐标和触摸点的 IDlaseMotionX = x;laseMotionY = y;activePointerId = ev.getPointerId(0);// 回收之前的 MotionEventif (currentMotionEvent != null) {currentMotionEvent.recycle();}// 复制当前的 MotionEventcurrentMotionEvent = MotionEvent.obtain(ev);}break;case MotionEvent.ACTION_MOVE:// 检查当前是否正在拖拽final int activePointerId = this.activePointerId;if (activePointerId == INVALID_POINTER) {break;}// 获取触摸点的坐标final int pointerIndex = ev.findPointerIndex(activePointerId);if (pointerIndex == -1) {break;}final int moveX = (int) ev.getX(pointerIndex);final int moveY = (int) ev.getY(pointerIndex);// 计算触摸点移动的距离final int xDiff = Math.abs(moveX - laseMotionX);final int yDiff = Math.abs(moveY - laseMotionY);// 如果垂直移动的距离大于水平移动的距离,并且大于 touchSlop,则认为正在拖拽if (yDiff > touchSlop && yDiff > xDiff) {isBeingDragged = true;}break;case MotionEvent.ACTION_CANCEL:case MotionEvent.ACTION_UP:// 取消拖拽状态isBeingDragged = false;needDispatchDown = true;this.activePointerId = INVALID_POINTER;// 清除 mCurrentMotionEventcurrentMotionEvent = null;// 将事件设置为 ACTION_CANCELev.setAction(MotionEvent.ACTION_CANCEL);// 如果有关联的子 View(如 NestedScrollView),则将事件发送给子 View 处理if (scrollViewBehaviorView != null) {scrollViewBehaviorView.dispatchTouchEvent(ev);return true;}break;}return isBeingDragged;}@Overridepublic boolean onTouchEvent(@NonNull CoordinatorLayout parent, @NonNull AppBarLayout child, @NonNull MotionEvent ev) {switch (ev.getActionMasked()) {case MotionEvent.ACTION_MOVE:// 是否在拖拽if (isBeingDragged) {final int offset = child.getHeight() - child.getBottom();if (needDispatchDown) {needDispatchDown = false;MotionEvent downEvent = MotionEvent.obtain(currentMotionEvent);downEvent.offsetLocation(0, offset);if (scrollViewBehaviorView != null) {scrollViewBehaviorView.dispatchTouchEvent(downEvent);}}MotionEvent moveEvent = MotionEvent.obtain(ev);moveEvent.offsetLocation(0, offset);if (scrollViewBehaviorView != null) {scrollViewBehaviorView.dispatchTouchEvent(moveEvent);return true;}}break;case MotionEvent.ACTION_UP:if (isBeingDragged) {ev.offsetLocation(0, child.getHeight() - child.getBottom());if (scrollViewBehaviorView != null) {scrollViewBehaviorView.dispatchTouchEvent(ev);return true;}}break;}return false;}@SuppressWarnings("rawtypes")@Overridepublic boolean onLayoutChild(@NonNull CoordinatorLayout parent, @NonNull AppBarLayout abl, int layoutDirection) {boolean handled = super.onLayoutChild(parent, abl, layoutDirection);// 查找与 AppBarLayout 关联的可滚动 View。for (int i = 0, c = parent.getChildCount(); i < c; i++) {View sibling = parent.getChildAt(i);final CoordinatorLayout.Behavior behavior = ((CoordinatorLayout.LayoutParams) sibling.getLayoutParams()).getBehavior();if (behavior instanceof AppBarLayout.ScrollingViewBehavior) {scrollViewBehaviorView = sibling;}}return handled;}
}
使用方式:
<com.google.android.material.appbar.AppBarLayoutstyle="@style/AppBarLayout"android:layout_width="match_parent"android:layout_height="wrap_content"// FlingBehavior的文件位置app:layout_behavior="com.xxx.xxx.FlingBehavior">
</com.google.android.material.appbar.AppBarLayout>
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
