Related
I am able to implement TabLayout with ViewPager nicely but when the content inside the ViewPager is being scrolled (the deceleration animation), I cannot swipe left and right to change to other fragments inside ViewPager. I have to wait until the scrolling animation stops and then swipe.
Below is some of my code snippets.
I have a simple activity_main.xml layout like below.
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbarHome"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/colorPrimary"/>
<LinearLayout
android:id="#+id/mainContainerLayout"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="vertical">
</LinearLayout>
<android.support.design.widget.BottomNavigationView
android:id="#+id/btmNavView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#android:color/white"
app:menu="#menu/menu_main" />
</LinearLayout>
I then inflate a fragment containing TabLayout and ViewPager into the LinearLayout in the middle of the activity_main.xml. This layout can be seen below:
<LinearLayout 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">
<com.pchatanan.sontana.custom_views.AppTabLayout
android:id="#+id/contactTabLayout"
app:tabMode="fixed"
android:layout_height="wrap_content"
android:layout_width="match_parent"
style="#style/AppTabLayout"/>
<android.support.v4.view.ViewPager
android:id="#+id/contactViewPager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
The logic inside my fragment is below:
fragmentArray = new Fragment[]{new SimpleFragment(), new SimpleFragment(), new SimpleFragment()};
titleArray = new String[]{"fragment1", "fragment2", "fragment3"};
contactPagerAdapter = new AppPagerAdapter(getChildFragmentManager(), fragmentArray, titleArray);
contactViewPager.setAdapter(contactPagerAdapter);
contactViewPager.setOffscreenPageLimit(fragmentArray.length - 1);
contactTabLayout.setupWithViewPager(contactViewPager);
Use this instead defualt ViewPager.
Detect when ScrollView or RecyclerView is scrolling. Then call
viewPager.setPagingEnabled(pagingEnable);
If pagingEnable
public class CustomViewPager extends ViewPager {
private boolean isPagingEnabled = true;
public CustomViewPager(Context context) {
super(context);
}
public CustomViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
public boolean onTouchEvent(MotionEvent event) {
return this.isPagingEnabled && super.onTouchEvent(event);
}
public boolean onInterceptTouchEvent(MotionEvent event) {
return this.isPagingEnabled && super.onInterceptTouchEvent(event);
}
public void setPagingEnabled(boolean b) {
this.isPagingEnabled = b;
}
}
Update:
You can do reverse to like you can disable ScrollView scroll when ViewPager is swiping
mScrollView.requestDisallowInterceptTouchEvent(true);
Similar to what suggested by #Khemraj, the problem is the touch is not intercepted by ViewPager. It is only intercepted by the ScrollView. To fix this, we should allow ViewPage to intercept the touch when scrolling:
scrollView.setOnScrollChangeListener(new View.OnScrollChangeListener() {
#Override
public void onScrollChange(View view, int i, int i1, int i2, int i3) {
viewPager.requestDisallowInterceptTouchEvent(false);
}
});
A better implementation is to make use of CallBackListener onto the fragment containing the scrollView.
Using BottomSheetBehavior from the google design library, it looks like the default behavior is for the bottom sheet to "cover" other views in the same CoordinatorLayout as it expands. I can anchor something like a FAB (or other view with an appropriately defined CoordinatorLayout.Behavior) to the top of the sheet and have it slide up as the sheet expands, which is nice, but what I want is to have a view "collapse" as the bottom sheet expands, showing a parallax effect.
This effect in Google Maps is similar to what I'm looking for; it starts as a parallax effect, and then switches back to just having the bottom sheet "cover" the map once a certain scroll position is reached:
One thing I tried (though I suspected from the start it wouldn't work), was setting the upper view's height programmatically in the onSlide call of my BottomSheetBehavior.BottomSheetCallback. This was somewhat successful, but the movement wasn't nearly as smooth as in Google Maps.
If anyone has an idea how the effect is accomplished I would appreciate it a lot!
After a bit more experimenting/research I realized from this post
How to make custom CoordinatorLayout.Behavior with parallax scrolling effect for google MapView? that a big part of my problem was not understanding the parallax effect, which translates views rather than shrinking them. Once I realized that, it was trivial to create a custom behavior that would apply the parallax to my main view when the bottom sheet expanded:
public class CollapseBehavior<V extends ViewGroup> extends CoordinatorLayout.Behavior<V>{
public CollapseBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public boolean onDependentViewChanged(CoordinatorLayout parent, V child, View dependency) {
if (isBottomSheet(dependency)) {
BottomSheetBehavior behavior = ((BottomSheetBehavior) ((CoordinatorLayout.LayoutParams) dependency.getLayoutParams()).getBehavior());
int peekHeight = behavior.getPeekHeight();
// The default peek height is -1, which
// gets resolved to a 16:9 ratio with the parent
final int actualPeek = peekHeight >= 0 ? peekHeight : (int) (((parent.getHeight() * 1.0) / (16.0)) * 9.0);
if (dependency.getTop() >= actualPeek) {
// Only perform translations when the
// view is between "hidden" and "collapsed" states
final int dy = dependency.getTop() - parent.getHeight();
ViewCompat.setTranslationY(child, dy/2);
return true;
}
}
return false;
}
private static boolean isBottomSheet(#NonNull View view) {
final ViewGroup.LayoutParams lp = view.getLayoutParams();
if (lp instanceof CoordinatorLayout.LayoutParams) {
return ((CoordinatorLayout.LayoutParams) lp)
.getBehavior() instanceof BottomSheetBehavior;
}
return false;
}
}
Then in my layout XML, I set the app:layout_behavior of my main view to be com.mypackage.CollapseBehavior and the app:layout_anchor to be my bottom sheet view so that the onDependentViewChanged callback would trigger. This effect was much smoother than trying to resize the view. I suspect returning to my initial strategy of using a BottomSheetBehavior.BottomSheetCallback would also work similarly to this solution.
Edit: per request, the relevant XML is below. I add a MapFragment into #+id/map_container at runtime, though this should also work with anything you drop into that container like a static image. The LocationListFragment could likewise be replaced with any view or fragment, so long as it still has the BottomSheetBehavior
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/fragment_coordinator">
<FrameLayout
android:id="#+id/map_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
app:layout_anchor="#+id/list_container"
app:layout_behavior="com.mypackage.behavior.CollapseBehavior"/>
<fragment
android:name="com.mypackage.fragment.LocationListFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/list_container"
app:layout_behavior="android.support.design.widget.BottomSheetBehavior"/>
</android.support.design.widget.CoordinatorLayout>
Patrick Grayson's post was very helpful. In my case though, I did need something that resized the map. I adopted the solution above to resize instead of translate. Perhaps others may be looking for a similar solution.
public class CollapseBehavior<V extends ViewGroup> extends CoordinatorLayout.Behavior<V> {
private int pixels = NO_RESIZE_BUFFER; // default value, in case getting a value from resources, bites the dust.
private static final int NO_RESIZE_BUFFER = 200; //The amount of dp to not have the bottom sheet ever push away.
public CollapseBehavior(Context context, AttributeSet attrs)
{
super(context, attrs);
pixels = (int)convertDpToPixel(NO_RESIZE_BUFFER,context);
}
#Override
public boolean onDependentViewChanged(CoordinatorLayout parent, V child, View dependency) {
// child is the map
// dependency is the bottomSheet
if(isBottomSheet(dependency))
{
BottomSheetBehavior behavior = ((BottomSheetBehavior) ((CoordinatorLayout.LayoutParams)dependency.getLayoutParams()).getBehavior());
int peekHeight;
if (behavior != null) {
peekHeight = behavior.getPeekHeight();
}
else
return true;
if(peekHeight > 0) { // Dodge the case where the sheet is hidden.
if (dependency.getTop() >= peekHeight) { // Otherwise we'd completely overlap the map
if(dependency.getTop() >= pixels) { // On resize when we have more than our NO_RESIZE_BUFFER of dp left.
if(dependency.getTop() > 0) { // Don't want to map to be gone completely.
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) child.getLayoutParams();
params.height = dependency.getTop();
child.setLayoutParams(params);
}
return true;
}
}
}
}
return false;
}
private static float convertDpToPixel(float dp, Context context)
{
float densityDpi = context.getResources().getDisplayMetrics().densityDpi;
return dp * (densityDpi / DisplayMetrics.DENSITY_DEFAULT);
}
private static boolean isBottomSheet(#NonNull View view)
{
final ViewGroup.LayoutParams lp = view.getLayoutParams();
if(lp instanceof CoordinatorLayout.LayoutParams)
{
return ((CoordinatorLayout.LayoutParams) lp).getBehavior() instanceof BottomSheetBehavior;
}
return false;
}
}
And the layout...
<FrameLayout
android:id="#+id/flMap"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="top"
android:layout_margin="0dp"
app:layout_anchor="#+id/persistentBottomSheet"
app:layout_behavior="com.yoursite.yourapp.CollapseBehavior">
<fragment
android:id="#+id/mapDirectionSummary"
android:name="com.google.android.gms.maps.SupportMapFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.yoursite.yourapp.YourActivity" />
</FrameLayout>
<android.support.constraint.ConstraintLayout
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="wrap_content"
android:id="#+id/persistentBottomSheet"
app:behavior_peekHeight="#dimen/bottom_sheet_peek_height"
app:behavior_hideable="false"
app:layout_behavior="android.support.design.widget.BottomSheetBehavior"
tools:context="com.yoursite.yourapp.YourActivity">
<!-- Whatever you want on the bottom sheet. -->
</android.support.constraint.ConstraintLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
app:cardElevation="8dp"
app:cardBackgroundColor="#324">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="56dp"
android:background="?attr/colorPrimary"
android:theme="#style/ThemeOverlay.AppCompat.ActionBar"
app:popupTheme="#style/Theme.AppCompat.Light">
<EditText
android:id="#+id/txtSearch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#android:color/transparent"
android:ems="10"
android:inputType="text"
android:maxLines="1" />
</android.support.v7.widget.Toolbar>
</android.support.v7.widget.CardView>
</LinearLayout>
I try to implement a search bar like in google maps android app:
When the recycler view is in its initial state, the toolbar has no elevation. Only when the users starts scrolling the elevation becomes visible. And the search bar (toolbar) never collapses. Here is what I tried to replicate this:
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<android.support.design.widget.AppBarLayout
android:id="#+id/appBarLayout"
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="64dp">
<!-- content -->
</android.support.v7.widget.Toolbar>
</android.support.design.widget.AppBarLayout>
</android.support.design.widget.CoordinatorLayout>
And here you can see the result:
So the problem with my solution is, that the elevation of the toolbar is always visible. But I want it to appear only when the recycler view scrolls behind it. Is there anything from the design support library that enables such behavior as seen in the google maps app?
I am using
com.android.support:appcompat-v7:23.2.0
com.android.support:design:23.2.0
The accepted answer is outdated. Now there is inbuilt functionality to do this. I am pasting the whole layout code so it will help you to understand.
You just need to use CoordinatorLayout with AppBarLayout. This design pattern is called Lift On Scroll and can be implemented by setting app:liftOnScroll="true" on your AppBarLayout.
Note: the liftOnScroll attribute requires that you apply the #string/appbar_scrolling_view_behavior layout_behavior to your scrolling view (e.g., NestedScrollView, RecyclerView, etc.).
<?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"
tools:context=".MainActivity"
android:background="#color/default_background">
<com.google.android.material.appbar.AppBarLayout
android:id="#+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:liftOnScroll="true">
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="#color/default_background" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/list_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="#+id/appbar"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
android:orientation="vertical" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
Refered this documentation https://github.com/material-components/material-components-android/blob/master/docs/components/AppBarLayout.md
EDIT As pointed out in the comments, my answer is now outdated, see https://stackoverflow.com/a/58272283/4291272
Whether you are using a CoordinatorLayout or not, a RecyclerView.OnScrollListener seems like the right way to go as far as the elevation is concerned. However, from my experience recyclerview.getChild(0).getTop() is not reliable and should not be used for determining the scrolling state. Instead, this is what's working:
private static final int SCROLL_DIRECTION_UP = -1;
// ...
// Put this into your RecyclerView.OnScrollListener > onScrolled() method
if (recyclerview.canScrollVertically(SCROLL_DIRECTION_UP)) {
// Remove elevation
toolbar.setElevation(0f);
} else {
// Show elevation
toolbar.setElevation(50f);
}
Be sure to assign a LayoutManager to your RecyclerView or the call of canScrollVertically may cause a crash!
This is a good question but none of the existing answers are good enough. Calling getTop() is absolutely not recommended as it's very unreliable. If you look at newer versions of Google apps that follow Material Design Refresh (2018) guidelines, they hide the elevation at the beginning and immediately add it as user scrolls down and hide it again as user scrolls and reaches the top again.
I managed to achieve the same effect using the following:
val toolbar: android.support.v7.widget.Toolbar? = activity?.findViewById(R.id.toolbar);
recyclerView?.addOnScrollListener(object: RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy);
if(toolbar == null) {
return;
}
if(!recyclerView.canScrollVertically(-1)) {
// we have reached the top of the list
toolbar.elevation = 0f
} else {
// we are not at the top yet
toolbar.elevation = 50f
}
}
});
This works perfectly with vertical recycler views (even with tab view or other recycler views inside them);
A couple of important notes:
Here I'm doing this inside a fragment hence activity?.findViewById...
If your Toolbar is nested inside an AppBarLayout, then instead of applying elevation to Toolbar, you should apply it to the AppBarLayout.
You should add android:elevation="0dp" and app:elevation="0dp" attributes to your Toolbar or AppBarLayout so that the recycler view doesn't have elevation at the beginning.
I have a RecyclerView in my fragment. I could achieve similar effect using code below:
It is not the Smartest way and you can wait for better answers.
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Initial Elevation
final Toolbar toolbar = (Toolbar) getActivity().findViewById(R.id.toolbar);
if(toolbar!= null)
toolbar.setElevation(0);
// get initial position
final int initialTopPosition = mRecyclerView.getTop();
// Set a listener to scroll view
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if(toolbar!= null && mRecyclerView.getChildAt(0).getTop() < initialTopPosition ) {
toolbar.setElevation(50);
} else {
toolbar.setElevation(0);
}
}
});
}
I found this when page when I wanted to do something similar, but for a more complex View Hierarchy.
After some research, I was able to get the same effect using a custom behavior. This works for any view in a coordinator layout (given that there's a nested scroll element such as RecyclerView or NestedScrollView)
Note: This only works on API 21 and above as ViewCompat.setElevation does not seem to have any effect pre lollipop and AppBarLayout#setTargetElevation is deprecated
ShadowScrollBehavior.java
public class ShadowScrollBehavior extends AppBarLayout.ScrollingViewBehavior
implements View.OnLayoutChangeListener {
int totalDy = 0;
boolean isElevated;
View child;
public ShadowScrollBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child,
View dependency) {
parent.addOnLayoutChangeListener(this);
this.child = child;
return super.layoutDependsOn(parent, child, dependency);
}
#Override
public boolean onStartNestedScroll(#NonNull CoordinatorLayout coordinatorLayout,
#NonNull View child, #NonNull View directTargetChild,
#NonNull View target, int axes, int type) {
// Ensure we react to vertical scrolling
return axes == ViewCompat.SCROLL_AXIS_VERTICAL ||
super.onStartNestedScroll(coordinatorLayout, child, directTargetChild,
target, axes, type);
}
#Override
public void onNestedPreScroll(#NonNull CoordinatorLayout coordinatorLayout,
#NonNull View child, #NonNull View target,
int dx, int dy, #NonNull int[] consumed, int type) {
totalDy += dy;
if (totalDy <= 0) {
if (isElevated) {
ViewGroup parent = (ViewGroup) child.getParent();
if (parent != null) {
TransitionManager.beginDelayedTransition(parent);
ViewCompat.setElevation(child, 0);
}
}
totalDy = 0;
isElevated = false;
} else {
if (!isElevated) {
ViewGroup parent = (ViewGroup) child.getParent();
if (parent != null) {
TransitionManager.beginDelayedTransition(parent);
ViewCompat.setElevation(child, dp2px(child.getContext(), 4));
}
}
if (totalDy > target.getBottom())
totalDy = target.getBottom();
isElevated = true;
}
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type);
}
private float dp2px(Context context, int dp) {
Resources r = context.getResources();
float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, r.getDisplayMetrics());
return px;
}
#Override
public void onLayoutChange(View view, int i, int i1, int i2, int i3, int i4, int i5, int i6, int i7) {
totalDy = 0;
isElevated = false;
ViewCompat.setElevation(child, 0);
}
}
my_activity_layout.xml
<android.support.design.widget.CoordinatorLayout
android:fitsSystemWindows="true"
android:layout_height="match_parent"
android:layout_width="match_parent">
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_height="match_parent"
android:layout_width="match_parent" />
<android.support.design.widget.AppBarLayout
android:id="#+id/appBarLayout"
android:layout_height="wrap_content"
android:layout_width="match_parent"
app:layout_behavior="com.myapp.ShadowScrollBehavior">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_height="64dp"
android:layout_width="match_parent">
<!-- content -->
</android.support.v7.widget.Toolbar>
</android.support.design.widget.AppBarLayout>
</android.support.design.widget.CoordinatorLayout>
If you use CoordinatorLayout you dont need any extra code to make this work by yourself just some setup on style and layout XML, check this:
Your app style should use a MaterialCompoment style, like src/main/res/values/styles.xml.
Setup you AppBarLayout:
Use any MaterialCompoments style for this component like: Widget.MaterialComponents.AppBarLayout.Surface.
Set app:liftOnScroll="true" to enable the automatic elevation based on scroll.
Setup your scrolling view:
Set app:layout_behavior="#string/appbar_scrolling_view_behavior.
https://github.com/danielgomezrico/spike-appbarlayout-toolbar-automatic-elevation
I have a fairly typical List functionality using a CoordinatorLayout, AppBarLayout, SwipeRefreshLayout and RecyclerView -
When the RecyclerView has enough content to scroll, the page seems fine. When the RecyclerView is empty or doesn't have enough content to scroll however, the behavior is that the AppBarLayout children with app:layout_scrollFlags="scroll|enterAlwaysCollapsed" will continue to scroll - which looks odd.
Is there a way to stop the AppBarLayout children scrolling when the NestedScrollView is empty?
<?xml version="1.0" encoding="utf-8"?>
<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">
<android.support.design.widget.CoordinatorLayout
android:id="#+id/coordinatorLayout"
android:background="#android:color/transparent"
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:background="#android:color/transparent"
android:elevation="4dp">
<LinearLayout
android:id="#+id/eventHeader"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:background="#color/green"
android:orientation="horizontal"
app:layout_scrollFlags="scroll|enterAlwaysCollapsed">
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="scroll|enterAlwaysCollapsed"
android:textColor="#color/white"
android:textSize="15sp"/>
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1"/>
</LinearLayout>
</android.support.design.widget.AppBarLayout>
<android.support.v4.widget.SwipeRefreshLayout
android:id="#+id/swipeToRefresh"
android:layout_width="match_parent"
android:layout_gravity="fill_vertical"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior">
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#android:color/transparent"
android:dividerHeight="0dp"
android:layout_gravity="fill_vertical"
android:drawSelectorOnTop="true"
android:listSelector="#drawable/selector_ripple_grey_transparent"
android:scrollbars="vertical"/>
</android.support.v4.widget.SwipeRefreshLayout>
</android.support.design.widget.CoordinatorLayout>
<TextView
android:id="#+id/noData"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:padding="16dp"
android:text="#string/no_data_available"
android:textSize="17sp"/>
</FrameLayout>
Not sure how elegant a solution this is but, I overrode the onStartNestedScroll() event to only fire if the NestedScrollView is scrollable (In this case a RecyclerView)
in onCreate():
CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) mAppBarLayout.getLayoutParams();
layoutParams.setBehavior(new AppBarLayoutNoEmptyScrollBehavior(mAppBarLayout, mRecyclerView));
Behavior:
public class AppBarLayoutNoEmptyScrollBehavior extends AppBarLayout.Behavior {
AppBarLayout mAppBarLayout;
RecyclerView mRecyclerView;
public AppBarLayoutNoEmptyScrollBehavior(AppBarLayout appBarLayout, RecyclerView recyclerView) {
mAppBarLayout = appBarLayout;
mRecyclerView = recyclerView;
}
public boolean isRecylerViewScrollable(RecyclerView recyclerView) {
int recyclerViewHeight = recyclerView.getHeight(); // Height includes RecyclerView plus AppBarLayout at same level
int appCompatHeight = mAppBarLayout != null ? mAppBarLayout.getHeight() : 0;
recyclerViewHeight -= appCompatHeight;
return recyclerView.computeVerticalScrollRange() > recyclerViewHeight;
}
#Override
public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child, View directTargetChild, View target, int nestedScrollAxes) {
if (isRecylerViewScrollable(mRecyclerView)) {
return super.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes);
}
return false;
}
}
EDIT
Edited solution as RecyclerView gives height as visible RecyclerView height and AppBarLayout height (which is the CoordinatorLayout height).
However, if your scroll gesture starts on the visible AppBarLayout area, a scroll will still take place, even if you add this Behavior to the AppBarLayout as well. This answer therefore is not a fix for the problem.
(Based on : Reference)
(1) Create this class.
public class AppBarLayoutBehaviorForEmptyRecyclerView extends AppBarLayout.Behavior
{
private boolean canRecyclerViewBeScrolled = false;
public AppBarLayoutBehaviorForEmptyRecyclerView()
{
}
public AppBarLayoutBehaviorForEmptyRecyclerView(Context context, AttributeSet attrs)
{
super(context, attrs);
}
#Override
public boolean onInterceptTouchEvent(CoordinatorLayout parent, AppBarLayout child, MotionEvent ev)
{
return canRecyclerViewBeScrolled && super.onInterceptTouchEvent(parent, child, ev);
}
#Override
public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child, View directTargetChild, View target, int nestedScrollAxes)
{
updateScrollable(target);
return canRecyclerViewBeScrolled && super.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes);
}
#Override
public boolean onNestedFling(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, float velocityX, float velocityY, boolean consumed)
{
return canRecyclerViewBeScrolled && super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, consumed);
}
private void updateScrollable(View targetChild)
{
if(targetChild instanceof RecyclerView)
{
RecyclerView.Adapter adapter = ((RecyclerView) targetChild).getAdapter();
canRecyclerViewBeScrolled = adapter != null && adapter.getItemCount() > 0;
}
else
{
canRecyclerViewBeScrolled = true;
}
}
}
(2) Add to your AppBarLayout XML element the following attribute:
app:layout_behavior="com.xxxx.xxxxxx.AppBarLayoutBehaviorForEmptyRecyclerView"
Graeme answer is ok but I also added in constructor
public AppBarLayoutOnEmptyRecyclerViewScrollBehavior(#NonNull AppBarLayout appBarLayout, #NonNull RecyclerView recyclerView) {
this.appBarLayout = checkNotNull(appBarLayout);
this.recyclerView = checkNotNull(recyclerView);
setDragCallback(new DragCallback() {
#Override
public boolean canDrag(#NonNull AppBarLayout appBarLayout) {
return isRecylerViewScrollable(recyclerView);
}
});
}
so when RecyclerView is empty I also disable drag from AppBarLayout
After loading data to your RecyclerView, if it's empty, just disalbe the scroll flag manually:
AppBarLayout.LayoutParams toolbarLayoutParams = (AppBarLayout.LayoutParams) mEventHeader.getLayoutParams();
toolbarLayoutParams.setScrollFlags(0);
mEventHeader.setLayoutParams(toolbarLayoutParams);
And if not empty, set back the scroll flag:
AppBarLayout.LayoutParams toolbarLayoutParams = (AppBarLayout.LayoutParams) mEventHeader.getLayoutParams();
toolbarLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS);
mEventHeader.setLayoutParams(toolbarLayoutParams);
I have an AppBarLayout that scrolls off screen when scrolling a RecyclerView.
Below the RecyclerView there is a RelativeLayout that is a footer.
The footer is shown only after scrolling up - it behave like it has
layout_scrollFlags="scroll|enterAlways"
but it doesn't have any scroll flags - is it a bug or am I doing something wrong? I want it to be always visible
before scroll
after scroll
Update
opened a google issue on this - it was marked 'WorkingAsIntended' this still doesn't help because I want a working solution of a footer inside a fragment.
Update 2
you can find the activity and the fragment xmls here -
note that if line 34 in activity.xml - the line containing app:layout_behavior="#string/appbar_scrolling_view_behavior" is commented out the text end is visible from the start - otherwise, it is visible only after scrolling up
I use a simplified version of Learn OpenGL ES's solution (https://stackoverflow.com/a/33396965/778951) -- which improves on Noa's solution (https://stackoverflow.com/a/31140112/1317564). It works fine for my simple quick-return toolbar above a TabLayout with footer buttons in each tab's ViewPager content.
Just set the FixScrollingFooterBehavior as the layout_behavior on the View/ViewGroup you want to keep aligned at the bottom of the screen.
Layout:
<?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.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="?android:attr/actionBarSize"
android:minHeight="?android:attr/actionBarSize"
app:title="Foo"
app:layout_scrollFlags="scroll|enterAlways|snap"
/>
<android.support.design.widget.TabLayout
android:id="#+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
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="com.spreeza.shop.ui.widgets.FixScrollingFooterBehavior"
/>
</android.support.design.widget.CoordinatorLayout>
Behavior:
public class FixScrollingFooterBehavior extends AppBarLayout.ScrollingViewBehavior {
private AppBarLayout appBarLayout;
public FixScrollingFooterBehavior() {
super();
}
public FixScrollingFooterBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
if (appBarLayout == null) {
appBarLayout = (AppBarLayout) dependency;
}
final boolean result = super.onDependentViewChanged(parent, child, dependency);
final int bottomPadding = calculateBottomPadding(appBarLayout);
final boolean paddingChanged = bottomPadding != child.getPaddingBottom();
if (paddingChanged) {
child.setPadding(
child.getPaddingLeft(),
child.getPaddingTop(),
child.getPaddingRight(),
bottomPadding);
child.requestLayout();
}
return paddingChanged || result;
}
// Calculate the padding needed to keep the bottom of the view pager's content at the same location on the screen.
private int calculateBottomPadding(AppBarLayout dependency) {
final int totalScrollRange = dependency.getTotalScrollRange();
return totalScrollRange + dependency.getTop();
}
}
Update
The solution below doesn't work for 5.1 as it works in 5 - instead of getTop use getTranslationY in any of the calculations you do.
layout.getTop()-->(int)layout.getTranslationY()
appbar.getTop()+toolbar.getHeight()-->(int)(appbar.getTranslationY()+toolbar.getHeight())
Update 2
with the new support library - 22.2.1 - there is no diff between 5.1 and prev versions, you should only use getTop and ignore the previous update in this answer
Original solution
After looking into many directions turns out the solution is actually simple - add paddingBottom to the fragment and adjust it as the page scrolls.
The padding is needed to cover for the changes in the toolbar y position - the coordinator layout is moving the entire page up and down as the toolbar disappears and reappears.
This can be achieved by extending AppBarLayout.ScrollingViewBehavior and setting this as the behavior of the fragment element of the activity.
Here are the basics of the code - it works for an activity with only a toolbar - you can replace it with appbar.getTop() + toolbar.getHeight() and this will work better if your appbar includes tabs.
activity.xml
<android.support.design.widget.CoordinatorLayout
android:id="#+id/main"
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.support.design.widget.AppBarLayout
android:id="#+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:elevation="3dp"
app:elevation="3dp">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_scrollFlags="scroll|enterAlways"
/>
</android.support.design.widget.AppBarLayout>
<fragment
android:id="#+id/fragment"
android:name="com.example.noa.footer2.MainActivityFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="com.example.noa.footer2.MyBehavior"
tools:layout="#layout/fragment"/>
</android.support.design.widget.CoordinatorLayout>
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"
android:paddingBottom="48dp"
android:background="#android:color/holo_green_dark"
tools:context=".MainActivityFragment">
<android.support.v7.widget.RecyclerView
android:id="#+id/list"
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"
android:background="#null"/>
<View
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_alignParentBottom="true"
android:background="#android:color/holo_red_light"/>
</RelativeLayout>
MainActivityFragment#onActivityCreated
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
CoordinatorLayout.LayoutParams lp = (LayoutParams) getView().getLayoutParams();
MyBehavior behavior = (MyBehavior) lp.getBehavior();
behavior.setLayout(getView());
}
MyBehavior
public class MyBehavior extends AppBarLayout.ScrollingViewBehavior {
private View layout;
public MyBehavior() {
}
public MyBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
boolean result = super.onDependentViewChanged(parent, child, dependency);
if (layout != null) {
layout.setPadding(layout.getPaddingLeft(), layout.getPaddingTop(), layout
.getPaddingRight(), layout.getTop());
}
return result;
}
public void setLayout(View layout) {
this.layout = layout;
}
}
I started out with Noa's solution (https://stackoverflow.com/a/31140112/1317564) and it worked for finger drags, but I was running into trouble with flings. After spending some time to trace the method calls and trying out different ideas, here is the solution I ended up with:
// Workaround for https://code.google.com/p/android/issues/detail?id=177195
// Based off of solution originally found here: https://stackoverflow.com/a/31140112/1317564
#SuppressWarnings("unused")
public class CustomScrollingViewBehavior extends AppBarLayout.ScrollingViewBehavior {
private AppBarLayout appBarLayout;
private boolean onAnimationRunnablePosted = false;
#SuppressWarnings("unused")
public CustomScrollingViewBehavior() {
}
#SuppressWarnings("unused")
public CustomScrollingViewBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
if (appBarLayout != null) {
// We need to check from when a scroll is started, as we may not have had the chance to update the layout at
// the start of a scroll or fling event.
startAnimationRunnable(child, appBarLayout);
}
return super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes);
}
#Override
public boolean onMeasureChild(CoordinatorLayout parent, final View child, int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
if (appBarLayout != null) {
final int bottomPadding = calculateBottomPadding(appBarLayout);
if (bottomPadding != child.getPaddingBottom()) {
// We need to update the padding in onMeasureChild as otherwise we won't have the correct padding in
// place when the view is flung, and the changes done in onDependentViewChanged will only take effect on
// the next animation frame, which means it will be out of sync with the new scroll offset. This is only
// needed when the view is flung -- when dragged with a finger, things work fine with just
// implementing onDependentViewChanged().
child.setPadding(child.getPaddingLeft(), child.getPaddingTop(), child.getPaddingRight(), bottomPadding);
}
}
return super.onMeasureChild(parent, child, parentWidthMeasureSpec, widthUsed, parentHeightMeasureSpec, heightUsed);
}
#Override
public boolean onDependentViewChanged(CoordinatorLayout parent, final View child, final View dependency) {
if (appBarLayout == null)
appBarLayout = (AppBarLayout) dependency;
final boolean result = super.onDependentViewChanged(parent, child, dependency);
final int bottomPadding = calculateBottomPadding(appBarLayout);
final boolean paddingChanged = bottomPadding != child.getPaddingBottom();
if (paddingChanged) {
// If we've changed the padding, then update the child and make sure a layout is requested.
child.setPadding(child.getPaddingLeft(),
child.getPaddingTop(),
child.getPaddingRight(),
bottomPadding);
child.requestLayout();
}
// Even if we didn't change the padding, if onDependentViewChanged was called then that means that the app bar
// layout was changed or was flung. In that case, we want to check for these changes over the next few animation
// frames so that we can ensure that we capture all the changes and update the view pager padding to match.
startAnimationRunnable(child, dependency);
return paddingChanged || result;
}
// Calculate the padding needed to keep the bottom of the view pager's content at the same location on the screen.
private int calculateBottomPadding(AppBarLayout dependency) {
final int totalScrollRange = dependency.getTotalScrollRange();
return totalScrollRange + dependency.getTop();
}
private void startAnimationRunnable(final View child, final View dependency) {
if (onAnimationRunnablePosted)
return;
final int onPostChildTop = child.getTop();
final int onPostDependencyTop = dependency.getTop();
onAnimationRunnablePosted = true;
// Start looking for changes at the beginning of each animation frame. If there are any changes, we have to
// ensure that layout is run again so that we can update the padding to take the changes into account.
child.postOnAnimation(new Runnable() {
private static final int MAX_COUNT_OF_FRAMES_WITH_NO_CHANGES = 5;
private int previousChildTop = onPostChildTop;
private int previousDependencyTop = onPostDependencyTop;
private int countOfFramesWithNoChanges;
#Override
public void run() {
// Make sure we request a layout at the beginning of each animation frame, until we notice a few
// frames where nothing changed.
final int currentChildTop = child.getTop();
final int currentDependencyTop = dependency.getTop();
boolean hasChanged = false;
if (currentChildTop != previousChildTop) {
previousChildTop = currentChildTop;
hasChanged = true;
countOfFramesWithNoChanges = 0;
}
if (currentDependencyTop != previousDependencyTop) {
previousDependencyTop = currentDependencyTop;
hasChanged = true;
countOfFramesWithNoChanges = 0;
}
if (!hasChanged) {
countOfFramesWithNoChanges++;
}
if (countOfFramesWithNoChanges <= MAX_COUNT_OF_FRAMES_WITH_NO_CHANGES) {
// We can still look for changes on subsequent frames.
child.requestLayout();
child.postOnAnimation(this);
} else {
// We've encountered enough frames with no changes. Do a final layout request, and don't repost.
child.requestLayout();
onAnimationRunnablePosted = false;
}
}
});
}
}
I'm not a fan of rechecking the layout on every animation frame, and this solution isn't perfect as I've seen some issues if programmatically expanding/collapsing the app bar layout, but for now I haven't found a better solution. The performance is fine on a new device and acceptable on an older device. If someone else does, please feel free to take my answer as a source and repost.
package pl.mkaras.utils;
import android.content.Context;
import android.support.design.widget.AppBarLayout;
import android.support.design.widget.CoordinatorLayout;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.Toolbar;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import java.util.List;
public class ScrollViewBehaviorFix extends AppBarLayout.ScrollingViewBehavior {
public ScrollViewBehaviorFix() {
super();
}
public ScrollViewBehaviorFix(Context context, AttributeSet attrs) {
super(context, attrs);
}
public boolean onMeasureChild(CoordinatorLayout parent, View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec,
int heightUsed) {
if (child.getLayoutParams().height == -1) {
List<View> dependencies = parent.getDependencies(child);
if (dependencies.isEmpty()) {
return false;
}
final AppBarLayout appBar = findFirstAppBarLayout(dependencies);
if (appBar != null && ViewCompat.isLaidOut(appBar)) {
int availableHeight = View.MeasureSpec.getSize(parentHeightMeasureSpec);
if (availableHeight == 0) {
availableHeight = parent.getHeight();
}
final int height = availableHeight - appBar.getMeasuredHeight();
int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.AT_MOST);
parent.onMeasureChild(child, parentWidthMeasureSpec, widthUsed, heightMeasureSpec, heightUsed);
int childContentHeight = getContentHeight(child);
if (childContentHeight <= height) {
updateToolbar(parent, appBar, parentWidthMeasureSpec, widthUsed, parentHeightMeasureSpec, heightUsed, false);
heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY);
parent.onMeasureChild(child, parentWidthMeasureSpec, widthUsed, heightMeasureSpec, heightUsed);
return true;
} else {
updateToolbar(parent, appBar, parentWidthMeasureSpec, widthUsed, parentHeightMeasureSpec, heightUsed, true);
return super.onMeasureChild(parent, child, parentWidthMeasureSpec, widthUsed, parentHeightMeasureSpec, heightUsed);
}
}
}
return false;
}
private static int getContentHeight(View view) {
if (view instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup) view;
int contentHeight = 0;
for (int index = 0; index < viewGroup.getChildCount(); ++index) {
View child = viewGroup.getChildAt(index);
contentHeight += child.getMeasuredHeight();
}
return contentHeight;
} else {
return view.getMeasuredHeight();
}
}
private static AppBarLayout findFirstAppBarLayout(List<View> views) {
int i = 0;
for (int z = views.size(); i < z; ++i) {
View view = views.get(i);
if (view instanceof AppBarLayout) {
return (AppBarLayout) view;
}
}
throw new IllegalArgumentException("Missing AppBarLayout in CoordinatorLayout dependencies");
}
private void updateToolbar(CoordinatorLayout parent, AppBarLayout appBar, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec,
int heightUsed, boolean toggle) {
toggleToolbarScroll(appBar, toggle);
appBar.forceLayout();
parent.onMeasureChild(appBar, parentWidthMeasureSpec, widthUsed, parentHeightMeasureSpec, heightUsed);
}
private void toggleToolbarScroll(AppBarLayout appBar, boolean toggle) {
for (int index = 0; index < appBar.getChildCount(); ++index) {
View child = appBar.getChildAt(index);
if (child instanceof Toolbar) {
Toolbar toolbar = (Toolbar) child;
AppBarLayout.LayoutParams params = (AppBarLayout.LayoutParams) toolbar.getLayoutParams();
int scrollFlags = params.getScrollFlags();
if (toggle) {
scrollFlags |= AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL;
} else {
scrollFlags &= ~AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL;
}
params.setScrollFlags(scrollFlags);
}
}
}
}
This behavior basically removes scroll flag SCROLL from AppBarLayout, when scrolling content in dependent view (RecyclerView, NestedScrollView) is less than view height, ie. when scrolling is not needed. It also overrides offsetting scrolling view, which is normally done by AppBarLayout.ScrollingViewBehavior. Works well when adding footer, ie. button, to scrolling view or in ViewPager, where content length may be different in each page.
I think creating a fixed header and footer could solver your problem. I would've wrote this in the comments but I don't have 50 rep. You could figure out how to do it here
I did something along the lines of ensuring I added
android:layout_gravity="end|bottom"
to the layout in XML that I wanted at the bottom of the CoordinatorLayout
and then set in code:
mRecyclerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#SuppressLint("NewApi")
#SuppressWarnings("deprecation")
#Override
public void onGlobalLayout() {
if (mFooterView != null) {
final int height = mFooterView.getHeight();
mRecyclerView.setPadding(0, 0, 0, height);
mRecyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
}
});
Note: that the footer View/ViewGroup needs to be higher in the z-axis (listed below the RecyclerView in XML) to function properly
Surround your elements with a linearlayout, like that:
<android.support.design.widget.CoordinatorLayout >
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout>
<android.support.v7.widget.Toolbar />
</android.support.design.widget.AppBarLayout>
<include layout="#layout/content_main" />
</LinearLayout>
</android.support.design.widget.CoordinatorLayout>
Android CoordinatorLayout Bottom Layout Behaviour Example
activity_bottom.xml
<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.support.design.widget.AppBarLayout
android:id="#+id/app_bar"
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="?attr/actionBarSize"
android:background="?attr/colorPrimaryDark"
app:layout_scrollFlags="scroll|enterAlways"
app:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar" />
</android.support.design.widget.AppBarLayout>
<android.support.v7.widget.RecyclerView
android:id="#+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#C0C0C0"
app:layout_behavior="#string/appbar_scrolling_view_behavior" />
<com.example.android.coordinatedeffort.widget.FooterBarLayout
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:layout_gravity="bottom">
<TextView
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="#007432"
android:gravity="center"
android:text="Footer View"
android:textColor="#android:color/white"
android:textSize="25sp" />
</com.example.android.coordinatedeffort.widget.FooterBarLayout>
</android.support.design.widget.CoordinatorLayout>
FooterBarLayout.java
FooterBarBehavior.java
There is a library for your problem. Hope this will really help for you
Here is the library
And another problem you have mentioned fixed the footer. the below one is the relative layout so use the feature android:layout_alignParentBottom="true" on your footer.
Hope you i have solved the issue