当RecyclerView适应屏幕时,不要折叠工具栏。

44


我使用了Android Design Library创建了一个应用程序,其中包含一个Toolbar和TabLayout。实际上有两个选项卡,都带有2个RecyclerView,在滚动时会自动折叠工具栏。

我的问题是:当RecyclerView只有少量项目并完全适合屏幕时(例如在第二个选项卡中),我可以禁用工具栏折叠吗?

我看到了很多示例,比如Google员工制作的CheeseSquare,问题仍然存在:即使RecyclerView只有一个项目,工具栏仍会在滚动时隐藏。

enter image description here

我认为我只需找出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_width="match_parent"
    android:layout_height="match_parent">

        <android.support.design.widget.AppBarLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:fitsSystemWindows="true"
            android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            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_width="match_parent"
            android:layout_height="wrap_content"
            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_width="match_parent"
            android:layout_height="wrap_content"/>

    <android.support.design.widget.FloatingActionButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        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>





1
嗨@PratikButani!我在互联网上找到了这个gif(https://mzgreen.github.io/2015/06/23/How-to-hideshow-Toolbar-when-list-is-scrolling(part3)/)。顺便说一下,您可以使用像https://gifs.com/这样的服务从YouTube视频中创建gif。 - Paolo Rotolo
你期望它如何工作?比如说,你在第一页折叠了工具栏,然后你滑动到第二页。应该发生什么?工具栏被隐藏了,因为项目太少而无法显示。至于禁用工具栏滚动——这可能是可能的,我稍后会尝试一下。也许自定义行为会是解决方案。 - Michał Z.
@MichałZ。我至少希望在Recycler只有一个项目时禁用折叠。看起来Whatsapp已经以某种方式解决了这个问题。 - Paolo Rotolo
1
请在适当的答案中编写您的解决方案,以便更容易找到。 - PicPuc
将我的最终解决方案移动到一个适当的答案中。谢谢。 - Paolo Rotolo
显示剩余2条评论
10个回答

31

最终解决方案(感谢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();
    }
}

3
你需要使用Handler-Runnable延迟调用updateToolbarBehaviour()方法,以便第一次调用时能正常运行。否则,findLastCompletelyVisibleItemPosition()方法在初始状态下会返回“-1”。 - Ugurcan Yildirim
该解决方案仍在运行中。并且对于折叠工具栏也适用。 - Pankaj Kumar
一个简单的逻辑功能需要大量的代码。当在带有工具栏的活动中使用片段时,情况会变得更加复杂。 - Sai

14

RecyclerView现在(自23.2版本以来)支持wrap_content。只需将高度设置为wrap_content即可。


4
很遗憾,当我使用SwipeRefreshLayout包装RecyclerView时,它对我无效。 - Artem_Iens
这对我来说很有效,使用了NestedScrollView。谢谢! - rjr-apps
这看起来是最好的解决方案。我不确定这是否完全是协调器/RecyclerView的默认功能,但它绝对可以在没有开发的情况下工作。当使用wrap_content时,如果视口中没有填充元素,则Recycler不会触发滚动事件。 - AlexG
也适用于我!需要注意的是,RecyclerView 必须是 CoordinatorLayout 的第一个子元素(我的 RecyclerView 最初在嵌套滚动视图中,这导致 Android 无法理解不需要滚动)。 - A.Mamode

8
您可以检查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);

很好,谢谢!我已经更新了我的问题,并提供了开和关的两种方法。 - Paolo Rotolo
功能正常,但如果视图的内容具有app:layout_behavior="@string/appbar_scrolling_view_behavior"属性,并且还具有layout_height="match_parent"属性,则在关闭滚动后,内容将无法居中。 - Earwin delos Santos
1
这个功能可以很好地禁用滚动,但如果要为不同的片段重新启用它,则在以编程方式设置滚动标志时无法正常工作。 - pallavi

6
我采用了略有不同的方法来解决这个问题。
我创建了一个自定义的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);

上一页更改了视图页的行为,更新了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) { }
    };

这应该可以适用于更改数据集。


非常有趣。谢谢分享。 - Paolo Rotolo
哦,另外你需要在旋转时重置recyclerview,记住view pager在恢复后即使不在那一页也会给出索引0。Fragments真是太棒了。 - Stuart Campbell

3
在你的适配器更改数据后,请添加以下代码:
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()
}

1

我想这是最好的解决方案。 您需要定义自定义的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_width="match_parent"
            android:layout_height="56dp"
            app:layout_behavior=".CustomScrollingViewBehavior">

...

就是这样。

注意:自定义行为还支持完全禁用滚动 - 您只需要将shouldScroll标志设置为false即可。

customScrollingViewBehavior.shouldScroll = false

0
如果有人需要基于正确答案的 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共享的内容,第一个Fragment使用RecyclerView,第二个Fragment用于显示详细信息。问题是当我设置菜单并从MainActivity更改MenuItem时。
这听起来可能很傻,但请记住,在更改片段时,始终在调用supportFragmentManager.beginTransaction()之前对菜单或菜单项进行更改,否则不起作用,或者不更新正确,无论更改.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()
}

-2

您可以在XML中添加属性layout_behaviour,并将其值设置为@string/appbar_scrolling_view_behavior,如下所示:

app:layout_behavior="@string/appbar_scrolling_view_behavior"

-2

只需从中删除滚动条

app:layout_scrollFlags="scroll|enterAlways"

应该是这样的

app:layout_scrollFlags="enterAlways"

-3
//turn off scrolling
AppBarLayout.LayoutParams toolbarLayoutParams = (AppBarLayout.LayoutParams) mToolbar.getLayoutParams();
toolbarLayoutParams.setScrollFlags(0);
mToolbar.setLayoutParams(toolbarLayoutParams);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接