I upgrade to the latest support library version and the Scrolling FAB behavior is not working. When scrolling down on a RecyclerView it correctly scrolls down, but when scrolling up again it is not. It stays hidden. Downgrading to 25.0.1 seems to mitigate this issue. For reference here is the code I use for this.
<android.support.v4.widget.DrawerLayout 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:id="#+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true"
android:fitsSystemWindows="true"
tools:context=".mainhost.MainActivity"
tools:openDrawer="start">
<android.support.design.widget.CoordinatorLayout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/activity_main_coordinator_layout_root_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".mainhost.MainActivity">
<android.support.design.widget.AppBarLayout
android:id="#+id/appbar_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/AppTheme.AppBarOverlay">
<android.support.design.widget.CollapsingToolbarLayout
android:id="#+id/collapsing_toolbar_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
app:contentScrim="?attr/colorPrimary"
app:layout_scrollFlags="scroll|enterAlways|snap">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:focusableInTouchMode="true"
app:layout_collapseMode="pin"
app:popupTheme="#style/AppTheme.PopupOverlay" />
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<!-- Layout for content is here. This can be a RelativeLayout -->
<FrameLayout
android:id="#+id/content_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="-3dp"
android:layout_marginRight="-3dp"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
tools:context="com.globyworks.citykey.mainhost.MainActivity" />
<android.support.design.widget.FloatingActionButton
android:id="#+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="#dimen/fab_margin"
app:layout_behavior="com.globyworks.citykey.helpers.ScrollingFABBehavior"
android:src="#drawable/drawer_new_report_white" />
</android.support.design.widget.CoordinatorLayout>
<android.support.design.widget.NavigationView
android:id="#+id/navigation_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:background="#android:color/white"
app:menu="#menu/menu_drawer">
</android.support.design.widget.NavigationView>
</android.support.v4.widget.DrawerLayout>
And the scrolling class:
public class ScrollingFABBehavior extends FloatingActionButton.Behavior {
public ScrollingFABBehavior(Context context, AttributeSet attrs) {
super();
}
public boolean onStartNestedScroll(CoordinatorLayout parent, FloatingActionButton child, View directTargetChild, View target, int nestedScrollAxes) {
return true;
}
#Override
public boolean layoutDependsOn(CoordinatorLayout parent, FloatingActionButton child, View dependency) {
if (dependency instanceof RecyclerView) {
return true;
}
return false;
}
#Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout,
FloatingActionButton child, View target, int dxConsumed,
int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed,
dxUnconsumed, dyUnconsumed
);
if (dyConsumed > 0 && child.getVisibility() == View.VISIBLE) {
child.hide();
} else if (dyConsumed < 0 && child.getVisibility() != View.VISIBLE) {
child.show();
}
}
}
Currently CoordinatorLayout is skipping views set to GONE when looking for behaviours to call in its onNestedScroll method.
A quick workaround here is setting the FAB's visibility to INVISIBLE at the end of the FAB's hide animation.
if (dyConsumed > 0 && child.getVisibility() == View.VISIBLE) {
child.hide(new FloatingActionButton.OnVisibilityChangedListener() {
#Override
public void onHidden(FloatingActionButton fab) {
super.onHidden(fab);
fab.setVisibility(View.INVISIBLE);
}
});
} else if (dyConsumed < 0 && child.getVisibility() != View.VISIBLE) {
child.show();
}
The above custom OnVisibilityChangedListener was only part of the solution for me. With the update setting the fab to INVISIBLE, it's also necessary to update the else if condition in the onNestedScroll() override to now test for the visibility being INVISIBLE, instead of GONE:
#Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
if(dyConsumed > 0 && child.getVisibility() == View.VISIBLE){
child.hide(new FloatingActionButton.OnVisibilityChangedListener() {
#Override
public void onHidden(FloatingActionButton fab) {
super.onHidden(fab);
fab.setVisibility(View.INVISIBLE);
}
});
} else if(dyConsumed < 0 && child.getVisibility() == View.INVISIBLE){
child.show();
}
}
I have a bit of the same issue with the tabview (after upgrading to 25.1.0) It shows the first time, but second time (After repopulating) it becomes invisible.
Related
I have to hide bottom navigation view on up scroll and show on down scroll .how to implement this?
my layout is like this
<?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">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_above="#+id/navigation"
android:layout_alignParentTop="true"
android:layout_marginBottom="5dp">
<FrameLayout
android:id="#+id/container1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>
<android.support.design.widget.BottomNavigationView
android:id="#+id/navigation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="?android:attr/windowBackground"
app:layout_scrollFlags="scroll|enterAlways|snap"
app:menu="#menu/dashboard_slider_menu" />
</RelativeLayout>
I have attached screenshot of view. Kindly check it.
UPDATE
Just add one attribute to BottomNavigationView
Material Library AndroidX
<com.google.android.material.bottomnavigation.BottomNavigationView
....
app:layout_behavior="com.google.android.material.behavior.HideBottomViewOnScrollBehavior"/>
Support Library Version 28.0.0 or higher version
<android.support.design.widget.BottomNavigationView
....
app:layout_behavior="#string/hide_bottom_view_on_scroll_behavior"/>
Note:- Your XML should follow the structure of XML given below in old answer.
**OLD ANSWER(Still Works)**
You need a helper class to do this .This solution works like Google Material Design Guideline.
Create a class BottomNavigationViewBehavior
public class BottomNavigationViewBehavior extends CoordinatorLayout.Behavior<BottomNavigationView> {
private int height;
#Override
public boolean onLayoutChild(CoordinatorLayout parent, BottomNavigationView child, int layoutDirection) {
height = child.getHeight();
return super.onLayoutChild(parent, child, layoutDirection);
}
#Override
public boolean onStartNestedScroll(#NonNull CoordinatorLayout coordinatorLayout,
BottomNavigationView child, #NonNull
View directTargetChild, #NonNull View target,
int axes, int type)
{
return axes == ViewCompat.SCROLL_AXIS_VERTICAL;
}
#Override
public void onNestedScroll(#NonNull CoordinatorLayout coordinatorLayout, #NonNull BottomNavigationView child,
#NonNull View target, int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed,
#ViewCompat.NestedScrollType int type)
{
if (dyConsumed > 0) {
slideDown(child);
} else if (dyConsumed < 0) {
slideUp(child);
}
}
private void slideUp(BottomNavigationView child) {
child.clearAnimation();
child.animate().translationY(0).setDuration(200);
}
private void slideDown(BottomNavigationView child) {
child.clearAnimation();
child.animate().translationY(height).setDuration(200);
}
}
For using this behavior you need to use cooradinator layout...
<android.support.v4.widget.DrawerLayout 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:id="#+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.kliff.digitaldwarka.activity.MainActivity">
<android.support.design.widget.CoordinatorLayout
android:id="#+id/coordinator_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
android:id="#+id/myAppBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:descendantFocusability="beforeDescendants"
android:focusableInTouchMode="true"
android:theme="#style/AppTheme.AppBarOverlay"
app:elevation="0dp">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:contentInsetStart="0dp"
app:layout_scrollFlags="scroll|enterAlways"
app:popupTheme="#style/AppTheme.PopupOverlay"/>
</android.support.design.widget.AppBarLayout>
<!---your RecyclerView/Fragment Container Layout-->
<FrameLayout
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior" />
<android.support.design.widget.BottomNavigationView
android:id="#+id/bottom_nav"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
app:itemBackground="#color/white"
app:menu="#menu/bottom_nav_menu" />
</android.support.design.widget.CoordinatorLayout>
<!---NavigationView-->
</android.support.v4.widget.DrawerLayout>
Add this code to your Activity that contains bottom nav..
mBottomNavigationView = (BottomNavigationView) findViewById(R.id.bottom_nav);
CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) mBottomNavigationView.getLayoutParams();
layoutParams.setBehavior(new BottomNavigationViewBehavior());
Try this,
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
if (dy > 0 && bottom_navigation.isShown()) {
bottom_navigation.setVisibility(View.GONE);
} else if (dy < 0 ) {
bottom_navigation.setVisibility(View.VISIBLE);
}
}
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
});
Image while scrolling up :-
Image while scrolling down:
Updated answer after the latest library updates:
Hiding the BottomNavigationView on scrolling is now available with just one flag in the layout! Starting from version 28.0.0-alpha1 or the material/androidX 1.0.0-alpha1.
I updated my project using the latter approach since the version now is a stable release candidate. Update: Use fully released version "1.0.0"!
The new out of the box available behaviour is called HideBottomViewOnScrollBehavior. Set it on the BottomNavigationView as
app:layout_behavior="#string/hide_bottom_view_on_scroll_behavior" as described in the latest docs.
Here is a full example:
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/navigation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:labelVisibilityMode="selected"
app:layout_behavior="#string/hide_bottom_view_on_scroll_behavior"
android:layout_gravity="bottom"
app:layout_insetEdge="bottom"
app:menu="#menu/navigation" />
As with the hiding of the Toolbar on scrolling, you have to ensure that the content is a class that supports the latest scrolling like RecyclerView and NestedScrollView.
This ensures all is working as shown in the animation on the design specs
PS: labelVisibilityMode is another cool addition you get for free for taking the trouble of updating and that is described in depth in the design specs.
Update your project to Androidx i.e Refactor >> Migrate to androidx (Minimum Android studio version 3.4)
Using the default Bottom Navigation Menu xml file, replace the parent Constraint Layout with Coordinator layout.
Add the line app:layout_behavior="com.google.android.material.behavior.HideBottomViewOnScrollBehavior"
i.e
<?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:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".dashboards.Admin_dashboard_main">
<include layout="#layout/toolbar" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/main_area"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintTop_toBottomOf="#+id/toolbar"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:layout_margin="0dp"
android:padding="0dp">
<!-- Fragments Container -->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
tools:context="MainActivity"
tools:showIn="#layout/activity_tenant_dashboard"
android:id="#+id/fragment_container">
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<!-- Bottom Navigation View -->
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/navigation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
android:background="?android:attr/windowBackground"
android:layout_gravity="bottom"
app:menu="#menu/menu_admin_dashboard_main"
app:layout_behavior="com.google.android.material.behavior.HideBottomViewOnScrollBehavior"
/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
Use this
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener()
{
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy)
{
if (dy > 0 ||dy<0 && csButtonLay.isShown())
{
bottomBar.setVisibility(View.GONE);
}
}
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState)
{
if (newState == RecyclerView.SCROLL_STATE_IDLE)
{
bottomBar.setVisibility(View.VISIBLE);
}
super.onScrollStateChanged(recyclerView, newState);
}
});
Just use CoordinatorLayout as a parent container and add the app:layout_behavior
in the child View and set the behavior #string/hide_bottom_view_on_scroll_behavior
this is the solution.
<?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:orientation="vertical"
tools:context=".Main2Activity">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_above="#id/nav_view"
android:layout_height="wrap_content"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/nav_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_gravity="bottom"
app:layout_behavior="#string/hide_bottom_view_on_scroll_behavior"
android:background="?android:attr/windowBackground"
app:menu="#menu/bottom_nav_menu" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
Happy Coding.
I encountered this issue working with the Recyclerview.
The attribute:
app:layout_behavior="#string/hide_bottom_view_on_scroll_behavior"/>
only partially worked for me, so I had to implement another solution.
I defined the BottomNavigationView inside the MainActivity so I had to set a couple of methods to animate it during the scrolling.
class MainActivity : AppCompatActivity() {
private var animator: ObjectAnimator? = null
.
.
.
fun slideDown() {
nav_view?.let {
if (animator == null && it.translationY == 0f) {
animator = translationObjectY(it, 0f, it.height.toFloat() + it.marginBottom.toFloat()).apply {
doOnEnd {
animator = null
}
}
}
}
}
fun slideUp() {
nav_view?.let {
if (animator == null && it.translationY == it.height.toFloat() + it.marginBottom.toFloat()) {
animator = translationObjectY(it, it.height.toFloat() + it.marginBottom.toFloat(), 0f).apply {
doOnEnd {
animator = null
}
}
}
}
}
}
The translationObjectY is an extended function:
fun translationObjectY(
targetView: View?,
startY: Float,
endY: Float,
duration: Long = 200L
) : ObjectAnimator {
return ObjectAnimator.ofFloat(targetView, "translationY", startY, endY).apply {
this.duration = duration
interpolator = LinearOutSlowInInterpolator()
start()
}
}
And I finally create a custom Recyclerview:
class CustomRecyclerView(
context: Context,
attrs: AttributeSet?,
defStyle: Int,
) : RecyclerView(context, attrs, defStyle) {
constructor(context: Context)
: this(context, null, 0)
constructor(context: Context, attrs: AttributeSet)
: this(context, attrs, 0)
init {
this.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
if (dy > 0) {
// Scrolling up
hideBottomMenu()
} else {
// Scrolling down
showBottomMenu()
}
}
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
}
})
}
private fun hideBottomMenu() {
(context as? MainActivity)?.slideDown()
}
private fun showBottomMenu() {
(context as? MainActivity)?.slideUp()
}
}
You can then implement it in your fragment like this:
<com.studio.mattiaferigutti.kamasutra.custom.CustomRecyclerView
android:id="#+id/searchRecycler"
android:layout_width="match_parent"
android:layout_height="match_parent" />
Just simply add this in your xml
<BottomNavigationView
....
....
app:layout_behavior="#string/hide_bottom_view_on_scroll_behavior"/>
This can help somebody
read more:https://material.io/develop/android/components/app-bars-bottom
add
app:hideOnScroll="true"
inside BottomAppBar Like below:
<androidx.coordinatorlayout.widget.CoordinatorLayout
...>
...
<com.google.android.material.bottomappbar.BottomAppBar
...
app:hideOnScroll="true"
/>
...
</androidx.coordinatorlayout.widget.CoordinatorLayout>
Use this code : when Scrolling down the Recyclerview to your fragment will hide the bottom navigation. then when Scrolled Up it will show the Bottom nav.
private View view;
private AppCompatActivity activity;
private ChipNavigationBar chipNavigationBar;
//...............................................
#Override
public View onCreateView(#NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
if (view == null) {
view = inflater.inflate(R.layout.list_fragment, container, false);
hide_NavigationBar_adwhen_Scrolling();
}
return view;
}
//...........................................................
private void hide_NavigationBar_adwhen_Scrolling() {
activity = (AppCompatActivity) view.getContext();
chipNavigationBar = activity.findViewById(R.id.chipNavigation);
RecyclerView recyclerView = view.findViewById(R.id.recylerView);
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
if (dy > 0) {//on_Scrolled_down
// chipNavigationBar.animate().translationY(200).setDuration(500);
chipNavigationBar.animate().translationY(banner_ad_card_1.getHeight()).setDuration(1000);
} else {//on_Scrolled_up
chipNavigationBar.setVisibility(View.VISIBLE);
chipNavigationBar.animate().translationY(0).setDuration(1000);
// chipNavigationBar.setItemSelected(R.id.home, true);
}
}
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
});
}
I'm trying to hide a FloatingActionButton when a NestedScrollView scroll down, and revealed itself when NestedScrollView scroll up.
Here is my layout:
<android.support.design.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">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true">
<android.support.design.widget.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
app:layout_scrollFlags="scroll|enterAlways">
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<android.support.v4.widget.NestedScrollView
app:layout_behavior="#string/appbar_scrolling_view_behavior"/>
<android.support.design.widget.FloatingActionButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="#dimen/grid_2"
android:layout_gravity="bottom|end"
android:src="#drawable/ic_place_white"
android:clickable="true"
app:backgroundTint="#color/colorPrimary"
app:layout_behavior="com.myapp.ScrollAnimationFAB"/>
</android.support.design.widget.CoordinatorLayout>
And here is my floatingActionButton behavior:
public class ScrollAnimationFAB extends FloatingActionButton.Behavior {
public ScrollAnimationFAB(Context context, AttributeSet attrs) {
super();
}
#Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child, View directTargetChild, View target, int nestedScrollAxes) {
return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL ||
super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target,
nestedScrollAxes);
}
#Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
if (dyConsumed > 0 && child.getVisibility() == View.VISIBLE) {
child.hide();
} else if (dyConsumed < 0 && child.getVisibility() != View.VISIBLE) {
child.show();
}
}
}
These code is not working for me, I wonder if it has something to do with NestedScrollView's behavior. Any help will be appreciated!
UPDATE
I found something wired! If I call fab's method (child.hide(), child.show()) in onNestedScroll, onStartNestedScroll and onNestedScroll never get called again, but if I didn't call methods in fab, onStartNestedScroll and onNestedScroll get called normally.
Take a look at what #woxingxiao is saying here
Pretty much onNestedScroll won't get fired if the visibility of the button is GONE. So, replace:
child.hide();
to:
child.hide(new FloatingActionButton.OnVisibilityChangedListener() {
#Override
public void onHidden(FloatingActionButton fab) {
super.onHidden(fab);
fab.setVisibility(View.INVISIBLE);
}
});
I've seen various posts about FABs responding to Snackbar popups at the bottom of the screen as well as scroll-sensitive FABs. But is there some implementation of FloatingActionButton.Behavior (or similar) to move the FAB above the keyboard when it pops up?
Right now, the keyboard covers the FAB when I click for example in an EditText box. My goal is to animate the FAB so it is always visible, independent of the keyboard status.
EDIT: Both android:windowSoftInputMode="adjustResize" and ...="adjustPan" won't change anything. Well, adjustResize resizes the underlying layout (which is in my case a map) but the FAB doesn't move.
Hi there i know it's old but for future or current readers/searchers and also the thread maker, he hasn't found answer yet. This is how i am having this behaviour in my app.
Fab hides on RecyclerView scroll, goes up when snack bar pops out, if fab is not shown and snackbar popus up and if you scroll then still Fab will be shown top of snack bar and will move down when SB disappears and last with keypad if it opens Fab will be pushed up. (sorry, i had to write coz i don't know how to give gif with eclipse emulator)
Image
Layout
<android.support.v4.widget.DrawerLayout 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:id="#+id/layout_drawer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/GrayGood"
android:clipToPadding="false"
android:fitsSystemWindows="true" >
<android.support.design.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:id="#+id/layout_coordinator"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<android.support.design.widget.AppBarLayout
android:id="#+id/layout_appLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/ThemeOverlay.AppCompat.Dark" >
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_scrollFlags="scroll|enterAlways|snap"
android:background="?attr/colorPrimary" />
</android.support.design.widget.AppBarLayout>
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerViewMain"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
android:clipToPadding="false" />
<android.support.design.widget.FloatingActionButton
android:id="#+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_gravity="bottom|end"
android:layout_margin="#dimen/floating_action_button_margin"
app:layout_behavior="com.faisal.cvcd.anim.ScrollingFABAnimation"
android:src="#drawable/ic_fab"
android:tint="#color/White"
app:backgroundTint="#color/colorPrimary"
app:borderWidth="0dp"
app:elevation="6dp"
app:fabSize="normal" />
</android.support.design.widget.CoordinatorLayout>
</android.support.v4.widget.DrawerLayout>
As you can see i am using FabAnimation class to override some of its default methods.
ScrollingFABAnimation
import android.content.Context;
import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.view.View;
public class ScrollingFABAnimation extends
CoordinatorLayout.Behavior<FloatingActionButton> {
public ScrollingFABAnimation(Context context, AttributeSet attrs) {
super(context, attrs);
}
//For SnackBar to push up the Floating Button when it pops up
#Override
public boolean layoutDependsOn(CoordinatorLayout parent, FloatingActionButton child, View dependency) {
return dependency instanceof Snackbar.SnackbarLayout;
}
#Override
public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton child, View dependency) {
float translationY = Math.min(0, dependency.getTranslationY() - dependency.getHeight());
child.setTranslationY(translationY);
return true;
}
//For FAB to hide or show on scroll
#Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child, View directTargetChild, View target,
int nestedScrollAxes) {
return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL || super.onStartNestedScroll(coordinatorLayout, child,
directTargetChild, target, nestedScrollAxes);
}
#Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child, View target, int dxConsumed,
int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
if (dyConsumed > 0 && child.getVisibility() == View.VISIBLE) {
child.hide();
} else if (dyConsumed < 0 && child.getVisibility() != View.VISIBLE) {
child.show();
}
}
}
I had the same problem, i tried to add a ScrollView inside my root view but it did not works because the content doesn't exceed the display's height (the fab react as expected in that case).
So i tried android:windowSoftInputMode="adjustResize" and it works for me.
For informations here's what my layout xml looks like:
<?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">
<ManyViewsExceptScrollViews/>
<android.support.design.widget.FloatingActionButton
[..]
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"/>
</RelativeLayout>
it's a Fragment inflated in an activity (in a FrameLayout that match_parent the root view) for which i added android:windowSoftInputMode="adjustResize" in the manifest.
Is it really intended that the Toolbar in a AppBarLayout is scrollable although the main container with the "appbar_scrolling_view_behavior" has not enough content to really scroll?
What I have tested so far:
When I use a NestedScrollView (with "wrap_content" attribute) as main container and a TextView as child, the AppBarLayout works properly and does not scroll.
However, when I use a RecyclerView with only a few entries and the "wrap_content" attribute (so that there is no need to scroll), the Toolbar in the AppBarLayout is scrollable even though the RecyclerView never receives a scroll event (tested with a OnScrollChangeListener).
Here's my layout code:
<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/coordinatorLayout"
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="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:layout_scrollFlags="scroll|enterAlways"
app:theme="#style/ToolbarStyle" />
</android.support.design.widget.AppBarLayout>
<android.support.v7.widget.RecyclerView
android:id="#+id/recycler"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_behavior="#string/appbar_scrolling_view_behavior" />
</android.support.design.widget.CoordinatorLayout>
With the following effect that the toolbar is scrollable although it's not necessary:
I've also found a way to deal with this by checking if all RecyclerView items are visible and using the setNestedScrollingEnabled() method of the RecyclerView.
Nevertheless, it does seem more like a bug as intended to me. Any opinions? :D
EDIT #1:
For people who are might be interested in my current solution, I had to put the setNestedScrollingEnabled() logic in the postDelayed() method of a Handler with 5 ms delay due to the LayoutManager which always returned -1 when calling the methods to find out whether the first and the last item is visible.
I use this code in the onStart() method (after my RecyclerView has been initialized) and every time after a content change of the RecyclerView occurs.
final LinearLayoutManager layoutManager = (LinearLayoutManager) mRecyclerView.getLayoutManager();
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
//no items in the RecyclerView
if (mRecyclerView.getAdapter().getItemCount() == 0)
mRecyclerView.setNestedScrollingEnabled(false);
//if the first and the last item is visible
else if (layoutManager.findFirstCompletelyVisibleItemPosition() == 0
&& layoutManager.findLastCompletelyVisibleItemPosition() == mRecyclerView.getAdapter().getItemCount() - 1)
mRecyclerView.setNestedScrollingEnabled(false);
else
mRecyclerView.setNestedScrollingEnabled(true);
}
}, 5);
EDIT #2:
I just played around with a new app and it seems that this (unintended) behavior has been fixed in support library version 23.3.0 (or even earlier). Thus, there is no need for workarounds anymore!
Edit 2:
Turns out the only way to ensure Toolbar is not scrollable when RecyclerView is not scrollable is to set setScrollFlags programmatically which requires to check if RecyclerView's is scrollable. This check has to be done every time adapter is modified.
Interface to communicate with the Activity:
public interface LayoutController {
void enableScroll();
void disableScroll();
}
MainActivity:
public class MainActivity extends AppCompatActivity implements
LayoutController {
private CollapsingToolbarLayout collapsingToolbarLayout;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
collapsingToolbarLayout =
(CollapsingToolbarLayout) findViewById(R.id.collapsing_toolbar);
final FragmentManager manager = getSupportFragmentManager();
final Fragment fragment = new CheeseListFragment();
manager.beginTransaction()
.replace(R.id.root_content, fragment)
.commit();
}
#Override
public void enableScroll() {
final AppBarLayout.LayoutParams params = (AppBarLayout.LayoutParams)
collapsingToolbarLayout.getLayoutParams();
params.setScrollFlags(
AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL
| AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS
);
collapsingToolbarLayout.setLayoutParams(params);
}
#Override
public void disableScroll() {
final AppBarLayout.LayoutParams params = (AppBarLayout.LayoutParams)
collapsingToolbarLayout.getLayoutParams();
params.setScrollFlags(0);
collapsingToolbarLayout.setLayoutParams(params);
}
}
activity_main.xml:
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/drawer_layout"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:fitsSystemWindows="true">
<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.design.widget.CollapsingToolbarLayout
android:id="#+id/collapsing_toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
app:contentScrim="?attr/colorPrimary">
<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"/>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<FrameLayout
android:id="#+id/root_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="fill_vertical"
app:layout_behavior="#string/appbar_scrolling_view_behavior"/>
</android.support.design.widget.CoordinatorLayout>
</android.support.v4.widget.DrawerLayout>
Test Fragment:
public class CheeseListFragment extends Fragment {
private static final int DOWN = 1;
private static final int UP = 0;
private LayoutController controller;
private RecyclerView rv;
#Override
public void onAttach(Context context) {
super.onAttach(context);
try {
controller = (MainActivity) getActivity();
} catch (ClassCastException e) {
throw new RuntimeException(getActivity().getLocalClassName()
+ "must implement controller.", e);
}
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
rv = (RecyclerView) inflater.inflate(
R.layout.fragment_cheese_list, container, false);
setupRecyclerView(rv);
// Find out if RecyclerView are scrollable, delay required
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
if (rv.canScrollVertically(DOWN) || rv.canScrollVertically(UP)) {
controller.enableScroll();
} else {
controller.disableScroll();
}
}
}, 100);
return rv;
}
private void setupRecyclerView(RecyclerView recyclerView) {
final LinearLayoutManager layoutManager = new LinearLayoutManager(recyclerView.getContext());
recyclerView.setLayoutManager(layoutManager);
final SimpleStringRecyclerViewAdapter adapter =
new SimpleStringRecyclerViewAdapter(
getActivity(),
// Test ToolBar scroll
getRandomList(/* with enough items to scroll */)
// Test ToolBar pin
getRandomList(/* with only 3 items*/)
);
recyclerView.setAdapter(adapter);
}
}
Sources:
Change scroll flags programmatically
Original code by Chris Banes
Need a postDelayed to ensure RecyclerView children are ready for calculations
Edit:
You should CollapsingToolbarLayout to control the behaviour.
Adding a Toolbar directly to an AppBarLayout gives you access to the enterAlwaysCollapsed and exitUntilCollapsed scroll flags, but not the detailed control on how different elements react to collapsing.
[...] setup uses CollapsingToolbarLayout’s app:layout_collapseMode="pin" to ensure that the Toolbar itself remains pinned to the top of the screen while the view collapses.http://android-developers.blogspot.com.tr/2015/05/android-design-support-library.html
<android.support.design.widget.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<android.support.v7.widget.Toolbar
android:id="#+id/drawer_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin"/>
</android.support.design.widget.CollapsingToolbarLayout>
Add
app:layout_collapseMode="pin"
to your Toolbar in xml.
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:layout_scrollFlags="scroll|enterAlways"
app:layout_collapseMode="pin"
app:theme="#style/ToolbarStyle" />
So, proper credit, this answer almost solved it for me https://stackoverflow.com/a/32923226/5050087. But since it was not showing the toolbar when you actually had an scrollable recyclerview and its last item was visible (it would not show the toolbar on the first scroll up), I decided to modify it and adapt it for an easier implementation and for dynamic adapters.
First, you must create a custom layout behavior for you appbar:
public class ToolbarBehavior extends AppBarLayout.Behavior{
private boolean scrollableRecyclerView = false;
private int count;
public ToolbarBehavior() {
}
public ToolbarBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public boolean onInterceptTouchEvent(CoordinatorLayout parent, AppBarLayout child, MotionEvent ev) {
return scrollableRecyclerView && super.onInterceptTouchEvent(parent, child, ev);
}
#Override
public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child, View directTargetChild, View target, int nestedScrollAxes, int type) {
updatedScrollable(directTargetChild);
return scrollableRecyclerView && super.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes, type);
}
#Override
public boolean onNestedFling(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, float velocityX, float velocityY, boolean consumed) {
return scrollableRecyclerView && super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, consumed);
}
private void updatedScrollable(View directTargetChild) {
if (directTargetChild instanceof RecyclerView) {
RecyclerView recyclerView = (RecyclerView) directTargetChild;
RecyclerView.Adapter adapter = recyclerView.getAdapter();
if (adapter != null) {
if (adapter.getItemCount()!= count) {
scrollableRecyclerView = false;
count = adapter.getItemCount();
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
if (layoutManager != null) {
int lastVisibleItem = 0;
if (layoutManager instanceof LinearLayoutManager) {
LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;
lastVisibleItem = Math.abs(linearLayoutManager.findLastCompletelyVisibleItemPosition());
} else if (layoutManager instanceof StaggeredGridLayoutManager) {
StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) layoutManager;
int[] lastItems = staggeredGridLayoutManager.findLastCompletelyVisibleItemPositions(new int[staggeredGridLayoutManager.getSpanCount()]);
lastVisibleItem = Math.abs(lastItems[lastItems.length - 1]);
}
scrollableRecyclerView = lastVisibleItem < count - 1;
}
}
}
} else scrollableRecyclerView = true;
}
}
Then, you only need to define this behavior for you appbar in your layout file:
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
app:layout_behavior="com.yourappname.whateverdir.ToolbarBehavior"
>
I haven't tested it for screen rotation so let me know if it works like this. I guess it should work as I don't think the count variable is saved when the rotation happens, but let me know if it doesn't.
This was the easiest and cleanest implementation for me, enjoy it.
It is not a bug, all the events in a viewGroup are handled this way. Because your recyclerview is a child of coordinatorLayout so whenever the event is generated, it is first checked for parent and if parent is not interested only then it is passed down to child.
See google documentation
Something like this in a LayoutManager subclass seems to result in the desired behavior:
#Override
public boolean canScrollVertically() {
int firstCompletelyVisibleItemPosition = findFirstCompletelyVisibleItemPosition();
if (firstCompletelyVisibleItemPosition == RecyclerView.NO_POSITION) return false;
int lastCompletelyVisibleItemPosition = findLastCompletelyVisibleItemPosition();
if (lastCompletelyVisibleItemPosition == RecyclerView.NO_POSITION) return false;
if (firstCompletelyVisibleItemPosition == 0 &&
lastCompletelyVisibleItemPosition == getItemCount() - 1)
return false;
return super.canScrollVertically();
}
The documentation for canScrollVertically() says:
/**
* Query if vertical scrolling is currently supported. The default implementation
* returns false.
*
* #return True if this LayoutManager can scroll the current contents vertically
*/
Notice the wording of "can scroll the current contents vertically", which I believe implies that the current state should be reflected by the return value.
However, that is not done by any of the LayoutManager subclasses provided through the v7 recyclerview library (23.1.1), which makes me somewhat hesitant whether it is a correct solution; it might cause undesired effects in other situations than the one discussed in this question.
I've implemented it using my own Behavior class which might be attached to AppBarLayout:
public class CustomAppBarLayoutBehavior extends AppBarLayout.Behavior {
private RecyclerView recyclerView;
private int additionalHeight;
public CustomAppBarLayoutBehavior(RecyclerView recyclerView, int additionalHeight) {
this.recyclerView = recyclerView;
this.additionalHeight = additionalHeight;
}
public boolean isRecyclerViewScrollable(RecyclerView recyclerView) {
return recyclerView.computeHorizontalScrollRange() > recyclerView.getWidth() || recyclerView.computeVerticalScrollRange() > (recyclerView.getHeight() - additionalHeight);
}
#Override
public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child, View directTargetChild, View target, int nestedScrollAxes) {
if (isRecyclerViewScrollable(mRecyclerView)) {
return super.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes);
}
return false;
}
}
And below is the code how to set this behavior:
final View appBarLayout = ((DrawerActivity) getActivity()).getAppBarLayoutView();
CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams();
layoutParams.setBehavior(new AppBarLayoutNoEmptyScrollBehavior(recyclerView, getResources().getDimensionPixelSize(R.dimen.control_bar_height)));
I suggested you try this sample that for support desing library elements.
this 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.CoordinatorLayout>
Thanks, I created a custom class of RecyclerView but the key is still using setNestedScrollingEnabled(). It worked fine on my side.
public class RecyclerViewCustom extends RecyclerView implements ViewTreeObserver.OnGlobalLayoutListener
{
public RecyclerViewCustom(Context context)
{
super(context);
}
public RecyclerViewCustom(Context context, #Nullable AttributeSet attrs)
{
super(context, attrs);
}
public RecyclerViewCustom(Context context, #Nullable AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
}
/**
* This supports scrolling when using RecyclerView with AppbarLayout
* Basically RecyclerView should not be scrollable when there's no data or the last item is visible
*
* Call this method after Adapter#updateData() get called
*/
public void addOnGlobalLayoutListener()
{
this.getViewTreeObserver().addOnGlobalLayoutListener(this);
}
#Override
public void onGlobalLayout()
{
// If the last item is visible or there's no data, the RecyclerView should not be scrollable
RecyclerView.LayoutManager layoutManager = getLayoutManager();
final RecyclerView.Adapter adapter = getAdapter();
if (adapter == null || adapter.getItemCount() <= 0 || layoutManager == null)
{
setNestedScrollingEnabled(false);
}
else
{
int lastVisibleItemPosition = ((LinearLayoutManager) layoutManager).findLastCompletelyVisibleItemPosition();
boolean isLastItemVisible = lastVisibleItemPosition == adapter.getItemCount() - 1;
setNestedScrollingEnabled(!isLastItemVisible);
}
unregisterGlobalLayoutListener();
}
private void unregisterGlobalLayoutListener()
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
{
getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
else
{
getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
}
}
I would like to add a little to user3623735's answer. The following code is absolutely incorrect.
// Find out if RecyclerView are scrollable, delay required
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
if (rv.canScrollVertically(DOWN) || rv.canScrollVertically(UP)) {
controller.enableScroll();
} else {
controller.disableScroll();
}
}
}, 100);
And even when it works - it doesn't cover all cases. There is absolutely no guarantee that a data will be displayed in 100 ms, and the data can stretch the height of the view in the process of working with it, not only in the onCreateView method. That's why you should use next code and track changes in view height:
view.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
#Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
if(bottom != oldBottom)
{
mActivity.setScrollEnabled(view.canScrollVertically(0) || view.canScrollVertically(1));
}
}
});
Moreover no need to create two separated method to control scrolling status, you should use one setScrollEnabled method:
public void setScrollEnabled(boolean enabled) {
final AppBarLayout.LayoutParams params = (AppBarLayout.LayoutParams)
mToolbar.getLayoutParams();
params.setScrollFlags(enabled ?
AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS : 0);
mToolbar.setLayoutParams(params);
}
In your Toolbar remove the scroll flag, leaving only the enterAlways flag and you should get the effect you intended. For completeness, your layout should look like:
<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/coordinatorLayout"
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="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:layout_scrollFlags="enterAlways"
app:theme="#style/ToolbarStyle" />
</android.support.design.widget.AppBarLayout>
<android.support.v7.widget.RecyclerView
android:id="#+id/recycler"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_behavior="#string/appbar_scrolling_view_behavior" />
</android.support.design.widget.CoordinatorLayout>
I am trying to achieve an effect like WhatsApp has, where the Toolbar (when scrolled) will clip into view magnetlike, or out of view magnetlike.
What I have im my MainActivity XML:
DrawerLayout - Base Layout
CoordinatorLayout - Layout for the Appbar and Toolbar and Tabs
AppBarLayout - For holding Toolbar and Tabs
Toolbar - has THIS flag: app:layout_scrollFlags="scroll|enterAlways"
SlidingTabLayout - Displays tabs
ViewPager - For tabs
RecyclerView - For coordinatorlayout
Now dont get me wrong, it works, when I scroll down the toolbar gets pushed out of view but say I stop scrolling halfway, then the toolbar just sits there half hidden out of view and the other half in view..
How can I approach solving this problem, as I want it to either snap out of view or into view.
This feature has been added in 23.1.0 version of android support library.
From release notes:
Added edge snapping support to the AppBarLayout class by adding the
SCROLL_FLAG_SNAP constant. When scrolling ends, if the view is only
partially visible, the view is snapped and scrolled to its closest
edge.
<android.support.design.widget.CoordinatorLayout
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:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|enterAlways|snap" />
-----
-----
For more info: http://android-developers.blogspot.in/2015/10/android-support-library-231.html
EDIT: as of support 23.1.0 this is no longer needed. See this answer instead.
One possible way to solve this is customizing the Behavior set to your AppBarLayout.
<android.support.design.widget.AppBarLayout
app:layout_behavior="com.myapp.AppBarLayoutSnapBehavior"
android:layout_width="match_parent"
android:layout_height="wrap_content">
...
Your AppBarLayoutSnapBehavior would change the default behavior of AppBarLayout.Behavior, by adding the snap logic when the scroll stops.
Hopefully, the code below is self explanatory.
package com.myapp;
public class AppBarLayoutSnapBehavior extends AppBarLayout.Behavior {
private ValueAnimator mAnimator;
private boolean mNestedScrollStarted = false;
public AppBarLayoutSnapBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child,
View directTargetChild, View target, int nestedScrollAxes) {
mNestedScrollStarted = super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes);
if (mNestedScrollStarted && mAnimator != null) {
mAnimator.cancel();
}
return mNestedScrollStarted;
}
#Override
public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target) {
super.onStopNestedScroll(coordinatorLayout, child, target);
if (!mNestedScrollStarted) {
return;
}
mNestedScrollStarted = false;
int scrollRange = child.getTotalScrollRange();
int topOffset = getTopAndBottomOffset();
if (topOffset <= -scrollRange || topOffset >= 0) {
// Already fully visible or fully invisible
return;
}
if (topOffset < -(scrollRange / 2f)) {
// Snap up (to fully invisible)
animateOffsetTo(-scrollRange);
} else {
// Snap down (to fully visible)
animateOffsetTo(0);
}
}
private void animateOffsetTo(int offset) {
if (mAnimator == null) {
mAnimator = new ValueAnimator();
mAnimator.setInterpolator(new DecelerateInterpolator());
mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
setTopAndBottomOffset((int) animation.getAnimatedValue());
}
});
} else {
mAnimator.cancel();
}
mAnimator.setIntValues(getTopAndBottomOffset(), offset);
mAnimator.start();
}
}
The only thing is, the scroll view (in my case a RecyclerView) snaps along with the Toolbar. I actually like it this way, but I'm not sure that's what you want.
I just hided action bar layout in main activity and set span for CollapsingToolbarLayout.
it works for me.
in main activity
setSupportActionBar(mToolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().hide();
CollapsingToolbarLayout collapsingToolbar =
(CollapsingToolbarLayout) findViewById(R.id.collapsing_toolbar);
collapsingToolbar.setTitle("Name");
loadBackdrop();
and layout_activity_main
<android.support.design.widget.AppBarLayout
android:id="#+id/appbar"
android:layout_width="match_parent"
android:layout_height="#dimen/detail_backdrop_height"
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="match_parent"
app:layout_scrollFlags="scroll|snap"
android:fitsSystemWindows="true"
app:contentScrim="?attr/colorPrimary"
app:expandedTitleMarginStart="48dp"
app:expandedTitleMarginEnd="64dp">
<ImageView
android:id="#+id/backdrop"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:fitsSystemWindows="true"
app:layout_collapseMode="parallax" />
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:popupTheme="#style/ThemeOverlay.AppCompat.Light"
app:layout_collapseMode="pin" />
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>