การเพิ่มคำตอบอื่นที่นี่เนื่องจากคำตอบข้างต้นไม่ได้ตอบสนองความต้องการของฉันอย่างสมบูรณ์หรือทำงานได้ไม่ดีนัก อันนี้บางส่วนขึ้นอยู่กับความคิดแพร่กระจายที่นี่
ดังนั้นสิ่งนี้จะทำอย่างไร
สถานการณ์พุ่งลงด้านล่าง: หาก AppBarLayout ถูกยุบมันจะทำให้ RecyclerView พุ่งด้วยตัวเองโดยไม่ต้องทำอะไรเลย มิฉะนั้นจะยุบ AppBarLayout และป้องกันไม่ให้ RecyclerView ทำการพุ่ง ทันทีที่มันยุบตัว (จนถึงจุดที่ต้องการความเร็วที่กำหนด) และหากมีความเร็วเหลืออยู่ RecyclerView จะหมุนด้วยความเร็วเดิมลบด้วยสิ่งที่ AppBarLayout ใช้ในการยุบ
สถานการณ์พุ่งขึ้นไปข้างบน: หากออฟเซ็ตการเลื่อนของ RecyclerView ไม่ใช่ศูนย์มันจะหมุนด้วยความเร็วดั้งเดิม ทันทีที่เสร็จสิ้นและหากยังมีความเร็วเหลืออยู่ (เช่น RecyclerView เลื่อนไปที่ตำแหน่ง 0) AppBarLayout จะขยายตัวจนถึงจุดที่ความเร็วดั้งเดิมลบความต้องการที่เพิ่งหมดไป ไม่เช่นนั้น AppBarLayout จะขยายไปจนถึงจุดที่ความต้องการความเร็วดั้งเดิม
AFAIK นี่คือพฤติกรรมที่ระบุ
มีการสะท้อนจำนวนมากที่เกี่ยวข้องและเป็นธรรมเนียมที่ค่อนข้างสวย ยังไม่พบปัญหา มันเขียนไว้ใน Kotlin ด้วย แต่การทำความเข้าใจมันไม่ควรมีปัญหา คุณสามารถใช้ปลั๊กอิน IntelliJ Kotlin เพื่อคอมไพล์ไปยัง bytecode -> และถอดรหัสกลับไปที่ Java หากต้องการใช้งานให้วางไว้ในแพ็คเกจ android.support.v7.widget และตั้งเป็นพฤติกรรมของผู้ประสานงานของ AppBarLayout ของ CoordinatorLayout.LayoutParams ในโค้ด (หรือเพิ่มคอนสตรัคเตอร์ xml ที่เกี่ยวข้องหรือสิ่งอื่น ๆ )
/*
* Copyright 2017 Julian Ostarek
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.support.v7.widget
import android.support.design.widget.AppBarLayout
import android.support.design.widget.CoordinatorLayout
import android.support.v4.widget.ScrollerCompat
import android.view.View
import android.widget.OverScroller
class SmoothScrollBehavior(recyclerView: RecyclerView) : AppBarLayout.Behavior() {
// We're using this SplineOverScroller from deep inside the RecyclerView to calculate the fling distances
private val splineOverScroller: Any
private var isPositive = false
init {
val scrollerCompat = RecyclerView.ViewFlinger::class.java.getDeclaredField("mScroller").apply {
isAccessible = true
}.get(recyclerView.mViewFlinger)
val overScroller = ScrollerCompat::class.java.getDeclaredField("mScroller").apply {
isAccessible = true
}.get(scrollerCompat)
splineOverScroller = OverScroller::class.java.getDeclaredField("mScrollerY").apply {
isAccessible = true
}.get(overScroller)
}
override fun onNestedFling(coordinatorLayout: CoordinatorLayout?, child: AppBarLayout, target: View?, velocityX: Float, givenVelocity: Float, consumed: Boolean): Boolean {
// Making sure the velocity has the correct sign (seems to be an issue)
var velocityY: Float
if (isPositive != givenVelocity > 0) {
velocityY = givenVelocity * - 1
} else velocityY = givenVelocity
if (velocityY < 0) {
// Decrement the velocity to the maximum velocity if necessary (in a negative sense)
velocityY = Math.max(velocityY, - (target as RecyclerView).maxFlingVelocity.toFloat())
val currentOffset = (target as RecyclerView).computeVerticalScrollOffset()
if (currentOffset == 0) {
super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, false)
return true
} else {
val distance = getFlingDistance(velocityY.toInt()).toFloat()
val remainingVelocity = - (distance - currentOffset) * (- velocityY / distance)
if (remainingVelocity < 0) {
(target as RecyclerView).addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
recyclerView.post { recyclerView.removeOnScrollListener(this) }
if (recyclerView.computeVerticalScrollOffset() == 0) {
super@SmoothScrollBehavior.onNestedFling(coordinatorLayout, child, target, velocityX, remainingVelocity, false)
}
}
}
})
}
return false
}
}
// We're not getting here anyway, flings with positive velocity are handled in onNestedPreFling
return false
}
override fun onNestedPreFling(coordinatorLayout: CoordinatorLayout?, child: AppBarLayout, target: View?, velocityX: Float, givenVelocity: Float): Boolean {
// Making sure the velocity has the correct sign (seems to be an issue)
var velocityY: Float
if (isPositive != givenVelocity > 0) {
velocityY = givenVelocity * - 1
} else velocityY = givenVelocity
if (velocityY > 0) {
// Decrement to the maximum velocity if necessary
velocityY = Math.min(velocityY, (target as RecyclerView).maxFlingVelocity.toFloat())
val topBottomOffsetForScrollingSibling = AppBarLayout.Behavior::class.java.getDeclaredMethod("getTopBottomOffsetForScrollingSibling").apply {
isAccessible = true
}.invoke(this) as Int
val isCollapsed = topBottomOffsetForScrollingSibling == - child.totalScrollRange
// The AppBarlayout is collapsed, we'll let the RecyclerView handle the fling on its own
if (isCollapsed)
return false
// The AppbarLayout is not collapsed, we'll calculate the remaining velocity, trigger the appbar to collapse and fling the RecyclerView manually (if necessary) as soon as that is done
val distance = getFlingDistance(velocityY.toInt())
val remainingVelocity = (distance - (child.totalScrollRange + topBottomOffsetForScrollingSibling)) * (velocityY / distance)
if (remainingVelocity > 0) {
(child as AppBarLayout).addOnOffsetChangedListener(object : AppBarLayout.OnOffsetChangedListener {
override fun onOffsetChanged(appBarLayout: AppBarLayout, verticalOffset: Int) {
// The AppBarLayout is now collapsed
if (verticalOffset == - appBarLayout.totalScrollRange) {
(target as RecyclerView).mViewFlinger.fling(velocityX.toInt(), remainingVelocity.toInt())
appBarLayout.post { appBarLayout.removeOnOffsetChangedListener(this) }
}
}
})
}
// Trigger the expansion of the AppBarLayout
super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, false)
// We don't let the RecyclerView fling already
return true
} else return super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY)
}
override fun onNestedPreScroll(coordinatorLayout: CoordinatorLayout?, child: AppBarLayout?, target: View?, dx: Int, dy: Int, consumed: IntArray?) {
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed)
isPositive = dy > 0
}
private fun getFlingDistance(velocity: Int): Double {
return splineOverScroller::class.java.getDeclaredMethod("getSplineFlingDistance", Int::class.javaPrimitiveType).apply {
isAccessible = true
}.invoke(splineOverScroller, velocity) as Double
}
}