preface
Recently, I encountered an interface with round – cast while writing a business
While the interaction was being finalized, my colleagues suggested that Carousel in MotionLayout be considered as a Carousel component, which is designed for Carousel.
After learning MotionLayout really useful, in the words of colleagues, is to make the world without difficult animation.
Interpret A MotionLayout defines the first frame of an animation and the second frame of an animation. It automatically handles the animation after the event is triggered.
The point of this article is not to learn MotionLayout, but to teach you how to use Carousel.
FIG. 1 | Figure 2 | FIG. 3 |
---|---|---|
Plus, the official effect is pretty cool:
Also, the sliding animation is very silky, so it is recommended to write your own experience.
Demo address: github.com/mCyp/Motion…
A list,
Before you learned Carousel, I assumed you had the basics of MotionLayout.
If you don’t know what a MotionLayout is, take a look at the official tutorial:
MotionLayout- Code Lab
Ok, back to the point, the Chinese meaning of Carousel – round cast, it is a round cast solution, can help us deal with more complex round cast animation.
1. Core concepts
We start with the official example to build a round broadcast as shown in the figure:
In MotionLayout, these views can be written:
In general, a rotation, will support sliding forward and sliding back these two actions. If we want to fully display a Carousel animation, we need at least three states:
previous
start
next
Start is the initial state of the component, start-previous represents the process of sliding forward, and start-next represents the process of sliding backward. The corresponding state is shown in the figure below:
As you can see from above, the screen can only display three views in the middle. Start is the starting state. At this time, there are five views A, B, C, D and E. The screen displays B, C and D. When A backward slide occurs, B, C, D, and E move to A, B, C, and D (the A component can do nothing about it) and see C, D, and E on the screen. Same with sliding forward.
Is three states really enough? This is the question of many students.
Carousel has an unexpected ability that resets the interface to start after each animation, but the data mapped to the control changes:
As this image shows, the interface resets to the start state after a start-next animation. Notice that although the state is reset, the relationship between the data and the control has changed!
Components from the original screen:
Widget B
The correspondingItem2
Widget C
The correspondingItem3
Widget D
The correspondingItem4
After resetting:
Widget B
The correspondingItem3
Widget C
The correspondingItem4
Widget D
The correspondingItem5
It’s the same as the data displayed on the screen in the Next state, so these three states are really good enough.
Code 2.
Explain the pseudocode for the procedure above
Step one
In the layout file MotionLayout, we write out all the elements in the interface:
<androidx.constraintlayout.motion.widget.MotionLayout . >
<ImageView android:id="@+id/imageView0" . />
<ImageView android:id="@+id/imageView1" . />
<ImageView android:id="@+id/imageView2" . />
<ImageView android:id="@+id/imageView3" . />
<ImageView android:id="@+id/imageView4" . />
</androidx.constraintlayout.motion.widget.MotionLayout>
Copy the code
Layout hierarchy is important!
Layout hierarchy is important!
Layout hierarchy is important!
Important things need to be said three times. Make sure controls are placed in the right order. The system determines the order of views according to the hierarchy of views.
Otherwise, after the code is written, you will ask: why does the code have no problem, but the data arrangement after the animation is executed does not match?
Step 2
Constraintlayout describes the positions of components in the start, Previous, and Next states in the MotionScene file, which is equivalent to constraintLayout:
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:motion="http://schemas.android.com/apk/res-auto">
<Transition
android:id="@+id/forward"
motion:constraintSetEnd="@+id/next"
motion:constraintSetStart="@id/start"
motion:duration="1000">
<OnSwipe
motion:dragDirection="dragUp"
motion:touchAnchorSide="left" />
</Transition>
<Transition
android:id="@+id/backward"
motion:constraintSetEnd="@+id/previous"
motion:constraintSetStart="@+id/start">
<OnSwipe
motion:dragDirection="dragDown"
motion:touchAnchorSide="right" />
</Transition>
<! -- Component location corresponding to previous state -->
<ConstraintSet android:id="@+id/previous">
<Constraint
android:id="@+id/iv1"
. />
<Constraint
android:id="@+id/iv2"
. />
<Constraint
android:id="@+id/iv3"
. />
<Constraint
android:id="@+id/iv4"
. />
</ConstraintSet>
<! -- Component position corresponding to the start state -->
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@+id/iv1"
. />
<Constraint
android:id="@+id/iv2"
. />
<Constraint
android:id="@+id/iv3"
. />
<Constraint
android:id="@+id/iv4"
. />
<Constraint
android:id="@+id/iv5"
. />
</ConstraintSet>
<! -- Next state corresponding component position -->
<ConstraintSet android:id="@+id/next">
<Constraint
android:id="@+id/iv2"
. />
<Constraint
android:id="@+id/iv3"
. />
<Constraint
android:id="@+id/iv4"
. />
<Constraint
android:id="@+id/iv5"
. />
</ConstraintSet>
</MotionScene>
Copy the code
ConstraintSet represents a state in which a constraint layout tells the system where the control with each ID should appear.
Then use the Transition corresponding to forward to represent the over-animation of start-previous, and use the Transition corresponding to backward to represent the over-animation of start-next, This is to tell the system what action will trigger what animation.
We did very little, but MotionLayout did a lot for us!
Step 3 Add Carousel
Introducing Carousel into the layout:
<androidx.constraintlayout.motion.widget.MotionLayout . >
<ImageView android:id="@+id/iv1" . />
<ImageView android:id="@+id/iv2" . />
<ImageView android:id="@+id/iv3" . />
<ImageView android:id="@+id/iv4" . />
<ImageView android:id="@+id/iv5" . />
<androidx.constraintlayout.helper.widget.Carousel
android:id="@+id/carousel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:carousel_forwardTransition="@+id/forward"
app:carousel_backwardTransition="@+id/backward"
app:carousel_previousState="@+id/previous"
app:carousel_nextState="@+id/next"
app:carousel_infinite="true"
app:carousel_firstView="@+id/iv2"
app:constraint_referenced_ids="iv0,iv1,iv2,iv3,iv4" />
</androidx.constraintlayout.motion.widget.MotionLayout>
Copy the code
Explain a few properties:
app:carousel_forwardTransition
: forward jump animation, referenceforward
The correspondingTransition
app:carousel_backwardTransition
Backward jump animation, referencebackward
The correspondingTransition
app:carousel_previousState
: Indicates the state after the forward jump animation is complete, referenceprevious
The state of theConstraintSet
app:carousel_nextState
: Indicates the state after the backward jump animation, referencenext
The state of theConstraintSet
app:carousel_infinite
: Starts an infinite loopapp:carousel_firstView
Which control displays the first data,iv2
Represents the control in the middleiv2
The first data will be shown
Step 4 Declare it in the Activity
The adapter needs to be set up:
carousel.setAdapter(object : Carousel.Adapter {
override fun count(a): Int {
// need to return the number of items we have in the carousel
}
override fun populate(view: View, index: Int) {
// need to implement this to populate the view at the given index
}
override fun onNewItem(index: Int) {
// called when an item is set}})Copy the code
In this adapter, we need to tell the adapter how many pieces of data to display, how the control should update the display when a new page arrives, and so on.
Second, the actual combat
Here’s an example:
Confident analysis of this animation, also need to deal with two points:
- The picture changed from black and white to color
- Image to flip
The flip animation is relatively simple and can be handled with rotationY, but there is a pit, which I’ll look at later.
What about black and white pictures changing to color? The answer is ImageFilterView, he can set black and white to color effect through app: Saturation one key.
1. Describe the interface
We need to write all the controls in the XML file. There are four ImageViews in this interface, which may not be obvious in the screenshot, so I took another 3D view:
GuideLine 0.2, 0.4, 0.6 and 0.8 are used for the left and right orientation, as shown in the figure below:
Simplified layout file code:
<androidx.constraintlayout.motion.widget.MotionLayout .>
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/iv1" ./>
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/iv2" ./>
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/iv4" ./>
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/iv3" ./>
<androidx.constraintlayout.widget.Guideline
android:id="@+id/glLeft"
.
app:layout_constraintGuide_percent="0.2" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/glm1"
.
app:layout_constraintGuide_percent="0.4" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/glm2"
.
app:layout_constraintGuide_percent="0.6" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/glRight"
.
app:layout_constraintGuide_percent="0.8" />
<androidx.constraintlayout.helper.widget.Carousel
android:id="@+id/carousel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:carousel_backwardTransition="@+id/backward"
app:carousel_firstView="@+id/iv1"
app:carousel_forwardTransition="@+id/forward"
app:carousel_infinite="true"
app:carousel_nextState="@+id/next"
app:carousel_previousState="@+id/previous"
app:constraint_referenced_ids="iv1,iv2,iv4,iv3" />
</androidx.constraintlayout.motion.widget.MotionLayout>
Copy the code
2. Draw animation
In the previous pseudocode, we learned that to define previou, Start, and Next states, let’s look at the basic start state. We can just express the constraints in the interface, just like we used in the constraint layout.
Take out the MotionScene file and put only the start state for now:
MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:motion="http://schemas.android.com/apk/res-auto">
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@+id/iv1"
android:layout_width="0dp"
android:layout_height="0dp"
android:translationZ="0dp"
motion:layout_constraintLeft_toLeftOf="@+id/glLeft"
motion:layout_constraintRight_toRightOf="@id/glRight"
motion:layout_constraintTop_toTopOf="parent"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintDimensionRatio="2:1">
<CustomAttribute
motion:attributeName="Saturation"
motion:customFloatValue="0.0"
/>
</Constraint>
<Constraint
android:id="@+id/iv2"
android:layout_width="0dp"
android:layout_height="0dp"
android:rotationY="30"
android:scaleX="0.8"
android:scaleY="0.8"
android:translationZ="4dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintDimensionRatio="2:1"
motion:layout_constraintLeft_toLeftOf="parent"
motion:layout_constraintRight_toRightOf="@id/glm2"
motion:layout_constraintTop_toTopOf="parent">
<CustomAttribute
motion:attributeName="Saturation"
motion:customFloatValue="0.0"
/>
</Constraint>
<Constraint
android:id="@+id/iv3"
android:layout_width="0dp"
android:layout_height="0dp"
android:rotationY="To 30"
android:scaleX="0.8"
android:scaleY="0.8"
android:translationZ="6dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintDimensionRatio="2:1"
motion:layout_constraintLeft_toLeftOf="@id/glm1"
motion:layout_constraintRight_toRightOf="parent"
motion:layout_constraintTop_toTopOf="parent">
<CustomAttribute
motion:attributeName="Saturation"
motion:customFloatValue="0.0"
/>
</Constraint>
<Constraint
android:id="@+id/iv4"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleX="1.2"
android:scaleY="1.2"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintDimensionRatio="2:1"
android:translationZ="10dp"
motion:layout_constraintLeft_toLeftOf="@+id/glLeft"
motion:layout_constraintRight_toRightOf="@id/glRight"
motion:layout_constraintTop_toTopOf="parent"
>
<CustomAttribute
motion:attributeName="Saturation"
motion:customFloatValue="1.0"
/>
</Constraint>
</ConstraintSet>
</MotionScene>
Copy the code
A brief description:
iv1
On the inside, the position is centered above and below, the left side is at 0.2, the right side is at 0.8,Saturation
Is 0, that is, the picture is black and white,translationZ
0 dp.iv2
On the far left, the position is centered above and below, the left side is on the left side of the parent layout, and the right side is on the 0.4 line,Saturation
Is zero,translationZ
As the 4 dp,rotationY
Offset by 30 degrees.iv3
On the far right, the position is centered above and below, the left is on the 0.6 line, the right is aligned to the right of the parent layout,Saturation
Is zero,translationZ
For the 6 dp,rotationY
Offset -30 degrees.iv3
At the top, the position is centered above and below, the left side is at 0.2, the right side is at 0.8, and the length and width are enlarged 1.2 times each.Saturation
Is 1, that is, color picture,translationZ
For 10 dp.
If you’re careful, you’ll notice that I’m using too much translationZ.
This property allows us to set the shadow if there is a background. In addition, it also helps us to set the hierarchy. The default hierarchy in the layout file is that the View declared first has a lower hierarchy and the View declared later has a higher hierarchy, which means that the View written later will hide the View written earlier.
If you animate at this level, the pit will appear:
The animation would be messy without changing the hierarchy, so we need to adjust the hierarchy dynamically as the animation progresses.
Next state is very simple, the corresponding position can be changed, IV3 to IV1, IV1 to IV2, IV2 to IV4, IV4 to IV3, simple position swap, the same state, for the sake of space, the code will not be put.
In addition to the three states, you need to declare the animation in the MotionScene:
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:motion="http://schemas.android.com/apk/res-auto">
<Transition
motion:constraintSetStart="@id/start"
motion:constraintSetEnd="@+id/next"
motion:duration="1000"
android:id="@+id/forward">
<OnSwipe
motion:dragDirection="dragLeft"
motion:touchAnchorSide="left" />
</Transition>
<Transition
motion:constraintSetStart="@+id/start"
motion:constraintSetEnd="@+id/previous"
android:id="@+id/backward">
<OnSwipe
motion:dragDirection="dragRight"
motion:touchAnchorSide="right" />
</Transition>
<ConstraintSet android:id="@+id/previous">.</ConstraintSet>
<ConstraintSet android:id="@+id/start">.</ConstraintSet>
<ConstraintSet android:id="@+id/next">.</ConstraintSet>
</MotionScene>
Copy the code
It tells the system to swipe left to animate start to next state, swipe right to animate start to previous state, and then reference Carousel in the layout file and the system knows what to do.
3. Set the number of rounds
Finally, you need to set the number of rounds in the call, and how to notify the View of changes after sliding index:
class CarMotionActivity : AppCompatActivity() {
var images = intArrayOf(
R.drawable.car1,
R.drawable.car3,
R.drawable.car4,
R.drawable.car2
)
override fun onCreate(savedInstanceState: Bundle?). {
/ /...
val carsoul = findViewById<Carousel>(R.id.carousel)
carsoul.setAdapter(object : Carousel.Adapter {
override fun count(a): Int {
return 4
}
override fun populate(view: View? , index:Int) {
if(view is ImageView){
view.setImageResource(images[index])
}
}
override fun onNewItem(index: Int) {
// called when an item is set}}}})Copy the code
Ok, the beginner version of Carousel has been successfully mastered.
Put it in the last sentence
Carousel is simple and based on ConstraintLayout, with low threshold of use.
Carousel is a great choice when you need a rotation with a few page elements and a little complex animation.
On the other hand, this may not be true when the page has a lot of changing elements and the animation is simple. Because you need to write three interfaces, the idea is simple, but it is not worth the amount of code!
After a wave of learning, I found that the design guru’s final dynamic effect is image overlay, one image needs two ImageViews to achieve!
Finished, learned again in vain!!
However, I took this opportunity to master the MotionLayout, later, when writing animation more technical reserve.