diff --git a/nestedtouch/src/main/java/mobile/yy/com/nestedtouch/StickyNestedLayout.kt b/nestedtouch/src/main/java/mobile/yy/com/nestedtouch/StickyNestedLayout.kt index 45aa779..b6d638e 100644 --- a/nestedtouch/src/main/java/mobile/yy/com/nestedtouch/StickyNestedLayout.kt +++ b/nestedtouch/src/main/java/mobile/yy/com/nestedtouch/StickyNestedLayout.kt @@ -4,12 +4,14 @@ import android.annotation.SuppressLint import android.content.Context import android.support.annotation.IdRes import android.support.annotation.MainThread +import android.support.annotation.Size import android.support.annotation.StringRes import android.support.v4.view.NestedScrollingChild2 import android.support.v4.view.NestedScrollingChildHelper import android.support.v4.view.NestedScrollingParent2 import android.support.v4.view.NestedScrollingParentHelper import android.support.v4.view.ViewCompat +import android.support.v4.view.ViewCompat.TYPE_NON_TOUCH import android.support.v4.view.ViewCompat.TYPE_TOUCH import android.util.AttributeSet import android.util.Log @@ -22,7 +24,9 @@ import android.view.ViewConfiguration import android.view.animation.Interpolator import android.widget.LinearLayout import android.widget.Scroller +import kotlin.math.abs import kotlin.math.min +import kotlin.math.roundToInt /** * 滑动冲突时起承上启下的作用: @@ -50,6 +54,7 @@ open class StickyNestedLayout : LinearLayout, * 是否正在嵌套滑动 */ private var isNestedScrollingStartedByChild = false + /** * 是否由当前View主动发起的嵌套滑动 */ @@ -61,9 +66,13 @@ open class StickyNestedLayout : LinearLayout, @Suppress("LeakingThis") private val childHelper = NestedScrollingChildHelper(this) + @Suppress("LeakingThis") private val parentHelper = NestedScrollingParentHelper(this) + @Size(2) + private val tempXY = IntArray(2) + constructor(context: Context) : super(context) constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) @@ -93,12 +102,15 @@ open class StickyNestedLayout : LinearLayout, override fun onFinishInflate() { super.onFinishInflate() - headView = findChildView(R.id.stickyHeadView, R.string.stickyHeadView, - "stickyHeadView") - navView = findChildView(R.id.stickyNavView, R.string.stickyNavView, - "stickyNavView") - contentView = findChildView(R.id.stickyContentView, R.string.stickyContentView, - "stickyContentView") + headView = findChildView( + R.id.stickyHeadView, R.string.stickyHeadView, "stickyHeadView" + ) + navView = findChildView( + R.id.stickyNavView, R.string.stickyNavView, "stickyNavView" + ) + contentView = findChildView( + R.id.stickyContentView, R.string.stickyContentView, "stickyContentView" + ) //让headView是可以收触摸事件的 dispatchTouchEvent才能处理滑动的事件 headView.isFocusable = true @@ -140,7 +152,11 @@ open class StickyNestedLayout : LinearLayout, setMeasuredDimension(measuredWidthAndState, measuredHeightAndState) } - private fun measureChildWithMargins(child: View, parentWidthMeasureSpec: Int, parentHeightMeasureSpec: Int) { + private fun measureChildWithMargins( + child: View, + parentWidthMeasureSpec: Int, + parentHeightMeasureSpec: Int + ) { val lp = child.layoutParams as MarginLayoutParams val childWidthMeasureSpec = getChildMeasureSpec( @@ -194,32 +210,58 @@ open class StickyNestedLayout : LinearLayout, } private var mScroller = Scroller(context, sQuinticInterpolator) - - /** - * 模拟惯性继续滑行 - * - * @param velocityY 当前的滚动速度 - */ - private fun startFling(velocityX: Float, velocityY: Float) { - mScroller.fling(0, scrollY, 0, Math.round(velocityY), - Int.MIN_VALUE, Int.MAX_VALUE, Int.MIN_VALUE, Int.MAX_VALUE) - ViewCompat.postInvalidateOnAnimation(this) - - //自己结束消费手势,交给parent消费 - dispatchNestedFling(velocityX, velocityY, true) - log { "stopNestedScroll" } - isNestedScrollingStartedByChild = false - isNestedScrollingStartedByThisView = false - stopNestedScroll() - } + private var lastFlingX = 0 + private var lastFlingY = 0 + private var inStateOfFling = false override fun computeScroll() { if (mScroller.computeScrollOffset()) { - scrollToWithUnConsumed(mScroller.currX, mScroller.currY, null) - ViewCompat.postInvalidateOnAnimation(this) + if (inStateOfFling) { //fling + val curY = mScroller.currY + val curX = mScroller.currX + var dy = curY - lastFlingX + var dx = curX - lastFlingY + lastFlingX = curY + lastFlingY = curX + if (dispatchNestedPreScroll(dx, dy, tempXY, null, TYPE_NON_TOUCH)) { + dx -= tempXY[0] + dy -= tempXY[1] + } + scrollByWithUnConsumed(0, dy, tempXY) + dispatchNestedScroll( + 0, dy - tempXY[1], + dx, tempXY[1], null, TYPE_NON_TOUCH + ) + } else { //scroll + scrollToWithUnConsumed(mScroller.currX, mScroller.currY, null) + } + + if (mScroller.isFinished) { + if (inStateOfFling) { + stopNestedScroll(TYPE_NON_TOUCH) + inStateOfFling = false + } + isNestedScrollingStartedByChild = false + isNestedScrollingStartedByThisView = false + } else { + ViewCompat.postInvalidateOnAnimation(this) + } } } + private fun fling(vx: Float, vy: Float) { + log { "startFling velocityY = $vy" } + mScroller.fling( + 0, scrollY, vx.roundToInt(), vy.roundToInt(), + Int.MIN_VALUE, Int.MAX_VALUE, Int.MIN_VALUE, Int.MAX_VALUE + ) + lastFlingX = 0 + lastFlingY = 0 + inStateOfFling = true + startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, TYPE_NON_TOUCH) + ViewCompat.postInvalidateOnAnimation(this) + } + // // @@ -234,41 +276,47 @@ open class StickyNestedLayout : LinearLayout, override fun isNestedScrollingEnabled() = childHelper.isNestedScrollingEnabled - override fun startNestedScroll(axes: Int) = childHelper.startNestedScroll(axes) + override fun startNestedScroll(axes: Int) = startNestedScroll(axes, TYPE_TOUCH) - override fun startNestedScroll(axes: Int, type: Int) = childHelper.startNestedScroll(axes, type) + override fun startNestedScroll(axes: Int, type: Int): Boolean { + log { "startNestedScroll $type" } + return childHelper.startNestedScroll(axes, type) + } - override fun stopNestedScroll(type: Int) = childHelper.stopNestedScroll(type) + override fun stopNestedScroll() = stopNestedScroll(TYPE_TOUCH) - override fun stopNestedScroll() = childHelper.stopNestedScroll() + override fun stopNestedScroll(type: Int) { + log { "stopNestedScroll $type" } + childHelper.stopNestedScroll(type) + } override fun dispatchNestedScroll( dxConsumed: Int, dyConsumed: Int, dxUnconsumed: Int, dyUnconsumed: Int, offsetInWindow: IntArray?, type: Int - ) = - childHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, - dyUnconsumed, offsetInWindow, type) + ) = childHelper.dispatchNestedScroll( + dxConsumed, dyConsumed, dxUnconsumed, + dyUnconsumed, offsetInWindow, type + ) override fun dispatchNestedScroll( dxConsumed: Int, dyConsumed: Int, dxUnconsumed: Int, dyUnconsumed: Int, offsetInWindow: IntArray? - ) = - childHelper.dispatchNestedScroll(dxConsumed, dyConsumed, - dxUnconsumed, dyUnconsumed, offsetInWindow) + ) = childHelper.dispatchNestedScroll( + dxConsumed, dyConsumed, + dxUnconsumed, dyUnconsumed, offsetInWindow + ) override fun dispatchNestedPreScroll( dx: Int, dy: Int, consumed: IntArray?, offsetInWindow: IntArray?, type: Int - ) = - childHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, type) + ) = childHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, type) override fun dispatchNestedPreScroll( dx: Int, dy: Int, consumed: IntArray?, offsetInWindow: IntArray? - ) = - childHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow) + ) = childHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow) override fun dispatchNestedFling(velocityX: Float, velocityY: Float, consumed: Boolean) = childHelper.dispatchNestedFling(velocityX, velocityY, consumed) @@ -286,9 +334,8 @@ open class StickyNestedLayout : LinearLayout, override fun onStopNestedScroll(child: View) = parentHelper.onStopNestedScroll(child) - override fun onStartNestedScroll(child: View, target: View, nestedScrollAxes: Int): Boolean { - return onStartNestedScroll(child, target, nestedScrollAxes, TYPE_TOUCH) - } + override fun onStartNestedScroll(child: View, target: View, nestedScrollAxes: Int): Boolean = + onStartNestedScroll(child, target, nestedScrollAxes, TYPE_TOUCH) override fun onNestedPreScroll(target: View, dx: Int, dy: Int, consumed: IntArray) { onNestedPreScroll(target, dx, dy, consumed, TYPE_TOUCH) @@ -301,51 +348,66 @@ open class StickyNestedLayout : LinearLayout, onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, TYPE_TOUCH) } + override fun onNestedPreFling(target: View, velocityX: Float, velocityY: Float): Boolean = + dispatchNestedPreFling(velocityX, velocityY) + + override fun onNestedFling( + target: View, + velocityX: Float, + velocityY: Float, + consumed: Boolean + ): Boolean = dispatchNestedFling(velocityX, velocityY, consumed) + + /** + * 记录当前开始中的嵌套滑动类型 + * @see TYPE_TOUCH + * @see TYPE_NON_TOUCH + */ + private val nestedScrollingType = mutableSetOf() + //child告诉我要开始嵌套滑动 override fun onStartNestedScroll(child: View, target: View, axes: Int, type: Int): Boolean { - if (type == TYPE_TOUCH) { - log { "onStartNestedScroll " } - isNestedScrollingStartedByThisView = false - isNestedScrollingStartedByChild = true - startNestedScroll(nestedScrollAxes or ViewCompat.SCROLL_AXIS_VERTICAL) //开始通知parent的嵌套滑动 - return true - } - return false + log { "onStartNestedScroll $type" } + nestedScrollingType.add(type) + isNestedScrollingStartedByThisView = false + isNestedScrollingStartedByChild = true + //开始通知parent的嵌套滑动 + startNestedScroll(nestedScrollAxes or ViewCompat.SCROLL_AXIS_VERTICAL, type) + return true } //child告诉我要停止嵌套滑动 override fun onStopNestedScroll(target: View, type: Int) { - if (type == TYPE_TOUCH) { - log { "onStopNestedScroll $target " } + log { "onStopNestedScroll $target, type = $type" } + nestedScrollingType.remove(type) + if (nestedScrollingType.isEmpty()) { isNestedScrollingStartedByThisView = false isNestedScrollingStartedByChild = false - stopNestedScroll() //结束parent的嵌套滑动 } + stopNestedScroll(type) //结束parent的嵌套滑动 } override fun onNestedPreScroll(target: View, dx: Int, dy: Int, consumed: IntArray?, type: Int) { - if (isNestedScrollingStartedByChild && type == TYPE_TOUCH) { + if (isNestedScrollingStartedByChild) { + log { "onNestedPreScroll dy = $dy, type = $type" } //dy > 0 上滑时处理 - val parentConsumed = IntArray(2) - val offset = IntArray(2) - dispatchNestedPreScroll(0, dy, parentConsumed, offset) //先分给parent搞事 + dispatchNestedPreScroll(dx, dy, tempXY, null) //先分给parent搞事 - val leftY = dy - parentConsumed[1] //parent留给我的 + val leftY = dy - tempXY[1] //parent留给我的 val headViewScrollDis = headViewHeight - scrollY - stickyOffsetHeight val headViewCanBeExpand = leftY > 0 && headViewScrollDis > 0 //上滑且headView能向上滚 - consumed?.set(0, parentConsumed[0]) //x方向全是parent吃的 + consumed?.set(0, tempXY[0]) //x方向全是parent吃的 if (headViewCanBeExpand) { if (leftY > headViewScrollDis) { //滑的距离超过了能滚的距离 scrollByWithUnConsumed(0, headViewScrollDis) - consumed?.set(1, headViewScrollDis + parentConsumed[1]) //只消费能滚的最大距离 + consumed?.set(1, headViewScrollDis + tempXY[1]) //只消费能滚的最大距离 } else { scrollByWithUnConsumed(0, leftY) //没超过滚的极限距离,那就滑多少滚多少 consumed?.set(1, dy) //把parent吃剩的全吃了 (parentConsumed[1] + leftY) } - } else { //headView不能滑了 全是parent吃的 - consumed?.set(1, parentConsumed[1]) + consumed?.set(1, tempXY[1]) } } } @@ -354,14 +416,15 @@ open class StickyNestedLayout : LinearLayout, target: View, dxConsumed: Int, dyConsumed: Int, dxUnconsumed: Int, dyUnconsumed: Int, type: Int ) { - if (isNestedScrollingStartedByChild && type == TYPE_TOUCH) { + if (isNestedScrollingStartedByChild) { + log { "onNestedScroll dyConsumed = $dyConsumed, dyUnconsumed = $dyUnconsumed, type = $type" } //dy < 0 下滑时处理 var dyUnconsumedAfterMe = dyUnconsumed var dyConsumedAfterMe = dyConsumed val headViewScrollDis = scrollY if (dyUnconsumed < 0 && headViewScrollDis >= 0) { //下滑而且headView能向下滚 - if (headViewScrollDis < Math.abs(dyUnconsumed)) { //滑动距离超过了可以滚的范围 + if (headViewScrollDis < abs(dyUnconsumed)) { //滑动距离超过了可以滚的范围 scrollByWithUnConsumed(0, -headViewScrollDis) //只滚我能滚的 dyUnconsumedAfterMe = dyUnconsumed + headViewScrollDis //只消费我能滑的 dyConsumedAfterMe = dyConsumed - headViewScrollDis @@ -372,37 +435,11 @@ open class StickyNestedLayout : LinearLayout, } } - dispatchNestedScroll(0, dyConsumedAfterMe, 0, - dyUnconsumedAfterMe, null) - } - } - - override fun onNestedPreFling(target: View, velocityX: Float, velocityY: Float): Boolean { - - //velocityY > 0 上滑 - if (dispatchNestedPreFling(velocityX, velocityY)) { //先给parent fling - return true - } - val headViewScrollDis = headViewHeight - scrollY - stickyOffsetHeight - if (velocityY > 0 && headViewScrollDis > 0) { //用户给了个向上滑动惯性 而且 headView还可以上滑 - startFling(velocityX, velocityY) - return true - } - return false - } - - override fun onNestedFling(target: View, velocityX: Float, velocityY: Float, consumed: Boolean): Boolean { - log { "onNestedFling vy = $velocityY, consumed = $consumed" } - //velocityY < 0 下滑 - val headViewScrollDis = scrollY - if (!consumed && - velocityY < 0 && - !target.canScrollVertically(-1) && - headViewScrollDis > 0) { - startFling(velocityX, velocityY) - return true + dispatchNestedScroll( + 0, dyConsumedAfterMe, 0, + dyUnconsumedAfterMe, null + ) } - return dispatchNestedFling(velocityX, velocityY, consumed) } // @@ -418,22 +455,20 @@ open class StickyNestedLayout : LinearLayout, private val gestureHandler = object : GestureDetector.SimpleOnGestureListener() { override fun onScroll( - e1: MotionEvent?, e2: MotionEvent, - distanceX: Float, distanceY: Float + e1: MotionEvent?, e2: MotionEvent, distanceX: Float, distanceY: Float ): Boolean { if (isNestedScrollingStartedByThisView) { - val scrollByHuman = Math.round(lastY - e2.y) //手势产生的距离 + val scrollByHuman = (lastY - e2.y).roundToInt() //手势产生的距离 log { "scroll y = ${e2.y} lastY = $lastY dy = $scrollByHuman" } - val consumedByParent = IntArray(2) - val offset = IntArray(2) + val consumedByParent = tempXY //先给parent消费 - dispatchNestedPreScroll(0, scrollByHuman, consumedByParent, offset) + dispatchNestedPreScroll(0, scrollByHuman, consumedByParent, null) val scrollAfterParent = scrollByHuman - consumedByParent[1] //parent吃剩的 - val unconsumed = IntArray(2) + val unconsumed = tempXY scrollByWithUnConsumed(0, scrollAfterParent, unconsumed) //自己滑 val consumeY = scrollByHuman - unconsumed[1] //滑剩的再给一次parent - dispatchNestedScroll(0, consumeY, 0, unconsumed[1], offset) + dispatchNestedScroll(0, consumeY, 0, unconsumed[1], null) } lastX = e2.x lastY = e2.y @@ -441,8 +476,7 @@ open class StickyNestedLayout : LinearLayout, } override fun onFling( - e1: MotionEvent?, e2: MotionEvent, - velocityX: Float, velocityY: Float + e1: MotionEvent?, e2: MotionEvent, velocityX: Float, velocityY: Float ): Boolean { log { "onFling velocity = $velocityY" } return onUpOrCancel(-velocityX, -velocityY) @@ -460,8 +494,12 @@ open class StickyNestedLayout : LinearLayout, if (isNestedScrollingStartedByThisView) { //根据当前速度 进行惯性滑行 //先让parent消费 - dispatchNestedPreFling(0f, velY) - startFling(velX, velY) + if (!dispatchNestedPreFling(velX, velY)) { + dispatchNestedFling(velX, velY, true) + + fling(velX, velY) + } + stopNestedScroll(TYPE_TOUCH) return true } return false @@ -475,7 +513,8 @@ open class StickyNestedLayout : LinearLayout, if (gestureDetector.onTouchEvent(event)) { return true } else if (action == MotionEvent.ACTION_UP || - action == MotionEvent.ACTION_CANCEL) { + action == MotionEvent.ACTION_CANCEL + ) { log { if (action == MotionEvent.ACTION_UP) "onUp" else "onCancel" } return gestureHandler.onUpOrCancel() } @@ -501,15 +540,17 @@ open class StickyNestedLayout : LinearLayout, downRawX = event.rawX isNestedScrollingStartedByThisView = false isNestedScrollingStartedByChild = false - startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL or - ViewCompat.SCROLL_AXIS_HORIZONTAL) + startNestedScroll( + ViewCompat.SCROLL_AXIS_VERTICAL or + ViewCompat.SCROLL_AXIS_HORIZONTAL + ) } MotionEvent.ACTION_MOVE -> { lastY = event.y lastX = event.x if (!isNestedScrollingStartedByChild) { - val dy = Math.abs(event.rawY - downRawY) - val dx = Math.abs(event.rawX - downRawX) + val dy = abs(event.rawY - downRawY) + val dx = abs(event.rawX - downRawX) if (dy > mTouchSlop && dy > 2 * dx) { isNestedScrollingStartedByThisView = true log { "onInterceptTouchEvent requestDisallowIntercept" } @@ -525,7 +566,6 @@ open class StickyNestedLayout : LinearLayout, private fun requestDisallowParentTouchEvent() { parent?.requestDisallowInterceptTouchEvent(true) } - // //