I'm trying to animate the height of the BottomSheet on layoutChange, but I can't get anything to work. I've tried several tutorials without luck.
Currently the height just snaps. I'm using a custom BottomSheetDialogFragment(). I've tried TransitionManager.beginDelayedTransition(sheetParent, AutoTransition()) with sheetParent obviously being the top most layout in my setup. It is a coordinatorLayout.
This is my layout file:
<?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/sheetParent"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="#+id/bottomSheetBehavior"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/bottomSheetContent"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<View
android:id="#+id/drag_pill"
android:layout_width="50dp"
android:layout_height="5dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:background="#drawable/bottom_sheet_pill"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>
</FrameLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
Below the normal view are more views, which I removed to make this a little more readable. I extend and minimize cards in the bottomSheet with the .isVisible = true/false.
How can I smoothly animate that?
You can animate any kind of value that you would like to change in a View using the ValueAnimator.
For example, in your case in order to animate smoothly the peeking height of a bottom sheet, you can use this in your BottomSheetDialogFragment():
Kotlin:
val behavior by lazy { (dialog as BottomSheetDialog?)?.behavior }
val originalHeight = 500 // current height
val updatedHeight = 1000 // desired height
private fun updatePeekHeight(originalHeight: Int, updatedHeight: Int) {
ValueAnimator.ofInt(originalHeight, updatedHeight).apply {
addUpdateListener {
behavior?.peekHeight = it.animatedValue as Int
}
duration = 300 // adjust the duration accordingly
start()
}
}
Related
I am building an Android screen that allows the user to place multiple "sticker" images on top of a background image. When the user taps on an icon, a BottomSheet containing a RecyclerView of stickers images is rendered from the bottom at around 75% of the screen height. As you will see in the attached GIF, when I scroll up, the top of the BottomSheet moves to the top rather than sticking to its peek height. I am using a BottomSheetBehavior and am setting the peekHeight to collapse/expand it. I was using a BottomSheetDialogFragment but was experiencing the same issue.
Can anyone see what I am missing? I read about nested scrolling so I set "nestedScrollingEnabled" to true for the RecyclerView but that did not seem to help.
In my activity, I render the BottomSheet via an include tag:
<include android:id="#+id/sticker_bottom_sheet" layout="#layout/fragment_sticker_bottom_sheet" />
Here is the bottom sheet layout file:
<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"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/black_overlay"
android:orientation="vertical"
app:layout_behavior="#string/bottom_sheet_behavior"
app:behavior_hideable="true"
app:behavior_peekHeight="0dp"
android:id="#+id/stickerCollectionBottomSheet"
>
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/stickerRecyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="bottom"
android:overScrollMode="never"
android:scrollbars="none"
android:nestedScrollingEnabled="true"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
/>
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
and then wire up the RecyclerView in the activity:
private fun setupStickerBottomSheet() {
val stickerBottomSheetLayout = binding.stickerBottomSheet?.stickerCollectionBottomSheet
stickerContainerBottomSheet = BottomSheetBehavior.from(stickerBottomSheetLayout)
val stickerRecyclerView = binding.stickerBottomSheet?.stickerRecyclerView
val adapter = StickersAdapter(StickersAdapter.OnStickerTappedListener { s, i ->
onStickerClicked(s, i)
}, viewModel.stickers.data?.toList() ?: emptyList())
val layoutManager = GridLayoutManager(this, 3, GridLayoutManager.VERTICAL, false)
stickerRecyclerView?.adapter = adapter
stickerRecyclerView?.layoutManager = layoutManager
}
Thanks in advance!
I had a bit of trouble figuring out how to word this one when researching so I've made a quick mockup of what I'm trying to achieve below.
my app has a persistent bottom sheet and I would like the contents of the sheet to be resized based on whether the sheet is expanded or half expanded.
My sheet consists of a recycler view and a view at the bottom with some buttons. Currently, the contents of the sheet are always sized as though the sheet is full expanded. This means that the buttons are not displayed unless the sheet is fully expanded and much of the recycler view is also hidden behind the cropped-off sheet. What I want to happen is for the buttons to always be displayed at the bottom of the sheet, even when said sheet is half expanded, and for the recyclerview to fill whatever space is left. Is this possible in a persistent bottom sheet and if now, what means should I use to achieve this? And help would be greatly appreciated.
You can make the buttons layout to always be displayed at the bottom of the sheet by setting its y coordinate by tracking the bottom sheet sliding using addBottomSheetCallback callbacks:
val bottomSheet = findViewById<ConstraintLayout>(R.id.bottom_sheet)
val bottomLayout = findViewById<LinearLayout>(R.id.button_layout)
// initial setting the y coordinates of the bottom layout
bottomSheet.post {
val bottomSheetVisibleHeight = bottomSheet.height - bottomSheet.top
bottomLayout.y =
(bottomSheetVisibleHeight - bottomLayout.height).toFloat()
}
BottomSheetBehavior.from(bottomSheet).apply {
addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
override fun onSlide(bottomSheet: View, slideOffset: Float) {
// set the y coordinates of the bottom layout on bottom sheet slide
val bottomSheetVisibleHeight = bottomSheet.height - bottomSheet.top
bottomLayout.y =
(bottomSheetVisibleHeight - bottomLayout.height).toFloat()
}
override fun onStateChanged(bottomSheet: View, newState: Int) {
}
})
}
Layout:
<?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"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- Main Layout -->
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/bottom_sheet"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
app:behavior_hideable="false"
app:layout_behavior="#string/bottom_sheet_behavior">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recyclerview"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="#CFE2F3"
app:layout_constraintBottom_toTopOf="#+id/button_layout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:id="#+id/button_layout"
android:layout_width="match_parent"
android:layout_height="60dp"
android:background="#FF03DAC5"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent">
<Button
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="8dp"
android:layout_marginEnd="40dp"
android:layout_weight="1"
android:text="cancel" />
<Button
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginEnd="8dp"
android:layout_weight="1"
android:text="ok" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
In my application I have recyclerView and for this I should use GridLayoutManager and set 2 items per column.
I want set this 2 items fit of screen, each items sticky to screen. Such as below image:
I write below codes, but after run application show me such as this :
And I want set 120dp for layout_width and layout_height!
My XML code :
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/rowMasterQuestion_root"
android:layout_width="#dimen/_120mdp"
android:layout_height="#dimen/_120mdp"
android:background="#drawable/gray_border">
<TextView
android:id="#+id/rowMasterQuestion_title"
android:layout_width="#dimen/_120mdp"
android:layout_height="wrap_content"
android:layout_margin="#dimen/_15mdp"
android:gravity="center_horizontal"
android:textColor="#color/black"
android:textSize="#dimen/_9font_mdp"
android:visibility="visible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/rowMasterQuestion_answer"
android:layout_width="wrap_content"
android:layout_height="#dimen/_15mdp"
android:layout_marginLeft="#dimen/_10mdp"
android:layout_marginRight="#dimen/_10mdp"
android:layout_marginBottom="#dimen/_20mdp"
android:gravity="center"
android:paddingLeft="#dimen/_5mdp"
android:paddingRight="#dimen/_5mdp"
android:textColor="#color/white"
android:textSize="#dimen/_8font_mdp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
My Activity codes :
requestMain_childList.apply {
layoutManager = GridLayoutManager(this#RequestMainPage, 2, GridLayoutManager.VERTICAL, false)
hasFixedSize()
adapter = masterChildAdapter
}
How can I fix this issue?
This is tricky because you are wanting a different layout gravity for each item, depending on the column. I think that will have to be set in the RecyclerView.Adapter.onBindViewHolder function.
First, wrap the ConstraintLayout of your item view in a FrameLayout. Make the width of the outer FrameLayout match_parent so it will fill its column in the RecyclerView. You can also give the FrameLayout the amount of start/end padding you want to hold the items away from the sides of the screen.
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingEnd="16dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/wrappedView"
android:layout_width="#dimen/_120mdp"
android:layout_height="#dimen/_120mdp"
...>
...
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>
Then in onBindViewHolder, you can set the gravity of the inner view to the left or right depending on which column it is in (even or odd data position).
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
// ...
val innerView = holder.wrappedView // or however you have it stored in view holder
(innerView.layoutParams as FrameLayout.LayoutParams).gravity = Gravity.CENTER_VERTICAL or
(if (position % 2 == 0) Gravity.START else Gravity.END)
}
Note, if you want the items to be centered in their columns instead, this is much easier. You can just set the gravity of the inner view to center in the XML and you don't have to do anything in the Adapter.
I have the following layout:
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:id="#+id/rootLayout"
android:layout_height="match_parent">
<!-- ... -->
<View
android:id="#+id/fieldView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginStart="4dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="4dp"
android:elevation="6dp"
app:layout_constraintTop_toBottomOf="#id/topLayout" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/actionsLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:orientation="horizontal"
app:layout_constraintTop_toBottomOf="parent"
tools:layout_constraintTop_toBottomOf="#id/fieldView">
<!-- ... -->
I want actionsLayout to slide from bottom of screen (from out-of-screen in fact) to the bottom of fieldView.
ConstraintSet()
.apply {
clone(context, R.layout.fragment_problem)
connect(R.id.actionsLayout, ConstraintSet.TOP, R.id.fieldView, ConstraintSet.BOTTOM)
}
.runAnimation(rootLayout, actionsLayoutAppearAnimationDuration, OvershootInterpolator())
runAnimation source:
fun ConstraintSet.runAnimation(rootLayout: ConstraintLayout, duration: Long, interpolator: Interpolator = LinearInterpolator()) {
val transition = AutoTransition()
.apply {
this.duration = duration
this.interpolator = interpolator
}
TransitionManager.beginDelayedTransition(rootLayout, transition)
this.applyTo(rootLayout)
}
Note: this extension function works fine on other layouts.
But on this layout and this animation it does not animate constraints change, but instead just instantly applies it. So, layout looks just like I expect, except animation — there is just no animation at all. Why?
Looks that starting animation in onViewCreated is not the best idea. The easiest solution is to call it in rootLayout.post().
I have such kind of fragment 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:background="#color/colorBackgroundBlack">
<MyCustomView
android:id="#+id/vBottomSlider"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="android.support.design.widget.BottomSheetBehavior" />
</android.support.design.widget.CoordinatorLayout>
For my vBottomSlider I create a BottomSheetBehavior instance:
val bh = BottomSheetBehavior.from(vBottomSlider)
bh.isHideable = false
bh.peekHeight = 50.dpToPx
bh.setBottomSheetCallback(mBottomCallback)
That's all inside the fragment.
My main activity layout looks like:
<android.support.constraint.ConstraintLayout
android:id="#+id/vMainConstraint"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="#+id/fContent"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.aurelhubert.ahbottomnavigation.AHBottomNavigation
android:id="#+id/vNavigation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent" />
</android.support.constraint.ConstraintLayout>
For my fContent i set bottom padding as vNavigation height. When i scroll down my vBottomSlider, i hide vNavigation by changing its translationY, and set padding for my fContent (by taking onSlide event from BottomSheetBehavior.BottomSheetCallback).
Sliding with finger completely, works fine.
But when i fling or set EXPANDED or COLLAPSED state programmatically for my BottomSheetBehavior instance its not get scrolled completely. Here is always some space (it seems to be height of my vNavigation).
I've managed to resolve this by editing the source of BottomSheetBehavior.
My solution is little hacky. Send e-mail if you need edited class.