CollapsingToolbarLayout with fixed/pinned Toolbar and functionality of "enterAlways" - android

I have a standard "CollapsingToolbarLayout" implementation based on material design guidelines.
With the the settings below I was able to achieve behavior depicted on the picture:
<CoordinatorLayout ...>
<AppBarLayout ...>
<CollapsingToolbarLayout
app:layout_scrollFlags="scroll|enterAlways"
...>
<Toolbar
app:layout_collapseMode="pin">
</Toolbar>
<MyCustomContent01 ... />
</CollapsingToolbarLayout>
</AppBarLayout>
<MyCustomContent02 ... />
</CoordinatorLayout>
Question
How to achieve the following behavior?:
Scrolling up: always expand the Toolbar fully, even if we are not on the top of the list.
Scrolling down: just collapse the Toolbar, but don't hide it.
In other words: How can I get rid of the step 3 while preserving the condition of step 4?
Research
For me the best article on this topic seems to be this one, however none of the presented configurations match my needs.
Attempt one:
<CollapsingToolbarLayout
app:layout_scrollFlags="scroll|enterAlways"
...>
Never hide fully the Toolbar (getting rid of step 3): FAIL
Expand the Toolbar even if we are not on the top: OK
Attempt two
<CollapsingToolbarLayout
app:layout_scrollFlags="scroll|exitUntilCollapsed"
...>
Never hide fully the Toolbar (getting rid of step 3): OK
Expand the Toolbar even if we are not on the top: FAIL

The default CollapsingToolbarLayout provided by the Material library is too limited to a set of predefined animations. To create our own custom effects, say you want to fully expand the header layout when scrolling up on the RecyclerView/ NestedScrollView even if you are not on top of the scroll view, you can use the powerful MotionLayout which is a subclass of ConstraintLayout for building animations. If you are happy to replace the existing view hierarchy with a flat Constraint-layout equivalent, read the detailed answer given below.
Here I am going to show you how to create the "enterAlways" effect with an always pinned header layout, all with in three simple steps.
Before writing any code, see the gif image given below to better understand what we are trying to create.
1. Add the ConstraintLayout dependency:
To use MotionLayout in your project, add the ConstraintLayout 2.0 dependency to your app's build.gradle file. If you're using AndroidX, add the following dependency:
dependencies {
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta2'
}
If you aren't using AndroidX, add the following support library dependency:
dependencies {
implementation 'com.android.support.constraint:constraint-layout:2.0.0-beta2'
}
2. Create the MotionLayout file:
MotionLayout is a subclass of ConstraintLayout, so you can transform any existing ConstraintLayout into a MotionLayout. So create one layout file as shown below.
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.motion.widget.MotionLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:motion="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
motion:layoutDescription="#xml/motionscene"
tools:showPaths="false">
<View
android:id="#+id/toolbar_image"
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="#345634"
android:fitsSystemWindows="true"
motion:layout_constraintBottom_toBottomOf="#id/toolbar"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="#+id/home"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:src="#drawable/abc_ic_ab_back_material"
android:tint="?android:attr/textColorPrimaryInverse"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:id="#+id/customHeader"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toEndOf="#id/home"
motion:layout_constraintTop_toTopOf="parent">
<LinearLayout
android:id="#+id/row1"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="8dp">
<View
android:layout_width="20dp"
android:layout_height="20dp"
android:background="#345678" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:text="some text"
android:textColor="#ffffff" />
</LinearLayout>
<LinearLayout
android:id="#+id/row2"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:layout_marginLeft="16dp"
android:layout_marginTop="8dp"
android:layout_marginRight="16dp"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="8dp">
<View
android:layout_width="20dp"
android:layout_height="20dp"
android:background="#345678" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:text="some text"
android:textColor="#ffffff" />
</LinearLayout>
<LinearLayout
android:id="#+id/row3"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:layout_marginLeft="16dp"
android:layout_marginTop="8dp"
android:layout_marginRight="16dp"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="8dp">
<View
android:layout_width="20dp"
android:layout_height="20dp"
android:background="#345678" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:text="some text"
android:textColor="#ffffff" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="#+id/toolbar"
android:layout_width="0dp"
android:layout_height="40dp"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:layout_marginTop="8dp"
android:background="#345634"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="8dp"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toEndOf="#id/home"
motion:layout_constraintTop_toBottomOf="#id/customHeader">
<View
android:layout_width="20dp"
android:layout_height="20dp"
android:background="#F44336" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:text="Toolbar Title"
android:textColor="#ffffff" />
<View
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginLeft="16dp"
android:background="#F44336" />
</LinearLayout>
<androidx.core.widget.NestedScrollView
android:id="#+id/scrollView"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="#ffffff"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toBottomOf="#+id/toolbar_image">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="#dimen/text_margin"
android:text="#string/large_text" />
</androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.motion.widget.MotionLayout>
3. Create a MotionScene:
In the previous step, motion:layoutDescription attribute references a MotionScene. A MotionScene is an XML resource file that contains all of the motion descriptions for the corresponding layout. To keep layout information separate from motion descriptions, each MotionLayout references a separate MotionScene. Note that definitions in the MotionScene take precedence over any similar definitions in the MotionLayout.
Following is a sample MotionScene file that creates the required fixed/pinned toolbar with 'enterAlways' effect:
Put the file in the xml folder under the res directory.
motionscene.xml
<?xml version="1.0" encoding="utf-8"?>
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:motion="http://schemas.android.com/apk/res-auto">
<Transition
motion:constraintSetEnd="#id/collapsed"
motion:constraintSetStart="#id/expanded">
<OnSwipe
motion:dragDirection="dragUp"
motion:moveWhenScrollAtTop="false"
motion:touchAnchorId="#id/scrollView"
motion:touchAnchorSide="top" />
</Transition>
<ConstraintSet android:id="#+id/expanded">
<Constraint android:id="#id/toolbar_image" />
<Constraint android:id="#id/toolbar" />
<Constraint android:id="#id/customHeader">
<PropertySet android:alpha="1" />
</Constraint>
</ConstraintSet>
<ConstraintSet android:id="#+id/collapsed">
<Constraint
android:id="#id/toolbar_image"
android:layout_height="?attr/actionBarSize"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent">
</Constraint>
<Constraint
android:id="#id/customHeader"
android:layout_width="0dp"
android:layout_height="wrap_content"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toEndOf="#id/home"
motion:layout_constraintTop_toTopOf="parent">
<PropertySet android:alpha="0" />
</Constraint>
<Constraint
android:id="#id/toolbar"
android:layout_height="?attr/actionBarSize"
android:layout_marginStart="16dp"
android:layout_marginTop="0dp"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toEndOf="#id/home"
motion:layout_constraintTop_toTopOf="parent">
</Constraint>
</ConstraintSet>
</MotionScene>
That's all. You created an amazing custom animation with out writing any java/kotlin code. MotionLayout is fully declarative, meaning that you can describe any transitions in XML, no matter how complex.
The following repo by Google includes more samples.
https://github.com/googlesamples/android-ConstraintLayoutExamples

you have to combine both flags to get that effect
Try this
app:layout_scrollFlags="scroll|exitUntilCollapsed|enterAlways"

Related

Android MotionLayout Swipe to reveal

I have a list of items that can be swiped away using a MotionLayout. I wanted to implement an animation similar to the one in the Gmail android app, where when you swipe an email away, there is that colored background that gets revealed. So what I tried is to place an ImageView in the MotionLayoutunder the view that moves, so that when that view goes away it will reveal the ImageView beneath.
Here is the layout of the element:
<androidx.constraintlayout.motion.widget.MotionLayout
android:id="#+id/reading_list_item_motion"
android:layout_width="match_parent"
android:layout_height="72dp"
app:layoutDescription="#xml/reading_list_item_scene">
<!-- The image under the element that moves -->
<ImageView
android:id="#+id/reading_list_item_back"
android:layout_width="match_parent"
android:layout_height="72dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:srcCompat="#tools:sample/backgrounds/scenic" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/reading_list_item_layout"
android:layout_width="match_parent"
android:layout_height="72dp"
android:gravity="center"
android:paddingLeft="#dimen/search_result_padding"
android:paddingRight="#dimen/search_result_padding"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="#+id/reading_list_item_icon"
android:layout_width="#dimen/ic_search_result"
android:layout_height="#dimen/ic_search_result"
android:layout_gravity="center"
android:layout_marginStart="8dp"
android:layout_marginTop="4dp"
android:layout_weight="1"
android:contentDescription="Search result icon"
app:error="#{#drawable/ic_default_search_result}"
app:imageUrl="#{book.best_book.small_image_url}"
app:layout_constraintEnd_toStartOf="#id/reading_list_item_info_layout"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="#drawable/ic_default_search_result" />
<LinearLayout
android:id="#+id/reading_list_item_info_layout"
android:layout_width="316dp"
android:layout_height="0dp"
android:layout_marginStart="8dp"
android:layout_marginTop="4dp"
android:layout_weight="1"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="0.4"
app:layout_constraintStart_toEndOf="#+id/reading_list_item_icon"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.6">
<TextView
android:id="#+id/reading_list_item_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="2"
android:singleLine="false"
android:text="#{book.best_book.title}"
android:textColor="#color/textSecondary"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:id="#+id/reading_list_item_author"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:singleLine="true"
android:text='#{"by " + book.best_book.author.name }'
android:textColor="#color/textSecondary" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.motion.widget.MotionLayout>
Even though in the Design editor the image appears to be there, and if I play the MotionLayout transition everything behaves as expected, when starting the app the ImageView is nowhere to be seen.
Another approach that I thought of would be to append the ImageView at the end of the view that moves, so that when it starts to go off-screen, the ImageView comes in view. I am not sure how could I implement this though since the ImageView should be as wide as the screen and match_parent obviously wouldn't work
Does anyone have any idea how can I implement this animation?
Thanks in advance!
Edit: Here is an image of how I'd like it to look. The more the item goes of screen, the more of the red background gets revealed.
Here is the MotionScene xml:
<?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">
<ConstraintSet android:id="#+id/start">
<Constraint
android:id="#+id/reading_list_item_layout"
android:layout_width="match_parent"
android:layout_height="72dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Constraint
app:layout_editor_absoluteY="1dp"
android:id="#+id/imageView" />
</ConstraintSet>
<ConstraintSet android:id="#+id/end">
<Constraint
android:id="#+id/reading_list_item_layout"
app:layout_constraintEnd_toEndOf="parent"
android:layout_width="0dp"
android:layout_height="72dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toEndOf="parent" />
</ConstraintSet>
<Transition
app:constraintSetEnd="#id/end"
app:constraintSetStart="#+id/start">
<OnSwipe
app:dragDirection="dragRight"
app:touchAnchorId="#id/reading_list_item_layout"
app:touchAnchorSide="left" />
</Transition>
</MotionScene>

Two Recycler Views inside a MotionLayout creating problems

I am new in using Motion Layout and I have used two Recycler views rv_horizontal and rv_vertical ,rv_horizontal is using LinearLayout Manger with horizontal orientation and rv_vertical is using GridLayout Manger. I have used both of them within Motion Layout. What I want is,when I scroll up the rv_vertical then rv_horizontal, along with the Text ViewtxtCategory, should disappear.
What I tried is following:
home.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.motion.widget.MotionLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".Home"
android:background="#color/bgcolor"
app:layoutDescription="#xml/fragment_home_scene">
<!-- TODO: Update blank fragment layout -->
<TextView
android:id="#+id/txtCategory"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="5dp"
android:text="#string/category"
android:textSize="8pt"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rv_horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="#id/txtCategory"
app:layout_constraintVertical_bias="0.0" />
<TextView
android:id="#+id/txtFamousPlaces"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp"
android:text="#string/famous_places"
android:textSize="8pt"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/rv_horizontal" />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rv_vertical"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/txtFamousPlaces"
app:layout_constraintVertical_bias="0.0" />
</androidx.constraintlayout.motion.widget.MotionLayout>
SceneFile of Home Fragment i.e., home_scene.xml :
<?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"
xmlns:motion="http://schemas.android.com/tools">
<ConstraintSet android:id="#+id/start">
<Constraint android:id="#+id/txtCategory"
android:layout_width="0dp"
android:layout_height="wrap_content"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent" />
<Constraint
android:layout_height="wrap_content"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:layout_width="match_parent"
app:layout_constraintTop_toBottomOf="#id/txtCategory"
android:id="#+id/rv_horizontal"
app:layout_constraintBottom_toTopOf="#+id/txtFamousPlaces" />
</ConstraintSet>
<ConstraintSet android:id="#+id/end">
<Constraint android:id="#id/txtCategory"
android:layout_width="0dp"
android:layout_height="wrap_content"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintBottom_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<Constraint
android:layout_height="0dp"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:layout_width="match_parent"
app:layout_constraintTop_toBottomOf="#id/txtCategory"
android:id="#+id/rv_horizontal"
app:layout_constraintBottom_toTopOf="parent" />
</ConstraintSet>
<Transition
app:constraintSetEnd="#id/end"
app:constraintSetStart="#+id/start">
<OnSwipe
motion:onTouchUp="stop"
motion:dragDirection="dragUp"
motion:touchAnchorId="#+id/rv_vertical"
app:moveWhenScrollAtTop="true"/>
</Transition>
</MotionScene>
I am facing the following problems, if anyone knows please help me:
I am scrolling up the rv_vertical but nothing is happening , although I have set the attribute motion:touchAnchorId="#+id/rv_vertical" within OnSwipe attribute in home_scene.xml file. As you can see.
When I scroll up the FamousPlaces then scroll down it then recycler view rv_horizontal does not load completey. As following
I tell you again what I want is,when I scroll up the rv_vertical, rv_horizontal, along with the Text ViewtxtCategory, should disappear.And when I scroll down rv_vertical, rv_horizontal should loaded completely. If anybody knows please help me.

Android: Show toolbar when scrolling UP(Drag UP) and hide when scrolling down(Drag down)

I have a scenario where the Toolbar/Action Bar, in an Activity, has an opposite behaviour than the general Toolbar behaviour. It should hide when the motion layout inside a fragment is scrolled UP and show when the motion layout is scrolled DOWN, which is the opposite of the general scroll behaviour.
I trie hiding the Support Action Bar and the toolbar layout completely but it is without any animation and does not bode well, since the Activity contains a BottomNavigation View, so the constant hiding and showing of Action bar does not look good.
supportActionBar?.hide()
containerToolbar.visibility = View.GONE
The AppBarLayout.LayoutParams scroll-flags apparently add the general behaviour.
fragment.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.motion.widget.MotionLayout
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"
app:layoutDescription="#xml/collapsing_header_arc"
app:showPaths="false"
android:background="#color/white"
tools:showPaths="true">
<com.github.florent37.shapeofview.shapes.ArcView
android:id="#+id/header"
android:layout_width="0dp"
android:layout_height="200dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:shape_arc_height="26dp"
android:background="#color/yellow_dark"
app:shape_arc_position="bottom">
<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#drawable/bg_actionbar_gradient" />
<ScrollView
android:id="#+id/scrollview_counts_container2"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<include
android:id="#+id/counts_container"
layout="#layout/layout_card_count"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</ScrollView>
</com.github.florent37.shapeofview.shapes.ArcView>
<View
android:id="#+id/guideline_anchor"
android:layout_width="wrap_content"
android:layout_height="5dp"
android:orientation="horizontal"
android:background="#color/red_dark"
app:layout_constraintTop_toBottomOf="#id/header"/>
<View
android:id="#+id/recyclerView"
android:layout_width="0dp"
android:layout_height="210dp"
app:layout_constraintBottom_toBottomOf="#+id/guideline_anchor"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="#+id/guideline_anchor"
android:background="#color/btnRedAlpha" />
<androidx.core.widget.NestedScrollView
android:id="#+id/parent_parent_dashboard"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="#+id/recyclerView"
android:background="#color/transparent"
android:layout_marginTop="#dimen/marginNormal">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/parent_dashboard"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ScrollView
android:id="#+id/scrollview_counts_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toTopOf="#+id/calendar_container"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintVertical_bias="0.0"
android:nestedScrollingEnabled="true"
android:visibility="gone">
<include
android:id="#+id/counts_container"
layout="#layout/layout_card_count"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"/>
</ScrollView>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="#color/white"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_margin="#dimen/marginBig"
app:layout_constraintTop_toBottomOf="#id/scrollview_counts_container"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintVertical_bias="0.0">
<View
android:layout_width="match_parent"
android:layout_height="400dp"
android:background="#color/yellow_dark" />
<View
android:layout_width="match_parent"
android:layout_height="400dp"
android:layout_marginTop="#dimen/marginBig"
android:background="#color/yellow_dark"/>
<View
android:layout_width="match_parent"
android:layout_height="400dp"
android:layout_marginTop="#dimen/marginBig"
android:background="#color/yellow_dark"/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>
<!--<androidx.appcompat.widget.AppCompatTextView-->
<!--android:id="#+id/headerText"-->
<!--android:layout_width="wrap_content"-->
<!--android:layout_height="wrap_content"-->
<!--android:layout_gravity="center_vertical"-->
<!--android:layout_marginLeft="23sp"-->
<!--android:elevation="4dp"-->
<!--android:gravity="center_vertical|left"-->
<!--android:text="I love paris"-->
<!--android:shadowColor="#3E3E3E"-->
<!--android:shadowDx="2"-->
<!--android:shadowDy="2"-->
<!--android:shadowRadius="4"-->
<!--android:textColor="#android:color/holo_blue_dark"/>-->
</androidx.constraintlayout.motion.widget.MotionLayout>
collapsing_header_arc.xml
<?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/guideline_anchor"
app:touchAnchorSide="top" />
</Transition>
<ConstraintSet android:id="#+id/start">
<Constraint
android:id="#id/header"
android:layout_height="240dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<CustomAttribute
app:attributeName="arcHeightDp"
app:customFloatValue="60" />
</Constraint>
<Constraint
android:id="#id/scrollview_counts_container2">
<CustomAttribute
app:attributeName="visibility"
app:customStringValue="visible" />
</Constraint>
</ConstraintSet>
<ConstraintSet android:id="#+id/end">
<Constraint
android:id="#id/header"
android:layout_height="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<CustomAttribute
app:attributeName="arcHeightDp"
app:customFloatValue="0" />
</Constraint>
<Constraint
android:id="#id/scrollview_counts_container2">
<CustomAttribute
app:attributeName="visibility"
app:customStringValue="gone" />
</Constraint>
</ConstraintSet>
</MotionScene>
Is there a way to implement this?
You can try to make collapse toolbar and manage it as you want. If you have problems to manage activity toolbar from the fragment you could use callback / or events / or observables to call activity animation when something happens from fragment. So for example you have a view in Activity with animation method something like here Show and hide a View with a slide up/down animation and just call it from the fragment.

MotionLayout with vertical ScrollView

I'm trying to make animation with motion layout, and want to set scrollView as targetAnchor for motion scene onSwipe Transition, but it doesn't work.
Maybe I don't know some thing about onSwipe transition.
Here is my layout file
<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"
android:background="#color/main_background_color"
app:layoutDescription="#xml/base_scroll_scene">
<ImageView
android:id="#+id/background_image"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginBottom="#dimen/scrollable_background_image_margin_bottom"
android:scaleType="centerCrop"
android:src="#drawable/lift_bg"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="#dimen/top_gradient_mask_height"
android:background="#drawable/top_gradient_mask"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="#+id/ski_more_logo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="#dimen/ski_more_logo_margin_top"
android:src="#drawable/ic_skimore_logo"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="#+id/locked_icon"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginTop="#dimen/locked_icon_margin_top"
android:src="#drawable/ic_lock"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/ski_more_logo" />
<FrameLayout
android:id="#+id/bottom_gradient_mask"
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="#drawable/bottom_gradient_mask"
app:layout_constraintBottom_toBottomOf="#+id/background_image" />
<ScrollView
android:id="#+id/content_scroll_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:overScrollMode="never"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent">
<FrameLayout
android:id="#+id/content_container_view"
android:layout_width="match_parent"
android:clickable="false"
android:layout_height="match_parent" />
</ScrollView>
</androidx.constraintlayout.motion.widget.MotionLayout>
In scroll view programmatically i'm adding some content with big height, which will be scrollable.
And here is my motion scene
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:motion="http://schemas.android.com/apk/res-auto">
<Transition
motion:constraintSetEnd="#+id/end"
motion:constraintSetStart="#+id/start"
motion:duration="1000">
<OnSwipe
motion:dragDirection="dragUp"
motion:touchAnchorId="#+id/content_scroll_view"
motion:touchAnchorSide="top" />
</Transition>
<ConstraintSet android:id="#+id/start"/>
<ConstraintSet android:id="#+id/end">
<Constraint
android:id="#+id/background_image"
android:layout_width="0dp"
android:layout_height="#dimen/top_gradient_mask_height"
android:layout_marginBottom="#dimen/scrollable_background_image_margin_bottom"
android:scaleType="centerCrop"
android:src="#drawable/lift_bg"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent" />
</ConstraintSet>
</MotionScene>
Please help me, Thanks.
Have you tried to replace ScrollView with NestedScrollView?
And, also, I would define ConstraintSet "start" as well.
I had the same issue on using a scrolling view with motion layout to implement my custom animations. But simply changing ScrollView to NestedScrollView fixed everything and animations work as expected

MotionLayout progress jumps from 0 to 1 without intermediate steps

I have a MotionLayout with a TransitionListener and progress seems to go from 0 to 1 for my MotionScene:
<MotionScene xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<Transition
app:constraintSetStart="#id/header_expanded"
app:constraintSetEnd="#id/header_condensed"
app:duration="2000">
<OnSwipe
app:touchAnchorId="#id/view_pager"
app:dragDirection="dragDown"
app:touchAnchorSide="top"/>
<KeyFrameSet>
<KeyAttribute
android:elevation="#dimen/header_elevation_expanded"
app:framePosition="0"
app:target="#id/header"/>
<KeyAttribute
android:elevation="#dimen/header_elevation_expanded"
app:framePosition="90"
app:target="#id/header"/>
<KeyAttribute
android:elevation="#dimen/header_elevation_collapsed"
app:framePosition="100"
app:target="#id/header" />
</KeyFrameSet>
</Transition>
<ConstraintSet android:id="#+id/header_expanded">
<Constraint
android:id="#+id/guideline_expanded_state"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.5" />
</ConstraintSet>
<ConstraintSet android:id="#+id/header_condensed">
<Constraint
android:id="#+id/guideline_expanded_state"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.18" />
</ConstraintSet>
</MotionScene>
If print the progress in the log it seems to jump from 0 to 1. I have tried changing the duration, or just changing the constraints for the header view to a different guideline but it doesn't seemt o affect the progress.
The only weird thing that I see in the logs is the fact that it cannot find the OnSwipe touchAnchorId view.
W/MotionLayout: WARNING could not find view id com.pitchero.ph:id/view_pager
The layout using this MotionScence is below:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.motion.widget.MotionLayout android:id="#+id/container"
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/activity_background"
app:layoutDescription="#xml/motion_scene_team">
<com.github.florent37.shapeofview.shapes.DiagonalView
android:id="#+id/header"
android:layout_width="match_parent"
android:layout_height="0dp"
android:elevation="#dimen/menu_header_elevation_expanded"
app:layout_constraintBottom_toTopOf="#+id/guideline_expanded_state"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:shape_diagonal_angle="#dimen/header_angle"
app:shape_diagonal_direction="left"
app:shape_diagonal_position="bottom">
<View
android:id="#+id/background"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/redE64"/>
</com.github.florent37.shapeofview.shapes.DiagonalView>
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/transparent"
app:layout_collapseMode="pin">
<LinearLayout
android:id="#+id/toolbar_title_container"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center"
android:elevation="5dp"
android:gravity="center"
android:orientation="horizontal">
<ImageView
android:id="#+id/toolbar_image"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginEnd="7dp"
android:visibility="gone"/>
<TextView
android:id="#+id/title"
style="#style/title.white"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:ellipsize="end"
android:gravity="center"
android:includeFontPadding="true"
android:maxLines="1"
tools:text="Help Center"/>
</LinearLayout>
</androidx.appcompat.widget.Toolbar>
<com.google.android.material.tabs.TabLayout
android:id="#+id/tabs_bar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="#color/transparent"
android:elevation="2dp"
android:textAlignment="center"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/toolbar"
app:tabBackground="#color/transparent"
app:tabIndicatorColor="#color/white"
app:tabSelectedTextColor="#color/white"
app:tabTextColor="#color/white_50_percent"/>
<androidx.viewpager.widget.ViewPager
android:id="#+id/view_pager"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/tabs_bar"/>
<androidx.constraintlayout.widget.Guideline
android:id="#+id/guideline_expanded_state"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.5"/>
</androidx.constraintlayout.motion.widget.MotionLayout>
I don't really understand why this is happening in this particular case. I suspect it's because it cannot find the ViewPager but I don't see why that would happen.
Also, I'm using the following version of the ConstraintLayout libs:
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha3'
implementation 'androidx.constraintlayout:constraintlayout-solver:2.0.0-alpha3'
Any help would be much appreciated, thank you!
You have implemented MotionLayout in a wrong way, first of all, you must not use Guideline to set animation, You can use ViewGroup containers or View for Animation.
Next thing, you must define all ContraintSet which are referencing another layout.
For ex.
<androidx.viewpager.widget.ViewPager
android:id="#+id/view_pager"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/tabs_bar"/>
Here you are using app:layout_constraintTop_toBottomOf="#id/tabs_bar" as a reference, so if you are animating That viewpager, you will need to add ConstraintSet for both of them, Then only you can get rid of that warning. :)

Categories

Resources