FAB Behavior prevents RecyclerView items from being clicked - android

I have a RecyclerView inside a CoordinatorLayout along with a FAB :
<androidx.coordinatorlayout.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">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recycler"
android:scrollbars="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<TextView
android:id="#+id/empty"
android:visibility="invisible"
android:textSize="#dimen/title"
android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="#+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="#dimen/margin"
android:src="#drawable/add"
android:layout_gravity="bottom|right"
app:fabSize="normal"
app:tint="#android:color/white" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
When attaching behaviour to FAB like this :
(fab.layoutParams as CoordinatorLayout.LayoutParams).behavior = ScrollAwareFABBehavior()
Then first click event is not dispatched to item views click listeners (ViewHolder.itemView.setOnClickListener()) when FAB is hidden. Here is behaviour implementation :
class ScrollAwareFABBehavior : FloatingActionButton.Behavior() {
override fun onStartNestedScroll(coordinatorLayout: CoordinatorLayout, child: FloatingActionButton, directTargetChild: View, target: View, axes: Int, type: Int): Boolean {
return axes == ViewCompat.SCROLL_AXIS_VERTICAL || super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, axes, type)
}
override fun onNestedScroll(coordinatorLayout: CoordinatorLayout, child: FloatingActionButton, target: View, dxConsumed: Int, dyConsumed: Int, dxUnconsumed: Int, dyUnconsumed: Int, type: Int, consumed: IntArray) {
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type, consumed)
if (dyConsumed > 0) {
child.hide(object : OnVisibilityChangedListener() {
override fun onHidden(fab: FloatingActionButton) {
super.onHidden(fab)
fab.visibility = View.INVISIBLE
}
})
} else if (dyConsumed < 0) {
child.show()
}
}
}
OnStartNestedScroll gets called but not the item click listener unless I really take care not to scroll screen from 1px when clicking recycler item.
Everything works as expected when :
removing behaviour from FAB
removing OnVisibilityChangedListener from behaviour implementation
or removing only fab.visibility = View.INVISIBLE from behaviour impl
But without the fab.visibility = View.INVISIBLE FAB never shows up again after it has been hidden by the FabBehaviour (F.A.B Hides but Doesn't Show)...
If anyone can help me show/hide the FAB using behaviour and not have first click not delivered to item view, it would be great !

Using a RecyclerView.OnScrollListener helped me solve this issue in a simpler manner :
// (fab.layoutParams as CoordinatorLayout.LayoutParams).behavior = ScrollAwareFABBehavior()
recyclerView.addOnScrollListener(object: RecyclerView.OnScrollListener() {
var scrolling : Boolean = false
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
if (!scrolling) return
if (dy > 0) {
fab.hide()
} else {
fab.show()
}
}
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
scrolling = newState == RecyclerView.SCROLL_STATE_DRAGGING
}
})

Related

Android CoordinatorLayout with AppbarLayout Double Fling Bug

AppbarLayout has a big child, so we can drag and fling it.
It goes wrong when I fling appbar with its child first and fling scrollview before previous fling animation is finished.
The scrolling then became a mess when double flinging occured on both appbar and nestedscrollview
Here is the layout.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$Behavior"
>
<View
android:layout_width="match_parent"
android:layout_height="800dp"
android:background="#8f8"
app:layout_scrollFlags="scroll"
/>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<View
android:layout_width="match_parent"
android:layout_height="800dp"
android:background="#88f" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
I have the same problem with RecyclerView
See HeaderViewBehavior.setHeaderTopBottomOffset, it's invoked with different directions for newOffset parameter.
You should cancel AppBarLayout.Behavior fling when onStartNestedScroll invoked.
How to fix:
Override AppBarLayout.Behavior
package com.google.android.material.appbar // original package
import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.widget.OverScroller
import androidx.coordinatorlayout.widget.CoordinatorLayout
class FixFlingBehavior #JvmOverloads constructor(
context: Context,
attributeSet: AttributeSet? = null
) : AppBarLayout.Behavior(context, attributeSet) {
private var isAppbarFlinging: Boolean = false
private var originalScroller: OverScroller? = null
private val fakeScroller: OverScroller = object : OverScroller(context) {
override fun computeScrollOffset(): Boolean {
scroller = originalScroller
return false // it cancels HeaderBehavior FlingRunnable
}
}
override fun setHeaderTopBottomOffset(coordinatorLayout: CoordinatorLayout, appBarLayout: AppBarLayout, newOffset: Int, minOffset: Int, maxOffset: Int): Int {
isAppbarFlinging = minOffset == Int.MIN_VALUE && maxOffset == Int.MAX_VALUE
if (scroller === fakeScroller) {
scroller = originalScroller
}
return super.setHeaderTopBottomOffset(coordinatorLayout, appBarLayout, newOffset, minOffset, maxOffset)
}
override fun onFlingFinished(parent: CoordinatorLayout, layout: AppBarLayout) {
super.onFlingFinished(parent, layout)
isAppbarFlinging = false
}
override fun onStartNestedScroll(parent: CoordinatorLayout, child: AppBarLayout, directTargetChild: View, target: View, nestedScrollAxes: Int, type: Int): Boolean {
if (isAppbarFlinging && scroller !== fakeScroller) {
originalScroller = scroller
scroller = fakeScroller
}
return super.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes, type)
}
}
And use it in your layout
...
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="com.google.android.material.appbar.FixFlingBehavior">
...

Changing Extended Floating Button to normal size when scrolling

Good Afternoon, I have been trying to add my extended floating button to change to a normal floating button when scrolling but nothing happen and it stays extended and im not sure when:
my orderdetail (Where I call the extension):
recyclerView.addOnScrollListener(FabExtendingOnScrollListener(extended_fab))
where it should do extend or squish:
class FabExtendingOnScrollListener(
private val floatingActionButton: ExtendedFloatingActionButton
) : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
if (newState == RecyclerView.SCROLL_STATE_IDLE
&& !floatingActionButton.isExtended
&& recyclerView.computeVerticalScrollOffset() == 0
) {
floatingActionButton.extend()
}
super.onScrollStateChanged(recyclerView, newState)
}
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
if (dy != 0 && floatingActionButton.isExtended) {
floatingActionButton.shrink()
}
super.onScrolled(recyclerView, dx, dy)
}
}
My xml file(Added only area that matters):
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="#+id/btnContainer">
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="#+id/extended_fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right|bottom"
android:layout_margin="16dp"
android:layout_marginEnd="275dp"
app:icon="#drawable/ic_hourglass"
android:text="Delay order"
android:backgroundTint="#color/standardOrange"
android:layout_marginBottom="667dp">
</com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</RelativeLayout>
</layout>
So this helps to show the button but however it doesnt shrink or extend at all
please use
floatingActionButton.shrink();

Wrong scroll position in CoordinatorLayout.Behavior

I am using CoordinatorLayout with custom behavior and NestedScrollView in my project. It works well except one issue. When I am call nestedScrollView.smoothScrollTo(0,0) I get wrong Y coordinated in MyCustomBehavior. When I scroll it by finger I receive correct coordinates. How to get correct coordinates in behavior class when it scrolling by smoothScrollTo() method?
Here is my code:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.CardView
android:layout_width="300dp"
android:layout_height="150dp"
android:background="#f00"
android:layout_gravity="center_horizontal"
app:layout_behavior="com.mysite.app.MyCustomBehavior" />
<com.mysite.app.CustomScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Very long text here" />
</com.mysite.app.CustomScrollView>
<android.support.v7.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:layout_marginTop="24dp"
android:elevation="20dp" />
</android.support.design.widget.CoordinatorLayout>
</android.support.constraint.ConstraintLayout>
CustomScrollView:
class CustomScrollView #JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : NestedScrollView(context, attrs, defStyleAttr) {
private val runnable = Runnable {
if (scrollY in 1..299) {
smoothScrollTo(0,0)
}
}
override fun stopNestedScroll(type: Int) {
super.stopNestedScroll(type)
removeCallbacks(runnable)
handler.postDelayed(runnable, 200)
}
}
Behavior:
class CollapsingImageBehavior(private val context: Context, attrs: AttributeSet?) : CoordinatorLayout.Behavior<CardView>(context, attrs) {
private var scroll = 0
override fun layoutDependsOn(parent: CoordinatorLayout, child: CardView, dependency: View): Boolean {
return dependency is NestedScrollView
}
override fun onStartNestedScroll(coordinatorLayout: CoordinatorLayout, child: CardView, directTargetChild: View, target: View, axes: Int, type: Int): Boolean {
return axes == ViewCompat.SCROLL_AXIS_VERTICAL || super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, axes, type)
}
override fun onNestedScroll(coordinatorLayout: CoordinatorLayout, child: CardView, target: View, dxConsumed: Int, dyConsumed: Int, dxUnconsumed: Int, dyUnconsumed: Int, type: Int) {
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type)
scroll += dyConsumed
Log.d("tag", "onNestedScroll " + scroll)
}
override fun onDependentViewChanged(parent: CoordinatorLayout, child: CardView, dependency: View): Boolean {
return true
}
}

How i can hide BottomNavigationView when list view scroll

I have created an app with BottomNavigationView but I want when user scroll the list view then BottomNavigationView should hide. I tried a lot but I am not able to do that. Can anyone help me?
Layout code:
<android.support.design.widget.BottomNavigationView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/navigation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/windowBackground"
app:menu="#menu/bottom_nav_menu"/>
you must create a class to handle the behavior of the BottomNavigationView.
Below I show a code made in Kotlin.
class BottomNavigationBehavior constructor(
context: Context,
attrs: AttributeSet? = null
) : CoordinatorLayout.Behavior<BottomNavigationView>(context, attrs) {
override fun layoutDependsOn(
parent: CoordinatorLayout,
child: BottomNavigationView,
dependency: View
): Boolean {
return dependency is FrameLayout
}
override fun onStartNestedScroll(
coordinatorLayout: CoordinatorLayout,
child: BottomNavigationView,
directTargetChild: View,
target: View,
axes: Int,
type: Int
): Boolean {
return axes == ViewCompat.SCROLL_AXIS_VERTICAL
}
/*this is where bottomnavigation is shown or hidden*/
override fun onNestedPreScroll(coordinatorLayout: CoordinatorLayout, child: BottomNavigationView,
target: View, dx: Int, dy: Int, consumed: IntArray, type: Int) {
if (dy < 0) {
showBottomNavigationView(child)
} else if (dy > 0) {
hideBottomNavigationView(child)
}
}
private fun hideBottomNavigationView(view: BottomNavigationView) {
view.animate().translationY(view.height.toFloat())
}
private fun showBottomNavigationView(view: BottomNavigationView) {
view.animate().translationY(0f)
}
}
After creating the class, we need to tell the BottomNavigationView which is the class that will control its behavior.
val layoutParams: CoordinatorLayout.LayoutParams = mBottomNavigation.layoutParams as CoordinatorLayout.LayoutParams
layoutParams.behavior = BottomNavigationBehavior(this)
I hope it helps you.

Collapse header on scrolling without using CollapsingToolbarLayout

I'm trying to implement the same behaviour than the CollapsingToolbarLayout for a view above my recyclerview that collapses to a minimum height on downscrolling and restores to its normal height when the scroll reaches the top. This header is not a Toolbar, hence the custom view.
I managed to do it, but when scrolling up it restores the height but the whole view starts flickering.
This is the header layout, I am trying to make the Textview disappear while keeping the inner linear layout visible at all times:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center">
<TextView
android:id="#+id/welcome_message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="12dp"
android:text="welcome"/>
<LinearLayout
android:id="#+id/always_show"
android:layout_width="match_parent"
android:layout_height="36dp"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_margin="12dp">
...
</LinearLayout>
</LinearLayout>
And this is the code
var maxHeight: Int = 0
var initialSize = false
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
...
header.getViewTreeObserver().addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
if (initialSize)
return
initialSize = true
maxHeight = header.measuredHeight
}
})
recyclerView.addOnScrollListener(object: RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView?, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if (dy > 0) {
//scroll down
val params = header.layoutParams as LinearLayout.LayoutParams
params.topMargin = Math.max(params.topMargin-dy, -maxHeight/2)
header.layoutParams = params
} else if (layoutManager.findFirstCompletelyVisibleItemPosition() == 0) {
//top
val params = header.layoutParams as LinearLayout.LayoutParams
params.topMargin = 0
header.layoutParams = params
}
}
})
}
Any ideas on how to make this animation more natural, closest to the CollapsingToolbarLayout behaviour.

Categories

Resources