From 5cca02e5233e674e0ddfe94345950db7ac6337e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A2=81=E4=BB=BB=E5=BD=A6?= Date: Tue, 21 Apr 2020 18:08:29 +0800 Subject: [PATCH 1/8] =?UTF-8?q?=E4=BF=AE=E6=94=B9bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../consecutivescroller/ConsecutiveScrollerLayout.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/consecutivescroller/src/main/java/com/donkingliang/consecutivescroller/ConsecutiveScrollerLayout.java b/consecutivescroller/src/main/java/com/donkingliang/consecutivescroller/ConsecutiveScrollerLayout.java index 3b2b057..cb05e82 100644 --- a/consecutivescroller/src/main/java/com/donkingliang/consecutivescroller/ConsecutiveScrollerLayout.java +++ b/consecutivescroller/src/main/java/com/donkingliang/consecutivescroller/ConsecutiveScrollerLayout.java @@ -159,6 +159,7 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); // 测量子view measureChildren(widthMeasureSpec, heightMeasureSpec); + } @Override @@ -504,7 +505,7 @@ private void scrollDown(int offset) { // 如果是要滑动到指定的View,判断滑动到目标位置,就停止滑动 if (mScrollToIndex != -1) { View view = getChildAt(mScrollToIndex); - if (getScrollY() + getPaddingTop() >= view.getTop() + if (getScrollY() + getPaddingTop() <= view.getTop() && ScrollUtils.getScrollTopOffset(view) >= 0) { mScrollToIndex = -1; mSmoothScrollOffset = 0; From 46cd635c81fbecb395cff780cc49786b1e52c5a4 Mon Sep 17 00:00:00 2001 From: teach Date: Wed, 22 Apr 2020 10:51:05 +0800 Subject: [PATCH 2/8] =?UTF-8?q?=E6=BB=91=E5=8A=A8=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ConsecutiveScrollerLayout.java | 41 ++++++++++--------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/consecutivescroller/src/main/java/com/donkingliang/consecutivescroller/ConsecutiveScrollerLayout.java b/consecutivescroller/src/main/java/com/donkingliang/consecutivescroller/ConsecutiveScrollerLayout.java index cb05e82..5e3beb7 100644 --- a/consecutivescroller/src/main/java/com/donkingliang/consecutivescroller/ConsecutiveScrollerLayout.java +++ b/consecutivescroller/src/main/java/com/donkingliang/consecutivescroller/ConsecutiveScrollerLayout.java @@ -230,8 +230,8 @@ public boolean dispatchTouchEvent(MotionEvent ev) { mEventX = (int) ev.getX(actionIndex); // 改变滑动的手指,重新询问事件拦截 requestDisallowInterceptTouchEvent(false); - isConsecutiveScrollerChild = ScrollUtils.isConsecutiveScrollerChild(getTouchTarget( - ScrollUtils.getRawX(this, ev, actionIndex), ScrollUtils.getRawY(this, ev, actionIndex))); +// isConsecutiveScrollerChild = ScrollUtils.isConsecutiveScrollerChild(getTouchTarget( +// ScrollUtils.getRawX(this, ev, actionIndex), ScrollUtils.getRawY(this, ev, actionIndex))); break; case MotionEvent.ACTION_MOVE: final int pointerIndex = ev.findPointerIndex(mActivePointerId); @@ -254,10 +254,10 @@ public boolean dispatchTouchEvent(MotionEvent ev) { return true; } } - - if (SCROLL_ORIENTATION == SCROLL_HORIZONTAL) { - ev.setLocation(ev.getX(), mFixedEventY); - } +// +// if (SCROLL_ORIENTATION == SCROLL_HORIZONTAL) { +// ev.setLocation(ev.getX(), mFixedEventY); +// } } mScrollOffset = offsetY; @@ -286,11 +286,11 @@ public boolean dispatchTouchEvent(MotionEvent ev) { } boolean dispatch; - if (isConsecutiveScrollerChild && SCROLL_ORIENTATION != SCROLL_HORIZONTAL) { - dispatch = adjustScroll(ev); - } else { +// if (isConsecutiveScrollerChild && SCROLL_ORIENTATION != SCROLL_HORIZONTAL) { +// dispatch = adjustScroll(ev); +// } else { dispatch = super.dispatchTouchEvent(ev); - } +// } return dispatch; } @@ -1099,16 +1099,17 @@ private boolean isIntercept(MotionEvent ev) { ScrollUtils.getRawY(this, ev, pointerIndex)); if (target != null) { - ViewGroup.LayoutParams lp = target.getLayoutParams(); - if (lp instanceof LayoutParams) { - if (!((LayoutParams) lp).isConsecutive) { - return false; - } - } - - if (ScrollUtils.canScrollVertically(target)) { - return true; - } + return ScrollUtils.isConsecutiveScrollerChild(target); +// ViewGroup.LayoutParams lp = target.getLayoutParams(); +// if (lp instanceof LayoutParams) { +// if (!((LayoutParams) lp).isConsecutive) { +// return false; +// } +// } +// +// if (ScrollUtils.canScrollVertically(target)) { +// return true; +// } } return false; From 80aff55bc83c76cf1b86ba473cc40ad0f2a462a0 Mon Sep 17 00:00:00 2001 From: teach Date: Wed, 22 Apr 2020 17:16:08 +0800 Subject: [PATCH 3/8] =?UTF-8?q?=E7=BB=98=E5=88=B6=E8=BE=B9=E7=95=8C?= =?UTF-8?q?=E9=98=B4=E5=BD=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ConsecutiveScrollerLayout.java | 297 +++++++++++------- 1 file changed, 187 insertions(+), 110 deletions(-) diff --git a/consecutivescroller/src/main/java/com/donkingliang/consecutivescroller/ConsecutiveScrollerLayout.java b/consecutivescroller/src/main/java/com/donkingliang/consecutivescroller/ConsecutiveScrollerLayout.java index 5e3beb7..4006397 100644 --- a/consecutivescroller/src/main/java/com/donkingliang/consecutivescroller/ConsecutiveScrollerLayout.java +++ b/consecutivescroller/src/main/java/com/donkingliang/consecutivescroller/ConsecutiveScrollerLayout.java @@ -3,6 +3,7 @@ import android.annotation.SuppressLint; import android.content.Context; import android.content.res.TypedArray; +import android.graphics.Canvas; import android.os.Build; import android.util.AttributeSet; import android.view.MotionEvent; @@ -12,6 +13,7 @@ import android.view.ViewGroup; import android.view.animation.Interpolator; import android.widget.AbsListView; +import android.widget.EdgeEffect; import android.widget.OverScroller; import java.util.ArrayList; @@ -21,6 +23,7 @@ import androidx.core.view.NestedScrollingParent; import androidx.core.view.NestedScrollingParentHelper; import androidx.core.view.ViewCompat; +import androidx.core.widget.EdgeEffectCompat; /** * @Author donkingliang QQ:1043214265 github:https://github.com/donkingliang @@ -50,7 +53,6 @@ public class ConsecutiveScrollerLayout extends ViewGroup implements NestedScroll */ private VelocityTracker mVelocityTracker; - private VelocityTracker mAdjustVelocityTracker; /** * MaximumVelocity */ @@ -69,12 +71,7 @@ public class ConsecutiveScrollerLayout extends ViewGroup implements NestedScroll private int mTouchY; private int mEventX; private int mEventY; - private float mFixedEventY; - private int mScrollOffset = 0; - private boolean isConsecutiveScrollerChild = false; - - private boolean isAdjust = true; /** * 是否处于状态 */ @@ -107,6 +104,22 @@ public class ConsecutiveScrollerLayout extends ViewGroup implements NestedScroll */ private int mSmoothScrollOffset = 0; + + /** + * 上边界阴影 + */ + private EdgeEffect mEdgeGlowTop; + /** + * 下边界阴影 + */ + private EdgeEffect mEdgeGlowBottom; + + /** + * fling时,保存最后的滑动位置,在下一帧时通过对比新的滑动位置,判断滑动的方向。 + */ + private int mLastScrollerY; + + // 这是RecyclerView的代码,让ConsecutiveScrollerLayout的fling效果更接近于RecyclerView。 static final Interpolator sQuinticInterpolator = new Interpolator() { @Override @@ -145,9 +158,26 @@ public void addView(View child, int index, ViewGroup.LayoutParams params) { super.addView(child, index, params); // 去掉子View的滚动条。选择在这里做这个操作,而不是在onFinishInflate方法中完成,是为了兼顾用代码add子View的情况 - child.setVerticalScrollBarEnabled(false); - child.setHorizontalScrollBarEnabled(false); - child.setOverScrollMode(OVER_SCROLL_NEVER); + + if (ScrollUtils.isConsecutiveScrollerChild(child)) { + if (child instanceof IConsecutiveScroller) { + List views = ((IConsecutiveScroller) child).getScrolledViews(); + if (views != null && !views.isEmpty()) { + int size = views.size(); + for (int i = 0; i < size; i++) { + child.setVerticalScrollBarEnabled(false); + child.setHorizontalScrollBarEnabled(false); + child.setOverScrollMode(OVER_SCROLL_NEVER); + } + } + + } else { + child.setVerticalScrollBarEnabled(false); + child.setHorizontalScrollBarEnabled(false); + child.setOverScrollMode(OVER_SCROLL_NEVER); + } + } + if (child instanceof ViewGroup) { ((ViewGroup) child).setClipToPadding(false); } @@ -159,7 +189,6 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); // 测量子view measureChildren(widthMeasureSpec, heightMeasureSpec); - } @Override @@ -221,7 +250,6 @@ public boolean dispatchTouchEvent(MotionEvent ev) { // 停止滑动 stopScroll(); checkTargetsScroll(false, false); - mFixedEventY = ev.getY(); mTouching = true; SCROLL_ORIENTATION = SCROLL_NONE; case MotionEvent.ACTION_POINTER_DOWN: @@ -230,8 +258,6 @@ public boolean dispatchTouchEvent(MotionEvent ev) { mEventX = (int) ev.getX(actionIndex); // 改变滑动的手指,重新询问事件拦截 requestDisallowInterceptTouchEvent(false); -// isConsecutiveScrollerChild = ScrollUtils.isConsecutiveScrollerChild(getTouchTarget( -// ScrollUtils.getRawX(this, ev, actionIndex), ScrollUtils.getRawY(this, ev, actionIndex))); break; case MotionEvent.ACTION_MOVE: final int pointerIndex = ev.findPointerIndex(mActivePointerId); @@ -254,13 +280,8 @@ public boolean dispatchTouchEvent(MotionEvent ev) { return true; } } -// -// if (SCROLL_ORIENTATION == SCROLL_HORIZONTAL) { -// ev.setLocation(ev.getX(), mFixedEventY); -// } } - mScrollOffset = offsetY; mEventY = (int) ev.getY(pointerIndex); mEventX = (int) ev.getX(pointerIndex); break; @@ -277,70 +298,14 @@ public boolean dispatchTouchEvent(MotionEvent ev) { break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: - mScrollOffset = 0; mEventY = 0; mEventX = 0; mTouching = false; -// SCROLL_ORIENTATION = SCROLL_NONE; + SCROLL_ORIENTATION = SCROLL_NONE; break; } - boolean dispatch; -// if (isConsecutiveScrollerChild && SCROLL_ORIENTATION != SCROLL_HORIZONTAL) { -// dispatch = adjustScroll(ev); -// } else { - dispatch = super.dispatchTouchEvent(ev); -// } - return dispatch; - } - - /** - * 追踪手指的垂直滑动轨迹,通过计算ConsecutiveScrollerLayout及其后代View的滑动情况来确定 - * ConsecutiveScrollerLayout是否需要消费事件,确保滑动布局的滑动情况与用户的滑动操作保持一致。 - * - * @param ev - * @return - */ - private boolean adjustScroll(MotionEvent ev) { - List views = ScrollUtils.getTouchViews(this, (int) ev.getRawX(), (int) ev.getRawY()); - List offset = ScrollUtils.getScrollOffsetForViews(views); - boolean dispatch = super.dispatchTouchEvent(ev); - - switch (ev.getActionMasked()) { - case MotionEvent.ACTION_DOWN: - case MotionEvent.ACTION_POINTER_DOWN: - case MotionEvent.ACTION_POINTER_UP: - initOrResetAdjustVelocityTracker(); - mAdjustVelocityTracker.addMovement(ev); - isAdjust = true; - break; - case MotionEvent.ACTION_MOVE: - if (isAdjust && Math.abs(mScrollOffset) >= mTouchSlop) { - if (ScrollUtils.equalsOffsets(offset, ScrollUtils.getScrollOffsetForViews(views))) { - scrollBy(0, -mScrollOffset); - } else { - isAdjust = false; - } - } - initAdjustVelocityTrackerIfNotExists(); - mAdjustVelocityTracker.addMovement(ev); - break; - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: - isConsecutiveScrollerChild = false; - if (mAdjustVelocityTracker != null) { - mAdjustVelocityTracker.addMovement(ev); - if (isAdjust && mScroller.isFinished()) { - mAdjustVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); - int yVelocity = (int) mAdjustVelocityTracker.getYVelocity(); - recycleAdjustVelocityTracker(); - fling(-yVelocity); - } - } - isAdjust = true; - break; - } - return dispatch; + return super.dispatchTouchEvent(ev); } @Override @@ -373,13 +338,45 @@ public boolean onTouchEvent(MotionEvent ev) { int y = (int) ev.getY(pointerIndex); int dy = y - mTouchY; mTouchY = y; + int oldY = mOwnScrollY; scrollBy(0, -dy); - + int deltaY = -dy; initVelocityTrackerIfNotExists(); mVelocityTracker.addMovement(ev); + + // 判断是否显示边界阴影 + final int range = getScrollRange(); + final int overscrollMode = getOverScrollMode(); + boolean canOverscroll = overscrollMode == View.OVER_SCROLL_ALWAYS + || (overscrollMode == View.OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0); + if (canOverscroll) { + ensureGlows(); + final int pulledToY = oldY + deltaY; + if (pulledToY < 0) { + // 滑动距离超出顶部边界,设置阴影 + EdgeEffectCompat.onPull(mEdgeGlowTop, (float) deltaY / getHeight(), + ev.getX(pointerIndex) / getWidth()); + if (!mEdgeGlowBottom.isFinished()) { + mEdgeGlowBottom.onRelease(); + } + } else if (pulledToY > range) { + // 滑动距离超出底部边界,设置阴影 + EdgeEffectCompat.onPull(mEdgeGlowBottom, (float) deltaY / getHeight(), + 1.f - ev.getX(pointerIndex) + / getWidth()); + if (!mEdgeGlowTop.isFinished()) { + mEdgeGlowTop.onRelease(); + } + } + if (mEdgeGlowTop != null + && (!mEdgeGlowTop.isFinished() || !mEdgeGlowBottom.isFinished())) { + ViewCompat.postInvalidateOnAnimation(this); + } + } break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: + endDrag(); mTouchY = 0; if (mVelocityTracker != null) { @@ -395,12 +392,76 @@ public boolean onTouchEvent(MotionEvent ev) { return true; } + @Override + public void draw(Canvas canvas) { + super.draw(canvas); + + // 绘制边界阴影 + if (mEdgeGlowTop != null) { + final int scrollY = getScrollY(); + if (!mEdgeGlowTop.isFinished()) { + final int restoreCount = canvas.save(); + int width = getWidth(); + int height = getHeight(); + int xTranslation = 0; + int yTranslation = scrollY; + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP || getClipToPadding()) { + width -= getPaddingLeft() + getPaddingRight(); + xTranslation += getPaddingLeft(); + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && getClipToPadding()) { + height -= getPaddingTop() + getPaddingBottom(); + yTranslation += getPaddingTop(); + } + canvas.translate(xTranslation, yTranslation); + mEdgeGlowTop.setSize(width, height); + if (mEdgeGlowTop.draw(canvas)) { + ViewCompat.postInvalidateOnAnimation(this); + } + canvas.restoreToCount(restoreCount); + } + if (!mEdgeGlowBottom.isFinished()) { + final int restoreCount = canvas.save(); + int width = getWidth(); + int height = getHeight(); + int xTranslation = 0; + int yTranslation = scrollY + height; + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP || getClipToPadding()) { + width -= getPaddingLeft() + getPaddingRight(); + xTranslation += getPaddingLeft(); + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && getClipToPadding()) { + height -= getPaddingTop() + getPaddingBottom(); + yTranslation -= getPaddingBottom(); + } + canvas.translate(xTranslation - width, yTranslation); + canvas.rotate(180, width, 0); + mEdgeGlowBottom.setSize(width, height); + if (mEdgeGlowBottom.draw(canvas)) { + ViewCompat.postInvalidateOnAnimation(this); + } + canvas.restoreToCount(restoreCount); + } + } + } + + int getScrollRange() { + int scrollRange = 0; + if (getChildCount() > 0) { + int childSize = computeVerticalScrollRange(); + int parentSpace = getHeight() - getPaddingTop() - getPaddingBottom(); + scrollRange = Math.max(0, childSize - parentSpace); + } + return scrollRange; + } + private void fling(int velocityY) { if (Math.abs(velocityY) > mMinimumVelocity) { mScroller.fling(0, mOwnScrollY, 1, velocityY, Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE); + mLastScrollerY = mOwnScrollY; invalidate(); } } @@ -412,13 +473,39 @@ public void computeScroll() { scrollBy(0, mSmoothScrollOffset); invalidate(); } else { + // fling if (mScroller.computeScrollOffset()) { - int curY = mScroller.getCurrY(); - dispatchScroll(curY); + final int y = mScroller.getCurrY(); + int unconsumed = y - mLastScrollerY; + mLastScrollerY = y; + dispatchScroll(y); + // 判断滑动方向和是否滑动到边界 + if ((unconsumed < 0 && isScrollTop()) || (unconsumed > 0 && isScrollBottom())) { + final int mode = getOverScrollMode(); + final boolean canOverscroll = mode == OVER_SCROLL_ALWAYS + || (mode == OVER_SCROLL_IF_CONTENT_SCROLLS && getScrollRange() > 0); + if (canOverscroll) { + ensureGlows(); + if (unconsumed < 0) { + // 设置上边界阴影 + if (mEdgeGlowTop.isFinished()) { + mEdgeGlowTop.onAbsorb((int) mScroller.getCurrVelocity()); + } + } else { + // 设置下边界阴影 + if (mEdgeGlowBottom.isFinished()) { + mEdgeGlowBottom.onAbsorb((int) mScroller.getCurrVelocity()); + } + } + } + stopScroll(); + } + invalidate(); } + if (mScroller.isFinished()) { // 滚动结束,校验子view内容的滚动位置 checkTargetsScroll(false, false); @@ -426,6 +513,26 @@ public void computeScroll() { } } + private void endDrag() { + if (mEdgeGlowTop != null) { + mEdgeGlowTop.onRelease(); + mEdgeGlowBottom.onRelease(); + } + } + + private void ensureGlows() { + if (getOverScrollMode() != View.OVER_SCROLL_NEVER) { + if (mEdgeGlowTop == null) { + Context context = getContext(); + mEdgeGlowTop = new EdgeEffect(context); + mEdgeGlowBottom = new EdgeEffect(context); + } + } else { + mEdgeGlowTop = null; + mEdgeGlowBottom = null; + } + } + /** * 分发处理滑动 * @@ -776,36 +883,6 @@ private void recycleVelocityTracker() { } } - /** - * 初始化VelocityTracker - */ - private void initOrResetAdjustVelocityTracker() { - if (mAdjustVelocityTracker == null) { - mAdjustVelocityTracker = VelocityTracker.obtain(); - } else { - mAdjustVelocityTracker.clear(); - } - } - - /** - * 初始化VelocityTracker - */ - private void initAdjustVelocityTrackerIfNotExists() { - if (mAdjustVelocityTracker == null) { - mAdjustVelocityTracker = VelocityTracker.obtain(); - } - } - - /** - * 回收VelocityTracker - */ - private void recycleAdjustVelocityTracker() { - if (mAdjustVelocityTracker != null) { - mAdjustVelocityTracker.recycle(); - mAdjustVelocityTracker = null; - } - } - /** * 停止滑动 */ @@ -1099,7 +1176,7 @@ private boolean isIntercept(MotionEvent ev) { ScrollUtils.getRawY(this, ev, pointerIndex)); if (target != null) { - return ScrollUtils.isConsecutiveScrollerChild(target); + return ScrollUtils.isConsecutiveScrollerChild(target); // ViewGroup.LayoutParams lp = target.getLayoutParams(); // if (lp instanceof LayoutParams) { // if (!((LayoutParams) lp).isConsecutive) { From 73601f1ac1e599ed68651a0018a4bf3dec52f14e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A2=81=E4=BB=BB=E5=BD=A6?= Date: Fri, 24 Apr 2020 16:26:09 +0800 Subject: [PATCH 4/8] =?UTF-8?q?=E5=A4=84=E7=90=86=E6=A8=AA=E5=90=91?= =?UTF-8?q?=E5=92=8C=E5=9E=82=E7=9B=B4=E6=BB=91=E5=8A=A8=E7=9A=84=E4=BA=8B?= =?UTF-8?q?=E4=BB=B6=E5=8C=BA=E5=88=86=EF=BC=9B=E5=A4=84=E7=90=86=E4=B8=80?= =?UTF-8?q?=E4=B8=8B=E6=BB=91=E5=8A=A8=E8=BF=BD=E8=B8=AA=E7=9A=84=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ConsecutiveScrollerLayout.java | 132 ++++++++++++++---- 1 file changed, 105 insertions(+), 27 deletions(-) diff --git a/consecutivescroller/src/main/java/com/donkingliang/consecutivescroller/ConsecutiveScrollerLayout.java b/consecutivescroller/src/main/java/com/donkingliang/consecutivescroller/ConsecutiveScrollerLayout.java index 4006397..4dae614 100644 --- a/consecutivescroller/src/main/java/com/donkingliang/consecutivescroller/ConsecutiveScrollerLayout.java +++ b/consecutivescroller/src/main/java/com/donkingliang/consecutivescroller/ConsecutiveScrollerLayout.java @@ -16,15 +16,15 @@ import android.widget.EdgeEffect; import android.widget.OverScroller; -import java.util.ArrayList; -import java.util.List; - import androidx.annotation.NonNull; import androidx.core.view.NestedScrollingParent; import androidx.core.view.NestedScrollingParentHelper; import androidx.core.view.ViewCompat; import androidx.core.widget.EdgeEffectCompat; +import java.util.ArrayList; +import java.util.List; + /** * @Author donkingliang QQ:1043214265 github:https://github.com/donkingliang * @Description @@ -52,6 +52,7 @@ public class ConsecutiveScrollerLayout extends ViewGroup implements NestedScroll * VelocityTracker */ private VelocityTracker mVelocityTracker; + private VelocityTracker mAdjustVelocityTracker; /** * MaximumVelocity @@ -71,6 +72,7 @@ public class ConsecutiveScrollerLayout extends ViewGroup implements NestedScroll private int mTouchY; private int mEventX; private int mEventY; + private float mFixedY; /** * 是否处于状态 @@ -104,7 +106,6 @@ public class ConsecutiveScrollerLayout extends ViewGroup implements NestedScroll */ private int mSmoothScrollOffset = 0; - /** * 上边界阴影 */ @@ -119,7 +120,6 @@ public class ConsecutiveScrollerLayout extends ViewGroup implements NestedScroll */ private int mLastScrollerY; - // 这是RecyclerView的代码,让ConsecutiveScrollerLayout的fling效果更接近于RecyclerView。 static final Interpolator sQuinticInterpolator = new Interpolator() { @Override @@ -216,7 +216,7 @@ protected void onLayout(boolean changed, int l, int t, int r, int b) { } // 布局发生变化,检测滑动位置 - checkLayoutChange(false); + checkLayoutChange(changed, false); } private void resetScrollToTopView() { @@ -245,6 +245,11 @@ public LayoutParams generateLayoutParams(AttributeSet attrs) { public boolean dispatchTouchEvent(MotionEvent ev) { final int actionIndex = ev.getActionIndex(); + if (SCROLL_ORIENTATION == SCROLL_HORIZONTAL) { + // 如果是横向滑动,设置ev的y坐标始终为开始的坐标,避免子view自己消费了垂直滑动事件。 + ev.setLocation(ev.getX(), mFixedY); + } + switch (ev.getActionMasked()) { case MotionEvent.ACTION_DOWN: // 停止滑动 @@ -252,14 +257,35 @@ public boolean dispatchTouchEvent(MotionEvent ev) { checkTargetsScroll(false, false); mTouching = true; SCROLL_ORIENTATION = SCROLL_NONE; + mFixedY = ev.getY(); + mActivePointerId = ev.getPointerId(actionIndex); + mEventY = (int) ev.getY(actionIndex); + mEventX = (int) ev.getX(actionIndex); + + initOrResetAdjustVelocityTracker(); + mAdjustVelocityTracker.addMovement(ev); + + initOrResetVelocityTracker(); + mVelocityTracker.addMovement(ev); + break; case MotionEvent.ACTION_POINTER_DOWN: mActivePointerId = ev.getPointerId(actionIndex); mEventY = (int) ev.getY(actionIndex); mEventX = (int) ev.getX(actionIndex); // 改变滑动的手指,重新询问事件拦截 requestDisallowInterceptTouchEvent(false); + + initAdjustVelocityTrackerIfNotExists(); + mAdjustVelocityTracker.addMovement(ev); + + initVelocityTrackerIfNotExists(); + mVelocityTracker.addMovement(ev); break; case MotionEvent.ACTION_MOVE: + + initVelocityTrackerIfNotExists(); + mVelocityTracker.addMovement(ev); + final int pointerIndex = ev.findPointerIndex(mActivePointerId); int offsetY = (int) ev.getY(pointerIndex) - mEventY; int offsetX = (int) ev.getX(pointerIndex) - mEventX; @@ -269,6 +295,8 @@ public boolean dispatchTouchEvent(MotionEvent ev) { if (Math.abs(offsetX) > Math.abs(offsetY)) { if (Math.abs(offsetX) >= mTouchSlop) { SCROLL_ORIENTATION = SCROLL_HORIZONTAL; + // 如果是横向滑动,设置ev的y坐标始终为开始的坐标,避免子view自己消费了垂直滑动事件。 + ev.setLocation(ev.getX(), mFixedY); } } else { if (Math.abs(offsetY) >= mTouchSlop) { @@ -284,6 +312,9 @@ public boolean dispatchTouchEvent(MotionEvent ev) { mEventY = (int) ev.getY(pointerIndex); mEventX = (int) ev.getX(pointerIndex); + + initAdjustVelocityTrackerIfNotExists(); + mAdjustVelocityTracker.addMovement(ev); break; case MotionEvent.ACTION_POINTER_UP: if (mActivePointerId == ev.getPointerId(actionIndex)) { // 如果松开的是活动手指, 让还停留在屏幕上的最后一根手指作为活动手指 @@ -295,9 +326,33 @@ public boolean dispatchTouchEvent(MotionEvent ev) { mEventY = (int) ev.getY(newPointerIndex); mEventX = (int) ev.getX(newPointerIndex); } + initAdjustVelocityTrackerIfNotExists(); + mAdjustVelocityTracker.addMovement(ev); + + initVelocityTrackerIfNotExists(); + mVelocityTracker.addMovement(ev); break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: + + if (mAdjustVelocityTracker != null) { + mAdjustVelocityTracker.addMovement(ev); + mAdjustVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); + int yVelocity = (int) mAdjustVelocityTracker.getYVelocity(); + recycleAdjustVelocityTracker(); + boolean canScrollVerticallyChild = ScrollUtils.canScrollVertically(getTouchTarget( + ScrollUtils.getRawX(this, ev, actionIndex), ScrollUtils.getRawY(this, ev, actionIndex))); + if (SCROLL_ORIENTATION == SCROLL_HORIZONTAL && canScrollVerticallyChild && Math.abs(yVelocity) > mMinimumVelocity) { + //如果当前是横向滑动,但是触摸的控件可以垂直滑动,并且产生垂直滑动的fling事件, + // 为了不让这个控件垂直fling,把事件设置为MotionEvent.ACTION_CANCEL。 + ev.setAction(MotionEvent.ACTION_CANCEL); + } + } + + if (mVelocityTracker != null) { + mVelocityTracker.addMovement(ev); + } + mEventY = 0; mEventX = 0; mTouching = false; @@ -305,7 +360,16 @@ public boolean dispatchTouchEvent(MotionEvent ev) { break; } - return super.dispatchTouchEvent(ev); + boolean dispatch = super.dispatchTouchEvent(ev); + + switch (ev.getActionMasked()) { + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + recycleVelocityTracker(); + break; + } + + return dispatch; } @Override @@ -327,8 +391,6 @@ public boolean onTouchEvent(MotionEvent ev) { case MotionEvent.ACTION_POINTER_DOWN: case MotionEvent.ACTION_POINTER_UP: mTouchY = (int) ev.getY(pointerIndex); - initOrResetVelocityTracker(); - mVelocityTracker.addMovement(ev); break; case MotionEvent.ACTION_MOVE: if (mTouchY == 0) { @@ -341,8 +403,6 @@ public boolean onTouchEvent(MotionEvent ev) { int oldY = mOwnScrollY; scrollBy(0, -dy); int deltaY = -dy; - initVelocityTrackerIfNotExists(); - mVelocityTracker.addMovement(ev); // 判断是否显示边界阴影 final int range = getScrollRange(); @@ -380,11 +440,9 @@ public boolean onTouchEvent(MotionEvent ev) { mTouchY = 0; if (mVelocityTracker != null) { - mVelocityTracker.addMovement(ev); mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); int yVelocity = (int) mVelocityTracker.getYVelocity(); yVelocity = Math.max(-mMaximumVelocity, Math.min(yVelocity, mMaximumVelocity)); - recycleVelocityTracker(); fling(-yVelocity); } break; @@ -702,14 +760,14 @@ private void scrollChild(View child, int y) { public void checkLayoutChange() { - checkLayoutChange(true); + checkLayoutChange(false, true); } /** * 布局发生变化,重新检查所有子View是否正确显示 */ - public void checkLayoutChange(boolean isForce) { - if (mScrollToTopView != null) { + public void checkLayoutChange(boolean changed, boolean isForce) { + if (mScrollToTopView != null && changed) { if (indexOfChild(mScrollToTopView) != -1) { scrollSelf(mScrollToTopView.getTop() + mAdjust); } @@ -883,6 +941,36 @@ private void recycleVelocityTracker() { } } + /** + * 初始化VelocityTracker + */ + private void initOrResetAdjustVelocityTracker() { + if (mAdjustVelocityTracker == null) { + mAdjustVelocityTracker = VelocityTracker.obtain(); + } else { + mAdjustVelocityTracker.clear(); + } + } + + /** + * 初始化VelocityTracker + */ + private void initAdjustVelocityTrackerIfNotExists() { + if (mAdjustVelocityTracker == null) { + mAdjustVelocityTracker = VelocityTracker.obtain(); + } + } + + /** + * 回收VelocityTracker + */ + private void recycleAdjustVelocityTracker() { + if (mAdjustVelocityTracker != null) { + mAdjustVelocityTracker.recycle(); + mAdjustVelocityTracker = null; + } + } + /** * 停止滑动 */ @@ -1177,16 +1265,6 @@ private boolean isIntercept(MotionEvent ev) { if (target != null) { return ScrollUtils.isConsecutiveScrollerChild(target); -// ViewGroup.LayoutParams lp = target.getLayoutParams(); -// if (lp instanceof LayoutParams) { -// if (!((LayoutParams) lp).isConsecutive) { -// return false; -// } -// } -// -// if (ScrollUtils.canScrollVertically(target)) { -// return true; -// } } return false; @@ -1344,4 +1422,4 @@ public void smoothScrollToChild(View view) { invalidate(); } } -} +} \ No newline at end of file From 051a6b31d2ea6cf66a79642cf321980975b8511c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A2=81=E4=BB=BB=E5=BD=A6?= Date: Fri, 24 Apr 2020 21:44:04 +0800 Subject: [PATCH 5/8] =?UTF-8?q?=E8=AE=A1=E7=AE=97=E6=BB=91=E5=8A=A8?= =?UTF-8?q?=E8=8C=83=E5=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../consecutivescroller/ConsecutiveScrollerLayout.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/consecutivescroller/src/main/java/com/donkingliang/consecutivescroller/ConsecutiveScrollerLayout.java b/consecutivescroller/src/main/java/com/donkingliang/consecutivescroller/ConsecutiveScrollerLayout.java index 4dae614..f1e6b06 100644 --- a/consecutivescroller/src/main/java/com/donkingliang/consecutivescroller/ConsecutiveScrollerLayout.java +++ b/consecutivescroller/src/main/java/com/donkingliang/consecutivescroller/ConsecutiveScrollerLayout.java @@ -1220,7 +1220,7 @@ public int computeVerticalScrollRange() { if (!ScrollUtils.isConsecutiveScrollerChild(child)) { range += child.getHeight(); } else { - range += Math.max(ScrollUtils.computeVerticalScrollRange(child) + child.getPaddingTop() + child.getPaddingBottom(), + range += Math.max(ScrollUtils.computeVerticalScrollRange(child), child.getHeight()); } } From 3090565cc67ad811200123d4a1ad99b1bda62074 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A2=81=E4=BB=BB=E5=BD=A6?= Date: Sun, 26 Apr 2020 17:28:49 +0800 Subject: [PATCH 6/8] =?UTF-8?q?=E8=87=AA=E5=8A=A8=E6=BB=91=E5=8A=A8?= =?UTF-8?q?=E7=9A=84=E8=BE=B9=E7=95=8C=E5=88=A4=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../consecutivescroller/ConsecutiveScrollerLayout.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/consecutivescroller/src/main/java/com/donkingliang/consecutivescroller/ConsecutiveScrollerLayout.java b/consecutivescroller/src/main/java/com/donkingliang/consecutivescroller/ConsecutiveScrollerLayout.java index f1e6b06..8d4105d 100644 --- a/consecutivescroller/src/main/java/com/donkingliang/consecutivescroller/ConsecutiveScrollerLayout.java +++ b/consecutivescroller/src/main/java/com/donkingliang/consecutivescroller/ConsecutiveScrollerLayout.java @@ -619,7 +619,7 @@ private void scrollUp(int offset) { // 如果是要滑动到指定的View,判断滑动到目标位置,就停止滑动 if (mScrollToIndex != -1) { View view = getChildAt(mScrollToIndex); - if (getScrollY() + getPaddingTop() >= view.getTop()) { + if (getScrollY() + getPaddingTop() >= view.getTop() || isScrollBottom()) { mScrollToIndex = -1; mSmoothScrollOffset = 0; break; @@ -670,8 +670,8 @@ private void scrollDown(int offset) { // 如果是要滑动到指定的View,判断滑动到目标位置,就停止滑动 if (mScrollToIndex != -1) { View view = getChildAt(mScrollToIndex); - if (getScrollY() + getPaddingTop() <= view.getTop() - && ScrollUtils.getScrollTopOffset(view) >= 0) { + if ((getScrollY() + getPaddingTop() <= view.getTop() + && ScrollUtils.getScrollTopOffset(view) >= 0) || isScrollTop()) { mScrollToIndex = -1; mSmoothScrollOffset = 0; break; From d624197c92c0f14a131b6b63f8f1c7dca4d4a647 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A2=81=E4=BB=BB=E5=BD=A6?= Date: Sun, 26 Apr 2020 18:00:15 +0800 Subject: [PATCH 7/8] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f09237d..dd6f9fb 100644 --- a/README.md +++ b/README.md @@ -20,12 +20,12 @@ allprojects { 在Module的build.gradle在添加以下代码 ```groovy // 使用了Androidx -implementation 'com.github.donkingliang:ConsecutiveScroller:2.4.0' +implementation 'com.github.donkingliang:ConsecutiveScroller:2.5.0' // 或者 // 使用Android support包 -implementation 'com.github.donkingliang:ConsecutiveScroller:1.4.0' +implementation 'com.github.donkingliang:ConsecutiveScroller:1.5.1' ``` 由于Androidx和Android support包不兼容,所以ConsecutiveScroller使用两个版本分别支持使用Androidx和使用Android support包的项目。 大版本号1使用Android support包,大版本号2使用Androidx。 @@ -301,7 +301,9 @@ public class MyFrameLayout extends FrameLayout implements IConsecutiveScroller { ``` 这样ConsecutiveScrollerLayout就能正确地处理ScrollView的滑动。这是一个简单的例子,在实际的需求中,我们一般不需要这样做。 -**注意:** getCurrentScrollerView()和getScrolledViews()必须正确地返回需要滑动的view,这些view可以是经过多层嵌套的,不一定是直接子view。所以使用者应该按照自己的实际场景去实现者两个方法。 +**注意:** +1、getCurrentScrollerView()和getScrolledViews()必须正确地返回需要滑动的view,这些view可以是经过多层嵌套的,不一定是直接子view。所以使用者应该按照自己的实际场景去实现者两个方法。 +2、滑动的控件应该跟嵌套它的子view的高度保持一致,也就是说滑动的控件高度应该是match_parent,并且包裹它的子view和它本身都不要设置上下边距(margin和ppadding)。宽度没有这个限制。 #### 对ViewPager的支持 IConsecutiveScroller的一个常用的场景是对ViewPager的支持。ViewPager是左右滑动的控件,但是我们一般会在ViewPager下嵌套RecyclerView等列表布局。为了能让ConsecutiveScrollerLayout正确地滑动ViewPager下的RecyclerView,使RecyclerView与ConsecutiveScrollerLayout形成一个滑动整体。需要让ViewPager实现IConsecutiveScroller接口,并返回需要滑动的RecyclerView。 From 15f5428e7ea115660db6dc1b54a4dc7ae95a1377 Mon Sep 17 00:00:00 2001 From: donkingliang Date: Sun, 26 Apr 2020 18:01:38 +0800 Subject: [PATCH 8/8] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index dd6f9fb..c5cb093 100644 --- a/README.md +++ b/README.md @@ -302,7 +302,9 @@ public class MyFrameLayout extends FrameLayout implements IConsecutiveScroller { 这样ConsecutiveScrollerLayout就能正确地处理ScrollView的滑动。这是一个简单的例子,在实际的需求中,我们一般不需要这样做。 **注意:** + 1、getCurrentScrollerView()和getScrolledViews()必须正确地返回需要滑动的view,这些view可以是经过多层嵌套的,不一定是直接子view。所以使用者应该按照自己的实际场景去实现者两个方法。 + 2、滑动的控件应该跟嵌套它的子view的高度保持一致,也就是说滑动的控件高度应该是match_parent,并且包裹它的子view和它本身都不要设置上下边距(margin和ppadding)。宽度没有这个限制。 #### 对ViewPager的支持