I'm trying to create a CollapsingToolbar animation using MotionLayout.
I've successfully animated everything to behave just like a CollapsingToolbar with a high level of flexibility, which means I can easily create awesome animations without writing a large amount of code.
My problem is no matter what I tried; I can't resize the title's TextView in a natural way.
I'm currently using ConstraintLayout version 2.0.0-beta3
Trial #1
CustomAttribute of textSize
<ConstraintSet android:id="#+id/dish_fragment_expanded_set">
...
<Constraint
android:id="#+id/dish_fragment_title_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:layout_marginTop="20dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/dish_fragment_cover_image">
<CustomAttribute
app:attributeName="textSize"
app:customFloatValue="24" />
</Constraint>
...
</ConstraintSet>
<ConstraintSet android:id="#+id/dish_fragment_collapsed_set">
...
<Constraint
android:id="#id/dish_fragment_title_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
app:layout_constraintBottom_toBottomOf="#+id/dish_fragment_navigation_icon"
app:layout_constraintStart_toEndOf="#+id/dish_fragment_navigation_icon"
app:layout_constraintTop_toTopOf="#id/dish_fragment_navigation_icon">
<CustomAttribute
app:attributeName="textSize"
app:customFloatValue="16" />
</Constraint>
...
</ConstraintSet>
Result
The solution above works, but the text flickers on movement, which means the animation is not smooth.
Trial #2
scaleX & scaleY
<ConstraintSet android:id="#+id/dish_fragment_expanded_set">
...
<Constraint
android:id="#+id/dish_fragment_title_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:layout_marginTop="20dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/dish_fragment_cover_image"
android:scaleX="1"
android:scaleY="1"/>
...
</ConstraintSet>
<ConstraintSet android:id="#+id/dish_fragment_collapsed_set">
...
<Constraint
android:id="#id/dish_fragment_title_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
app:layout_constraintBottom_toBottomOf="#+id/dish_fragment_navigation_icon"
app:layout_constraintStart_toEndOf="#+id/dish_fragment_navigation_icon"
app:layout_constraintTop_toTopOf="#id/dish_fragment_navigation_icon"
android:scaleX="0.70"
android:scaleY="0.70"/>
...
</ConstraintSet>
Result
The solution above changes the size but not the layout params, which means it breaks the constraints in a way that I can't align it correctly with the navigation icon.
I prefer to keep using MotionLayout because creating a smooth and detailed animation using CollapsingToolbar is a nightmare.
I used Trial 2 - 'scale' method and set the following textview attributes in the actual XML layout (NOT on the MotionScene XML).
android:transformPivotX="0sp"
android:transformPivotY= Equal to 1/2 of the size of the text view in expanded mode (in sp).
This changes the pivot point around which the text view will scale.
Related
In my animation one of the views must grow from fixed width to app:layout_constraintEnd_toEndOf="parent" (which is the value from the destination ConstraintSet) by 70% of animation time.
I tried to achive it with the following KeyAttribute:
<KeyAttribute
motion:motionTarget="#id/my_view"
motion:framePosition="70"
>
<CustomAttribute
motion:attributeName="layout_constraintEnd_toEndOf"
motion:customReference="#id/root"/>
</KeyAttribute>
But it has no effect
I would be grateful for working solution of this problem.
If in any case you are interested in Motion Layout for Constraint layout, then you can refer to this provided link for your animation Motion Layout with Stevdza.
MotionLayout animates Views between two ConstraintSets.
It cannot animate Constraints.
You define KeyPosition
With its Size reaching 100% of the size by frame 70.
Constraints control the start and end.
KeyPosition controls the evolution position & size from "start" to "end"
Attributes typically control the viewTransform
For a brief introduction see the KeyPosition video in the MotionTags Series
This is roughly what it should look like:
<Transition
motion:constraintSetEnd="#+id/end"
motion:constraintSetStart="#id/start"
motion:duration="1000">
<KeyFrameSet>
<KeyPosition
motion:framePosition="70"
motion:motionTarget="#+id/view"
motion:sizePercent="1"/>
</KeyFrameSet>
</Transition>
<ConstraintSet android:id="#+id/end"
motion:deriveConstraintsFrom="#id/start" >
<Constraint
android:id="#+id/view"
android:layout_width="40dp"
android:layout_height="wrap_content"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintTop_toTopOf="parent"
motion:layout_constraintStart_toStartOf="parent"
/>
</ConstraintSet>
<ConstraintSet android:id="#+id/start">
<Constraint
android:id="#+id/view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintTop_toTopOf="parent"
motion:layout_constraintStart_toStartOf="parent"
/>
</ConstraintSet>
I need animation which expands a view from height and width of wrap_contents to match constraints, in my case it is to expand as much as parent allows in width but in 50% animation I want that view to first move to center without modifying its height or width.
In first constraint set i have:
<ConstraintSet android:id="#+id/base">
<Constraint android:id="#+id/backgroundView">
<Layout
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_marginEnd="12dp"
android:layout_marginBottom="12dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<CustomAttribute
app:attributeName="radius"
app:customDimension="30dp" />
</Constraint>
</ConstraintSet>
And the final one is:
<ConstraintSet
android:id="#+id/final"
app:deriveConstraintsFrom="#id/base">
<Constraint android:id="#+id/backgroundView">
<Layout
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginEnd="0dp"
android:layout_marginBottom="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<CustomAttribute
app:attributeName="radius"
app:customDimension="12dp" />
</Constraint>
</ConstraintSet>
My transition is:
<Transition
app:constraintSetEnd="#id/final"
app:constraintSetStart="#id/base"
app:duration="300"
app:motionInterpolator="easeInOut">
<KeyFrameSet>
<KeyPosition
app:framePosition="50"
app:keyPositionType="parentRelative"
app:motionTarget="#id/backgroundView"
app:pathMotionArc="startHorizontal"
app:percentX="0.5"
app:percentY="0.5" />
<KeyAttribute
app:framePosition="50"
app:motionTarget="#id/backgroundView">
<CustomAttribute
app:attributeName="radius"
app:customDimension="30dp" />
</KeyAttribute>
</KeyFrameSet>
</Transition>
With this transition I managed to move the view to center but at the same time view is expanding to match new constraints. How can I keep the view to have width and height of 60dp until frame position goes past 50?
Just add this key position to your keyframe set:
<KeyPosition
app:keyPositionType="deltaRelative"
app:framePosition="50"
app:motionTarget="#id/backgroundView"
app:sizePercent="0" />
By combining keyPositionType="deltaRelative" and sizePercent="0" you define that start size should remain unchanged until this key frame.
I'm trying to make an animation with MotionLayout, and I need to hide some elements.
I tested visibility attribute in an individual element and it works, but to make the XML shorter I would like to be able to specify just a group (from the ConstraintLayout helpers) containing all this elements
Something like this
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<Transition
app:constraintSetStart="#id/start"
app:constraintSetEnd="#id/end"
app:duration="300">
<OnSwipe
app:touchAnchorId="#id/details_group"
app:touchAnchorSide="bottom"
app:dragDirection="dragDown"
/>
</Transition>
<ConstraintSet
android:id="#+id/start">
<Constraint
android:id="#+id/details_group"
android:layout_width="0dp"
android:layout_height="0dp"
android:visibility="gone"
app:constraint_referenced_ids="detail_value_topl,detail_icon_topl,detail_value_topr" />
</ConstraintSet>
<ConstraintSet
android:id="#+id/end">
<Constraint
android:id="#+id/details_group"
android:layout_width="0dp"
android:layout_height="0dp"
android:visibility="visible"
app:constraint_referenced_ids="detail_value_topl,detail_icon_topl,detail_value_topr" />
</ConstraintSet>
</MotionScene>
But it doesn't work, any idea how to make it work ?
Also, I would prefer not to use alpha, since all constraints are set so that when they are gone the container resizes
Rather than declaring the visibility on the Constraint, you should declare the visibility as a custom attribute. So for your first constraint try this:
<Constraint
android:id="#+id/details_group"
android:layout_width="0dp"
android:layout_height="0dp"
app:constraint_referenced_ids="detail_value_topl,detail_icon_topl,detail_value_topr">
<CustomAttribute
motion:attributeName="visibility"
motion:customIntegerValue="8" />
</Constraint>
and for your second constraint try this
<Constraint
android:id="#+id/details_group"
android:layout_width="0dp"
android:layout_height="0dp"
app:constraint_referenced_ids="detail_value_topl,detail_icon_topl,detail_value_topr">
<CustomAttribute
motion:attributeName="visibility"
motion:customIntegerValue="0" />
</Constraint>
By declaring the visibility as a custom attribute, that should help the motion layout properly interpolate between visibility values. It's a little unintuitive which int value is which visibility but they are defined as follows
Visible = 0
Invisible = 4
Gone = 8
I have started using motion layout for some top bar scrolling, and looks like there is an issue that prevents recycler view from showing updated data. Currently I am using 2.0.0-alpha3 of ConstraintLayout. In the view I have toolbar and 2 tabs that act as filter, lets say filterX and filterY those pass some rx stuff that basically just filters list of items based on the type this is not important because data is filtered properly, thread is correct, data is passed to adapter every time, but when I do scroll my motion layout to top or bottom, from time to time the changes are not reflected in recycler view, they reload after I scroll it a little bit or even touch, its not happening in case of standard ConstraitLayout. Have anyone experienced this and know any solution?
Edit: Layout with motion layout:
<androidx.constraintlayout.motion.widget.MotionLayout 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"
app:layoutDescription="#xml/scene_month">
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navigationIcon="#drawable/ic_back"/>
<TextView
android:id="#+id/title"
style="#style/ActivityTitle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/toolbar" />
<com.example.FilterView
android:id='#+id/filter_view'
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#android:color/white"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/title"/>
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recycler"
android:layout_width="0dp"
android:layout_height="0dp"
android:clipToPadding="false"
android:paddingBottom="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/filter_view" />
</androidx.constraintlayout.motion.widget.MotionLayout>
And motion scene
<?xml version="1.0" encoding="utf-8"?>
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<Transition
app:constraintSetEnd="#id/end"
app:constraintSetStart="#id/start">
<OnSwipe
app:dragDirection="dragUp"
app:touchAnchorId="#id/recycler"
app:touchAnchorSide="top" />
</Transition>
<ConstraintSet android:id="#+id/start">
<Constraint android:id="#id/title"
android:text="#string/title"
android:layout_width="match_parent"
android:layout_height="48dp"
android:gravity="center_vertical"
android:textSize="28sp"
android:textColor="#color/dark_gray"
android:fontFamily="#font/rubik_medium"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/toolbar">
<CustomAttribute
app:attributeName="textSize"
app:customFloatValue="28"/>
</Constraint>
</ConstraintSet>
<ConstraintSet android:id="#+id/end">
<Constraint android:id="#id/title"
android:text="#string/title"
android:layout_width="match_parent"
android:layout_height="?actionBarSize"
android:gravity="center_vertical"
android:minHeight="?actionBarSize"
android:textSize="28sp"
android:textColor="#color/dark_gray"
android:fontFamily="#font/rubik_regular"
android:layout_marginStart="72dp"
android:layout_marginEnd="16dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<CustomAttribute
app:attributeName="textSize"
app:customFloatValue="20"/>
</Constraint>
</ConstraintSet>
</MotionScene>
By following the possible solution as discussed here:
https://issuetracker.google.com/issues/130386093
adding the tag motion:layoutDuringTransition="honorRequest" to the Transition fixed the issue
Example:
<Transition
android:id="#+id/myTransition"
motion:constraintSetEnd="#+id/end"
motion:constraintSetStart="#+id/start"
motion:layoutDuringTransition="honorRequest"
motion:motionInterpolator="linear">
<OnSwipe
motion:dragDirection="dragUp"
motion:onTouchUp="stop"
motion:touchAnchorId="#+id/swipeRefreshLayout"
motion:touchAnchorSide="top" />
besides that, can also try to update to newer version, in my case, I'm currently using
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta08'
implementation 'androidx.recyclerview:recyclerview:1.2.0-alpha05'
Note: layoutDuringTransition tag only available with beta04 version of constraint layout and above
RecyclerView.scrollBy(0, 0) as temporary workaround.
Link to these Google Issue Tracker
https://issuetracker.google.com/issues/130386093
https://issuetracker.google.com/issues/130862165
For some reason RecyclerView.scrollBy(0, 0) didn't work for me. I am scrolling the whole motion layout to top using below method when there are updates to recyclerView.
motionLayoutView.transitionToStart()
update to newer version
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta7'
as they fixed that problem as listed with release notes
Nested scroll view issues in MotionLayout
Transition listener issues with MotionLayout
Memory leak in MotionLayout
RecyclerView performances
Group visibility
Padding issues
https://androidstudio.googleblog.com/2020/06/constraintlayout-200-beta-7.html
None of these workarounds worked for me. I gave up on fixing these and I was fixing other UI bugs I had. I have several views sliding up/down and hiding/showing.
I was using TransitionManager.beginDelayedTransition(...) in my code and it wasn't inside a Runnable. After wrapping it in view.post{} like this the problem disappeared:
view.post{
TransitionManager.beginDelayedTransition()
}
This may seem irrelevant but I suggest checking your code to see if the problem is somewhere else. Like everyone else I thought this is caused by MotionLayout because when I swapped it with ConstraintLayout it worked fine.
I'm developing a webrtc for android application. Everything works as expected regarding the webrtc connection but now I'm animating the SurfaceViewRenderer so the remote video transitions from filling the entire screen to be a small rectangle in the upper right corner and vice versa (going from small to large).
I'm using a MotionLayout for achieving this, along with a MotionScene with several states defining the SurfaceViewRenderer properties to change over time to get a nice and smooth resizing effect.
The Views themselves resize properly over time, but the video playback stops during the animation so, when going from small to large, the last video frame remains small while the view increases its size. And at the end of the animation, when the video playback resumes, a new big video frame appears and fills the view. So instead of a smooth resizing effect from small to large I end up getting more like a skip effect, where the small video playback stops and then after a few milliseconds it resumes playback and gets larger.
What I've tried
To delve into the classes involved (SurfaceViewRenderer, VideoRenderer, VideoTrack) to find a way to get the current video frame and resize it, but found no way of doing so
Steps to reproduce
It'll be hard to reproduce the exact scenario since you'll need to set up a webrtc environment all along with signaling server, but I'll share the pieces of code I'm using. My main layout looks like this:
<android.support.constraint.motion.MotionLayout
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:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:orientation="vertical"
app:layoutDescription="#xml/motion_master_view_scene"
android:id="#+id/webRtcViewsLayout">
<org.webrtc.SurfaceViewRenderer
android:id="#+id/slaveVideoView"
android:layout_width="300dp"
android:layout_height="169dp"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_marginEnd="84dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<org.webrtc.SurfaceViewRenderer
android:id="#+id/masterVideoView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</android.support.constraint.motion.MotionLayout>
Then my motion_master_view_scene.xml MotionScene looks like this:
<?xml version="1.0" encoding="utf-8"?>
<MotionScene xmlns:motion="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<Transition
motion:duration="500">
</Transition>
<StateSet
motion:defaultState="#id/localMasterState">
<State android:id="#+id/bothMinimizedState" motion:constraints="#id/bothMinimizedConstraintSet"></State>
<State android:id="#+id/localMasterState" motion:constraints="#id/localMasterConstraintSet"></State>
</StateSet>
<ConstraintSet android:id="#+id/bothMinimizedConstraintSet">
<Constraint
android:id="#+id/masterVideoView"
android:layout_width="300dp"
android:layout_height="169dp"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_marginTop="196dp"
android:layout_marginRight="10dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
<Constraint
android:id="#+id/slaveVideoView"
android:layout_width="300dp"
android:layout_height="169dp"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_marginTop="10dp"
android:layout_marginRight="10dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
</ConstraintSet>
<ConstraintSet android:id="#+id/localMasterConstraintSet">
<Constraint
android:id="#+id/masterVideoView"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
<Constraint
android:id="#+id/slaveVideoView"
android:layout_width="300dp"
android:layout_height="169dp"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_marginTop="10dp"
android:layout_marginRight="10dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
</ConstraintSet>
</MotionScene>
And finally, I change between states in my MainActivity by doing this
webRtcViewsLayout.transitionToState(newState)
Any clues on how to solve this situation?