CollapsingToolbarLayout causes RecyclerView's bottom to be underscreen - android

I have AppBarLayout with fixed height (#dimen/app_bar_height = 200dp), which has CollapsingToolbarLayout in it. When scrolling down, part of recyclerview is hidden under screen bottom.
If I remove scroll flags (ie. disable scrolling collapse) I remove app:layout_scrollFlags="scroll|exitUntilCollapsed|snap" from CollapsingToolbarLayout then it is normally aligning screen bottom.
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:ignore="MergeRootFrame">
<com.google.android.material.appbar.AppBarLayout
android:id="#+id/app_bar"
android:layout_width="match_parent"
android:layout_height="#dimen/app_bar_height"
android:fitsSystemWindows="true"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar">
<!-- Problem HERE in app:layout_scrollFlags -->
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="#+id/toolbar_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
app:expandedTitleGravity="bottom"
app:contentScrim="?attr/colorPrimary"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
app:toolbarId="#+id/toolbar">
<TextView
android:text="Hello, Hello, Hello, Hello, Hello, Hello, Hello, Hello, Hello, Hello"
android:textSize="19sp"
android:textStyle="bold"
android:id="#+id/txtDescr"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_collapseMode="parallax"
app:layout_collapseParallaxMultiplier="0.5"/>
<androidx.appcompat.widget.Toolbar
android:id="#+id/detail_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin"
app:popupTheme="#style/ThemeOverlay.AppCompat.Light" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="#+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|start"
android:layout_margin="#dimen/fab_margin"
app:layout_collapseMode="pin"
app:srcCompat="#android:drawable/stat_notify_chat" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recycler_view"
android:layout_width="match_parent"
android:scrollbars="vertical"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
android:scrollbarThumbVertical="#android:color/darker_gray"
android:scrollbarSize="7dp"
android:layout_height="match_parent"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

Tried your code multiple times with no luck. Finally found a workaround. Just add the height of view which you want to pin at the top as margin-bottom to the recyclerView keeping recyclerView's height as wrap_content.
For Ex. If you want to pin your toolbar then add it's height as margin-bottom to the recycler view
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recycler_view"
android:layout_width="match_parent"
android:scrollbars="vertical"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
android:scrollbarThumbVertical="#android:color/darker_gray"
android:scrollbarSize="7dp"
android:layout_height="wrap_content"
android:layout_marginBottom="?attr/actionBarSize"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
Hope this solves your problem.

I ended up using the followiing, to detect last item scroll and notifyDataSetChanged(), that solves the issue :
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
private boolean hasFixedLastItemNotVisible = false;
#Override
public void onScrollStateChanged(#NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (!hasFixedLastItemNotVisible &&
!recyclerView.canScrollVertically(10) &&
newState==RecyclerView.SCROLL_STATE_IDLE) {
hasFixedLastItemNotVisible = true;
recyclerView.getAdapter().notifyDataSetChanged();
}
}
});

My guess is that you are loading the items in the RecyclerView too soon, before the CoordinatorLayout has finished measuring the AppbarLayout. This may happen if you have your list of items immediately available in your onViewCreated and you create and assign your adapter right away.
I don't know how you setup your RecyclerView in your code, but I had this same problem and I solved it by using View.post(Runnable):
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val adapter = MyAdapter(viewModel.items)
// replace:
// recyclerView.adapter = adapter
// with:
view.post { recyclerView.adapter = adapter }
}
Also note that if you happened to use a subclass of ListAdapter, which works asynchronously from the main thread, infact you would likely not have had this issue.

when scrolling down recycleview....if you dont want a toolbar as recycleview is getting hidden behind toolbar....
use this--> app:layout_scrollFlags="scroll|enterAlways|enterAlwaysCollapsed"
so your collapsingtoolbarlayout code will be-->
<com.google.android.material.appbar.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="350dp"
app:layout_scrollFlags="scroll|enterAlways|enterAlwaysCollapsed"
app:contentScrim="?attr/colorPrimary"
app:expandedTitleTextAppearance="#android:color/transparent"
android:fitsSystemWindows="true"
>

Related

How to make content draw behind a non full width Toolbar (Like new Gmail or Google App)

What I'm trying to implement is a Toolbar that is not full width (has a margin on all sides of 16dp) like the following:
Gmail - Please note that the RecyclerView can be seen scrolling behind the toolbar
Google App - Same thing, the cards can be seen behind the toolbar.
Additionally, these toolbars hide when scrolling down and appear when scrolling up.
The content of the toolbar is not what I'm worried about right now.
I'm assuming this is done using a Coordinator Layout so this is the skeleton I have:
Coordinator Layout
AppBarLayout
Toolbar
NestedScrollView (appbar_scrolling_view_behavior)
ConstraintLayout
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true"
tools:context=".MainContentFragment">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_margin="32dp"
android:background="#android:color/transparent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="#android:color/holo_red_dark"
app:layout_scrollFlags="scroll|enterAlways">
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior">
...
This is the output before scrolling
And after scrolling down
As you can see the space outside the toolbar is not transparent but has that grey background, since the content is going under it and cannot be seen.
The idea behind showing the content behind the toolbar:
Setting a negative margin to the scrolling view (NestedScrollView/RecyclerView) which equals to what you want the size of the blank area when all the content are scrolled up (all the stuff below the toolbar, nothing behind); it's assumed -100dp in the below demo
Reverse back this negative margin on the child of the NestedScrollView (in layout), or the 1st child of the RecyclerView (programmatically)
Put the scrolling view on top of the AppBarLayout so that the background of the AppBarLayout don't obscure the scrolling content to let them appear in the back of the toolbar.
Cosmetics:
Remove the elevation of the AppBarLayout to 0 to make it as not existing; just the ToolBar is there.
Set the background color of the AppBarLayout to transparent, so now it takes the background of the root layout.
Demo:
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".MainActivity">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="-100dp"
app:layout_behavior="#string/appbar_scrolling_view_behavior">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="100dp"
android:text="#string/longText" />
</androidx.core.widget.NestedScrollView>
<com.google.android.material.appbar.AppBarLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#android:color/transparent"
app:elevation="0dp">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:background="#drawable/rounded_toolbar"
app:layout_scrollFlags="scroll|enterAlways">
<com.google.android.material.appbar.MaterialToolbar
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
For the RecyclerView, you can't control its children with layouts, so reversing the margin can either by
Having 2 layouts one with the 100dp top margin for the first child, another without margins for the rest children. And decide which layout in the adapter's onCreateViewHolder()
Adding the margin in adapter's onBindViewHolder():
#Override
public void onBindViewHolder(#NonNull CustomViewHolder holder, final int position) {
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) holder.itemView.getLayoutParams();
if (position == 0) {
Resources resources = holder.itemView.getContext().getResources();
float dip = 100f;
float px = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
dip,
resources.getDisplayMetrics()
);
params.topMargin = (int) px;
} else
params.topMargin = 0;
holder.itemView.setLayoutParams(params);
//...... rest of code
}
You should just wrap all widgets inside CoordinatorLayout with FrameLayout and change AppBarLayout position to the end:
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true"
tools:context=".MainContentFragment">
<FrameLayout android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior">
...
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_margin="32dp"
android:background="#android:color/transparent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="#android:color/holo_red_dark"
app:layout_scrollFlags="scroll|enterAlways">
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.AppBarLayout>
</FrameLayout>
<androidx.coordinatorlayout.widget.CoordinatorLayout

Nested RecyclerView scrolling issue

In my application I have vertical parent RecyclerView with few horizontal childs inside its ViewHolders. But I have pretty annoying scrolling issue - after I scroll parent RV vertically I want to scroll one of my child RVs but parent just intercepts all motion events until I remove my finger from screen and then put it back. Here's the example of this annoying behaviour.
https://i.imgur.com/dPtmAXD.gif
I tried every solution from this question - Nested RecyclerView. How to prevent parent RecyclerView from getting scrolled while child RecyclerView is scrolling?
Nothing works for me.
It looks like Google Play Market has the same RV hierarchy, but ofc scroll is perfectly fine. I tried to implement few solutions from other topics, but nothing works as intended.
I don't know what code should I post, but here's my Parent RV's ViewHolder example with nested RV.
private class UserEventsViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
private RecyclerView rvUserEvents;
private HomeUserEventsRVAdapter rvAdapter;
public UserEventsViewHolder(View v) {
super(v);
rvUserEvents = v.findViewById(R.id.rv_user_events);
rvUserEvents.setLayoutManager(new LinearLayoutManager(itemView.getContext(), LinearLayoutManager.HORIZONTAL, false));
rvUserEvents.setNestedScrollingEnabled(false);
rvUserEvents.setRecycledViewPool(viewPool);
rvAdapter = new HomeUserEventsRVAdapter(presenter);
rvUserEvents.setAdapter(rvAdapter);
v.findViewById(R.id.btn_all_user_events).setOnClickListener(this);
}
private void bind(UserItemViewModel userItem) {
rvAdapter.updateAdapter(userItem);
}
#Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_all_user_events:
presenter.openUserEventsList();
break;
}
}
}
EDIT: XML code for my activity
<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/cl_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#android:color/white">
<android.support.design.widget.AppBarLayout
android:id="#+id/ab_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar"
android:fitsSystemWindows="true">
<android.support.design.widget.CollapsingToolbarLayout
android:id="#+id/collapsing_toolbar"
android:layout_width="match_parent"
android:layout_height="190dp"
app:layout_scrollFlags="scroll|exitUntilCollapsed"
android:fitsSystemWindows="true"
app:contentScrim="#android:color/white"
app:expandedTitleMarginStart="48dp"
app:expandedTitleMarginEnd="64dp">
<ImageView
android:id="#+id/iv_pic"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="#drawable/ic_home_screen_background"
android:fitsSystemWindows="true"
app:layout_collapseMode="parallax"
app:layout_collapseParallaxMultiplier="0.5"/>
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:elevation="7dp"
android:theme="#style/ToolbarTheme"
app:layout_collapseMode="pin"/>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<android.support.v4.widget.SwipeRefreshLayout
android:id="#+id/sr_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="-6dp"
app:layout_behavior="#string/appbar_scrolling_view_behavior">
<android.support.v7.widget.RecyclerView
android:id="#+id/rv_results"
android:clipToPadding="false"
android:scrollbars="vertical"
android:scrollbarThumbVertical="#color/orange_juice_80"
android:scrollbarSize="2dp"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#drawable/shape_rounded_top_grey"
android:fitsSystemWindows="true" />
</android.support.v4.widget.SwipeRefreshLayout>
<android.support.design.widget.FloatingActionButton
android:id="#+id/fab_add"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_marginEnd="15dp"
app:backgroundTint="#color/dark_background"
app:layout_anchor="#id/rv_results"
app:layout_anchorGravity="top|right|end"
app:layout_scrollFlags="scroll|exitUntilCollapsed"
app:srcCompat="#drawable/ic_vector_plus_white" />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:descendantFocusability="blocksDescendants"
android:layout_height="wrap_content"
android:scrollbars="none" />
Use android:descendantFocusability="blocksDescendants" attribute inside nested recyclerview
The problem with nested RecyclerView is that the correct slope of the finger fling is not detected correctly.
Here is a snippet of code that actually calculates the correct slope of the fling https://github.com/minarja1/NestedRecyclerSample/blob/developv2/app/src/main/java/com/example/nestedrecyclersample/utils/ViewExtensions.kt
After you add the class to your codebase, you can call the function with a Kotlin Extension.
fun RecyclerView.enforceSingleScrollDirection() {
val enforcer = SingleScrollDirectionEnforcer()
addOnItemTouchListener(enforcer)
addOnScrollListener(enforcer)
}
You disabled the nested scrolling of RecyclerView in this line
rvUserEvents.setNestedScrollingEnabled(false);
You need to replace this line with below to scrolling work properly
ViewCompat.setNestedScrollingEnabled(rvUserEvents,true);
recyclerView.setNestedScrollingEnabled(false);

BottomNavigationView hides when scrolling up instead of down

The new BottomNavigationView from support library v25.0.0 is supposed to hide when scrolling down, in order to see all the items from a list. However, in my testing scenario, the view hides when scrolling up. Any ideas what can cause this reverse behavior?
The inner_fragment is set up as a Fragment inserted inside the activity_main_framelayout_content Framelayout. XML layouts below:
main_activity.xml:
<android.support.design.widget.CoordinatorLayout
android:id="#+id/activity_main_coordinatorlayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
android:id="#+id/activity_main_appbarlayout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.Toolbar
android:id="#+id/activity_main_toolbar"
android:layout_height="?attr/actionBarSize"
android:layout_width="match_parent"
android:background="?attr/colorPrimary"
app:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="#style/ThemeOverlay.AppCompat.Light"
app:layout_scrollFlags="scroll|enterAlways">
<include layout="#layout/activity_main_spinner_layout"/>
</android.support.v7.widget.Toolbar>
</android.support.design.widget.AppBarLayout>
<FrameLayout
android:id="#+id/activity_main_framelayout_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
android:fitsSystemWindows="true"/>
</android.support.design.widget.CoordinatorLayout>
<android.support.design.widget.NavigationView
android:id="#+id/activity_main_framelayout_navigation_drawer"
android:layout_width="#dimen/drawer_width"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
android:background="#color/color_black_700"/>
inner_fragment.xml:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="#+id/inner_fragment_framelayout"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<android.support.design.widget.BottomNavigationView
android:id="#+id/inner_fragment_bottom_navigation_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
app:menu="#menu/inner_fragment"
app:itemBackground="#drawable/bg_bottom_navigation"
app:itemIconTint="#color/ic_bottom_navigation"
app:itemTextColor="#color/ic_bottom_navigation"/>
</FrameLayout>
A simple solution is to just add an offset listener to appbarlayout. Works perfectly for me.
So something like this:
((AppBarLayout)toolbar.getParent()).addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
#Override
public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
mNavigationBar.setTranslationY(verticalOffset*-1);
}
});
This release of BottomNavigationView is missing scrolling behavior to work out of the box as specified in the guidelines.
I wrote an article on what's missing and how you can fix it. This includes implementing scrolling behavior of the BottomNavigationView in CoordinatorLayout.
My solution was to replace the FrameLayout with a NestedCoordinatorLayout from here https://stackoverflow.com/a/37660246/2233621 and then add the BottomNavigationBehavior from Nikola's blog post https://medium.com/#nullthemall/bottomnavigationview-missing-pearls-eaa950f9ad4e#.p1i01wwui that way your bottom navigation behaviour can listen for nested scrolling of the fragment inside the NestedCoordinatorLayout
I believe you could use another view that implements NestedScrollParent + NestedScrollChild for the same behaviour.

Android CoordinatorLayout + AppbarLayout + Viewpager always scrolling

I have a classic layout with a ToolBar on the top, a TabLayout below it, and a ViewPager switching tabs from the TabLayout. When content in the ViewPager is scrollable, the ToolBar should scroll out of sight, and the TabLayout should follow and stick when it reaches the top.
All this is good in my current code, except, the ToolBar is always scrollable, regardless of the size of the ViewPager's content. See my code below. Any brilliant ideas on how to fix this?
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.design.widget.AppBarLayout
android:id="#+id/app_bar_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/primary"
android:orientation="vertical">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/AppTheme.ToolBar"
app:layout_scrollFlags="scroll|enterAlways" />
<android.support.design.widget.TabLayout
android:id="#+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
android:scrollbars="horizontal"
app:tabIndicatorColor="#color/black_text" />
</android.support.design.widget.AppBarLayout>
<android.support.v4.view.ViewPager
android:id="#+id/tabs_activity_view_pager"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="#string/appbar_scrolling_view_behavior" />
</android.support.design.widget.CoordinatorLayout>
EDIT:
I can see that the viewPager's height is the same as the height for the entire root view. This might be intentded, as the appbar_scrolling_view_behavior does seem to add a top and bottom offset. It does however seem weird, since it will result in always scrolling the toolbar and tabbar.
I have solved the issue, tried over the example Google template and find out that
app:layout_behavior="#string/appbar_scrolling_view_behavior"
line must be added into view pager properties xml. It solved my problem.
I suggested you try this sample.
this is a layout like your layout in the sample.
<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/main_content"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
android:id="#+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="#style/ThemeOverlay.AppCompat.Light"
app:layout_scrollFlags="scroll|enterAlways" />
<android.support.design.widget.TabLayout
android:id="#+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</android.support.design.widget.AppBarLayout>
<android.support.v4.view.ViewPager
android:id="#+id/viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior" />
<android.support.design.widget.FloatingActionButton
android:id="#+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:layout_margin="#dimen/fab_margin"
android:src="#drawable/ic_done" />
</android.support.design.widget.CoordinatorLayout>
using ListView as the data for ViewPager? If so, you need listView.setNestedScrollingEnabled(true);
Try adding these attributes on TabLayout:
app:layout_collapseMode="pin"
app:tabMode="fixed"
And this on AppBarLayout:
android:fitsSystemWindows="true"
* UPDATE *
I tried and this is not enough, since toolbar is still scrollable.
The solution is to make some logic about ViewPager (and its content).
Remove from xml layout file the toolbar scroll_flag attribute.
You have to implement some java code to check if ViewPager content height is > then screenHeight - (toolbar + tabBar). If true, set programmatically the scroll_flags as this:
LayoutParams params;
params = // get layout params from your toolbar
params.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL
| AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS);
// set params
toolbar.setLayoutParams(params);
Based on other samples, my own code, and the (somewhat messy) source code of the appbar_scrolling_view_behavior:
public boolean onDependentViewChanged(CoordinatorLayout parent, View child,
View dependency) {
final CoordinatorLayout.Behavior behavior =
((CoordinatorLayout.LayoutParams) dependency.getLayoutParams()).getBehavior();
if (behavior instanceof Behavior) {
// Offset the child so that it is below the app-bar (with any overlap)
final int appBarOffset = ((Behavior) behavior)
.getTopBottomOffsetForScrollingSibling();
final int expandedMax = dependency.getHeight() - mOverlayTop;
final int collapsedMin = parent.getHeight() - child.getHeight();
if (mOverlayTop != 0 && dependency instanceof AppBarLayout) {
// If we have an overlap top, and the dependency is an AppBarLayout, we control
// the offset ourselves based on the appbar's scroll progress. This is so that
// the scroll happens sequentially rather than linearly
final int scrollRange = ((AppBarLayout) dependency).getTotalScrollRange();
setTopAndBottomOffset(AnimationUtils.lerp(expandedMax, collapsedMin,
Math.abs(appBarOffset) / (float) scrollRange));
} else {
setTopAndBottomOffset(dependency.getHeight() - mOverlayTop + appBarOffset);
}
}
return false;
}
I'm reading this in a way explaining the problem, as this is expected behavior with this code.
I think we need to write our own scroll behavior, specially for the RecyclerView,
The ViewPager height should be match_parent and not wrap_content.
i've just had the same problem.
The solution is very simple, just set your viewpager height
android:layout_height="wrap_content"
Simple solution is
- wrap your appbar layour and page viewer with a relative layout.
- give your appbar layout some id
- in page view set android:layout_below="Your_appbar_layout"
Eg:
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
android:id="#+id/your_appBar_ID"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/primary"
android:orientation="vertical">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/AppTheme.ToolBar"
app:layout_scrollFlags="scroll|enterAlways" />
<android.support.design.widget.TabLayout
android:id="#+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
android:scrollbars="horizontal"
app:tabIndicatorColor="#color/black_text" />
</android.support.design.widget.AppBarLayout>
<android.support.v4.view.ViewPager
android:id="#+id/tabs_activity_view_pager"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#+id/your_appBar_ID"
app:layout_behavior="#string/appbar_scrolling_view_behavior" />
</RelativeLayout>

Recycleview in coordinatorlayout

I am trying to create RelativeLayout that has CoordinatorLayout and LinearLayout at bottom and found some strange behavior that I can't resolve. This is my layout
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.CoordinatorLayout
android:id="#+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="#+id/sender"
android:background="#android:color/white"
android:fitsSystemWindows="true">
<android.support.design.widget.AppBarLayout
android:id="#+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?actionBarSize"
android:background="?colorPrimary"
app:layout_scrollFlags="scroll|enterAlways" />
</android.support.design.widget.AppBarLayout>
<android.support.v7.widget.RecyclerView
android:id="#+id/messages_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior" />
</android.support.design.widget.CoordinatorLayout>
<LinearLayout
android:id="#+id/sender"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:gravity="center_vertical"
android:background="#android:color/white">
<EditText
android:id="#+id/inputText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:inputType="text" />
<Button
android:fontFamily="sans-serif"
style="?android:attr/borderlessButtonStyle"
android:textAppearance="?android:textAppearanceButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/send"
android:textColor="?colorPrimary"
android:id="#+id/send"/>
</LinearLayout>
</RelativeLayout>
After changing data in adapter I trying to scroll to last element ( for example by recyclerView.smoothScrollToPosition(size); )and all I see is part of last view (not full size). If recycleview not nest to CoordinatorLayout - all works as expecting - I see full sized last element view. How can I change layout to works it all correctly ?
Issue seems to be that smoothScrollToPosition() will silently scroll RecyclerView without letting CoordinatorLayout about the scroll being happening. This is what I came up with. Nice thing about it is that it should only scroll AppBarLayout if you have enough items in Adapter.
final AppBarLayout layout = (AppBarLayout) findViewById(R.id.appbar);
layout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
#Override
public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
lastVerticalOffset = verticalOffset;
}
});
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (tryCollapseAppbarOnNextScroll && lastVerticalOffset != -layout.getTotalScrollRange()) {
layout.setExpanded(false);
tryCollapseAppbarOnNextScroll = false;
}
}
});
Now whenever you send a message, do this:
tryCollapseAppbarOnNextScroll = true;
recyclerView.smoothScrollToPosition(adapter.getItemCount()-1);
in your code remove app:layout_scrollFlags="scroll|enterAlways" from Toolbar and instead add it to AppBarLayout so it should be like this
<android.support.design.widget.AppBarLayout
android:id="#+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|enterAlways">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?actionBarSize"
android:background="?colorPrimary"/>
</android.support.design.widget.AppBarLayout>
Try this way:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="16dp">
The main point is to use to add android:layout_marginBottom to your activity
The last element is clipped because the RecyclerView is not entirely visible on the screen. It is being pushed down by the expanded AppBar. Note that when scrolling manually, the AppBar will shrink as you reach the last element.
What worked best in my case was to simply collapse the AppBar before scrolling:
AppBarLayout appBarLayout = (AppBarLayout) getActivity().findViewById(R.id.appbar);
appBarLayout.setExpanded(false, false);
recyclerView.smoothScrollToPosition(position);
I imagine you could improve on this solution by collapsing it only when needed.
If collapsing the AppBar is not desired, then you could add bottom padding of the same height as the expanded AppBar. However, there will be other glitches (e.g. when scrolling to a position which is already in the RecyclerView, but just outside of the screen)

Categories

Resources