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.
Related
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">
...
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
}
})
My screen consists of a ConstraintLayout with two children. The one child is a RecyclerView which takes up the entire screen. The other child is another ConstraintLayout that contains a TextView (the screen is actually more complex than this, but I'm reducing it to something here to make it easier to comprehend). The child ConstraintLayout is positioned over the RecyclerView and scrolls up whenever you scroll the RecyclerView. This is done using a scroll listener on the RecyclerView and re-adjusting the Y position of the child ConstraintLayout. The child ConstraintLayout needs to be clickable.
The problem I am having is that if you try scrolling the RecyclerView by first touching down on the child ConstraintLayout, the scrolling will not work. The click event handler for the child ConstraintLayout will work, however.
From my understanding of how Android handles touch events, if a view returns false in its onTouchEvent handler, that view will not get notified of any further motion events. So theoretically, if my child ConstraintLayout returns false in its onTouchEvent handler, the RecyclerView should still get the scroll motion events.
Here is my layout xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:background="#55338855">
<androidx.constraintlayout.motion.widget.MotionLayout
android:id="#+id/dashboardConstraintLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
app:layoutDescription="#xml/dashboard_initial_animation_start"
app:showPaths="false">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/contentRecyclerView"
android:layout_width="0dp"
android:layout_height="0dp"
android:clipChildren="false"
android:fitsSystemWindows="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="#id/guideWelcome"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_default="percent"
app:layout_constraintWidth_percent="1.0" />
<ReservationCard
android:id="#+id/reservationConstraintLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="60dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:background="#color/dashboard_reservation_card_background_color"
android:padding="16dp"
android:visibility="invisible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/txtSearch">
<TextView
android:id="#+id/txtReservationHeader"
style="#style/AppTheme.Label"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="#string/dashboard_screen_reservation_card_headline"
android:textColor="#color/dashboard_reservation_card_text_color" />
</com.sinnerschrader.motelone.screens.dashboard.ReservationCard>
</androidx.constraintlayout.motion.widget.MotionLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
And here is my custom ConstraintLayout (ReservationCard):
class ReservationCard : androidx.constraintlayout.widget.ConstraintLayout {
constructor(context: Context?) : super(context)
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
private val gestureDetector: GestureDetector
init {
gestureDetector = GestureDetector(GestureListener())
}
override fun onTouchEvent(ev: MotionEvent?): Boolean {
if (ev?.action == MotionEvent.ACTION_DOWN) {
gestureDetector.onTouchEvent(ev)
return true
} else if (ev?.action == MotionEvent.ACTION_UP) {
gestureDetector.onTouchEvent(ev)
return false
} else {
super.onTouchEvent(ev)
return false
}
}
private inner class GestureListener : GestureDetector.OnGestureListener {
override fun onFling(p0: MotionEvent?, p1: MotionEvent?, p2: Float, p3: Float): Boolean {
return false
}
override fun onScroll(p0: MotionEvent?, p1: MotionEvent?, p2: Float, p3: Float): Boolean {
return false
}
override fun onLongPress(p0: MotionEvent?) {
}
override fun onDown(p0: MotionEvent?): Boolean {
return false
}
override fun onShowPress(p0: MotionEvent?) {
}
override fun onSingleTapUp(p0: MotionEvent?): Boolean {
return false
}
}
}
I want to add a headerview that hides when user scrolls to down and shows again when user scrolls to up.
Example: https://imgur.com/a/tTq70B0
As you can see in the link "You are writing as..." pharase is showing only when user scrolls to top. Is there something like that in Android sdk?
How can i achive the same thing?
Obtaining the scroll event is just the first step to achieving this. Animations is required to achieve the effect. I recreated a simple version of the gif example you posted.
Layout for the main activity, activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:animateLayoutChanges="true"> <!-- Note the last line-->
<TextView
android:id="#+id/textview_hello"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="8dp"
android:text="Hello Stack Overflow!"/>
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recyclerview_main"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
Below is the code for the main activity where we populate the RecyclerView and use the addOnScrollListener to provide animations to the TextView. Do note the commented out lines these will provide a default fade-out or fade-in animation due to the noted line in the xml layout above. The method slideAnimation() is an example of creating a custom animation. This link proved useful for creating the animations.
class MainActivity : AppCompatActivity() {
private lateinit var viewAdapter: RecyclerView.Adapter<*>
private lateinit var viewManager: LinearLayoutManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Some data for the RecyclerView
val data: List<String> = (1..100).toList().map { it.toString() }
viewManager = LinearLayoutManager(this)
viewAdapter = TextAdapter(data)
findViewById<RecyclerView>(R.id.recyclerview_main).apply {
setHasFixedSize(true)
layoutManager = viewManager
adapter = viewAdapter
addOnScrollListener(
object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
val pastVisibleItems = viewManager.findFirstCompletelyVisibleItemPosition()
if (pastVisibleItems == 0) {
slideAnimation(0f, 1f, View.VISIBLE)
//textview_hello.visibility = View.VISIBLE
} else if (textview_hello.visibility != View.GONE) {
slideAnimation(-150f, 0f, View.GONE)
//textview_hello.visibility = View.GONE
}
}
}
)
}
... // SlideAnimation function
}
}
The slideAnimation function
private fun slideAnimation(translationY: Float, alpha: Float, viewVisibility: Int) {
textview_hello.visibility = View.VISIBLE
textview_hello.clearAnimation()
textview_hello
.animate()
.translationY(translationY)
.alpha(alpha)
.setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
super.onAnimationEnd(animation)
textview_hello.clearAnimation()
textview_hello.visibility = viewVisibility
}
})
.duration = 500
}
The adapter for the RecycleView:
class TextAdapter(private val textList: List<String>) :
RecyclerView.Adapter<TextAdapter.TextViewHolder>() {
class TextViewHolder(val textView: TextView) : RecyclerView.ViewHolder(textView)
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): TextViewHolder {
val textView = LayoutInflater.from(parent.context)
.inflate(R.layout.item_text_view, parent, false) as TextView
return TextViewHolder(textView)
}
override fun onBindViewHolder(holder: TextViewHolder, position: Int) {
holder.textView.text = textList[position]
}
override fun getItemCount() = textList.size
}
Item to display in the RecyclerView, item_text_view.xml
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_height="wrap_content"
android:layout_margin="8dp"
android:padding="16dp"
android:textAlignment="center"
android:background="#android:color/darker_gray">
</TextView>
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
}
}