RecyclerView 适合屏幕时不要折叠 Toolbar
Posted
技术标签:
【中文标题】RecyclerView 适合屏幕时不要折叠 Toolbar【英文标题】:Don't collapse Toolbar when RecyclerView fits the screen 【发布时间】:2015-12-01 00:30:01 【问题描述】:我使用 Android 设计库 制作了一个应用程序,带有一个工具栏和 TabLayout。 实际上存在 2 个选项卡,都带有 2 个 RecyclerView,滚动时会自动折叠工具栏。
我的问题是:当 RecyclerView 的项目很少且完全适合屏幕时(如 TAB 2 中),我可以禁用工具栏折叠吗?
我见过很多类似CheeseSquare 的例子,由 Google 员工制作,但问题仍然存在:即使 RecyclerView 只有 1 个项目,工具栏仍会在滚动时隐藏。
我想我可以找出 RecyclerView 的第一项是否在屏幕上可见,如果是,则禁用工具栏折叠。前者容易实现,后者呢?
这是我的布局:
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/coordinator_layout"
android:layout_
android:layout_>
<android.support.design.widget.AppBarLayout
android:layout_
android:layout_
android:fitsSystemWindows="true"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_
android:layout_
app:layout_scrollFlags="scroll|enterAlwaysCollapsed"
android:background="?attr/colorPrimary"
app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>
<android.support.design.widget.TabLayout
android:id="@+id/tab_layout"
android:layout_
android:layout_
android:background="@color/glucosio_pink"
app:tabSelectedTextColor="@android:color/white"
app:tabIndicatorColor="@color/glucosio_accent"
app:tabTextColor="#80ffffff"/>
</android.support.design.widget.AppBarLayout>
<android.support.v4.view.ViewPager
android:id="@+id/pager"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
android:layout_
android:layout_/>
<android.support.design.widget.FloatingActionButton
android:layout_
android:layout_
android:id="@+id/main_fab"
android:layout_margin="16dp"
android:onClick="onFabClicked"
app:backgroundTint="@color/glucosio_accent"
android:src="@drawable/ic_add_black_24dp"
android:layout_gravity="bottom|right"
/>
</android.support.design.widget.CoordinatorLayout>
【问题讨论】:
你能告诉我你是如何制作这个 gif 的吗? 嗨@PratikButani!我在互联网上找到了 gif (mzgreen.github.io/2015/06/23/…)。顺便说一句,您可以使用 gifs.com 之类的服务从 YouTube 视频中创建 gif。 用你的链接发给那个人 :) 愿他能帮助你。谢谢 :) twitter.com/pratik13butani/status/644355243174526976 期望它有什么作用?假设您在第一页折叠工具栏,然后滑动到第二页。应该发生什么?工具栏已隐藏,您无法显示它,因为项目太少。并且为了禁用工具栏滚动 - 有可能,我稍后会玩它。也许自定义行为将成为解决方案。 请将您的解决方案写成正确的答案,以便于查找。 【参考方案1】:最终解决方案(感谢 Michał Z.) 关闭/打开工具栏滚动的方法:
public void turnOffToolbarScrolling()
Toolbar mToolbar = (Toolbar) findViewById(R.id.toolbar);
AppBarLayout appBarLayout = (AppBarLayout) findViewById(R.id.appbar_layout);
//turn off scrolling
AppBarLayout.LayoutParams toolbarLayoutParams = (AppBarLayout.LayoutParams) mToolbar.getLayoutParams();
toolbarLayoutParams.setScrollFlags(0);
mToolbar.setLayoutParams(toolbarLayoutParams);
CoordinatorLayout.LayoutParams appBarLayoutParams = (CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams();
appBarLayoutParams.setBehavior(null);
appBarLayout.setLayoutParams(appBarLayoutParams);
public void turnOnToolbarScrolling()
Toolbar mToolbar = (Toolbar) findViewById(R.id.toolbar);
AppBarLayout appBarLayout = (AppBarLayout) findViewById(R.id.appbar_layout);
//turn on scrolling
AppBarLayout.LayoutParams toolbarLayoutParams = (AppBarLayout.LayoutParams) mToolbar.getLayoutParams();
toolbarLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS);
mToolbar.setLayoutParams(toolbarLayoutParams);
CoordinatorLayout.LayoutParams appBarLayoutParams = (CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams();
appBarLayoutParams.setBehavior(new AppBarLayout.Behavior());
appBarLayout.setLayoutParams(appBarLayoutParams);
找出 RecyclerView 的最后一项是否在我的 Fragment 中可见。 如果是,请禁用滚动:
public void updateToolbarBehaviour()
if (mLayoutManager.findLastCompletelyVisibleItemPosition() == items.size()-1)
((MainActivity) getActivity()).turnOffToolbarScrolling();
else
((MainActivity)getActivity()).turnOnToolbarScrolling();
【讨论】:
第一次需要延迟调用updateToolbarBehaviour()
(使用Handler-Runnable)。否则,findLastCompletelyVisibleItemPosition()
方法最初返回“-1”。
这个解决方案仍然有效。并用于折叠工具栏。
一个简单的逻辑东西的大量代码。在带有工具栏的活动中使用片段时,它变得更加复杂。【参考方案2】:
RecyclerView
现在(从 23.2 版开始)支持wrap_content
。只需使用wrap_content
作为高度。
【讨论】:
不幸的是,当我用RecyclerView
包装 SwipeRefreshLayout
时它对我不起作用
这对我使用 NestedScrollView 有效。谢谢!
这看起来是最好的解决方案。我不确定这是 coordinator/recyclerView 的一个完全假定的功能,但它绝对可以在没有开发人员的情况下使用。在 wrap_content 中,如果它的视口没有被元素填充,回收器不会触发滚动事件。
也为我工作!重要的是要注意 RecyclerView 必须是 CoordinatorLayout 的第一个孩子(我的首先是在嵌套滚动视图中,这阻止了 android 理解滚动不是必需的)。【参考方案3】:
您可以检查RecyclerView
中的最后一项是否可见。如果不是,则使用此方法以编程方式关闭滚动:
//turn off scrolling
AppBarLayout.LayoutParams toolbarLayoutParams = (AppBarLayout.LayoutParams) mToolbar.getLayoutParams();
toolbarLayoutParams.setScrollFlags(0);
mToolbar.setLayoutParams(toolbarLayoutParams);
CoordinatorLayout.LayoutParams appBarLayoutParams = (CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams();
appBarLayoutParams.setBehavior(null);
appBarLayout.setLayoutParams(appBarLayoutParams);
【讨论】:
效果很好,谢谢!我已经用 off 和 on 方法更新了我的问题。 效果很好,但是如果视图的内容具有 app:layout_behavior="@string/appbar_scrolling_view_behavior" 并且还具有 layout_ 关闭滚动后内容不会居中 这只是为了禁用滚动,但如果为不同的片段重新启用相同的功能将不起作用,请以编程方式设置滚动标志。【参考方案4】:我采取了稍微不同的方法来解决这个问题。
我创建了一个自定义 AppBarBehavior,它根据单元格禁用其自身。
public class CustomAppBarBehavior extends AppBarLayout.Behavior
private RecyclerView recyclerView;
private boolean enabled;
public CustomAppBarBehavior()
public CustomAppBarBehavior(Context context, AttributeSet attrs)
super(context, attrs);
@Override
public boolean onInterceptTouchEvent(CoordinatorLayout parent, AppBarLayout child, MotionEvent ev)
updatedEnabled();
return enabled && super.onInterceptTouchEvent(parent, child, ev);
@Override
public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child, View directTargetChild, View target, int nestedScrollAxes)
return enabled && super.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes);
@Override
public boolean onNestedFling(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, float velocityX, float velocityY, boolean consumed)
return enabled && super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, consumed);
private void updatedEnabled()
enabled = false;
if(recyclerView != null)
RecyclerView.Adapter adapter = recyclerView.getAdapter();
if (adapter != null)
int count = adapter.getItemCount();
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
if (layoutManager != null)
int lastItem = 0;
if (layoutManager instanceof LinearLayoutManager)
LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;
lastItem = Math.abs(linearLayoutManager.findLastCompletelyVisibleItemPosition());
else if (layoutManager instanceof StaggeredGridLayoutManager)
StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) layoutManager;
int[] lastItems = staggeredGridLayoutManager.findLastCompletelyVisibleItemPositions(new int[staggeredGridLayoutManager.getSpanCount()]);
lastItem = Math.abs(lastItems[lastItems.length - 1]);
enabled = lastItem < count - 1;
public void setRecyclerView(RecyclerView recyclerView)
this.recyclerView = recyclerView;
然后在应用栏布局上设置自定义行为
appBarBehavior = new CustomAppBarBehavior();
CoordinatorLayout.LayoutParams appBarLayoutParams = (CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams();
appBarLayoutParams.setBehavior(appBarBehavior);
appBarLayout.setLayoutParams(appBarLayoutParams);
View pager 的 Last on page change 更新了 RecyclerView 上的行为
private ViewPager.OnPageChangeListener pageChangeListener = new ViewPager.OnPageChangeListener()
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels)
@Override
public void onPageSelected(final int position)
appBarLayout.setExpanded(true, true);
appBarLayout.post(new Runnable()
@Override
public void run()
appBarBehavior.setRecyclerView(childFragments.get(position).getRecyclerView());
);
@Override
public void onPageScrollStateChanged(int state)
;
这应该适用于更改数据集。
【讨论】:
真的很有趣。感谢分享。 哦,您还需要在旋转时重置 recyclerview,记住视图寻呼机在恢复后会给出索引 0,即使它不在该页面上。碎片是一种快乐。【参考方案5】:在您更改适配器中的数据后添加此代码:
recyclerView.afterMeasured
val isTurnedOff = recyclerView.turnOffNestedScrollingIfEnoughItems()
if (isTurnedOff) appBarLayout.setExpanded(true)
这是功能:
inline fun <T: View> T.afterMeasured(crossinline action: T.() -> Unit)
viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener
override fun onGlobalLayout()
viewTreeObserver.removeOnGlobalLayoutListener(this)
action()
)
fun RecyclerView.turnOffNestedScrollingIfEnoughItems(): Boolean
val lm = (layoutManager as LinearLayoutManager)
val count = if (lm.itemCount <= 0) 0 else lm.itemCount - 1
val isFirstVisible = lm.findFirstCompletelyVisibleItemPosition() == 0
val isLastItemVisible = lm.findLastCompletelyVisibleItemPosition() == count
isNestedScrollingEnabled = !(isLastItemVisible && isFirstVisible)
return isNestedScrollingEnabled.not()
【讨论】:
【参考方案6】:我想这是最好的解决方案。 您需要定义您的自定义 AppBarLayout 行为:
class CustomScrollingViewBehavior : AppBarLayout.Behavior
constructor() : super()
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
var shouldScroll = true
override fun onStartNestedScroll(coordinatorLayout: CoordinatorLayout, child: AppBarLayout, directTargetChild: View, target: View, axes: Int, type: Int): Boolean
return shouldScroll && when (target)
is NestedScrollView, is ScrollView, is RecyclerView ->
return target.canScrollVertically(1) || target.canScrollVertically(-1)
else -> super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, axes, type)
然后你只需要在你的布局中使用它作为 AppBarLayout 的属性:
...
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
android:layout_
android:layout_
app:layout_behavior=".CustomScrollingViewBehavior">
...
就是这样。
注意:自定义行为还支持完全禁用滚动 - 您只需将 shouldScroll 标志设置为 false
customScrollingViewBehavior.shouldScroll = false
【讨论】:
【参考方案7】:如果根据正确答案帮助 Kotlin 中的任何人,我为 Kotlin 做了这个:
fun changeToolbarScroll(isToScrolling: Boolean)
val params = toolbar.layoutParams as AppBarLayout.LayoutParams
val appBarLayoutParams = appBarLayout.layoutParams as
CoordinatorLayout.LayoutParams
params.scrollFlags = 0
toolbar.layoutParams = params
appBarLayoutParams.behavior = null
appBarLayout.layoutParams = appBarLayoutParams
if(isToScrolling)
params.scrollFlags =
AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL or
AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS
toolbar.layoutParams = params
appBarLayoutParams.behavior = AppBarLayout.Behavior()
appBarLayout.layoutParams = appBarLayoutParams
在我的例子中,我有一个 MainActivity 的问题,它由 2 个 Fragment 管理导航、工具栏和其他共享的东西,第一个 Framgent 使用 RecyclerView,第二个用于显示细节。 问题是当我设置一个菜单并从 MainAcitivity 更改 MenuItem 时
这听起来很傻而且完全合乎逻辑,但请记住在调用 supportFragmentManager.beginTransaction() 之前总是对 Menu 或 MenuItems 进行更改,否则更改片段不起作用或无法正确更新,无论如何.add、.replace()、show() 的变化...
fun showDetailImageFragment(searchImage: SearchImage)
val searchFragment =
supportFragmentManager.findFragmentByTag(SEARCH_IMAGES)
changeToolbarScroll(false)
if (supportActionBar != null)
supportActionBar!!.collapseActionView()
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
supportActionBar!!.title = getString(R.string.detail_image_title)
actionSearch.isVisible = false
actionNighMOde.isVisible = false
actionAppSetings.isVisible = false
actionAbout.isVisible = false
supportFragmentManager.beginTransaction()
.setCustomAnimations(
R.animator.fade_in,
R.animator.fade_out,
R.animator.fade_in,
R.animator.fade_out
)
.hide(searchFragment!!)
.add(
R.id.frameLayout,
DetailImageFragment().newInstance(searchImage)
).addToBackStack(null)
.commit()
【讨论】:
【参考方案8】:只需从
中删除滚动条app:layout_scrollFlags="scroll|enterAlways"
应该是这样的
app:layout_scrollFlags="enterAlways"
【讨论】:
【参考方案9】:您可以在您的 XML 中添加属性 layout_behaviour
,其值为 @string/appbar_scrolling_view_behavior
:
app:layout_behavior="@string/appbar_scrolling_view_behavior"
【讨论】:
【参考方案10】://turn off scrolling
AppBarLayout.LayoutParams toolbarLayoutParams = (AppBarLayout.LayoutParams) mToolbar.getLayoutParams();
toolbarLayoutParams.setScrollFlags(0);
mToolbar.setLayoutParams(toolbarLayoutParams);
【讨论】:
以上是关于RecyclerView 适合屏幕时不要折叠 Toolbar的主要内容,如果未能解决你的问题,请参考以下文章
在 ConstraintLayout 中滚动 RecyclerView 时如何折叠工具栏?
带有 MotionLayout 的 Android 折叠工具栏 - 当 RecyclerView 为空/不可滚动时禁用运动