Recycleview in coordinatorlayout - android

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)

Related

CollapsingToolbarLayout causes RecyclerView's bottom to be underscreen

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"
>

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

Recycler view inside coordinator layout is not scrolling programmatically

I have following XML layout. I am using recycler inside Coordinator layout. Although when I scroll manually, it works as expected. But when I am calling
mRecyclerView.getLayoutManager().scrollToPosition(selectedPosition); or mRecyclerView.scrollToPosition(selectedPosition); to scroll the recycler view programmatically its not scrolling. Below is my XML code
<android.support.design.widget.AppBarLayout
android:id="#+id/tab_appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/backgroundBlue">
<android.support.design.widget.CollapsingToolbarLayout
android:id="#+id/collapsing_toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_scrollFlags="scroll|enterAlways">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="#dimen/standard_layout_margin"
android:layout_marginRight="#dimen/standard_layout_margin"
app:cardElevation="#dimen/small_layout_margin"
app:cardUseCompatPadding="true">
<include
android:id="#+id/details"
layout="#layout/detail_layout" />
</android.support.v7.widget.CardView>
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="#dimen/standard_layout_margin"
android:layout_marginRight="#dimen/standard_layout_margin"
app:cardElevation="#dimen/small_layout_margin"
app:cardUseCompatPadding="true">
<include layout="#layout/store_layout" />
</android.support.v7.widget.CardView>
</LinearLayout>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#+id/collapsing_toolbar"
android:layout_marginLeft="#dimen/standard_layout_margin"
android:layout_marginRight="#dimen/standard_layout_margin"
app:layout_behavior="#string/appbar_scrolling_view_behavior" />
</android.support.design.widget.CoordinatorLayout>
At particular instance, I want my particular item in the recycler view to be on top of screen. I have below java code,
if (selectedPosition != 0) {
AppBarLayout appBarLayout = (AppBarLayout) mView.findViewById(R.id.tab_appbar);
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams();
AppBarLayout.Behavior behavior = (AppBarLayout.Behavior) params.getBehavior();
if(behavior!=null) {
behavior.onNestedPreScroll(mCoordinatorLayout, appBarLayout, mRecyclerView, 0, (int) mRecyclerView.getY(), new int[]{0, 0});
mRecyclerView.getLayoutManager().scrollToPosition(selectedPosition);
mRecyclerView.scrollToPosition(selectedPosition);
}
}
any idea, how to pull an item of recycler view to the top of screen?
CoordinatorLayout.Behaviour is works with only NestedScroll event. When you try to scroll RecyclerView programmatically it is treat as normal scroll.
Write below line to inform RecyclerView start NesteadScroll, With ViewCompat.SCROLL_AXIS_VERTICAL and ViewCompat.TYPE_NON_TOUCH
ViewCompat.SCROLL_AXIS_VERTICAL: Indicates scrolling along the vertical axis.
ViewCompat.TYPE_NON_TOUCH: Indicates that the input type for the gesture is caused by something which is not a user touching a screen. This is usually from a fling which is settling.
recycler_view.startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_NON_TOUCH)
recycler_view.smoothScrollBy(0,200)

How to custom hide bottom aligned view using custom CoordinatorLayout Behavior

I'd like to hide a View aligned to the bottom of my Fragment used within a ViewPager when the RecyclerView in the same fragment animates.
I'm able to apply the AppBarLayout.ScrollingViewBehavior Behavior to the view and have the View start off as hidden and then animate in when scrolling down. When scrolling back up, the view hides. (This behavior matches what the TabLayout toolbar does with scroll|enterAlways scrollFlags set.)
What I'd like is the opposite of this. The View starts off as visible, and scrolls downwards to hide itself when scrolling down, then shows itself again when scrolling up.
Is this possible?
My activity XML:
<android.support.design.widget.CoordinatorLayout
android:id="#+id/main_content"
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="wrap_content"
android:orientation="vertical">
<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.design.widget.TabLayout
android:id="#+id/tabs"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_scrollFlags="scroll|enterAlways"
app:tabBackground="#color/colorPrimary"
app:tabGravity="fill"
app:tabMode="fixed"/>
</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"/>
<!-- This is where I set the behavior for the fragment used in the viewpager-->
<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="16dp"
android:src="#drawable/ic_done"
app:borderWidth="0dp"
app:elevation="2dp"/>
</android.support.design.widget.CoordinatorLayout>
My Fragment XML:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivityFragment">
<android.support.v7.widget.RecyclerView
android:id="#+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="#+id/text_view"/>
<TextView
android:id="#+id/text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="#android:color/holo_red_light"
android:gravity="center"
android:padding="10dp"
android:text="Bottom Bar Test"
android:textSize="30sp"/>
</RelativeLayout>
Thanks
Zach
UPDATE 1:
I get close to the desired behavior by using the following custom Behavior class. When scrolling, the bottom bar does scroll off in the right direction! However, there is a white space that appears at the top of the view as well, sized to be the height of the bottom view that is currently hidden. I do not think the animations are supposed to be used in this manner, maybe?
public class BottomBarScrollOffBehavior extends AppBarLayout.ScrollingViewBehavior
{
private int appBarHeight;
public BottomBarScrollOffBehavior()
{
}
public BottomBarScrollOffBehavior(Context context, AttributeSet attrs)
{
super(context, attrs);
}
#Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency)
{
appBarHeight = dependency.getMeasuredHeight();
return super.onDependentViewChanged(parent, child, dependency);
}
#Override
public boolean setTopAndBottomOffset(int offset)
{
return super.setTopAndBottomOffset(appBarHeight - offset);
}
}

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>

Categories

Resources