假设您创建的应用程序具有与您可以通过“滚动活动”向导创建的应用程序类似的用户界面,但您希望滚动标记具有对齐功能,如下所示:
<android.support.design.widget.CollapsingToolbarLayout ... app:layout_scrollFlags="scroll|exitUntilCollapsed|snap" >
事实证明,在许多情况下,它存在捕捉问题。有时UI不会捕捉到顶部/底部,使得CollapsingToolbarLayout保持在两者之间。
有时它也会尝试捕捉到一个方向,然后决定捕捉到另一个方向。
您可以在附加的视频here上看到这两个问题。
我认为这是我在RecyclerView上使用setNestedScrollingEnabled(false)时遇到的问题之一,所以我问了它here,但后来我注意到即使使用解决方案也没有使用它命令,甚至当使用简单的NestedScrollView(由向导创建)时,我仍然可以注意到这种行为。
这就是为什么我决定报告这个问题,here。
可悲的是,我找不到StackOverflow上那些奇怪错误的解决方法。
为什么会发生,更重要的是:如何在仍然使用它应该具有的行为时避免这些问题?
编辑:这是一个很好的改进Kotlin版本的接受答案:
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)
}
}
答案 0 :(得分:9)
<强>更新强>
我已稍微更改了代码以解决剩余问题 - 至少是我可以重现的问题。关键更新是仅在AppBar展开或折叠时处置dy
。在第一次迭代中,dispatchNestedPreScroll()
处理滚动而不检查折叠状态的AppBar的状态。
其他变化很小,属于清理类别。代码块在下面更新。
此答案解决了有关RecyclerView
的问题。我给出的另一个答案仍然适用于此处。 RecyclerView
与支持库的26.0.0-beta2中引入的NestedScrollView
具有相同的问题。
以下代码基于this answer相关问题,但包含针对AppBar不稳定行为的修复程序。我已经删除了修复奇数滚动的代码,因为它似乎不再需要了。
<强> AppBarTracking.java 强>
public interface AppBarTracking {
boolean isAppBarIdle();
boolean isAppBarExpanded();
}
<强> MyRecyclerView.java 强>
public class MyRecyclerView extends RecyclerView {
public MyRecyclerView(Context context) {
this(context, null);
}
public MyRecyclerView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyRecyclerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
private AppBarTracking mAppBarTracking;
private View mView;
private int mTopPos;
private LinearLayoutManager mLayoutManager;
@Override
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow,
int type) {
// App bar latching trouble is only with this type of movement when app bar is expanded
// or collapsed. In touch mode, everything is OK regardless of the open/closed status
// of the app bar.
if (type == ViewCompat.TYPE_NON_TOUCH && mAppBarTracking.isAppBarIdle()
&& isNestedScrollingEnabled()) {
// Make sure the AppBar stays expanded when it should.
if (dy > 0) { // swiped up
if (mAppBarTracking.isAppBarExpanded()) {
// Appbar can only leave its expanded state under the power of touch...
consumed[1] = dy;
return true;
}
} else { // swiped down (or no change)
// Make sure the AppBar stays collapsed when it should.
// Only dy < 0 will open the AppBar. Stop it from opening by consuming dy if needed.
mTopPos = mLayoutManager.findFirstVisibleItemPosition();
if (mTopPos == 0) {
mView = mLayoutManager.findViewByPosition(mTopPos);
if (-mView.getTop() + dy <= 0) {
// Scroll until scroll position = 0 and AppBar is still collapsed.
consumed[1] = dy - mView.getTop();
return true;
}
}
}
}
boolean returnValue = super.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, type);
// Fix the scrolling problems when scrolling is disabled. This issue existed prior
// to 26.0.0-beta2.
if (offsetInWindow != null && !isNestedScrollingEnabled() && offsetInWindow[1] != 0) {
offsetInWindow[1] = 0;
}
return returnValue;
}
@Override
public void setLayoutManager(RecyclerView.LayoutManager layout) {
super.setLayoutManager(layout);
mLayoutManager = (LinearLayoutManager) getLayoutManager();
}
public void setAppBarTracking(AppBarTracking appBarTracking) {
mAppBarTracking = appBarTracking;
}
@SuppressWarnings("unused")
private static final String TAG = "MyRecyclerView";
}
<强> ScrollingActivity.java 强>
public class ScrollingActivity extends AppCompatActivity
implements AppBarTracking {
private MyRecyclerView mNestedView;
private int mAppBarOffset;
private boolean mAppBarIdle = false;
private int mAppBarMaxOffset;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_scrolling);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
mNestedView = findViewById(R.id.nestedView);
final AppBarLayout appBar = findViewById(R.id.app_bar);
appBar.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
@Override
public final void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
mAppBarOffset = verticalOffset;
// mAppBarOffset = 0 if app bar is expanded; If app bar is collapsed then
// mAppBarOffset = mAppBarMaxOffset
// mAppBarMaxOffset is always <=0 (-AppBarLayout.getTotalScrollRange())
// mAppBarOffset should never be > zero or less than mAppBarMaxOffset
mAppBarIdle = (mAppBarOffset >= 0) || (mAppBarOffset <= mAppBarMaxOffset);
}
});
appBar.post(new Runnable() {
@Override
public void run() {
mAppBarMaxOffset = -appBar.getTotalScrollRange();
}
});
findViewById(R.id.disableNestedScrollingButton).setOnClickListener(new OnClickListener() {
@Override
public void onClick(final View v) {
// If the AppBar is fully expanded or fully collapsed (idle), then disable
// expansion and apply the patch; otherwise, set a flag to disable the expansion
// and apply the patch when the AppBar is idle.
setExpandEnabled(false);
}
});
findViewById(R.id.enableNestedScrollingButton).setOnClickListener(new OnClickListener() {
@Override
public void onClick(final View v) {
setExpandEnabled(true);
}
});
mNestedView.setAppBarTracking(this);
mNestedView.setLayoutManager(new LinearLayoutManager(this));
mNestedView.setAdapter(new Adapter() {
@Override
public ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) {
return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(
android.R.layout.simple_list_item_1,
parent,
false)) {
};
}
@SuppressLint("SetTextI18n")
@Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
((TextView) holder.itemView.findViewById(android.R.id.text1)).setText("item " + position);
}
@Override
public int getItemCount() {
return 100;
}
});
}
private void setExpandEnabled(boolean enabled) {
mNestedView.setNestedScrollingEnabled(enabled);
}
@Override
public boolean isAppBarExpanded() {
return mAppBarOffset == 0;
}
@Override
public boolean isAppBarIdle() {
return mAppBarIdle;
}
@SuppressWarnings("unused")
private static final String TAG = "ScrollingActivity";
}
这里发生了什么?
从问题来看,很明显,当用户的手指不在屏幕上时,布局无法将应用栏关闭或打开。拖动时,应用栏会按预期运行。
在版本26.0.0-beta2中,引入了一些新方法 - 特别是dispatchNestedPreScroll(),带有新的type
参数。 type
参数指定dx
和dy
指定的移动是由于用户触摸屏幕ViewCompat.TYPE_TOUCH
还是ViewCompat.TYPE_NON_TOUCH
而导致的。
虽然未确定导致问题的特定代码,但修复的方法是在需要时通过不让垂直移动传播来消除dispatchNestedPreScroll()
中的垂直移动(处置dy
)。实际上,应用栏在展开时将被锁定到位,并且在通过触摸手势关闭之前不允许开始关闭。关闭时,应用栏也会被锁定,直到RecyclerView
位于最顶层,并且在执行触摸手势时有足够的dy
打开应用栏。
所以,这并不像对有问题的条件的沮丧那么多。
MyRecyclerView
代码的最后一部分处理了此question中标识的问题,该问题涉及禁用嵌套滚动时的不正确滚动移动。这是调用dispatchNestedPreScroll()
超级用户后改变offsetInWindow[1]
值的部分。此代码背后的想法与问题的已接受答案中提供的内容相同。唯一的区别是,由于底层嵌套滚动代码已更改,因此参数offsetInWindow
有时为空。幸运的是,它在重要时似乎是非空的,所以最后一部分继续有效。
需要注意的是这个&#34;修复&#34;问题非常具体,不是一般解决方案。这个修复可能会有很短的保质期,因为我预计很快就会解决这个明显的问题。
答案 1 :(得分:9)
看起来onStartNestedScroll
和onStopNestedScroll
来电可以重新排序,导致“摇摆不定”。我在AppBarLayout.Behavior中做了一个小黑客。不要像其他答案所提出的那样,真的想要搞乱活动中的所有东西。
@SuppressWarnings("unused")
public class ExtAppBarLayoutBehavior extends AppBarLayout.Behavior {
private int mStartedScrollType = -1;
private boolean mSkipNextStop;
public ExtAppBarLayoutBehavior() {
super();
}
public ExtAppBarLayoutBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child, View directTargetChild, View target, int nestedScrollAxes, int type) {
if (mStartedScrollType != -1) {
onStopNestedScroll(parent, child, target, mStartedScrollType);
mSkipNextStop = true;
}
mStartedScrollType = type;
return super.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes, type);
}
@Override
public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout abl, View target, int type) {
if (mSkipNextStop) {
mSkipNextStop = false;
return;
}
if (mStartedScrollType == -1) {
return;
}
mStartedScrollType = -1;
// Always pass TYPE_TOUCH, because want to snap even after fling
super.onStopNestedScroll(coordinatorLayout, abl, target, ViewCompat.TYPE_TOUCH);
}
}
XML布局中的用法:
<android.support.design.widget.CoordinatorLayout>
<android.support.design.widget.AppBarLayout
app:layout_behavior="com.example.ExtAppBarLayoutBehavior">
<!-- Put here everything you usually add to AppBarLayout: CollapsingToolbarLayout, etc... -->
</android.support.design.widget.AppBarLayout>
<!-- Content: recycler for example -->
<android.support.v7.widget.RecyclerView
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
...
</android.support.design.widget.CoordinatorLayout>
RecyclerView
中很可能是问题的根本原因。现在没有机会深入挖掘。
答案 2 :(得分:5)
编辑代码已更新,使其更符合接受答案的代码。此答案涉及NestedScrollView
,而接受的答案约为RecyclerView
。
这是API 26.0.0-beta2版本中引入的问题。它不会发生在beta 1版本或API 25上。正如您所指出的,API 26.0.0也会发生这种情况。通常,问题似乎与beta2中如何处理flings和嵌套滚动有关。对嵌套滚动进行了重大改写(请参阅"Carry on Scrolling"),因此出现此类问题并不奇怪。
我的想法是在NestedScrollView
的某处没有正确处理过多的滚动。解决方法是悄悄地使用某些非触摸式的卷轴。在展开或折叠AppBar时滚动(type == ViewCompat.TYPE_NON_TOUCH
)。这会停止弹跳,允许捕捉,通常会使AppBar表现得更好。
ScrollingActivity
以跟踪AppBar的状态,以报告其是否已展开。一个新的类调用&#34; MyNestedScrollView&#34;覆盖dispatchNestedPreScroll()
(新的,请参阅here)来操纵多余滚动的消耗。
以下代码应足以阻止AppBarLayout
摆动并拒绝捕捉。 (XML也必须更改以容纳MyNestedSrollView
。以下仅适用于支持lib 26.0.0-beta2及更高版本。)
<强> AppBarTracking.java 强>
public interface AppBarTracking {
boolean isAppBarIdle();
boolean isAppBarExpanded();
}
<强> ScrollingActivity.java 强>
public class ScrollingActivity extends AppCompatActivity implements AppBarTracking {
private int mAppBarOffset;
private int mAppBarMaxOffset;
private MyNestedScrollView mNestedView;
private boolean mAppBarIdle = true;
@Override
protected void onCreate(Bundle savedInstanceState) {
AppBarLayout appBar;
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_scrolling);
final Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
appBar = findViewById(R.id.app_bar);
mNestedView = findViewById(R.id.nestedScrollView);
mNestedView.setAppBarTracking(this);
appBar.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
@Override
public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
mAppBarOffset = verticalOffset;
}
});
appBar.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
@Override
public final void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
mAppBarOffset = verticalOffset;
// mAppBarOffset = 0 if app bar is expanded; If app bar is collapsed then
// mAppBarOffset = mAppBarMaxOffset
// mAppBarMaxOffset is always <=0 (-AppBarLayout.getTotalScrollRange())
// mAppBarOffset should never be > zero or less than mAppBarMaxOffset
mAppBarIdle = (mAppBarOffset >= 0) || (mAppBarOffset <= mAppBarMaxOffset);
}
});
mNestedView.post(new Runnable() {
@Override
public void run() {
mAppBarMaxOffset = mNestedView.getMaxScrollAmount();
}
});
}
@Override
public boolean isAppBarIdle() {
return mAppBarIdle;
}
@Override
public boolean isAppBarExpanded() {
return mAppBarOffset == 0;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_scrolling, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
@SuppressWarnings("unused")
private static final String TAG = "ScrollingActivity";
}
<强> MyNestedScrollView.java 强>
public class MyNestedScrollView extends NestedScrollView {
public MyNestedScrollView(Context context) {
this(context, null);
}
public MyNestedScrollView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyNestedScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setOnScrollChangeListener(new View.OnScrollChangeListener() {
@Override
public void onScrollChange(View view, int x, int y, int oldx, int oldy) {
mScrollPosition = y;
}
});
}
private AppBarTracking mAppBarTracking;
private int mScrollPosition;
@Override
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow,
int type) {
// App bar latching trouble is only with this type of movement when app bar is expanded
// or collapsed. In touch mode, everything is OK regardless of the open/closed status
// of the app bar.
if (type == ViewCompat.TYPE_NON_TOUCH && mAppBarTracking.isAppBarIdle()
&& isNestedScrollingEnabled()) {
// Make sure the AppBar stays expanded when it should.
if (dy > 0) { // swiped up
if (mAppBarTracking.isAppBarExpanded()) {
// Appbar can only leave its expanded state under the power of touch...
consumed[1] = dy;
return true;
}
} else { // swiped down (or no change)
// Make sure the AppBar stays collapsed when it should.
if (mScrollPosition + dy < 0) {
// Scroll until scroll position = 0 and AppBar is still collapsed.
consumed[1] = dy + mScrollPosition;
return true;
}
}
}
boolean returnValue = super.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, type);
// Fix the scrolling problems when scrolling is disabled. This issue existed prior
// to 26.0.0-beta2. (Not sure that this is a problem for 26.0.0-beta2 and later.)
if (offsetInWindow != null && !isNestedScrollingEnabled() && offsetInWindow[1] != 0) {
Log.d(TAG, "<<<<offsetInWindow[1] forced to zero");
offsetInWindow[1] = 0;
}
return returnValue;
}
public void setAppBarTracking(AppBarTracking appBarTracking) {
mAppBarTracking = appBarTracking;
}
@SuppressWarnings("unused")
private static final String TAG = "MyNestedScrollView";
}
答案 3 :(得分:1)
由于到2020年2月该问题仍未得到解决(最新的材料库版本为1.2.0-alpha5),所以我想分享对有问题的AppBar动画的解决方案。
想法是通过扩展AppBarLayout.Behavior(科特林版)来实现自定义捕捉逻辑:
package com.example
import android.content.Context
import android.os.Handler
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import androidx.coordinatorlayout.widget.CoordinatorLayout
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.appbar.AppBarLayout.LayoutParams
@Suppress("unused")
class AppBarBehaviorFixed(context: Context?, attrs: AttributeSet?) :
AppBarLayout.Behavior(context, attrs) {
private var view: AppBarLayout? = null
private var snapEnabled = false
private var isUpdating = false
private var isScrolling = false
private var isTouching = false
private var lastOffset = 0
private val handler = Handler()
private val snapAction = Runnable {
val view = view ?: return@Runnable
val offset = -lastOffset
val height = view.run { height - paddingTop - paddingBottom - getChildAt(0).minimumHeight }
if (offset > 1 && offset < height - 1) view.setExpanded(offset < height / 2)
}
private val updateFinishDetector = Runnable {
isUpdating = false
scheduleSnapping()
}
private fun initView(view: AppBarLayout) {
if (this.view != null) return
this.view = view
// Checking "snap" flag existence (applied through child view) and removing it
val child = view.getChildAt(0)
val params = child.layoutParams as LayoutParams
snapEnabled = params.scrollFlags hasFlag LayoutParams.SCROLL_FLAG_SNAP
params.scrollFlags = params.scrollFlags removeFlag LayoutParams.SCROLL_FLAG_SNAP
child.layoutParams = params
// Listening for offset changes
view.addOnOffsetChangedListener(AppBarLayout.OnOffsetChangedListener { _, offset ->
lastOffset = offset
isUpdating = true
scheduleSnapping()
handler.removeCallbacks(updateFinishDetector)
handler.postDelayed(updateFinishDetector, 50L)
})
}
private fun scheduleSnapping() {
handler.removeCallbacks(snapAction)
if (snapEnabled && !isUpdating && !isScrolling && !isTouching) {
handler.postDelayed(snapAction, 50L)
}
}
override fun onLayoutChild(
parent: CoordinatorLayout,
abl: AppBarLayout,
layoutDirection: Int
): Boolean {
initView(abl)
return super.onLayoutChild(parent, abl, layoutDirection)
}
override fun onTouchEvent(
parent: CoordinatorLayout,
child: AppBarLayout,
ev: MotionEvent
): Boolean {
isTouching =
ev.actionMasked != MotionEvent.ACTION_UP && ev.actionMasked != MotionEvent.ACTION_CANCEL
scheduleSnapping()
return super.onTouchEvent(parent, child, ev)
}
override fun onStartNestedScroll(
parent: CoordinatorLayout,
child: AppBarLayout,
directTargetChild: View,
target: View,
nestedScrollAxes: Int,
type: Int
): Boolean {
val started = super.onStartNestedScroll(
parent, child, directTargetChild, target, nestedScrollAxes, type
)
if (started) {
isScrolling = true
scheduleSnapping()
}
return started
}
override fun onStopNestedScroll(
coordinatorLayout: CoordinatorLayout,
abl: AppBarLayout,
target: View,
type: Int
) {
isScrolling = false
scheduleSnapping()
super.onStopNestedScroll(coordinatorLayout, abl, target, type)
}
private infix fun Int.hasFlag(flag: Int) = flag and this == flag
private infix fun Int.removeFlag(flag: Int) = this and flag.inv()
}
现在将这种行为应用于xml中的AppBarLayout:
<android.support.design.widget.CoordinatorLayout>
<android.support.design.widget.AppBarLayout
app:layout_behavior="com.example.AppBarBehaviorFixed">
<com.google.android.material.appbar.CollapsingToolbarLayout
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
<!-- Toolbar declaration -->
</com.google.android.material.appbar.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<!-- Scrolling view (RecyclerView, NestedScrollView) -->
</android.support.design.widget.CoordinatorLayout>
那仍然是一个hack,但是它似乎运行得很好,它不需要在您的活动中放入肮脏的代码,也不需要扩展RecyclerView和NestedScrollView小部件(感谢@vyndor提供这个想法)。