Фон
Предположим, что у вас есть приложение, которое вы создали с аналогичным пользовательским интерфейсом, как тот, который вы можете создать с помощью мастера "прокрутки", но вы хотите, чтобы флажки прокрутки имели привязку, как таковые:
<android.support.design.widget.CollapsingToolbarLayout ... app:layout_scrollFlags="scroll|exitUntilCollapsed|snap" >
Проблема
Как оказалось, во многих случаях он имеет проблемы с привязкой. Иногда пользовательский интерфейс не привязывается к вершине/дну, что делает CollapsingToolbarLayout находящимся между ними.
Иногда он также пытается привязать в одном направлении, а затем решает привязать к другому.
Вы можете увидеть обе проблемы на подключенном видео здесь.
Что я пробовал
Я думал, что это одна из проблем, которые возникают у меня, когда я использую setNestedScrollingEnabled (false) в RecyclerView внутри, поэтому я спросил об этом здесь, но потом я заметил, что даже с решением и без использования этой команды вообще и даже при использовании простого NestedScrollView (как создается мастером), я все еще замечаю это поведение.
Вот почему я решил сообщить об этом как о проблеме, здесь.
К сожалению, я не мог найти обходных путей для этих странных ошибок здесь, в StackOverflow.
Вопрос
Почему это происходит, и что еще более важно: как я могу избежать этих проблем, сохраняя при этом поведение, которое он должен иметь?
EDIT: здесь хорошая улучшенная версия Котлина принятого ответа:
class RecyclerViewEx @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) : RecyclerView(context, attrs, defStyle) {
private var mAppBarTracking: AppBarTracking? = null
private var mView: View? = null
private var mTopPos: Int = 0
private var mLayoutManager: LinearLayoutManager? = null
interface AppBarTracking {
fun isAppBarIdle(): Boolean
fun isAppBarExpanded(): Boolean
}
override fun dispatchNestedPreScroll(dx: Int, dy: Int, consumed: IntArray?, offsetInWindow: IntArray?, type: Int): Boolean {
if (mAppBarTracking == null)
return super.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, type)
if (type == ViewCompat.TYPE_NON_TOUCH && mAppBarTracking!!.isAppBarIdle()
&& isNestedScrollingEnabled) {
if (dy > 0) {
if (mAppBarTracking!!.isAppBarExpanded()) {
consumed!![1] = dy
return true
}
} else {
mTopPos = mLayoutManager!!.findFirstVisibleItemPosition()
if (mTopPos == 0) {
mView = mLayoutManager!!.findViewByPosition(mTopPos)
if (-mView!!.top + dy <= 0) {
consumed!![1] = dy - mView!!.top
return true
}
}
}
}
if (dy < 0 && type == ViewCompat.TYPE_TOUCH && mAppBarTracking!!.isAppBarExpanded()) {
consumed!![1] = dy
return true
}
val returnValue = super.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, type)
if (offsetInWindow != null && !isNestedScrollingEnabled && offsetInWindow[1] != 0)
offsetInWindow[1] = 0
return returnValue
}
override fun setLayoutManager(layout: RecyclerView.LayoutManager) {
super.setLayoutManager(layout)
mLayoutManager = layoutManager as LinearLayoutManager
}
fun setAppBarTracking(appBarTracking: AppBarTracking) {
mAppBarTracking = appBarTracking
}
fun setAppBarTracking(appBarLayout: AppBarLayout) {
val appBarIdle = AtomicBoolean(true)
val appBarExpanded = AtomicBoolean()
appBarLayout.addOnOffsetChangedListener(object : AppBarLayout.OnOffsetChangedListener {
private var mAppBarOffset = Integer.MIN_VALUE
override fun onOffsetChanged(appBarLayout: AppBarLayout, verticalOffset: Int) {
if (mAppBarOffset == verticalOffset)
return
mAppBarOffset = verticalOffset
appBarExpanded.set(verticalOffset == 0)
appBarIdle.set(mAppBarOffset >= 0 || mAppBarOffset <= -appBarLayout.totalScrollRange)
}
})
setAppBarTracking(object : AppBarTracking {
override fun isAppBarIdle(): Boolean = appBarIdle.get()
override fun isAppBarExpanded(): Boolean = appBarExpanded.get()
})
}
override fun fling(velocityX: Int, inputVelocityY: Int): Boolean {
var velocityY = inputVelocityY
if (mAppBarTracking != null && !mAppBarTracking!!.isAppBarIdle()) {
val vc = ViewConfiguration.get(context)
velocityY = if (velocityY < 0) -vc.scaledMinimumFlingVelocity
else vc.scaledMinimumFlingVelocity
}
return super.fling(velocityX, velocityY)
}
}