【问题标题】:Don't collapse Toolbar when RecyclerView fits the screenRecyclerView 适合屏幕时不要折叠 Toolbar
【发布时间】: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_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>




【问题讨论】:

  • 你能告诉我你是如何制作这个 gif 的吗?
  • 嗨@PratikButani!我在互联网上找到了 gif (mzgreen.github.io/2015/06/23/…)。顺便说一句,您可以使用 gifs.com 之类的服务从 YouTube 视频中创建 gif。
  • 用你的链接发给那个人 :) 愿他能帮助你。谢谢 :) twitter.com/pratik13butani/status/644355243174526976
  • 期望它有什么作用?假设您在第一页折叠工具栏,然后滑动到第二页。应该发生什么?工具栏已隐藏,您无法显示它,因为项目太少。并且为了禁用工具栏滚动 - 有可能,我稍后会玩它。也许自定义行为将成为解决方案。
  • 请将您的解决方案写成正确的答案,以便于查找。

标签: android toolbar android-recyclerview android-appcompat android-design-library


【解决方案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_height="match_parent" 关闭滚动后内容不会居中
  • 这只是为了禁用滚动,但如果为不同的片段重新启用相同的功能将不起作用,请以编程方式设置滚动标志。
【解决方案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_width="match_parent"
                android:layout_height="56dp"
                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);
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2018-01-14
              • 1970-01-01
              • 1970-01-01
              • 2015-11-08
              • 2016-05-25
              • 1970-01-01
              相关资源
              最近更新 更多