Android布局:滑动FragmentContainerView时隐藏Toolbar的实现方案
方案一:让Fragment内的可滑动视图主动分发滚动事件
CoordinatorLayout的AppBarLayout滚动逻辑依赖子视图传递的滚动事件,FragmentContainerView本身不会触发滚动事件,因此需要让其内部的可滑动视图主动将事件传递给CoordinatorLayout:
WebView场景:
自定义WebView实现NestedScrollingChild接口,通过NestedScrollingChildHelper分发滚动事件:public class NestedScrollWebView extends WebView implements NestedScrollingChild { private final NestedScrollingChildHelper mChildHelper; private int mLastY; public NestedScrollWebView(Context context) { this(context, null); } public NestedScrollWebView(Context context, AttributeSet attrs) { this(context, attrs, android.R.attr.webViewStyle); } public NestedScrollWebView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mChildHelper = new NestedScrollingChildHelper(this); setNestedScrollingEnabled(true); } @Override public void setNestedScrollingEnabled(boolean enabled) { mChildHelper.setNestedScrollingEnabled(enabled); } @Override public boolean isNestedScrollingEnabled() { return mChildHelper.isNestedScrollingEnabled(); } @Override public boolean startNestedScroll(int axes) { return mChildHelper.startNestedScroll(axes); } @Override public void stopNestedScroll() { mChildHelper.stopNestedScroll(); } @Override public boolean hasNestedScrollingParent() { return mChildHelper.hasNestedScrollingParent(); } @Override public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) { return mChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow); } @Override public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) { return mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow); } @Override protected void onScrollChanged(int l, int t, int oldl, int oldt) { super.onScrollChanged(l, t, oldl, oldt); int dy = t - mLastY; startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL); dispatchNestedPreScroll(0, dy, null, null); mLastY = t; } }在Fragment布局中使用该自定义WebView,即可让CoordinatorLayout接收到滚动事件,驱动Toolbar隐藏/显示。
RecyclerView/ListView场景:
这类视图默认已实现NestedScrollingChild接口,只需确保布局中android:nestedScrollingEnabled="true"(默认开启),滚动事件会自动传递给CoordinatorLayout。
方案二:给FragmentContainerView自定义Behavior
如果不想修改Fragment内部视图,可给FragmentContainerView编写自定义Behavior,监听其内部子视图的滚动事件并主动通知AppBarLayout:
public class FragmentContainerScrollBehavior extends AppBarLayout.ScrollingViewBehavior { private View mChildView; public FragmentContainerScrollBehavior() { } public FragmentContainerScrollBehavior(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection) { boolean result = super.onLayoutChild(parent, child, layoutDirection); if (child instanceof FragmentContainerView) { mChildView = ((FragmentContainerView) child).getChildAt(0); if (mChildView != null) { setupScrollListener(); } } // 监听Fragment切换,更新子视图引用 parent.addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { if (child instanceof FragmentContainerView) { mChildView = ((FragmentContainerView) child).getChildAt(0); if (mChildView != null) { setupScrollListener(); } } }); return result; } private void setupScrollListener() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { mChildView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> { int dy = scrollY - oldScrollY; dispatchNestedScrollEvent(v, dy); }); } else { if (mChildView instanceof WebView) { ((WebView) mChildView).setOnScrollChangeListener((View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) -> { int dy = scrollY - oldScrollY; dispatchNestedScrollEvent(v, dy); }); } else if (mChildView instanceof RecyclerView) { ((RecyclerView) mChildView).addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); dispatchNestedScrollEvent(recyclerView, dy); } }); } } } private void dispatchNestedScrollEvent(View v, int dy) { if (v instanceof NestedScrollingChild) { ((NestedScrollingChild) v).startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL); ((NestedScrollingChild) v).dispatchNestedPreScroll(0, dy, null, null); } } }
在布局中给FragmentContainerView设置该Behavior:
app:layout_behavior="com.abc.myApp.features.main.FragmentContainerScrollBehavior"
方案三:修复NestedScrollView嵌套FragmentContainerView的显示问题
之前嵌套后视图不显示,核心原因是NestedScrollView未开启fillViewport属性,导致内容高度被压缩为0,按以下步骤修复:
- 修改布局,将FragmentContainerView嵌套进NestedScrollView:
<androidx.core.widget.NestedScrollView android:layout_width="match_parent" android:layout_height="match_parent" android:fillViewport="true" app:layout_behavior="@string/appbar_scrolling_view_behavior"> <androidx.fragment.app.FragmentContainerView android:id="@+id/nav_host_fragment" android:name="androidx.navigation.fragment.NavHostFragment" android:layout_width="match_parent" android:layout_height="match_parent" app:defaultNavHost="true" app:navGraph="@navigation/nav_graph" /> </androidx.core.widget.NestedScrollView>
- 确保Fragment的根布局高度设置为
match_parent或wrap_content,避免内容被截断。 - 对于WebView场景,需开启自适应设置:
webView.getSettings().setLoadWithOverviewMode(true); webView.getSettings().setUseWideViewPort(true);
注意:此方案可能导致内部可滚动视图与NestedScrollView出现双重滚动冲突,需根据实际场景禁用内部视图的滚动(如WebView设置setNestedScrollingEnabled(false))。
内容的提问来源于stack exchange,提问作者Franklin84

