π Transform views, activity, and fragments into other components with container transform animations.
Go to the Releases to download the demo APK.
If you want to implement morphing animation in Jetpack Compose, check out Orbital.
Add the dependency below to your module's build.gradle
file:
dependencies {
implementation("com.github.skydoves:transformationlayout:1.1.4")
}
Add the XML namespace below inside your XML layout file:
xmlns:app="http://schemas.android.com/apk/res-auto"
TransformationLayout
is an essential concept to transform your Views, Activities, and Fragments into other components. You must wrap one or more Views that are supposed to be transformed using TransformationLayout
like the example code below:
<com.skydoves.transformationlayout.TransformationLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:transformation_targetView="@+id/my_cardView" // sets a target view.
app:transformation_duration="450" // sets a duration of the transformation.
app:transformation_direction="auto" // auto, entering, returning
app:transformation_fadeMode="in" // in, out, cross, through
app:transformation_fitMode="auto" // auto, height, width
app:transformation_pathMode="arc" // arc, linear
>
<!-- other complicated views -->
</com.skydoves.transformationlayout.TransformationLayout>
For instance, you can transform a floating button into a CardView as you've seen in the example below:
<com.skydoves.transformationlayout.TransformationLayout
android:id="@+id/transformationLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:transformation_duration="550"
app:transformation_targetView="@+id/myCardView">
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:backgroundTint="@color/colorPrimary"
android:src="@drawable/ic_write"/>
</com.skydoves.transformationlayout.TransformationLayout>
<com.google.android.material.card.MaterialCardView
android:id="@+id/myCardView"
android:layout_width="240dp"
android:layout_height="312dp"
android:layout_marginLeft="30dp"
android:layout_marginTop="30dp"
app:cardBackgroundColor="@color/colorPrimary" />
With the attribute below in your XML file, you can bind a targetView
that should be transformed from the TransformationLayout
. If you bind a targetView with a TransformationLayout
, the targetView's visibility will be GONE
by default.
app:transformation_targetView="@+id/myCardView"
You can also bind a targetView with a TransformationLayout
using bindTargetView
method like the code below:
transformationLayout.bindTargetView(myCardView)
After binding a targetView, we can start or finish transformation using the below methods.
// start transformation when touching the fab.
fab.setOnClickListener {
transformationLayout.startTransform()
}
// finish transformation when touching the myCardView.
myCardView.setOnClickListener {
transformationLayout.finishTransform()
}
Here are other functionalities to starting and finishing transformation.
// starts and finishes transformation 1000 milliseconds later.
// If we use this method on onCreate() method, it will starts transformation automatically 200ms later.
transformationLayout.startTransformWithDelay(200)
transformationLayout.finishTransformWithDelay(200)
// starts and finishes transformation with stopping a parent layout.
transformationLayout.startTransform(parent)
transformationLayout.finishTransform(parent)
We can listen a TransformationLayout
is transformed or not using OnTransformFinishListener
.
transformationLayout.setOnTransformFinishListener {
Toast.makeText(context, "is transformed: $it", Toast.LENGTH_SHORT).show()
}
Here is the Java way.
transformationLayout.onTransformFinishListener = new OnTransformFinishListener() {
@Override public void onFinish(boolean isTransformed) {
Toast.makeText(context, "is transformed:" + isTransformed, Toast.LENGTH_SHORT).show();
}
};
We can implement transformation between activities easily using TransformationActivity
and TransformationCompat
.
Here is an example of transforming a floating action button to Activity.
We don't need to bind a targetView.
<com.skydoves.transformationlayout.TransformationLayout
android:id="@+id/transformationLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:transformation_duration="550">
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:backgroundTint="@color/colorPrimary"
android:src="@drawable/ic_write"/>
</com.skydoves.transformationlayout.TransformationLayout>
We should add onTransformationStartContainer()
to the Activity that has the floating action button. If your view is in the fragment, the code should be added to the fragment's Activity. It must be called before super.onCreate
.
override fun onCreate(savedInstanceState: Bundle?) {
onTransformationStartContainer() // should be called before super.onCreate().
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
Here is the Java way.
TransformationCompat.onTransformationStartContainer(this);
Extends TransformationAppCompatActivity
or TransformationActivity
to your activity that will be transformed.
class DetailActivity : TransformationAppCompatActivity()
Here is the Java way.
public class DetailActivity extends TransformationAppCompatActivity
And start the DetailActivity
using the TransformationCompat.startActivity
method.
val intent = Intent(context, DetailActivity::class.java)
TransformationCompat.startActivity(transformationLayout, intent)
Here is the Java way.
Intent intent = new Intent(context, DetailActivity.class);
TransformationCompat.startActivity(transformationLayout, intent);
Here is an example of transforming a floating action button to Activity.
We don't need to bind a targetView.
<com.skydoves.transformationlayout.TransformationLayout
android:id="@+id/transformationLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:transformation_duration="550">
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:backgroundTint="@color/colorPrimary"
android:src="@drawable/ic_write"/>
</com.skydoves.transformationlayout.TransformationLayout>
We should add onTransformationStartContainer()
to the Activity that has the floating action button. If your view is in the fragment, the code should be added to the fragment's Activity. It must be called before super.onCreate
.
override fun onCreate(savedInstanceState: Bundle?) {
onTransformationStartContainer() // should be called before super.onCreate().
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
Here is the Java way.
TransformationCompat.onTransformationStartContainer(this);
And we should call startActivity
with bundle and intent data.
We should get a bundle
using withActivity
method. It needs a context and any name of transition.
The bundle
must be used as startActivity
's parameter.
We should put parcelable data to the intent using getParcelableParams()
method.
The extra name of the parcelable data can be anything, and it will be reused later.
fab.setOnClickListener {
val bundle = transformationLayout.withActivity(this, "myTransitionName")
val intent = Intent(this, DetailActivity::class.java)
intent.putExtra("TransformationParams", transformationLayout.getParcelableParams())
startActivity(intent, bundle)
}
If we want to get bundle data in RecyclerView or other classes,
we can use withView
and withContext
instead of withActivty
.
// usage in the RecyclerView.Adapter
override fun onBindViewHolder(holder: PosterViewHolder, position: Int) {
val bundle = transformationLayout.withView(holder.itemView, "myTransitionName")
}
Here is the Java way.
Bundle bundle = transformationLayout.withActivity(this, "myTransitionName");
Intent intent = new Intent(this, DetailActivity.class);
intent.putExtra("TransformationParams", transformationLayout.getParcelableParams());
startActivity(intent, bundle);
And finally, we should add onTransformationEndContainer()
to the Activity that will be started.
It must be added before super.onCreate
.
override fun onCreate(savedInstanceState: Bundle?) {
onTransformationEndContainer(intent.getParcelableExtra("TransformationParams"))
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_detail)
}
Here is the Java way.
TransformationLayout.Params params = getIntent().getParcelableExtra("TransformationParams");
TransformationCompat.onTransformationEndContainer(this, params);
We can implement transformation between fragments for a single Activity application.
Here is an example of transforming a floating action button in Fragment A to Fragment B.
<com.skydoves.transformationlayout.TransformationLayout
android:id="@+id/transformationLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:transformation_duration="550">
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:backgroundTint="@color/colorPrimary"
android:src="@drawable/ic_write"/>
</com.skydoves.transformationlayout.TransformationLayout>
We should call onTransformationStartContainer()
in the Fragment A that has the floating action button.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
onTransformationStartContainer()
}
Here is the Java way.
TransformationCompat.onTransformationStartContainer(this);
We should get a bundle from the TransformationLayout
and put it into the argument.
And in the fragment manager's transaction, we should add the TransformationLayout
using addTransformation
method.
val fragment = MainSingleDetailFragment()
val bundle = transformationLayout.getBundle("TransformationParams")
bundle.putParcelable(MainSingleDetailFragment.posterKey, poster)
fragment.arguments = bundle
requireFragmentManager()
.beginTransaction()
.addTransformation(transformationLayout)
.replace(R.id.main_container, fragment, MainSingleDetailFragment.TAG)
.addToBackStack(MainSingleDetailFragment.TAG)
.commit()
}
Here is the Java way
MainSingleDetailFragment fragment = new MainSingleDetailFragment();
Bundle bundle = transformationLayout.getBundle("TransformationParams", "transitionName");
fragment.setArguments(bundle);
FragmentTransaction fragmentTransaction = requireFragmentManager().beginTransaction();
TransformationCompat.addTransformation(
fragmentTransaction, transformationLayout, "transitionName");
fragmentTransaction.replace(R.id.main_container, fragment, MainSingleDetailFragment.TAG)
.addToBackStack(MainSingleDetailFragment.TAG)
.commit();
We must set a specific transition name to the TransformationLayout
.
If you want to transform a recyclerView's item, set transiton name in onBindViewHolder
.
transformationLayout.transitionName = "myTransitionName"
Here is the Java way.
transformationLayout.setTransitionName("myTransitionName");
We should get a TransformationLayout.Params
from the arguments, and call onTransformationEndContainer
method.
It must be called in onCreate
method.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val params = arguments?.getParcelable<TransformationLayout.Params>("TransformationParams")
onTransformationEndContainer(params)
}
Here is the Java way.
TransformationLayout.Params params = getArguments().getParcelable("TransformationParams");
TransformationCompat.onTransformationEndContainer(this, params);
And finally set the specific transition name (same as the transformationLayot in Fragment A)
to the target view in Fragment B in onViewCreated
.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
detail_container.transitionName = "myTransitionName"
}
Attributes | Type | Default | Description |
---|---|---|---|
targetView | resource id | none | Bind a targetView that will be transformed. |
duration | Long | 350L | Duration of the transformation. |
pathMotion | Motion.ARC, Motion.LINEAR | default layout | Indicates that this transition should be drawn as the which path. |
containerColor | Color | Color.TRANSPARENT | Set the container color to be used as the background of the morphing container. |
allContainerColor | Color | Color.TRANSPARENT | The all container colors (start and end) to be used as the background of the morphing container. |
scrimColor | Color | Color.TRANSPARENT | Set the color to be drawn under the morphing container. |
direction | Direction.AUTO, Direction.ENTER, Direction.RETURN | Direction.AUTO | Set the direction to be used by this transform. |
fadeMode | FadeMode.IN, FadeMode.OUT, FadeMode.CROSS, FadeMode.THROUGH | FadeMode.IN | Set the FadeMode to be used to swap the content of the start View with that of the end View. |
fitMode | FitMode.AUTO, FitMode.WIDTH, FitMode.HEIGHT | FitMode.AUTO | Set the fitMode to be used when scaling the incoming content of the end View. |
startElevation | Float | ELEVATION_NOT_SET | The elevation that will be used to render a shadow around the container at the start of the transition. |
endElevation | Float | ELEVATION_NOT_SET | The elevation that will be used to render a shadow around the container at the end of the transition. |
elevationShadowEnabled | Boolean | true if (version > Pie) | Whether shadows should be drawn around the container to approximate native elevation shadows on the start and end views. |
holdAtEndEnabled | Boolean | false | Whether to hold the last frame at the end of the animation. |
You can reference the usage of the TransformationLayout in another repository MarvelHeroes.
A demo application based on modern Android application tech-stacks and MVVM architecture.
Support it by joining stargazers for this repository. β
And follow me for my next creations! π€©
Copyright 2020 skydoves (Jaewoong Eum)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.