This is going to be the result after we finish with the RecyclerView
item animations and the self-contained MotionScene
s.
But for now let's focus in putting everything into place. By the end of this step you'll have created the screen below. Simple as it is right now, it's the base for us to work with.
In a RecyclerView
we can customize the way it appears as a ViewGroup
(enter animation), or animate the differences in its children (add/ remove item). Together we're going to see the second one.
As mentioned in the beginning of this workshop, there are 2 ways you can specify an animation; either from the code or from the XML and the RecyclerView
animations are not any different than this. In this workshop we're going to create them through XML.
Let's start by adding some logic to our DetailActivity
to prepare for adding our new animations.
Copy <vector android:height="24dp"
android:tint="#333333"
android:viewportHeight="24.0"
android:viewportWidth="24.0"
android:width="24dp"
xmlns:android="http://schemas.android.com/apk/res/android">
<path
android:fillColor="#FF000000"
android:pathData="M19,13H5v-2h14v2z" />
</vector>
Copy <vector android:height="24dp"
android:tint="#333333"
android:viewportHeight="24.0"
android:viewportWidth="24.0"
android:width="24dp"
xmlns:android="http://schemas.android.com/apk/res/android">
<path
android:fillColor="#FF000000"
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" />
</vector>
Copy <ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/colorPrimaryDark">
<item android:id="@android:id/mask">
<shape android:shape="oval">
<solid android:color="@color/colorPrimaryDark" />
<corners android:radius="40dp" />
</shape>
</item>
</ripple>
Copy <?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
tools:context=".DetailActivity" ...>
<!-- CircleImageView avatar -->
<!-- TextView name -->
<ImageButton
android:id="@+id/bt_increase"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:background="?selectableItemBackgroundBorderless"
android:foreground="@drawable/button_background"
android:padding="28dp"
android:src="@drawable/ic_add"
app:layout_constraintBottom_toTopOf="@+id/guideline_horizontalHalf"
app:layout_constraintEnd_toStartOf="@+id/guideline_vertical25"
app:layout_constraintStart_toStartOf="@+id/guideline_vertical25"
app:layout_constraintTop_toTopOf="@+id/guideline_horizontalHalf"
tools:ignore="ContentDescription" />
<TextView
android:id="@+id/tv_counter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:textAppearance="@style/TextAppearance.AppCompat.Body2"
android:textSize="20sp"
app:layout_constraintBottom_toTopOf="@+id/guideline_horizontalHalf"
app:layout_constraintEnd_toStartOf="@+id/guideline_verticalHalf"
app:layout_constraintStart_toStartOf="@+id/guideline_verticalHalf"
app:layout_constraintTop_toTopOf="@+id/guideline_horizontalHalf" />
<ImageButton
android:id="@+id/bt_decrease"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:background="?selectableItemBackgroundBorderless"
android:foreground="@drawable/button_background"
android:padding="28dp"
android:src="@drawable/ic_remove"
app:layout_constraintBottom_toTopOf="@+id/guideline_horizontalHalf"
app:layout_constraintEnd_toStartOf="@+id/guideline_vertical75"
app:layout_constraintStart_toStartOf="@+id/guideline_vertical75"
app:layout_constraintTop_toTopOf="@+id/guideline_horizontalHalf"
tools:ignore="ContentDescription" />
<!-- View second background -->
<android.support.v7.widget.RecyclerView
android:id="@+id/rv_added_items"
android:layout_width="282dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:orientation="horizontal"
app:reverseLayout="true"
app:layoutManager="android.support.v7.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="@+id/fab"
app:layout_constraintEnd_toStartOf="@+id/fab"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/fab"
tools:listitem="@layout/li_detail" />
<!-- FAB -->
<android.support.constraint.Guideline
android:id="@+id/guideline_verticalHalf"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.5" />
<android.support.constraint.Guideline
android:id="@+id/guideline_horizontalHalf"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.5" />
<android.support.constraint.Guideline
android:id="@+id/guideline_vertical25"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.25" />
<android.support.constraint.Guideline
android:id="@+id/guideline_vertical75"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.75" />
<android.support.constraint.Guideline
android:id="@+id/guideline_horizontal25"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.25" />
</android.support.constraint.ConstraintLayout>
Copy class DetailActivity : AppCompatActivity() {
private var counter = 0
...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
tv_counter.text = counter.toString()
fab.setImageDrawable(animDrawable)
fab.setOnClickListener {
if (!isEditMode) {
...
bt_decrease.visibility = View.VISIBLE
bt_increase.visibility = View.VISIBLE
} else {
...
bt_decrease.visibility = View.GONE
bt_increase.visibility = View.GONE
}
isEditMode= !isEditMode
}
bt_increase.setOnClickListener { tv_counter.text = "${++counter}" }
bt_decrease.setOnClickListener {
tv_counter.text = when {
counter > 0 -> "${--counter}"
else -> "$counter"
}
}
}
...
Copy <android.support.constraint.motion.MotionLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/added_item_container"
xmlns:app="http://schemas.android.com/apk/res-auto">
<ImageView
android:id="@+id/iv_added_item"
android:layout_width="42dp"
android:layout_height="42dp"
android:src="@drawable/avatar_1"
tools:ignore="ContentDescription" />
</android.support.constraint.motion.MotionLayout>
We've put almost everything into place!
Right now, it doesn't do much since we haven't added the adapter yet but let's rewind what we've done so far.
We added 2 buttons in our Detail Activity layout; one for adding items and one for removing
We also added a counter so that we know how many items we've added and removed
We added a RecyclerView
to our Detail Activity layout
And finally we created the layout for our list item of that RecyclerView
. You may have noticed that in contrast with our previous motion layouts, in this one we haven't specified any constraints. The reason for this is because we are going to add our view constraints from our MotionScene
XML file. We call this MotionScene "self-contained" since all the ConstraintSets are maintained in a single file.