ConstraintLayout ConstraintSet applies instantly instead of animation - android

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().

Related

How to create an android Bottom sheet that fits the size of the sheet with view always aligned to bottom?

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>

Smoothly animate BottomSheet peek height

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()
}
}

AndroidStudio: Explode animation doesn't work when i set background color to the root layout in the xml

I don't know if it is a bug or i am making some mistake but when i set Explode animation when opening an Activity it animates like a Slide animation from top to bottom. I did some trial and error and it turns out that when i use a custom background color in the root layout in the xml file this unexpected behavior occures. When i remove the background color everything works as expected.
Can anyone tell me what's going on here??? Because it is important for me to set background color in the root layout.
Here's a sample of 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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#000000" // This causes the unexpected behavior.
tools:context=".ExplodeActivity">
<ImageView
android:id="#+id/imageView2"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_marginTop="100dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="#drawable/b" />
<Button
android:id="#+id/button2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
This is the ExplodeActivity 's onCreate method where i am assigning the animation:
class ExplodeActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
window.requestFeature(Window.FEATURE_CONTENT_TRANSITIONS)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_explode)
val enterTransition = Explode()
enterTransition.duration = 500
window.enterTransition = enterTransition
}
}
Here's the previous activity's code which responsible for starting the ExplodeActivity:
val options = ActivityOptions.makeSceneTransitionAnimation(this).toBundle()
val intent = Intent(this, ExplodeActivity::class.java)
startActivity(intent, options)

Broken elevation shadow during animation on Android Pie

I am experiencing weird shadow behavior, when animation of parent viewGroup is taking place on Android Pie. It is happening on all phones with android 9.0 I have. Except for emulator.
So, the shadow is blinking and kinda offset.
To simulate, I have this simple Activity:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<View>(R.id.animated_item).startAnimation(AlphaAnimation(0.9f, 1.0f).apply {
duration = 500
repeatCount = Animation.INFINITE
repeatMode = Animation.REVERSE
})
}
}
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<View
android:layout_width="120dp"
android:layout_height="200dp"
android:layout_margin="8dp"
android:background="#fff"
android:elevation="6dp" />
</FrameLayout>
<FrameLayout
android:id="#+id/animated_item"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<View
android:layout_width="120dp"
android:layout_height="200dp"
android:layout_margin="8dp"
android:background="#fff"
android:elevation="6dp" />
</FrameLayout>
</LinearLayout>
Result:
Probably, it's too late, but to get rid of this effect I had to apply animation to the View with elevation itself, but not to its parent layout.

Animate LayoutParams changes in ConstraintLayout

I am creating app in which there is screen with searchBar by this lib.
My activity layout looks 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:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.constraint.ConstraintLayout
android:id="#+id/wrapper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true">
<TextView
android:id="#+id/title"
android:textColor="#color/colorPrimary"
android:text="#string/mainScreenTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textAlignment="center"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:paddingTop="100dp"/>
<com.arlib.floatingsearchview.FloatingSearchView
android:id="#+id/searchView"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_marginTop="100dp"
android:animateLayoutChanges="true"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="#+id/title"/>
</android.support.constraint.ConstraintLayout>
</RelativeLayout>
There i have to move FloatingSearchingView to top of screen and change height to for example 500dp - like this:
app:layout_constraintTop_toTopOf="parent"
android:layout_height="500dp"
And later i will have to move it back and change height again to default value
app:layout_constraintTop_toBottomOf="#+id/title"
android:layout_height="50dp"
The problem is, i have to animate it. I have spent a lot of time searching for solution but unfortunately i didnt find anything.
I had to animate the TextView height inside a ConstraintLayout.
<androidx.constraintlayout.widget.ConstraintLayout
...
android:animateLayoutChanges="true"
>
<TextView
...
I first informed the ConstraintLayout that the layout was about to change:
constraintlayout.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING);
and then updated the textView height (or whatever other param)
textView.getLayoutParams().height = 200;
textView.requestLayout();
for something simple as that, no need to set animators or involve other libraries.
#beeb's version didn't work for me either, unfortunately. Although the question is old, here is a solution that worked for me, using the TransitionManager. It's a bit "redundant", as you have to duplicate some code, but therefor very easy and includes only two basic steps:
Create two layout.xml files. One for each state (before/after animation).
If your constraint layout is one of multiple children of a parent layout, you can create two files containing only this constraint layout and include the version you wan't at app-start via <include #layout=""/>, where previously you constraint layout has been. That way, you don't have to duplicate the entire layout file. This however caused the findViewById() method to return null, when my include element had an id. So be careful with this.
In your case this would be e.g.: floating_searching_view_at_bottom.xml:
<com.arlib.floatingsearchview.FloatingSearchView
android:id="#+id/searchView"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_marginTop="100dp"
android:animateLayoutChanges="true"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="#+id/title"
android:layout_height="50dp" />
and floating_searching_view_at_top.xml:
<com.arlib.floatingsearchview.FloatingSearchView
android:id="#+id/searchView"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_marginTop="100dp"
android:animateLayoutChanges="true"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="#+id/title"
android:layout_height="50dp"
app:layout_constraintTop_toTopOf="parent"
android:layout_height="500dp" />
Animate the transition between these layout files using TransitionManager:
boolean toggle = false // you should give this guy a more descriptive name!
Context context = this; // in scope of the activity.
public void animateTransition() {
// Load ConstraintSets from the two layouts
ConstraintSet atBottom = new ConstraintSet();
ConstraintSet atTop = new ConstraintSet();
atBottom.clone(context, R.layout.floating_searching_view_at_bottom);
atTop.clone(context, R.layout.floating_searching_view_at_top);
// apply ConstraintSet depending on "toggle"
ConstraintSet newLayout = (toggle) ? expanded : collapsed;
TransitionManager.beginDelayedTransition(constraintLayout);
newLayout.applyTo(constraintLayout);
// invert the toggle
toggle = !toggle;
}
Note:
1. Make sure context is of type Activity and you're using an
AppCompat theme. I accidentally used getApplicationContext(), what
obviously resulted in a crash.
2. If you are receiving an NPE on your constraintk
Try to use Animation class for this:
Animation animation = new Animation() {
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
LayoutParams params = yourView.getLayoutParams();
// modify your layout params here
yourView.setLayoutParams(params);
}
};
animation.setDuration(500); // in ms
yourView.startAnimation(animation);

Categories

Resources