提交 bb083021 编写于 作者: E ester.zhou

Update docs (21221)

Signed-off-by: Nester.zhou <ester.zhou@huawei.com>
上级 3b8676f0
......@@ -40,14 +40,26 @@
- [Drawing Geometric Shapes (Shape)](arkts-geometric-shape-drawing.md)
- [Drawing Custom Graphics on the Canvas (Canvas)](arkts-drawing-customization-on-canvas.md)
- Using Animation
- [Animation Overview](arkts-animation-overview.md)
- Animation Within a Page
- [Layout Update Animation](arkts-layout-update-animation.md)
- [Transition Animation Within a Component](arkts-transition-animation-within-component.md)
- [Spring Curve Animation](arkts-spring-animation.md)
- Animation Between Pages
- [Zoom Animation](arkts-zoom-animation.md)
- [Page Transition Animation](arkts-page-transition-animation.md)
- [Animation Overview](arkts-animation.md)
- Attribute Animation
- [Attribute Animation Overview](arkts-attribute-animation-overview.md)
- [Attribute Animation APIs](arkts-attribute-animation-apis.md)
- [Custom Attribute Animation](arkts-custom-attribute-animation.md)
- Transition Animation
- [Transition Animation Overview](arkts-transition-overview.md)
- [Enter/Exit Transition](arkts-enter-exit-transition.md)
- [Navigation Transition](arkts-navigation-transition.md)
- [Modal Transition](arkts-modal-transition.md)
- [Shared Element Transition](arkts-shared-element-transition.md)
- [Component Animation](arkts-component-animation.md)
- Animation Curve
- [Traditional Curve](arkts-traditional-curve.md)
- [Spring Curve](arkts-spring-curve.md)
- [Animation Smoothing](arkts-animation-smoothing.md)
- Animation Effects
- [Blur Effect](arkts-blur-effect.md)
- [Shadow Effect](arkts-shadow-effect.md)
- [Color Effect](arkts-color-effect.md)
- Using Interaction Events
- [Interaction Event Overview](arkts-event-overview.md)
- Universal Events
......
# Animation Overview
The principle of animation is that the UI appearance is changed for multiple times within a period of time. Because human eyes retain persistence of vision, what you finally see is a continuous animation. A change of the UI is called an animation frame, which corresponds to a screen refresh. An important indicator that determines the animation smoothness is the frame rate (FPS), that is, the number of animation frames per second. The higher the frame rate, the smoother the animation.
In ArkUI, an animation is generated by changing the attribute value and specifying the animation parameters. Animation parameters include animation duration, change rule (that is, curve), and more. After the attribute value changes, the original state is transited to the new state according to the animation parameters. In this way, an animation is formed.
The animation capability provided by ArkUI can be classified into intra-page animation and inter-page animation based on the page classification mode. As shown in the following figure, an animation on a page refers to an animation that can occur on a page, and an animation between pages refers to an animation that occurs only with redirection between pages.
**Figure 1** Animation by page
![en-us_image_0000001562700385](figures/en-us_image_0000001562700385.png)
By capability, the animation can be divided into three parts: attribute animation, explicit animation, and transition animation, as shown in the following figure.
**Figure 2** Animation classified by basic capability
![en-us_image_0000001562820753](figures/en-us_image_0000001562820753.png)
This topic will introduce you to the usage and precautions of animations by the preceding classification and use cases.
# Animation Smoothing
When running animations, the UI is also interacting with users in real time. It must respond immediately to changes in user behavior. For example, if the user swipes up to exit in the midst of an application launch process, the UI should immediately transit from the startup animation to the exit animation, rather than finishing the startup animation before exiting. In the scenario where the animation triggered when the user lifts their fingers off the screen, the initial velocity of the animation must inherit the gesture speed, so as to avoid pauses caused by speed disconnection. For the preceding and similar scenarios, OpenHarmony provides efficient APIs for smoothing between animations and between animations and gestures.
## Smoothing Between Animations
Assume that there is a running animation for an animatable attribute. If the end value of the attribute changes due to an operation on the UI, just change the attribute value in the **animateTo** closure or change the input parameter value of the **animation** API to create an animation. OpenHarmony then automatically connects the previous animation with the current animation – the created animation.
```ts
import curves from '@ohos.curves'
// Step 1: Declare the related state variable.
@state scaleToggle: boolean = true;
...
Column() {
Button()
// Step 2: Set the declared state variable to the related animatable attribute API.
.scale(this.scaleToggle ? 1 : 0.5)
// Step 3: Change the state variable value through the click event, which then changes the attribute value.
.onclick(() => {
this.scaleToggle = !this.scaleToggle;
})
// Step 4: Enable the implicit animation. When the end value of the animation changes, the system automatically adds the smoothing animation.
.animation({
curve: curves.springMotion()
})
}
...
```
A complete example is as follows: By clicking **click**, you change the **scale** attribute of the red square. When you click **click** repeatedly, the end value of the **scale** attribute changes continuously, and the current animation smoothly moves towards the new end value of the **scale** attribute.
```ts
import curves from '@ohos.curves';
@Entry
@Component
struct AnimationToAnimationDemo {
@State isAnimation: boolean = false;
build() {
Column() {
Text('ArkUI')
.fontWeight(FontWeight.Bold)
.fontSize(12)
.fontColor(Color.White)
.textAlign(TextAlign.Center)
.borderRadius(10)
.backgroundColor(0xf56c6c)
.width(100)
.height(100)
.scale({ x: this.isAnimation ? 2 : 1, y: this.isAnimation ? 2 : 1 })
.animation({ curve: curves.springMotion(0.4, 0.8) })
Button('Click')
.margin({ top: 200 })
.onClick(() => {
this.isAnimation = !this.isAnimation;
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}
```
![en-us_image_0000001599971890](figures/en-us_image_0000001599971890.gif)
## Smoothing Between Gestures and Animations
In scenarios where gestures are used, an attribute change is generally triggered when the user places or moves their finger (or fingers) on the screen, and continues after the user lifts their finger (or fingers) off the screen until the end value of the attributes is reached.
The initial velocity of the attribute change after the user lifts their finger (or fingers) should be consistent with the velocity of the attribute change at the moment before the user lifts their finger (or fingers). If the former is **0**, it feels like a running car stops suddenly, an unusual abrupt change not welcomed by users.
In cases where smoothing between gestures and animations is required, for example, when scrolling a list, you can apply a responsive spring curve for the attribute animation running when the user places or moves their finger (or fingers) on the screen; and apply a spring curve for the attribute animation running after the user lifts their finger (or fingers) off the screen. For the animation that uses the [springMotion](../reference/apis/js-apis-curve.md#curvesspringmotion9) curve, the attribute animation running when the user places or moves their finger (or fingers) on the screen automatically inherits the previous velocity and starts from where the previous animation leaves off.
```ts
import curves from '@ohos.curves'
// Step 1: Declare related state variables.
@state offsetX: number = 0;
@State offsetY: number = 0;
targetOffsetX: number = 100;
targetOffsetY: number = 100;
...
Column()
// Step 2: Set the declared state variables to the related animatable attribute APIs.
.translate({ x: this.offsetX, y: this.offsetY})
.gesture(
PanGesture({})
.onActionUpdate((event: GestureEvent) => {
// Step 3: Change the state variable value for the time when the user places or moves their finger (or fingers) on the screen and use reponsiveSpringMotion for movement toward the new value.
animateTo({
curve: curves.responsiveSpringMotion()
}, () => {
this.offsetX = event.offsetX;
this.offsetY = event.offsetY;
})
})
.onActionEnd(() => {
// Step 4: Set the end value of the state variable for after the user lifts their finger (or fingers), and use springMotion for movement toward the new value. The springMotion animation inherits the previous velocity.
animateTo({
curve: curves.SpringMotion()
}, () => {
this.offsetX = targetOffsetX;
this.offsetY = targetOffsetY;
})
})
)
...
```
Below is the complete sample code and effect.
```ts
import curves from '@ohos.curves';
@Entry
@Component
struct SpringMotionDemo {
@State positionX: number = 100;
@State positionY: number = 100;
diameter: number = 50;
build() {
Column() {
Row() {
Circle({ width: this.diameter, height: this.diameter })
.fill(Color.Blue)
.position({ x: this.positionX, y: this.positionY })
.onTouch((event: TouchEvent) => {
if (event.type === TouchType.Move) {
// When the user places or moves their finger on the screen, use the responsiveSpringMotion curve.
animateTo({ curve: curves.responsiveSpringMotion() }, () => {
// Subtract the radius so that the center of the ball moves to where the finger is placed.
this.positionX = event.touches[0].screenX - this.diameter / 2;
this.positionY = event.touches[0].screenY - this.diameter / 2;
console.info(`move, animateTo x:${this.positionX}, y:${this.positionY}`);
})
} else if (event.type === TouchType.Up) {
// After the user lifts their finger off the screen, use the springMotion curve.
animateTo({ curve: curves.springMotion() }, () => {
this.positionX = 100;
this.positionY = 100;
console.info(`touchUp, animateTo x:100, y:100`);
})
}
})
}
.width("100%").height("80%")
.clip(true) // If the ball moves beyond the parent component, it is invisible.
.backgroundColor(Color.Orange)
Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Start, justifyContent: FlexAlign.Center }) {
Text("Drag the ball").fontSize(16)
}
.width("100%")
Row() {
Text('Click position: [x: ' + Math.round(this.positionX) + ', y:' + Math.round(this.positionY) + ']').fontSize(16)
}
.padding(10)
.width("100%")
}.height('100%').width('100%')
}
}
```
![en-us_image_0000001647027001](figures/en-us_image_0000001647027001.gif)
# Animation Overview
The UI contains various components (such as time and wallpaper) that users can see when interacting with their devices. Attributes are APIs used to control the behavior of components. For example, you can adjust the location of a component on the screen through the location attribute.
In general cases, if the value of an attribute is changed, the UI will be updated accordingly. Animation can add smooth transition effects when the UI is updated. Let's look at an example. When the user touches an application icon on the home screen, the application window opens and replaces the home screen as the primary display area. In this application launch process, if no animation is added, the related attributes change immediately, and the application window directly replaces the home screen, resulting in a sense of visual discontinuity. By adding animation, we enable attribute changes to take place over time, thereby creating an illusion of visual continuity.
ArkUI provides a wide range of animation APIs (such as attribute animation and transition animation), which you can leverage to cause attributes to gradually change from the start value to the end value based on the specified settings. Although the attribute values are not absolutely continuous during the change, but rather discrete to some extent, what you finally see is a continuous animation, since human eyes retain persistence of vision. A change of the UI is called an animation frame, which corresponds to a screen refresh. An important indicator that determines the animation smoothness is the frame rate (FPS), that is, the number of animation frames per second. The higher the frame rate, the smoother the animation. In ArkUI, animation parameters include animation duration, animation curve, and more. As the main factor of the animation, the animation curve determines the law of attribute value changes. For example, with a linear animation curve, the attribute changes from the start value to the end value at a constant speed over the given duration. If the attribute changes too fast or too slow, the visual experience may suffer. Therefore, animation parameters, especially animation curves, must be well designed and adjusted to be tailored to use cases.
Animation APIs drive attribute values to continuously transit from one state to another according to the rule determined by the animation parameters, and thereby generate a continuous visual effect on the UI. This walkthrough demonstrates the steps and precautions to follow, for creating a fascinating animation experience for users. It is organized as follows.
![en-us_image_0000001595763076](figures/en-us_image_0000001595763076.png)
- Attribute animation: It is the most basic animation type. It drives attribute changes frame by frame based on animation parameters to create an animation on a frame-by-frame basis.
- Transition animation: animation for transitioning when components appear and disappear. To maintain animation consistency, some animation curves have been built in and cannot be customized.
- Whenever possible, avoid ability redirection in the animation. An ability is a task in effect and is individually displayed on the recent tasks screen. Redirection between abilities is redirection between tasks. In the typical scenario of viewing large images in an application, if you call the gallery ability from the application to open large images, then the gallery ability will appear on the recent tasks screen. This is not recommended. A more recommended practice is as follows: Build a large image component in the application and invoke that component through modal transition. In this way, the entire animation can be completed in one ability.
- To implement navigation, use the **\<Navigation>** component, instead of the page routing mode. The page routing mode causes page separation, which is inconvenient for implementing linked transition effects. In addition, it does not allow for one-time development for multi-device deployment.
- Component animation: Components provide default animations (such as the slide animation of the **\<List>** component). Some of them even support custom animations.
- Animation curve: You can use traditional and spring curves to control how the attribute values change, to create an engaging animation effect.
- Animation connection: Make the transition between animations and between gestures and animations as natural as possible.
- Advanced animation effect: You can up your animation game with advanced effects such as blur, shadow, color gradient, and the like.
# Attribute Animation APIs
ArkUI provides two types of APIs, namely, [animateTo](../reference/arkui-ts/ts-explicit-animation.md) and [animation](../reference/arkui-ts/ts-animatorproperty.md), to implement an attribute animation – a visual illusion of continuity created by driving component attributes to change over time based on animation parameters such as the animation curve.
| API| Scope| Principle| Description|
| -------- | -------- | -------- | -------- |
| animateTo | GUI changes caused by attribute changes in closures.<br>Transition for appearance and disappearance.| As a general function, animates the differences between the UIs before and after the state variable in the closure is changed.<br>This API can be called multiple times and can be nested.| Applicable when a single set of animation parameters is used to animate multiple attributes.<br>Applicable when animations need to be nested.|
| animation | GUI changes caused by attribute changes bound to components through attribute APIs.| Identifies the change of the animatable attributes of a component and automatically adds an animation.<br>As the API call sequence of the component is from bottom to top, this API applies only to the attributes above it.<br>In a component, you can set individual **animation** for attributes based on the call sequence.| Applicable when different sets of animation parameters are used to animate different attributes.|
## Using animateTo
```
animateTo(value: AnimateParam, event: () => void): void
```
Among the parameters of [animateTo](../reference/arkui-ts/ts-explicit-animation.md), **value** indicates the [animation parameters](../reference/arkui-ts/ts-explicit-animation.md#animateparam) (including duration and [curve](../reference/apis/js-apis-curve.md#curve); **event** indicates the closure of the animation. Attribute animations generated by variable changes within the closure follow the same animation parameters.
```ts
import curves from '@ohos.curves'
@Entry
@Component
struct AnimateToDemo {
@State animate: boolean = false;
// Step 1: Declare related state variables.
@State rotateValue: number = 0; // Rotation angle of component 1.
@State translateY: number = 0; // Offset of component 2
@State opacityValue: number = 1; // Opacity of component 2.
//Step 2: Set the declared state variables to the related animatable attribute APIs.
build() {
Column() {
// Component 1
Column() {
Text('ArkUI')
.fontWeight(FontWeight.Bold)
.fontSize(20)
.fontColor(Color.White)
}
.justifyContent(FlexAlign.Center)
.width(150)
.height(150)
.borderRadius(10)
.rotate({ angle: this.rotateValue })
.backgroundColor(0xf56c6c)
// Component 2
Column() {
Text('ArkUI')
.fontWeight(FontWeight.Bold)
.fontSize(20)
.fontColor(Color.White)
}
.justifyContent(FlexAlign.Center)
.width(150)
.height(150)
.backgroundColor(0x67C23A)
.borderRadius(10)
.opacity(this.opacityValue)
.translate({ y: this.translateY })
Button('Click')
.margin({ top: 120 })
.onClick(() => {
this.animate = !this.animate;
// Step 3: Enable the implicit animation.
animateTo({ curve: curves.springMotion() }, () => {
// Step 4: Change the state variables in the closure to update the UI.
// You can write any logic that can change the UI, such as array adding and visibility control. The system detects the differences between the new UI and the previous UI and adds animations for the differences.
// The rotate attribute of component 1 is changed. Therefore, a rotate animation is added to component 1.
this.rotateValue = this.animate ? 90 : 0;
// The scale attribute of component 2 is changed. Therefore, a scale animation is added to component 2.
this.opacityValue = this.animate ? 0.6 : 1;
// The offset attribute of component 2 is changed. Therefore, an offset animation is added to component 2.
this.translateY = this.animate ? 100 : 0;
})
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}
```
![en-us_image_0000001599958466](figures/en-us_image_0000001599958466.gif)
## Using animation
Compared with the **animateTo** API, the [animation](../reference/arkui-ts/ts-animatorproperty.md) API does not need to use a closure. Just add it to the end of the target animatable attribute. Then the API automatically adds an attribute animation as long as it detects that the bound attribute is changed.
```ts
import curves from '@ohos.curves';
@Entry
@Component
struct AnimationDemo {
@State animate: boolean = false;
// Step 1: Declare related state variables.
@State rotateValue: number = 0; // Rotation angle of component 1.
@State translateY: number = 0; // Offset of component 2
@State color: Color = Color.White; // Font color of component 2.
@State opacityValue: number = 1; // Opacity of the parent component.
//Step 2: Set the declared state variables to the related animatable attribute APIs.
build() {
Column() {
Column() {
// Component 1
Text('ArkUI')
.fontWeight(FontWeight.Bold)
.fontSize(20)
.fontColor(Color.White)
.textAlign(TextAlign.Center)
.borderRadius(10)
.backgroundColor(0xf56c6c)
.rotate({ angle: this.rotateValue })
// Step 3: Enable the implicit animation. As the API call sequence of the component is from bottom to top, the API takes effect on the rotate attribute.
.animation({ curve: curves.springMotion(0.3, 1.0) })
.width(150)
.height(150)
// Component 2
Text('ArkUI')
.fontWeight(FontWeight.Bold)
.fontSize(20)
.fontColor(this.color)
// Step 3: Enable the implicit animation. As the API call sequence of the component is from bottom to top, the API takes effect on the fontColor attribute.
.animation({ curve: curves.springMotion(0.6, 1.2) })
.textAlign(TextAlign.Center)
.borderRadius(10)
.backgroundColor(0x67C23A)
.width(150)
.height(150)
.translate({ y: this.translateY })
// Step 3: Enable the implicit animation. As the API call sequence of the component is from bottom to top, the API takes effect on the translate attribute.
.animation({ curve: curves.springMotion(0.3, 0.6) })
}
.justifyContent(FlexAlign.Center)
.opacity(this.opacityValue)
// The animation API takes effect on the opacity attribute. This results in an opacity change of the parent component <Column>, which in turn results in an opacity change of its child components. Therefore, animations are added to the opacity attributes of <Column> and its child components.
.animation({ curve: curves.springMotion() })
// Step 4: Change the state variable to update the UI. The system detects the differences between the new UI and the previous UI and adds animations for the differences.
Button('Click')
.margin({ top: 120 })
.onClick(() => {
this.animate = !this.animate;
// The rotate attribute of component 1 is changed. Therefore, a rotate animation is added to component 1.
this.rotateValue = this.animate ? 90 : 0;
// The translate attribute of component 2 is changed. Therefore, a translate animation is added to component 2.
this.translateY = this.animate ? 100 : 0;
// The fontColor attribute of component 2 is changed. Therefore, a fontColor animation is added to component 2.
this.color = this.animate ? Color.Black : Color.White;
// The opacity attribute of the parent component <Column> is changed, which results in an opacity change of its child components. Therefore, opacity animations are added to <Column> and its child components.
this.opacityValue = this.animate ? 0.6 : 1;
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}
```
![en-us_image_0000001649279705](figures/en-us_image_0000001649279705.gif)
> **NOTE**
> - When an animation is applied to the position or size change of a component, as layout measurement is involved, performance overheads are high. To reduce performance overheads, use the [scale](../reference/arkui-ts/ts-universal-attributes-transformation.md) instead, whose value change does not involve layout measurement. This practice is applicable where the component location and size change continuously, for example, where the component size changes as a response to gestures.
>
> - Attribute animations should be applied to the components that are always present. For the components that may appear or disappear, use the [transition animation](arkts-transition-overview.md).
>
> - Avoid using callbacks invoked when an animation ends. Implicit animations are applied to states that have occurred. You do not need to process the end logic. If such callbacks are needed, be sure to handle the data management of continuous operations correctly.
# Attribute Animation Overview
An attribute API (attribute for short) defines a multitude of attributes, such as size, layout, and location, for controlling behavior of the owning component. Changes to some attributes (such as location) on the UI will cause UI re-render. You can animate the value change of an attribute from the start point to the end point. Attributes can be classified as animatable or non-animatable, depending on whether an animation can be applied to their value changes. Specifically, an animatable attribute meets the following requirements:
1. Its value changes can cause UI re-render. For example, the [enabled](../reference/arkui-ts/ts-universal-attributes-enable.md) attribute is used to control whether a component can respond to events such as clicks and touches. However, as its value change does not cause UI re-render, it does not qualify as an animatable attribute.
2. An animation can be applied to the transition between value changes. For example, the [focusable](../reference/arkui-ts/ts-universal-attributes-focus.md) attribute determines whether the current component can obtain focus. If the value of this attribute changes, the component should immediately switch to the end state to respond to user behavior, and no animation is appropriate here. Therefore, the attribute does not qualify as an animatable attribute.
**Attribute classification:**
- Animatable attributes:
- Built-in animatable attributes
| Category | Example |
| -------- | ---------------------------------------------- |
| Layout| Position, size, padding, margin, alignment, and weight.|
| Affine transformation| Translation, rotation, scaling, and anchor. |
| Background | Background color and blur. |
| Content | Font size, font color, image alignment, and blur. |
| Foreground | Foreground color. |
| Overlay | Overlay. |
| Appearance | Opacity, rounded corner, border, and shadow. |
| ... | ... |
- Custom animatable attributes: animatable attributes abstracted through the custom attribute animation mechanism.
- Non-animatable attributes: **zIndex** and **focusable**, among others.
Generally, an animatable attribute accepts only continuous parameters, which allows for using interpolation to fill in gaps between data points, so as to create a visual illusion of continuity. That said, whether the parameter type of an attribute can be interpolated is not a key factor that determines whether an animation can be applied to the attribute. For example, with regard to the [direction](../reference/arkui-ts/ts-universal-attributes-location.md) attribute, which sets the horizontal layout of an element, as it is animatable, ArkUI allows you to apply an animation to the transition between its value changes, though its parameter type is enum.
Animatable attributes include both built-in and custom attributes.
- Built-in animatable attributes: system-provided component attributes for changing the UI, for example, position, zoom, and blur attributes.
- [Custom animatable attributes](../quick-start/arkts-animatable-extend.md): attributes made animatable with the use of the [@AnimatableExtend](../quick-start/arkts-animatable-extend.md) decorator. You can abstract animatable attributes from the custom drawing content to control the drawing content of each frame, for example, customizing the volume icon. By defining custom animatable attributes, you can apply animations to some attributes that otherwise do not support animations in ArkUI.
# Blur Effect
Animation effects can add detail to your animations and create a sense of realism. For example, blur and shadow effects can lend a 3D look to objects and deliver a more engaging animation experience. ArkUI provides a diverse array of efficient APIs for you to develop exquisite and personalized effects. This topic covers the common blur, shadow, and color effects.
Blur effects add a sense of depth and allow for distinction of hierarchical relationship between elements.
| API | Description |
| ---------------------------------------- | ---------------------- |
| [backdropBlur](../reference/arkui-ts/ts-universal-attributes-image-effect.md) | Applies a background blur effect to the component. The input parameter is the blur radius.|
| [blur](../reference/arkui-ts/ts-universal-attributes-image-effect.md) | Applies a foreground blur effect to the component. The input parameter is the blur radius.|
| [backgroundBlurStyle](../reference/arkui-ts/ts-universal-attributes-background.md) | Applies a background blur effect to the component. The input parameter is the blur style.|
| [foregroundBlurStyle](../reference/arkui-ts/ts-universal-attributes-foreground-blur-style.md) | Applies a foreground blur effect to the component. The input parameter is the blur style.|
## Applying Background Blur with backdropBlur
```ts
@Entry
@Component
struct BlurEffectsExample {
build() {
Column({ space: 10 }) {
Text('backdropblur')
.width('90%')
.height('90%')
.fontSize(20)
.fontColor(Color.White)
.textAlign(TextAlign.Center)
.backdropBlur(10) // Apply background blur.
.backgroundImage($r('app.media.share'))
.backgroundImageSize({ width: 400, height: 300 })
}
.width('100%')
.height('50%')
.margin({ top: 20 })
}
}
```
![en-us_image_0000001599812870](figures/en-us_image_0000001599812870.png)
## Applying Foreground Blur with blur
```ts
@Entry
@Component
struct Index {
@State radius: number = 0;
@State text: string = '';
@State y: string = 'Finger not on the screen';
aboutToAppear() {
this.text = "Press a finger on the screen and slide up and down\n" + "Current finger position on the y-axis: " + this.y +
"\n" + "Blur radius:" + this.radius;
}
build() {
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceBetween }) {
Text(this.text)
.height(200)
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontFamily("cursive")
.fontStyle(FontStyle.Italic)
Image($r("app.media.wall"))
.blur(this.radius) // Apply foreground blur.
.height('100%')
.width("100%")
.objectFit(ImageFit.Cover)
}.height('100%')
.width("100%")
.onTouch((event: TouchEvent) => {
if (event.type === TouchType.Move) {
this.y = parseInt(event.touches[0].y.toString()).toString();
this.radius = parseInt(this.y) / 10; // Modify the blur radius based on the sliding distance.
}
if (event.type === TouchType.Up) {
this.radius = 0;
this.y = 'Finger off the screen';
}
this.text = "Press a finger on the screen and slide up and down\n" + "Current finger position on the y-axis: " + this.y +
"\n" + "Blur radius:" + this.radius;
})
}
}
```
![en-us_image_0000001599813588](figures/en-us_image_0000001599813588.gif)
## Applying Background Blur with backgroundBlurStyle
```ts
@Entry
@Component
struct BackDropBlurStyleDemo {
build() {
Grid() {
GridItem() {
Column() {
Column() {
Text ('Original')
.fontSize(20)
.fontColor(Color.White)
.textAlign(TextAlign.Center)
.width('100%')
.height('100%')
}
.height(100)
.aspectRatio(1)
.borderRadius(10)
.backgroundImage($r('app.media.share'))
Text ('Original')
.fontSize(12)
.fontColor(Color.Black)
}
.height('100%')
.justifyContent(FlexAlign.Start)
}
.width(200)
.height(200)
GridItem() {
Column() {
Column() {
Text('Thin')
.fontSize(20)
.fontColor(Color.White)
.textAlign(TextAlign.Center)
.width('100%')
.height('100%')
}
.height(100)
.aspectRatio(1)
.borderRadius(10)
.backgroundImage($r('app.media.share'))
// BlurStyle.Thin: Thin blur is applied.
// ThemeColorMode.LIGHT: The light color mode is used.
// AdaptiveColor.DEFAULT: Adaptive color mode is not used. The default color is used as the mask color.
// scale: blurredness of the background material. The default value is 1.
.backgroundBlurStyle(BlurStyle.Thin, {
colorMode: ThemeColorMode.LIGHT,
adaptiveColor: AdaptiveColor.DEFAULT,
scale: 0.1
})
Text('Thin')
.fontSize(12)
.fontColor(Color.Black)
}
.height('100%')
.justifyContent(FlexAlign.Start)
}
.width(200)
.height(200)
GridItem() {
Column() {
Column() {
Text('Regular')
.fontSize(20)
.fontColor(Color.White)
.textAlign(TextAlign.Center)
.width('100%')
.height('100%')
}
.height(100)
.aspectRatio(1)
.borderRadius(10)
.backgroundImage($r('app.media.share'))
.backgroundBlurStyle(BlurStyle.Regular, {
colorMode: ThemeColorMode.LIGHT,
adaptiveColor: AdaptiveColor.DEFAULT,
scale: 0.1
})
Text('Regular')
.fontSize(12)
.fontColor(Color.Black)
}
.height('100%')
.justifyContent(FlexAlign.Start)
}
.width(200)
.height(200)
GridItem() {
Column() {
Column() {
Text('Thick')
.fontSize(20)
.fontColor(Color.White)
.textAlign(TextAlign.Center)
.width('100%')
.height('100%')
}
.height(100)
.aspectRatio(1)
.borderRadius(10)
.backgroundImage($r('app.media.share'))
.backgroundBlurStyle(BlurStyle.Thick, {
colorMode: ThemeColorMode.LIGHT,
adaptiveColor: AdaptiveColor.DEFAULT,
scale: 0.1
})
Text('Thick')
.fontSize(12)
.fontColor(Color.Black)
}
.height('100%')
.justifyContent(FlexAlign.Start)
}
.width(200)
.height(200)
GridItem() {
Column() {
Column() {
Text('BACKGROUND_THIN')
.fontSize(12)
.fontColor(Color.White)
.textAlign(TextAlign.Center)
.width('100%')
.height('100%')
}
.height(100)
.aspectRatio(1)
.borderRadius(10)
.backgroundImage($r('app.media.share'))
.backgroundBlurStyle(BlurStyle.BACKGROUND_THIN, {
colorMode: ThemeColorMode.LIGHT,
adaptiveColor: AdaptiveColor.DEFAULT,
scale: 0.1
})
Text('BACKGROUND_THIN')
.fontSize(12)
.fontColor(Color.Black)
}
.height('100%')
.justifyContent(FlexAlign.Start)
}
.width(200)
.height(200)
GridItem() {
Column() {
Column() {
Text('BACKGROUND_REGULAR')
.fontSize(12)
.fontColor(Color.White)
.textAlign(TextAlign.Center)
.width('100%')
.height('100%')
}
.height(100)
.aspectRatio(1)
.borderRadius(10)
.backgroundImage($r('app.media.share'))
.backgroundBlurStyle(BlurStyle.BACKGROUND_REGULAR, {
colorMode: ThemeColorMode.LIGHT,
adaptiveColor: AdaptiveColor.DEFAULT,
scale: 0.1
})
Text('BACKGROUND_REGULAR')
.fontSize(12)
.fontColor(Color.Black)
}
.height('100%')
.justifyContent(FlexAlign.Start)
}
.width(200)
.height(200)
GridItem() {
Column() {
Column() {
Text('BACKGROUND_THICK')
.fontSize(12)
.fontColor(Color.White)
.textAlign(TextAlign.Center)
.width('100%')
.height('100%')
}
.height(100)
.aspectRatio(1)
.borderRadius(10)
.backgroundImage($r('app.media.share'))
.backgroundBlurStyle(BlurStyle.BACKGROUND_THICK, {
colorMode: ThemeColorMode.LIGHT,
adaptiveColor: AdaptiveColor.DEFAULT,
scale: 0.1
})
Text('BACKGROUND_THICK')
.fontSize(12)
.fontColor(Color.Black)
}
.height('100%')
.justifyContent(FlexAlign.Start)
}
.width(200)
.height(200)
GridItem() {
Column() {
Column() {
Text('BACKGROUND_ULTRA_THICK')
.fontSize(12)
.fontColor(Color.White)
.textAlign(TextAlign.Center)
.width('100%')
.height('100%')
}
.height(100)
.aspectRatio(1)
.borderRadius(10)
.backgroundImage($r('app.media.share'))
.backgroundBlurStyle(BlurStyle.BACKGROUND_ULTRA_THICK, {
colorMode: ThemeColorMode.LIGHT,
adaptiveColor: AdaptiveColor.DEFAULT,
scale: 0.1
})
Text('BACKGROUND_ULTRA_THICK')
.fontSize(12)
.fontColor(Color.Black)
}
.height('100%')
.justifyContent(FlexAlign.Start)
}
.width(200)
.height(200)
}
.columnsTemplate('1fr 1fr')
.rowsTemplate('1fr 1fr 1fr 1fr')
.width('100%')
.height('100%')
.margin({ top: 40 })
}
}
```
![en-us_image_0000001649455517](figures/en-us_image_0000001649455517.png)
## Applying Foreground Blur with foregroundBlurStyle
```ts
@Entry
@Component
struct ForegroundBlurStyleDemo {
build() {
Grid() {
GridItem() {
Column() {
Column() {
Text ('Original')
.fontSize(20)
.fontColor(Color.White)
.textAlign(TextAlign.Center)
.width('100%')
.height('100%')
}
.height(100)
.aspectRatio(1)
.borderRadius(10)
.backgroundImage($r('app.media.share'))
Text ('Original')
.fontSize(12)
.fontColor(Color.Black)
}
.height('100%')
.justifyContent(FlexAlign.Start)
}
.width(200)
.height(200)
GridItem() {
Column() {
Column() {
Text('Thin')
.fontSize(20)
.fontColor(Color.White)
.textAlign(TextAlign.Center)
.width('100%')
.height('100%')
}
.height(100)
.aspectRatio(1)
.borderRadius(10)
.backgroundImage($r('app.media.share'))
// BlurStyle.Thin: Thin blur is applied.
// ThemeColorMode.LIGHT: The light color mode is used.
// AdaptiveColor.DEFAULT: Adaptive color mode is not used. The default color is used as the mask color.
// scale: blurredness of the background material. The default value is 1.
.foregroundBlurStyle(BlurStyle.Thin, {
colorMode: ThemeColorMode.LIGHT,
adaptiveColor: AdaptiveColor.DEFAULT,
scale: 0.1
})
Text('Thin')
.fontSize(12)
.fontColor(Color.Black)
}
.height('100%')
.justifyContent(FlexAlign.Start)
}
.width(200)
.height(200)
GridItem() {
Column() {
Column() {
Text('Regular')
.fontSize(20)
.fontColor(Color.White)
.textAlign(TextAlign.Center)
.width('100%')
.height('100%')
}
.height(100)
.aspectRatio(1)
.borderRadius(10)
.backgroundImage($r('app.media.share'))
.foregroundBlurStyle(BlurStyle.Regular, {
colorMode: ThemeColorMode.LIGHT,
adaptiveColor: AdaptiveColor.DEFAULT,
scale: 0.1
})
Text('Regular')
.fontSize(12)
.fontColor(Color.Black)
}
.height('100%')
.justifyContent(FlexAlign.Start)
}
.width(200)
.height(200)
GridItem() {
Column() {
Column() {
Text('Thick')
.fontSize(20)
.fontColor(Color.White)
.textAlign(TextAlign.Center)
.width('100%')
.height('100%')
}
.height(100)
.aspectRatio(1)
.borderRadius(10)
.backgroundImage($r('app.media.share'))
.foregroundBlurStyle(BlurStyle.Thick, {
colorMode: ThemeColorMode.LIGHT,
adaptiveColor: AdaptiveColor.DEFAULT,
scale: 0.1
})
Text('Thick')
.fontSize(12)
.fontColor(Color.Black)
}
.height('100%')
.justifyContent(FlexAlign.Start)
}
.width(200)
.height(200)
GridItem() {
Column() {
Column() {
Text('BACKGROUND_THIN')
.fontSize(12)
.fontColor(Color.White)
.textAlign(TextAlign.Center)
.width('100%')
.height('100%')
}
.height(100)
.aspectRatio(1)
.borderRadius(10)
.backgroundImage($r('app.media.share'))
.foregroundBlurStyle(BlurStyle.BACKGROUND_THIN, {
colorMode: ThemeColorMode.LIGHT,
adaptiveColor: AdaptiveColor.DEFAULT,
scale: 0.1
})
Text('BACKGROUND_THIN')
.fontSize(12)
.fontColor(Color.Black)
}
.height('100%')
.justifyContent(FlexAlign.Start)
}
.width(200)
.height(200)
GridItem() {
Column() {
Column() {
Text('BACKGROUND_REGULAR')
.fontSize(12)
.fontColor(Color.White)
.textAlign(TextAlign.Center)
.width('100%')
.height('100%')
}
.height(100)
.aspectRatio(1)
.borderRadius(10)
.backgroundImage($r('app.media.share'))
.foregroundBlurStyle(BlurStyle.BACKGROUND_REGULAR, {
colorMode: ThemeColorMode.LIGHT,
adaptiveColor: AdaptiveColor.DEFAULT,
scale: 0.1
})
Text('BACKGROUND_REGULAR')
.fontSize(12)
.fontColor(Color.Black)
}
.height('100%')
.justifyContent(FlexAlign.Start)
}
.width(200)
.height(200)
GridItem() {
Column() {
Column() {
Text('BACKGROUND_THICK')
.fontSize(12)
.fontColor(Color.White)
.textAlign(TextAlign.Center)
.width('100%')
.height('100%')
}
.height(100)
.aspectRatio(1)
.borderRadius(10)
.backgroundImage($r('app.media.share'))
.foregroundBlurStyle(BlurStyle.BACKGROUND_THICK, {
colorMode: ThemeColorMode.LIGHT,
adaptiveColor: AdaptiveColor.DEFAULT,
scale: 0.1
})
Text('BACKGROUND_THICK')
.fontSize(12)
.fontColor(Color.Black)
}
.height('100%')
.justifyContent(FlexAlign.Start)
}
.width(200)
.height(200)
GridItem() {
Column() {
Column() {
Text('BACKGROUND_ULTRA_THICK')
.fontSize(12)
.fontColor(Color.White)
.textAlign(TextAlign.Center)
.width('100%')
.height('100%')
}
.height(100)
.aspectRatio(1)
.borderRadius(10)
.backgroundImage($r('app.media.share'))
.foregroundBlurStyle(BlurStyle.BACKGROUND_ULTRA_THICK, {
colorMode: ThemeColorMode.LIGHT,
adaptiveColor: AdaptiveColor.DEFAULT,
scale: 0.1
})
Text('BACKGROUND_ULTRA_THICK')
.fontSize(12)
.fontColor(Color.Black)
}
.height('100%')
.justifyContent(FlexAlign.Start)
}
.width(200)
.height(200)
}
.columnsTemplate('1fr 1fr')
.rowsTemplate('1fr 1fr 1fr 1fr')
.width('100%')
.height('100%')
.margin({ top: 40 })
}
}
```
![en-us_image_0000001599658168](figures/en-us_image_0000001599658168.png)
# Color Effect
## APIs
You can use the color gradient APIs to apply a background color gradient effect to a component.
| API| Description|
| -------- | -------- |
| [linearGradient](../reference/arkui-ts/ts-universal-attributes-gradient-color.md) | Applies a linear gradient to the component.|
| [sweepGradient](../reference/arkui-ts/ts-universal-attributes-gradient-color.md) | Applies a sweep gradient effect to the component.|
| [radialGradient](../reference/arkui-ts/ts-universal-attributes-gradient-color.md) | Applies a radial gradient effect to the component.|
## Applying Linear Gradient Effect
```ts
@Entry
@Component
struct LinearGradientDemo {
build() {
Grid() {
GridItem() {
Column() {
Text('angle: 180')
.fontSize(15)
}
.width(100)
.height(100)
.justifyContent(FlexAlign.Center)
.borderRadius(10)
.linearGradient({
// A positive value indicates a clockwise rotation from the origin, (0, 0). The default value of the linear gradient start angle is 180°.
colors: [
[0xf56c6c, 0.0], // Color and weight of color stop 1, corresponding to the start position of the component in the 180° direction.
[0xffffff, 1.0], // Color and weight of color stop 2, corresponding to the end position of the component in the 180° direction.
]
})
}
GridItem() {
Column() {
Text('angle: 45')
.fontSize(15)
}
.width(100)
.height(100)
.justifyContent(FlexAlign.Center)
.borderRadius(10)
.linearGradient({
angle: 45, // Set the linear gradient start angle to 45°.
colors: [
[0xf56c6c, 0.0],
[0xffffff, 1.0],
]
})
}
GridItem() {
Column() {
Text('repeat: true')
.fontSize(15)
}
.width(100)
.height(100)
.justifyContent(FlexAlign.Center)
.borderRadius(10)
.linearGradient({
repeating: true, // Repeat the gradients of the area from 0 to 0.3 in the area from 0.3 to 1.0.
colors: [
[0xf56c6c, 0.0],
[0xE6A23C, .3],
]
})
}
GridItem() {
Column() {
Text('repeat: fasle')
.fontSize(15)
}
.width(100)
.height(100)
.justifyContent(FlexAlign.Center)
.borderRadius(10)
.linearGradient({
colors: [
[0xf56c6c, 0.0], // As repeating is not specified, the default value false is used. In this case, only the area from 0 to 0.3 in the component have the color gradient effect.
[0xE6A23C, .3],
]
})
}
}
.columnsGap(10)
.rowsGap(10)
.columnsTemplate('1fr 1fr')
.rowsTemplate('1fr 1fr 1fr')
.width('100%')
.height('100%')
}
}
```
![en-us_image_0000001641176829](figures/en-us_image_0000001641176829.png)
## Applying Sweep Gradient Effect
```ts
@Entry
@Component
struct SweepGradientDemo {
build() {
Grid() {
GridItem() {
Column() {
Text('center: 50')
.fontSize(15)
}
.width(100)
.height(100)
.justifyContent(FlexAlign.Center)
.borderRadius(10)
.sweepGradient({
center: [50, 50], // Center point of the sweep gradient.
start: 0, // Start point of the sweep gradient.
end: 360, // End point of the sweep gradient.
repeating: true, // The gradients are repeated.
colors: [
// Based on the center point, start point, and end point settings,
// the color changes from color stop 1 to color stop 2 in the area within angles from 0 to 0.125;
// the color changes from color stop 2 to color stop 3 in the area within angles from 0.125 to 0.25;
// the color gradients of the area within angles from 0 to 0.25 are repeated in the area within angles from 0.25 to 1.
[0xf56c6c, 0], // Color and weight of color stop 1. The corresponding angle is 0° (0 x 360°), and the corner is the center point.
[0xffffff, 0.125], // Color and weight of color stop 2.
[0x409EFF, 0.25] // Color and weight of color stop 3.
]
})
}
GridItem() {
Column() {
Text('center: 0')
.fontSize(15)
}
.width(100)
.height(100)
.justifyContent(FlexAlign.Center)
.borderRadius(10)
.sweepGradient({
center: [0, 0], // Center point of the sweep gradient, which is the coordinate of the upper left corner of the component in this example.
start: 0,
end: 360,
repeating: true,
colors: [
// In the current component, the sweep gradient center is the upper left corner of the component. Therefore, the angle range from color stop 1 to color stop 3 can cover the entire component.
[0xf56c6c, 0], // Color and weight of color stop 1. The corresponding angle is 0° (0 x 360°)
[0xffffff, 0.125], // Color and weight of color stop 2. The corresponding angle is 45° (0.125 x 360°).
[0x409EFF, 0.25] // Color and weight of color stop 3. The corresponding angle is 90° (0.25 x 360°).
]
})
}
GridItem() {
Column() {
Text('repeat: true')
.fontSize(15)
}
.width(100)
.height(100)
.justifyContent(FlexAlign.Center)
.borderRadius(10)
.sweepGradient({
center: [50, 50],
start: 0,
end: 360,
repeating: true,
colors: [
[0xf56c6c, 0],
[0xffffff, 0.125],
[0x409EFF, 0.25]
]
})
}
GridItem() {
Column() {
Text('repeat: false')
.fontSize(15)
}
.width(100)
.height(100)
.justifyContent(FlexAlign.Center)
.borderRadius(10)
.sweepGradient({
center: [50, 50],
start: 0,
end: 360,
repeating: false, // The color gradient effect is generated only within the coverage of the color stop angles.
colors: [
[0xf56c6c, 0],
[0xffffff, 0.125],
[0x409EFF, 0.25]
]
})
}
}
.columnsGap(10)
.rowsGap(10)
.columnsTemplate('1fr 1fr')
.rowsTemplate('1fr 1fr 1fr')
.width('100%')
.height(437)
}
}
```
![en-us_image_0000001641177073](figures/en-us_image_0000001641177073.png)
## Applying Radial Gradient Effect
```ts
@Entry
@Component
struct radialGradientDemo {
build() {
Grid() {
GridItem() {
Column() {
Text('center: 50')
.fontSize(15)
}
.width(100)
.height(100)
.justifyContent(FlexAlign.Center)
.borderRadius(10)
.radialGradient({
center: [50, 50], // Center point of the radial gradient.
radius: 100, // Radius of the radial gradient.
repeating: true, // The gradients are repeated outside the specified range to fill the entire component.
colors: [
// With [50, 50] as the center point, the gradient changes from color stop 1 to color stop 2 within the range of radius 0 to 12.5;
// the gradient changes from color stop 2 to color stop 3 within the range of radius 12.5 to 25;
// the gradients in the range of radius 0 to 25 are repeated to fill the entire component.
[0xf56c6c, 0], // Color and weight of color stop 1. The corresponding radius is 0 (0 x 100).
[0xffffff, 0.125], // Color and weight of color stop 2. The corresponding radius is 12.5 (0.125 x 100).
[0x409EFF, 0.25] // Color and weight of color stop 3. The corresponding radius is 25 (0.25 x 100).
]
})
}
GridItem() {
Column() {
Text('center: 0')
.fontSize(15)
}
.width(100)
.height(100)
.justifyContent(FlexAlign.Center)
.borderRadius(10)
.radialGradient({
center: [0, 0], // Center point of the radial gradient, which is the coordinate of the upper left corner of the component in this example.
radius: 100,
repeating: true,
colors: [
[0xf56c6c, 0],
[0xffffff, 0.125],
[0x409EFF, 0.25]
]
})
}
GridItem() {
Column() {
Text('repeat: true')
.fontSize(15)
}
.width(100)
.height(100)
.justifyContent(FlexAlign.Center)
.borderRadius(10)
.radialGradient({
center: [50, 50],
radius: 100,
repeating: true,
colors: [
[0xf56c6c, 0],
[0xffffff, 0.125],
[0x409EFF, 0.25]
]
})
}
GridItem() {
Column() {
Text('repeat: false')
.fontSize(15)
}
.width(100)
.height(100)
.justifyContent(FlexAlign.Center)
.borderRadius(10)
.radialGradient({
center: [50, 50],
radius: 100,
repeating: false, // The gradients are not repeated.
colors: [
[0xf56c6c, 0],
[0xffffff, 0.125],
[0x409EFF, 0.25]
]
})
}
}
.columnsGap(10)
.rowsGap(10)
.columnsTemplate('1fr 1fr')
.rowsTemplate('1fr 1fr 1fr')
.width('100%')
.height('100%')
}
}
```
![en-us_image_0000001592904050](figures/en-us_image_0000001592904050.png)
# Component Animation
In addition to universal attribute animation and transition animation APIs, ArkUI provides default animation effects for certain components, for example, the swipe effect for the [\<List>](../reference/arkui-ts/ts-container-list.md) component and the click effect of the [\<Button>](../reference/arkui-ts/ts-basic-components-button.md#button) component. Based on these default animation effects, you can apply custom animations to the child components through the attribute animation and transition animation APIs.
## Using Default Component Animation
The default animation of a component exhibits the following features:
- Indicate the current state of the component. For example, after the user clicks a **\<Button>** component, the component turns gray, indicating that it is selected.
- Make UI interactions more intuitive and pleasurable.
- Reduce development workload, as the APIs are readily available.
For more effects, see [Component Overview](../reference/arkui-ts/ts-components-summary.md).
Below is the sample code and effect:
```ts
@Entry
@Component
struct ComponentDemo {
build() {
Row() {
Checkbox({ name: 'checkbox1', group: 'checkboxGroup' })
.select(true)
.selectedColor(0xed6f21)
.size({ width: 50, height: 50 })
Checkbox({ name: 'checkbox2', group: 'checkboxGroup' })
.select(false)
.selectedColor(0x39a2db)
.size({ width: 50, height: 50 })
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}
```
![en-us_image_0000001649338585](figures/en-us_image_0000001649338585.gif)
## Customizing Component Animation
Some components allow for animation customization for their child components through the [attribute animation](arkts-attribute-animation-overview.md) and [transition animation](arkts-transition-overview.md) APIs. For example, in the [\<Scroll>](../reference/arkui-ts/ts-container-scroll.md) component, you can customize the animation effect for when scrolling through its child components.
- For a scroll or click gesture, you can implement various effects by changing affine attributes of the child component.
- To customize the animation for a scroll , you can add a listener to listen for scroll distance in the **onScroll** callback and calculate the affine attribute of each component. You can also define gestures, monitor positions through the gestures, and manually call **ScrollTo** to change the scrolled-to position.
- Fine-tune the final scrolled-to position in the **onScrollStop** callback or gesture end callback.
Below is the sample code and effect for customizing the scroll effect of the child components of the **\<Scroll>** component:
```ts
import curves from '@ohos.curves';
import window from '@ohos.window';
import display from '@ohos.display';
import mediaquery from '@ohos.mediaquery';
/**
* Encapsulates the WindowManager class.
*/
export class WindowManager {
private static instance: WindowManager = null;
private displayInfo: display.Display = null;
private orientationListener = mediaquery.matchMediaSync('(orientation: landscape)');
private portraitFunc = null;
constructor() {
this.portraitFunc = this.onPortrait.bind(this);
this.orientationListener.on('change', this.portraitFunc)
this.loadDisplayInfo()
}
/**
* Sets the main window.
* @param win Indicates the current application window.
*/
setMainWin(win: window.Window) {
if (win == null) {
return
}
globalThis.mainWin = win;
win.on("windowSizeChange", (data: window.Size) => {
if (globalThis.mainWindowSize == undefined || globalThis.mainWindowSize == null) {
globalThis.mainWindowSize = data;
} else {
if (globalThis.mainWindowSize.width == data.width && globalThis.mainWindowSize.height == data.height) {
return
}
globalThis.mainWindowSize = data;
}
let winWidth = this.getMainWindowWidth();
AppStorage.SetOrCreate<number>('mainWinWidth', winWidth)
let winHeight = this.getMainWindowHeight();
AppStorage.SetOrCreate<number>('mainWinHeight', winHeight)
globalThis.context.eventHub.emit("windowSizeChange", winWidth, winHeight)
})
}
static getInstance(): WindowManager {
if (this.instance == null) {
this.instance = new WindowManager();
}
return this.instance
}
private onPortrait(mediaQueryResult: mediaquery.MediaQueryResult) {
if (mediaQueryResult.matches == AppStorage.Get<boolean>('isLandscape')) {
return
}
AppStorage.SetOrCreate<boolean>('isLandscape', mediaQueryResult.matches)
this.loadDisplayInfo()
}
/**
* Changes the screen orientation.
* @param ori Indicates the orientation.
*/
changeOrientation(ori: window.Orientation) {
if (globalThis.mainWin != null) {
globalThis.mainWin.setPreferredOrientation(ori)
}
}
private loadDisplayInfo() {
this.displayInfo = display.getDefaultDisplaySync()
AppStorage.SetOrCreate<number>('displayWidth', this.getDisplayWidth())
AppStorage.SetOrCreate<number>('displayHeight', this.getDisplayHeight())
}
/**
* Obtains the width of the main window, in vp.
*/
getMainWindowWidth(): number {
return globalThis.mainWindowSize != null ? px2vp(globalThis.mainWindowSize.width) : 0
}
/**
* Obtains the height of the main window, in vp.
*/
getMainWindowHeight(): number {
return globalThis.mainWindowSize != null ? px2vp(globalThis.mainWindowSize.height) : 0
}
/**
* Obtains the screen width, in vp.
*/
getDisplayWidth(): number {
return this.displayInfo != null ? px2vp(this.displayInfo.width) : 0
}
/**
* Obtains the screen height, in vp.
*/
getDisplayHeight(): number {
return this.displayInfo != null ? px2vp(this.displayInfo.height) : 0
}
/**
* Releases resources.
*/
release() {
if (this.orientationListener) {
this.orientationListener.off('change', this.portraitFunc)
}
if (globalThis.mainWin != null) {
globalThis.mainWin.off('windowSizeChange')
}
WindowManager.instance = null;
}
}
/**
* Encapsulates the TaskData class.
*/
export class TaskData {
bgColor: Color | string | Resource = Color.White;
index: number = 0;
taskInfo: string = 'music';
constructor(bgColor: Color | string | Resource, index: number, taskInfo: string) {
this.bgColor = bgColor;
this.index = index;
this.taskInfo = taskInfo;
}
}
export const taskDataArr: Array<TaskData> =
[
new TaskData(0xFA8072, 0, 'music'),
new TaskData(0xF4A460, 1, 'mall'),
new TaskData(0xFFFACD, 2, 'photos'),
new TaskData(0x98FB98, 3, 'setting'),
new TaskData(0x7FFFD4, 4, 'call'),
new TaskData(0x87CEFA, 5, 'music'),
new TaskData(0x7B68EE, 6, 'mall'),
new TaskData(0x909399, 7, 'photos'),
new TaskData(0x888888, 8, 'setting'),
new TaskData(0xFFC0CB, 9, 'call'),
new TaskData(0xFFC0CB, 10, 'music'),
new TaskData(0x888888, 11, 'mall'),
new TaskData(0x909399, 12, 'photos'),
new TaskData(0x7B68EE, 13, 'setting'),
new TaskData(0x87CEFA, 14, 'call'),
new TaskData(0x7FFFD4, 15, 'music'),
new TaskData(0x98FB98, 16, 'mall'),
new TaskData(0xFFFACD, 17, 'photos'),
new TaskData(0xF4A460, 18, 'setting'),
new TaskData(0xFA8072, 19, 'call'),
];
@Entry
@Component
export struct TaskSwitchMainPage {
displayWidth: number = WindowManager.getInstance().getDisplayWidth();
scroller: Scroller = new Scroller();
bgImage: Resource = $r('app.media.share');
cardSpace: number = 0; // Widget spacing
cardWidth: number = this.displayWidth / 2 - this.cardSpace / 2; // Widget width
cardHeight: number = 400; // Widget height
cardPosition: Array<number> = []; // Initial position of the widget
clickIndex: boolean = false;
@State taskViewOffsetX: number = 0;
@State cardOffset: number = this.displayWidth / 4;
lastCardOffset: number = this.cardOffset;
startTime: number
// Initial position of each widget
aboutToAppear() {
for (let i = 0; i < taskDataArr.length; i++) {
this.cardPosition[i] = i * (this.cardWidth + this.cardSpace);
}
}
// Position of each widget
getProgress(index: number): number {
let progress = (this.cardOffset + this.cardPosition[index] - this.taskViewOffsetX + this.cardWidth / 2) / this.displayWidth;
return progress
}
build() {
Stack({ alignContent: Alignment.Bottom }) {
// Background
Column()
.width('100%')
.height('100%')
.backgroundColor(0xF0F0F0)
// <Scroll> component
Scroll(this.scroller) {
Row({ space: this.cardSpace }) {
ForEach(taskDataArr, (item, index) => {
Column()
.width(this.cardWidth)
.height(this.cardHeight)
.backgroundColor(item.bgColor)
.borderStyle(BorderStyle.Solid)
.borderWidth(1)
.borderColor(0xAFEEEE)
.borderRadius(15)
// Calculate the affine attributes of child components.
.scale((this.getProgress(index) >= 0.4 && this.getProgress(index) <= 0.6) ?
{
x: 1.1 - Math.abs(0.5 - this.getProgress(index)),
y: 1.1 - Math.abs(0.5 - this.getProgress(index))
} :
{ x: 1, y: 1 })
.animation({ curve: Curve.Smooth })
// Apply a pan animation.
.translate({ x: this.cardOffset })
.animation({ curve: curves.springMotion() })
.zIndex((this.getProgress(index) >= 0.4 && this.getProgress(index) <= 0.6) ? 2 : 1)
}, item => item)
}
.width((this.cardWidth + this.cardSpace) * (taskDataArr.length + 1))
.height('100%')
}
.gesture(
GestureGroup(GestureMode.Parallel,
PanGesture({ direction: PanDirection.Horizontal, distance: 5 })
.onActionStart((event: GestureEvent) => {
this.startTime = event.timestamp;
})
.onActionUpdate((event: GestureEvent) => {
this.cardOffset = this.lastCardOffset + event.offsetX;
})
.onActionEnd((event: GestureEvent) => {
let time = event.timestamp - this.startTime;
let speed = event.offsetX / (time / 1000000000);
let moveX = Math.pow(speed, 2) / 7000 * (speed > 0 ? 1 : -1);
this.cardOffset += moveX;
// When panning left to a position beyond the rightmost position
let cardOffsetMax = -(taskDataArr.length - 1) * (this.displayWidth / 2);
if (this.cardOffset < cardOffsetMax) {
this.cardOffset = cardOffsetMax;
}
// When panning right to a position beyond the rightmost position
if (this.cardOffset > this.displayWidth / 4) {
this.cardOffset = this.displayWidth / 4;
}
// Processing when the pan distance is less than the minimum distance
let remainMargin = this.cardOffset % (this.displayWidth / 2);
if (remainMargin < 0) {
remainMargin = this.cardOffset % (this.displayWidth / 2) + this.displayWidth / 2;
}
if (remainMargin <= this.displayWidth / 4) {
this.cardOffset += this.displayWidth / 4 - remainMargin;
} else {
this.cardOffset -= this.displayWidth / 4 - (this.displayWidth / 2 - remainMargin);
}
// Record the pan offset.
this.lastCardOffset = this.cardOffset;
})
), GestureMask.IgnoreInternal)
.scrollable(ScrollDirection.Horizontal)
.scrollBar(BarState.Off)
// Move to the beginning and end positions.
Button('Move to first/last')
.backgroundColor(0x888888)
.margin({ bottom: 30 })
.onClick(() => {
this.clickIndex = !this.clickIndex;
if (this.clickIndex) {
this.cardOffset = this.displayWidth / 4;
} else {
this.cardOffset = this.displayWidth / 4 - (taskDataArr.length - 1) * this.displayWidth / 2;
}
this.lastCardOffset = this.cardOffset;
})
}
.width('100%')
.height('100%')
}
}
```
![en-us_image_0000001599808406](figures/en-us_image_0000001599808406.gif)
# Custom Attribute Animation
The attribute animation is an illusion of movement created on the UI when the value of an animatable attribute changes over time. It is implemented by setting continuous value changes of an attribute to the attribute API that can cause the UI re-render.
ArkUI provides the [@AnimatableExtend](../quick-start/arkts-animatable-extend.md) decorator for customizing animatable attribute APIs, which accepts only parameters of the number type or a custom type that implements [AnimtableArithmetic<T>](../quick-start/arkts-animatable-extend.md) to deliver continuous value changes. During execution of **animateTo** or **animation** with a custom animatable attribute API and the animatable data type, the frame-by-frame callback is used to change the value of a non-animatable attribute so that an animation effect can be applied to the attribute.
## Animating Font Size Changes with Number Data Type and \@AnimatableExtend Decorator
```ts
// Step 1: Use the @AnimatableExtend decorator to customize an animatable attribute API.
@AnimatableExtend(Text) function animatableFontSize(size: number) {
.fontSize(size) // Invoke the system attribute API.
}
@Entry
@Component
struct AnimatablePropertyExample {
@State fontSize: number = 20;
build() {
Column() {
Text("AnimatableProperty")
.animatableFontSize(this.fontSize) // Step 2: Set the custom animatable attribute API on the component.
.animation({ duration: 1000, curve: "ease" }) // Step 3: Bind an animation to the custom animatable attribute API.
Button("Play")
.onClick(() => {
this.fontSize = this.fontSize == 20 ? 36 : 20; // Step 4: Change the value of the custom animatable attribute to generate an animation.
})
}.width("100%")
.padding(10)
}
}
```
![en-us_image_0000001600119626](figures/en-us_image_0000001600119626.gif)
## Animating Polyline Changes with Custom Data Type and \@AnimatableExtend Decorator
```ts
declare type Point = [x: number, y: number];
// Define the parameter type of the animatable attribute API and implement the addition, subtraction, multiplication, and equivalence judgment functions in the AnimtableArithmetic<T> API.
class PointClass extends Array<number> {
constructor(value: Point) {
super(value[0], value[1])
}
add(rhs: PointClass): PointClass {
let result = new Array<number>() as Point;
for (let i = 0; i < 2; i++) {
result.push(rhs[i] + this[i])
}
return new PointClass(result);
}
subtract(rhs: PointClass): PointClass {
let result = new Array<number>() as Point;
for (let i = 0; i < 2; i++) {
result.push(this[i] - rhs[i]);
}
return new PointClass(result);
}
multiply(scale: number): PointClass {
let result = new Array<number>() as Point;
for (let i = 0; i < 2; i++) {
result.push(this[i] * scale)
}
return new PointClass(result);
}
}
// Define the parameter type of the animatable attribute API and implement the addition, subtraction, multiplication, and equivalence judgment functions in the AnimtableArithmetic<T> API.
// Template T supports nested implementation of the AnimtableArithmetic<T> type.
class PointVector extends Array<PointClass> implements AnimatableArithmetic<Array<Point>> {
constructor(initialValue: Array<Point>) {
super();
if (initialValue.length) {
initialValue.forEach(p => this.push(new PointClass(p)))
}
}
// implement the IAnimatableArithmetic interface
plus(rhs: PointVector): PointVector {
let result = new PointVector([]);
const len = Math.min(this.length, rhs.length)
for (let i = 0; i < len; i++) {
result.push(this[i].add(rhs[i]))
}
return result;
}
subtract(rhs: PointVector): PointVector {
let result = new PointVector([]);
const len = Math.min(this.length, rhs.length)
for (let i = 0; i < len; i++) {
result.push(this[i].subtract(rhs[i]))
}
return result;
}
multiply(scale: number): PointVector {
let result = new PointVector([]);
for (let i = 0; i < this.length; i++) {
result.push(this[i].multiply(scale))
}
return result;
}
equals(rhs: PointVector): boolean {
if (this.length !== rhs.length) {
return false;
}
for (let index = 0, size = this.length; index < size; ++index) {
if (this[index][0] !== rhs[index][0] || this[index][1] !== rhs[index][1]) {
return false;
}
}
return true;
}
}
function randomInt(min, max) {
return Math.floor(Math.random() * (max - min) + min);
}
// Define a custom animatable attribute API.
@AnimatableExtend(Polyline) function animatablePoints(points: PointVector) {
.points(points)
}
// Define a custom animatable attribute API.
@AnimatableExtend(Text) function animatableFontSize(size: number) {
.fontSize(size)
}
@Entry
@Component
struct AnimatedShape {
@State pointVec1: PointVector = new PointVector([
[50, randomInt(0, 200)],
[100, randomInt(0, 200)],
[150, randomInt(0, 200)],
[250, randomInt(0, 200)],
[350, randomInt(0, 200)]
]);
@State pointVec2: PointVector = new PointVector([
[70, randomInt(0, 200)],
[120, randomInt(0, 200)],
[180, randomInt(0, 200)],
[220, randomInt(0, 200)],
[320, randomInt(0, 200)]
]);
@State color: Color = Color.Green;
@State fontSize: number = 20.0;
@State polyline1Vec: PointVector = this.pointVec1;
@State polyline2Vec: PointVector = this.pointVec2;
build() {
Column() {
Text("AnimatableExtend test")
.width(400)
.height(30)
.margin(1)
.fontSize(25)
.textAlign(TextAlign.Center)
.backgroundColor("#ffee44")
.border({ width: '1vp', color: "#88ff00", radius: 20, style: BorderStyle.Solid })
Polyline()
.width(400)
.height(240)
.backgroundColor("#eeaacc")
.fill(this.color)
.stroke(Color.Red)
.animatablePoints(this.polyline1Vec)
.animation({ duration: 2000, delay: 0, curve: Curve.Ease })
Polyline()
.width(400)
.height(240)
.backgroundColor("#bbffcc")
.fill(this.color)
.stroke(Color.Red)
.animatablePoints(this.polyline2Vec)
.animation({ duration: 2000, delay: 0, curve: Curve.Ease })
Text("Animatable Fontsize")
.animatableFontSize(this.fontSize)
.animation({ duration: 2000, delay: 0, curve: Curve.Ease })
.width(400)
.height(150)
.margin(5)
.textAlign(TextAlign.Center)
.backgroundColor("#ffddcc")
.border({ width: '2vp', color: "#88ff00", radius: 20, style: BorderStyle.Solid })
.onClick(() => {
console.log("Text onClick()")
})
Row() {
Button("Polyline1 default")
.width(100).height(60)
.margin({ left: 5, right: 5 })
.onClick(() => {
if (this.polyline1Vec.equals(this.pointVec1)) {
this.polyline1Vec = this.pointVec2;
} else {
this.polyline1Vec = this.pointVec1;
}
})
Button("Polyline2 ANIM")
.width(100).height(60)
.onClick(() => {
if (this.polyline2Vec.equals(this.pointVec1)) {
this.polyline2Vec = this.pointVec2;
} else {
this.polyline2Vec = this.pointVec1;
}
})
Button("FontSize")
.width(100).height(60)
.margin({ left: 5, right: 5 })
.onClick(() => {
this.fontSize = (this.fontSize == 20.0) ? 40.0 : 20.0;
})
}
.alignItems(VerticalAlign.Center)
.margin(5)
}
.width('100%')
.alignItems(HorizontalAlign.Center)
}
}
```
![en-us_image_0000001592669598](figures/en-us_image_0000001592669598.gif)
# Enter/Exit Transition
You can use [transition](../reference/arkui-ts/ts-transition-animation-component.md), a basic component transition API, to animate the appearance and disappearance of a component. You can even use it with [TransitionEffect](../reference/arkui-ts/ts-transition-animation-component.md#transitioneffect10) to up your animation game.
**Table 1** Transition effect APIs
| API | Description| Animation|
| -------- | -------- | -------- |
| IDENTITY | Disables the transition effect.| None.|
| OPACITY | Applies the default opacity transition effect.| The component appears by changing the opacity from 0 to 1 and disappears by changing the opacity from 1 to 0.|
| SLIDE | Applies a sliding transition effect.| The component appears by sliding in from the left edge of the window and disappears by sliding out from the right edge of the window.|
| translate | Applies a translation transition effect.| The component appears by moving from the position set by the **translate** API to the default position (value **0**), and disappears by moving from the default position (value **0**) to the position set by the **translate** API.|
| rotate | Applies a rotation transition effect.| The component appears by rotating from the position set by the **rotate** API to the default position (value **0**), and disappears by rotating from the default position (value **0**) to the position set by the **rotate** API.|
| opacity | Applies an opacity transition effect.| The component appears by changing the opacity from the set value to **1** (default value) and disappears by changing the opacity from **1** to the set value.|
| move | Applies a transition effect by specifying which edge the component slides in and out of through [TransitionEdge](../reference/arkui-ts/ts-appendix-enums.md#transitionedge10).| The component appears by sliding in from the edge specified by **TransitionEdge** and disappears by sliding out of the same edge.|
| asymmetric | Applies an asymmetric transition effect.<br>**appear**: transition effect for appearance.<br>**disappear**: transition effect for disappearance.| The component appears by applying the transition effect specified by **appear** and disappears by applying the transition effect specified by **disappear**.|
| combine | Combines with other transition effects.| The component appears and disappears by combing with other transition effects.|
| animation | Defines the animation settings for the transition effect.<br>- If animation settings are not specified here, the animation settings of **animateTo** will be used.<br>- Animation settings cannot be configured through the **animation** API of the component.<br>- The **onFinish** callback of the **animation** parameter in **TransitionEffect** does not take effect. | The API call sequence is from top to bottom. This means that the **animation** settings of **TransitionEffect** at the upper level also take effect on **TransitionEffect** at the lower level .|
1. Create a **TransitionEffect** object.
```ts
// The component appears by applying all transition effects for appearance and disappears by applying all transition effects for disappearance.
// Define the animation settings for each transition effect.
private effect: TransitionEffect =
TransitionEffect.OPACITY // Apply an opacity transition effect. As the animation API is not called here, the animation settings of animateTo are used.
// Apply a scaling transition effect and specify springMotion (0.6, 1.2) as the curve.
.combine(TransitionEffect.scale({ x: 0, y: 0 }).animation({curve: curves.springMotion(0.6, 1.2) }))
// Apply a rotation transition effect, whose animation settings follow TransitionEffect above, that is, springMotion (0.6, 1.2).
.combine(TransitionEffect.rotate({ angle: 90 }))
// Apply a translation transition effect, whose animation settings follow TransitionEffect above, that is, springMotion (0.6, 1.2).
.combine(TransitionEffect.translate({ x: 150, y: 150 })
// Apply a move transition effect and specify springMotion as the curve.
.combine(TransitionEffect.move(TransitionEdge.END)).animation({curve: curves.springMotion()}))
// Apply an asymmetric transition effect. As the animation API is not called here, the animation settings follow TransitionEffect above, that is, springMotion.
.combine(TransitionEffect.asymmetric(TransitionEffect.scale({ x: 0, y: 0 }), TransitionEffect.rotate({angle: 90})));
```
2. Set the transition effects to the component by calling [transition](../reference/arkui-ts/ts-transition-animation-component.md).
```ts
Text('test')
.transition(effect)
```
3. Add or delete the component to trigger transition.
```ts
@state isPresent: boolean = true;
...
if (isPresent) {
Text('test')
.transition(effect)
}
...
// Control the addition or deletion of the component.
// Method 1: Place the control variable in the animateTo closure. In this case, the transition effect for which the animation API is not call will follow the animation settings of animateTo.
animateTo({curve: curves.springMotion()}) {
this.isPresent = false;
}
// Method 2: Directly delete or add the component. In this case, the transition effects follow the animation settings specified by animation.
this.isPresent = false;
```
Below is the complete sample code and effect.
```ts
import curves from '@ohos.curves';
@Entry
@Component
struct TransitionEffectDemo {
@State isPresent: boolean = false;
// Step 1: Create a TransitionEffect object.
private effect: TransitionEffect =
// Apply the default opacity transition effect and specify springMotion (0.6, 0.8) as the curve.
TransitionEffect.OPACITY.animation({ curve: curves.springMotion(0.6, 0.8) })
// Combine with a scale transition effect, whose animation settings follow TransitionEffect above, that is, springMotion(0.6, 0.8).
.combine(TransitionEffect.scale({ x: 0, y: 0 }))
// Apply a rotation transition effect, whose animation settings follow TransitionEffect above, that is, springMotion(0.6, 0.8).
.combine(TransitionEffect.rotate({ angle: 90 }))
// Apply a translation transition effect, whose animation settings are specified by animation, which is springMotion().
.combine(TransitionEffect.translate({ y: 150 }).animation({ curve: curves.springMotion() }))
// Apply a movement transition effect, whose animation settings follow TransitionEffect above, that is, springMotion().
.combine(TransitionEffect.move(TransitionEdge.END))
build() {
Stack() {
if (this.isPresent) {
Column() {
Text('ArkUI')
.fontWeight(FontWeight.Bold)
.fontSize(20)
.fontColor(Color.White)
}
.justifyContent(FlexAlign.Center)
.width(150)
.height(150)
.borderRadius(10)
.backgroundColor(0xf56c6c)
// Step 2: Set the transition effects to the component through the transition API.
.transition(this.effect)
}
// Border
Column()
.width(155)
.height(155)
.border({
width: 5,
radius: 10,
color: Color.Black,
})
// Step 3: Add or delete the component to trigger transition. Control the addition or deletion of the component.
Button('Click')
.margin({ top: 320 })
.onClick(() => {
this.isPresent = !this.isPresent;
})
}
.width('100%')
.height('60%')
}
}
```
![en-us_image_0000001599818064](figures/en-us_image_0000001599818064.gif)
# Layout Update Animation
[Explicit animation](../reference/arkui-ts/ts-explicit-animation.md) (**animateTo**) and [attribute animation](../reference/arkui-ts/ts-animatorproperty.md) (**animation**) are the most basic and common animation features provided by ArkUI. When the layout attributes (such as the [size](../reference/arkui-ts/ts-universal-attributes-size.md) and [position](../reference/arkui-ts/ts-universal-attributes-location.md)) attributes change, you can use the attribute animation or explicit animation to transit to the new layout parameter status based on the animation parameters.
| Animation Type| Description |
| ---- | ---------------------------------------- |
| Explicit animation| Triggered by changes in a closure, including component addition and deletion caused by data changes and component attribute changes.| Complex animation scenarios|
| Attribute animation| Triggered when the attribute changes. The animation setting is simple. |
## Using Explicit Animation to Create Layout Update Animation
The API for explicit animation is as follows:
```ts
animateTo(value: AnimateParam, event: () => void): void
```
The first parameter specifies the animation parameter, and the second parameter is the closure function of the animation.
The following is an example of using explicit animation to create a layout update animation. In the example, when the **\<Column>** component's **alignItems** attribute is updated, the layout of its child components changes. As long as the attribute is updated in the closure function of **animateTo**, animation is performed as configured through **animateTo** for all changes caused by the attribute toward the end value.
```ts
@Entry
@Component
struct LayoutChange {
// Used to control the alignItems attribute of a column.
@State itemAlign: HorizontalAlign = HorizontalAlign.Start;
allAlign: HorizontalAlign[] = [HorizontalAlign.Start, HorizontalAlign.Center, HorizontalAlign.End];
alignIndex: number = 0;
build() {
Column() {
Column({ space: 10 }) {
Button("1").width(100).height(50)
Button("2").width(100).height(50)
Button("3").width(100).height(50)
}
.margin(20)
.alignItems(this.itemAlign)
.borderWidth(2)
.width("90%")
.height(200)
Button("click").onClick(() => {
// The animation duration is 1000 ms, and the curve is EaseInOut.
animateTo({ duration: 1000, curve: Curve.EaseInOut }, () => {
this.alignIndex = (this.alignIndex + 1) % this.allAlign.length;
// Modify the this.itemAlign parameter in the closure function to change the layout of child elements in the <Column> container. The animation for transition to the new position is applied.
this.itemAlign = this.allAlign[this.alignIndex];
});
})
}
.width("100%")
.height("100%")
}
}
```
![layoutChange1](figures/layoutChange1.gif)
In addition to directly changing the layout, you can also change the width, height, and position of a component.
```ts
@Entry
@Component
struct LayoutChange2 {
@State myWidth: number = 100;
@State myHeight: number = 50;
// Flag. true and false correspond to a group of myWidth and myHeight values, respectively.
@State flag: boolean = false;
build() {
Column({ space: 10 }) {
Button("text")
.type(ButtonType.Normal)
.width(this.myWidth)
.height(this.myHeight)
.margin(20)
Button("area: click me")
.fontSize(12)
.margin(20)
.onClick(() => {
animateTo({ duration: 1000, curve: Curve.Ease }, () => {
// In the animation closure, the state variables that control the width and height of the first button are changed based on the flag settings so that the width and height of the first button are animated.
if (this.flag) {
this.myWidth = 100;
this.myHeight = 50;
} else {
this.myWidth = 200;
this.myHeight = 100;
}
this.flag = !this.flag;
});
})
}
.width("100%")
.height("100%")
}
}
```
In the click event of the second button, the **animateTo** API is used to modify the **this.myWidth** and **this.myHeight** state variables in the closure. As these two state variables set the width and height of the first button, the width and height animation is performed for the first button. The display effect is shown below.
![layoutChange2_animateTo](figures/layoutChange2_animateTo.gif)
At the same time, the second button also produces a position animation. After the width and height of the first button are changed, the layout of other components in the column is also changed, and the second button is among those other components.
If you do not want the second button to have an animation effect, you can use either of the following methods: 1. Add a container outside the first button so that the sizes before and after the animation are within the range of the container. In this way, the position of the second button is not affected by the position of the first button. The key code is as follows:
```ts
Column({ space: 10 }) {
Column() {
// The button is placed in a container that is large enough so that it does not affect the position of the outer component.
Button("text")
.type(ButtonType.Normal)
.width(this.myWidth)
.height(this.myHeight)
}
.margin(20)
.width(200)
.height(100)
Button("area: click me")
.fontSize(12)
.onClick(() => {
animateTo({ duration: 1000, curve: Curve.Ease }, () => {
// In the animation closure, the state variables that control the width and height of the first button are changed based on the flag settings so that the width and height of the first button are animated.
if (this.flag) {
this.myWidth = 100;
this.myHeight = 50;
} else {
this.myWidth = 200;
this.myHeight = 100;
}
this.flag = !this.flag;
});
})
}
.width("100%")
.height("100%")
```
![layoutChange2_animateTo_change](figures/layoutChange2_animateTo_change.gif)
2. Add layout constraints to the second button. For example, add position constraints so that the position of the second button is not affected by the width and height of the first button. The sample code is as follows:
```ts
Column({ space: 10 }) {
Button("text")
.type(ButtonType.Normal)
.width(this.myWidth)
.height(this.myHeight)
.margin(20)
Button("area: click me")
.fontSize(12)
// Set the position attribute to a fixed value so that the position of the second button is not affected by the width and height of the first button.
.position({ x: "30%", y: 200 })
.onClick(() => {
animateTo({ duration: 1000, curve: Curve.Ease }, () => {
// In the animation closure, the state variables that control the width and height of the first button are changed based on the flag settings so that the width and height of the first button are animated.
if (this.flag) {
this.myWidth = 100;
this.myHeight = 50;
} else {
this.myWidth = 200;
this.myHeight = 100;
}
this.flag = !this.flag;
});
})
}
.width("100%")
.height("100%")
```
## Using Attribute Animation to Generate Layout Update Animation
Unlike explicit animation, which requires the attribute changes for triggering animation to be placed in the closure function, attribute animation does not need to use the closure. You only need to append the **animation** attribute to the target component attribute.
The API of the attribute animation is as follows:
```ts
animation(value: AnimateParam)
```
This API accepts an animation parameter as its argument. If you want the component to generate an animation with the value change of an attribute, add this attribute before the **animation** attribute. Otherwise, you can place the attribute after the **animation** attribute. The previous example of explicit animation can be easily implemented with attribute animation. The sample code is as follows:
```ts
@Entry
@Component
struct LayoutChange2 {
@State myWidth: number = 100;
@State myHeight: number = 50;
@State flag: boolean = false;
@State myColor: Color = Color.Blue;
build() {
Column({ space: 10 }) {
Button("text")
.type(ButtonType.Normal)
.width(this.myWidth)
.height(this.myHeight)
// The animation takes effect only for the type, width, and height attributes. The duration is 1000 ms, and the curve is Ease.
.animation({ duration: 1000, curve: Curve.Ease })
// The animation does not take effect for the backgroundColor and margin attributes.
.backgroundColor(this.myColor)
.margin(20)
Button("area: click me")
.fontSize(12)
.onClick(() => {
// Change the attribute value. Animation transition is performed for attributes configured with attribute animation.
if (this.flag) {
this.myWidth = 100;
this.myHeight = 50;
this.myColor = Color.Blue;
} else {
this.myWidth = 200;
this.myHeight = 100;
this.myColor = Color.Pink;
}
this.flag = !this.flag;
})
}
}
}
```
In the preceding example, the **animation** attribute of the first button takes effect only for the **type**, **width**, and **height** attributes written before the **animation** attribute, but does not take effect for the **backgroundColor** and **margin** attributes written after. In the running result, the **width** and **height** attributes execute the animation based on the **animation** settings, while the **backgroundColor** attribute changes without any animation applied. The display effect is shown below.
![size-change-animation](figures/size-change-animation.gif)
>**NOTE**
>
> 1. Attribute animations are executed according to the configured attribute animation settings. Each component can have its own attribute animation settings.
>
> 2. Explicit animations are executed on all GUI differences caused before and after animation closures, and they share the same animation settings. Therefore, explicit animations are applicable to scenarios where animations are executed in a unified manner. Explicit animations can also be used for animations caused by non-attribute variables, such as **if/else** statements and deletion of array elements used by **ForEach**.
>
> 3. If an attribute animation is configured for an attribute and the attribute value is changed in the explicit animation closure, the attribute animation takes precedence, under the configured animation settings.
# Modal Transition
Modal transition is a type of transition achieved by a modal – a view that appears on top of the current view while the current view remains.
**Table 1** Modal transition APIs
| API| Description| Usage|
| -------- | -------- | -------- |
| [bindContentCover](../reference/arkui-ts/ts-universal-attributes-modal-transition.md) | Binds a modal to the component.| Use this API to display a custom modal. It can work with the transition animation and shared element animation to implement complex transition animation effects, for example, displaying an image in full in the modal upon the click of a thumbnail.|
| [bindSheet](../reference/arkui-ts/ts-universal-attributes-sheet-transition.md) | Binds a sheet to the component.| Use this API to display a custom sheet, for example, a sharing confirmation dialog box.|
| [bindMenu](../reference/arkui-ts/ts-universal-attributes-menu.md) | Binds a menu to the component, which is displayed when the component is clicked.| Use this API where a menu is required, for example, for the plus sign (+), a common menu indicator in applications.|
| [bindContextMenu](../reference/arkui-ts/ts-universal-attributes-menu.md) | Binds a context menu to the component, which is displayed when the user long-presses or right-clicks the component.| Use this API for components that bounce up when long-pressed, for example, home screen icons.|
| [bindPopup](../reference/arkui-ts/ts-universal-attributes-popup.md) | Binds a popup to the component.| Use this API to display a popup containing additional information about a component when the component is clicked.|
| if | Adds or deletes the component.| Use this API to display a temporary page in a certain state. In this mode, the return navigation needs to be implemented with a listener.|
## Creating Modal Transition with bindContentCover
You can bind a full-screen modal to a component through the [bindContentCover](../reference/arkui-ts/ts-universal-attributes-modal-transition.md) attribute. Better yet, with the **ModalTransition** parameter, you can apply a transition effect for when the component is inserted or deleted.
1. Define [bindContentCover](../reference/arkui-ts/ts-universal-attributes-modal-transition.md).
2. Define the modal view.
```ts
// Use @Builder to build a modal view.
@Builder MyBuilder() {
Column() {
Text('my model view')
}
// Use the transition API to implement the transition animation for component appearance and disappearance. The transition API must be added to the first component of the builder.
.transition(TransitionEffect.translate(y:300).animation({ curve: curves.springMotion(0.6, 0.8) }))
}
```
3. Call the modal API to display the modal. Implement an animation by using the animation or shared element transition APIs.
```ts
// Define the state variable to control the visibility of the modal.
@State isPresent: boolean = false;
Button('Click to present model view')
// Bind the modal to the component. ModalTransition indicates the transition mode of the modal. The value None means no transition animation for the modal.
.bindContentCover($$this.isPresent, this.MyBuilder, ModalTransition.None)
.onClick(() => {
// Change the state variable to display the modal.
this.isPresent = !this.isPresent;
})
```
Below is the complete sample code and effect.
```ts
import curves from '@ohos.curves';
@Entry
@Component
struct BindContentCoverDemo {
// Step 1: Define bindContentCover.
// Define the state variable to control the visibility of the modal.
@State isPresent: boolean = false;
// Step 2: Define the modal view.
// Use @Builder to build a modal view.
@Builder MyBuilder() {
Column() {
Column() {
Column() {
Text('back')
.fontSize(24)
.fontColor(Color.White)
}
.justifyContent(FlexAlign.Center)
.width(100)
.height(100)
.borderRadius(5)
.backgroundColor(0xf56c6c)
.onClick(() => {
this.isPresent = false;
})
}
.height('100%')
.width('100%')
.backgroundColor(0x909399)
.justifyContent(FlexAlign.Center)
.border({
radius: {
topLeft: 15,
topRight: 15,
}
})
}
.height('100%')
.justifyContent(FlexAlign.End)
// Use the transition API to implement the transition animation for component appearance and disappearance.
.transition(TransitionEffect.translate({ y: 1000 }).animation({ curve: curves.springMotion(0.6, 0.8) }))
}
build() {
Column() {
Column() {
Text('Click Me')
.fontSize(24)
.fontColor(Color.White)
}
// Step 3: Call the modal API to display the modal. Implement an animation by using the animation or shared element transition APIs.
.onClick(() => {
// Change the state variable to display the modal.
this.isPresent = !this.isPresent;
})
// Bind the modal to the component. ModalTransition indicates the transition mode of the modal. The value None means no transition animation for the modal.
.bindContentCover($$this.isPresent, this.MyBuilder(), ModalTransition.DEFAULT)
.justifyContent(FlexAlign.Center)
.backgroundColor(0XF56C6C)
.width(100)
.height(100)
.borderRadius(5)
}
.justifyContent(FlexAlign.Center)
.width('100%')
.height('100%')
}
}
```
![en-us_image_0000001646921957](figures/en-us_image_0000001646921957.gif)
## Creating Sheet Transition with bindSheet
You can bind a sheet to a component through the [bindSheet](../reference/arkui-ts/ts-universal-attributes-sheet-transition.md) attribute. You can also set the sheet to the preset or custom height for when the component is inserted. The process of creating a sheet transition is basically the same as that of creating a modal transition.
Below is the complete sample code and effect.
```ts
@Entry
@Component
struct BindSheetDemo {
// Define the state variable to control the sheet height.
@State sheetHeight: number = 300;
// Define the state variable to control the visibility of the drag bar.
@State showDragBar: boolean = true;
// Use @Builder to build a sheet view.
@Builder myBuilder() {
Column() {
Button("change height")
.margin(10)
.fontSize(20)
.onClick(() => {
this.sheetHeight = 500;
})
Button("Set Illegal height")
.margin(10)
.fontSize(20)
.onClick(() => {
this.sheetHeight = null;
})
Button("close dragbar")
.margin(10)
.fontSize(20)
.onClick(() => {
this.showDragBar = !this.showDragBar;
})
Button("close modal 1")
.margin(10)
.fontSize(20)
.onClick(() => {
this.isPresent = false;
})
}
.width('100%')
.height('100%')
}
// Define the state variable to control the visibility of the sheet.
@State isPresent: boolean = false;
build() {
Column() {
Button("Click to present sheet view")
.onClick(() => {
// Change the state variable to display the sheet.
this.isPresent = !this.isPresent;
})
.fontSize(20)
.margin(10)
// Bind the sheet to the component. You can specify the sheet height and whether to display the drag bar.
.bindSheet($$this.isPresent, this.myBuilder(), { height: this.sheetHeight, dragBar: this.showDragBar })
}
.justifyContent(FlexAlign.Center)
.width('100%')
.height('100%')
}
}
```
![en-us_image_0000001599977924](figures/en-us_image_0000001599977924.gif)
## Creating a Menu with bindMenu
You can bind a menu to component through the [bindMenu](../reference/arkui-ts/ts-universal-attributes-menu.md) attribute. The menu can then be triggered by clicking. Below is the complete sample code and effect.
```ts
@Entry
@Component
struct BindMenuDemo {
// Step 1: Define a data array to represent menu items.
private items = [
{
value:'Menu item 1',
action: () => {
console.info('handle Menu1 select')
}
},
{
value:'Menu item 2',
action: () => {
console.info('handle Menu2 select')
}
},
]
build() {
Column() {
Button('click')
.backgroundColor(0x409eff)
.borderRadius(5)
// Step 2: Bind the menu data to the component through bindMenu.
.bindMenu(this.items)
}
.justifyContent(FlexAlign.Center)
.width('100%')
.height(437)
}
}
```
![en-us_image_0000001599643478](figures/en-us_image_0000001599643478.gif)
## Creating a Context Menu with bindContextMenu
You can bind a context menu to component through the [bindContextMenu](../reference/arkui-ts/ts-universal-attributes-menu.md) attribute. The menu can then be triggered by long-pressing or right-clicking.
Below is the complete sample code and effect.
```ts
@Entry
@Component
struct BindContextMenuDemo {
private num: number[] = [1, 2, 3, 4];
private colors: Color[] = [0x67C23A, 0xE6A23C, 0xf56c6c, 0x909399];
// Use @Builder to build custom menu items.
@Builder MyMenu() {
Row() {
Column() {
ForEach(this.num, (item: number, index: number) => {
Row() {
Text(item.toString())
.fontSize(20)
.fontColor(Color.White)
}
.backgroundColor(this.colors[index])
.width('100%')
.aspectRatio(2)
.justifyContent(FlexAlign.Center)
})
}
.width('100%')
}
.width(150)
.justifyContent(FlexAlign.Center)
.padding(5)
}
build() {
Column() {
Column() {
Text('longPress')
.fontSize(20)
.fontColor(Color.White)
}
.justifyContent(FlexAlign.Center)
.width(170)
.height(50)
.bindContextMenu(this.MyMenu, ResponseType.LongPress)
.backgroundColor(0xf56c6c)
.borderRadius(5)
}
.justifyContent(FlexAlign.Center)
.width('100%')
.height(437)
}
}
```
![en-us_image_0000001600137920](figures/en-us_image_0000001600137920.gif)
## Creating a Popup with bindPopUp
You can bind a popup to a component through the [bindpopup](../reference/arkui-ts/ts-universal-attributes-popup.md) attribute, specifying its content, interaction logic, and display status.
Below is the complete sample code and effect.
```ts
@Entry
@Component
struct BindPopupDemo {
// Step 1: Define the state variable to control the visibility of the popup.
@State customPopup: boolean = false;
// Step 2: Use @Builder to build a custom popup.
@Builder popupBuilder() {
Column({ space: 2 }) {
Row().width(64)
.height(64)
.backgroundColor(0x409eff)
Text('Popup')
.fontSize(10)
.fontColor(Color.White)
}
.justifyContent(FlexAlign.SpaceAround)
.width(100)
.height(100)
.padding(5)
}
build() {
Column() {
Button('click')
// Step 4: Add a click event to control the visibility of the popup.
.onClick(() => {
this.customPopup = !this.customPopup;
})
.backgroundColor(0xf56c6c)
// Step 5: Bind the popup to the component through bindPopup.
.bindPopup(this.customPopup, {
builder: this.popupBuilder,
placement: Placement.Top,
maskColor: 0x33000000,
popupColor: 0xf56c6c,
enableArrow: true,
onStateChange: (e) => {
if (!e.isVisible) {
this.customPopup = false;
}
}
})
}
.justifyContent(FlexAlign.Center)
.width('100%')
.height(437)
}
}
```
![en-us_image_0000001649282285](figures/en-us_image_0000001649282285.gif)
## Creating Modal Transition with if
In addition to the preceding modal transition APIs, you can also use the **if** syntax to create a modal transition, eliminating the need for binding to the component and listening for state variable changes.
Below is the complete sample code and effect.
```ts
@Entry
@Component
struct ModalTransition1 {
// Step 1: Define a state variable to control page display.
@State isShow: boolean = false;
build() {
// Step 2: Define a stack layout to display the current view and modal view.
Stack() {
Column() {
Text('Page1')
.fontSize(40)
.fontColor(Color.White)
.fontWeight(FontWeight.Bolder)
Text('Click to transition')
.fontSize(15)
.fontColor(Color.White)
}
.justifyContent(FlexAlign.Center)
.width('100%')
.height('100%')
.linearGradient({
colors: [
[0xf56c6c, 0.0],
[0xffffff, 1.0]
]
})
// Step 3: Change the state variable to display the modal view.
.onClick(() => {
animateTo({ duration: 500 }, () => {
this.isShow = !this.isShow;
})
})
// Step 4: Define the modal view in if and display it at the top layer. Use if to control the appearance and disappearance of the modal view.
if (this.isShow) {
Column() {
Text('Page2')
.fontSize(40)
.fontColor(Color.Gray)
.fontWeight(FontWeight.Bolder)
Text('Click to transition')
.fontSize(15)
.fontColor(Color.Gray)
}
.justifyContent(FlexAlign.Start)
.width('100%')
.height('100%')
.linearGradient({
colors: [
[0xffffff, 0.0],
[0x409eff, 1.0]
]
})
// Step 5: Define the mode in which the modal view disappears.
.transition(TransitionEffect.OPACITY.combine(TransitionEffect.rotate({ angle: 90, y: 1 })))
.onClick(() => {
animateTo({ duration: 500 }, () => {
this.isShow = !this.isShow;
})
})
}
}
.width('100%')
.height('100%')
}
}
```
![en-us_image_0000001597792146](figures/en-us_image_0000001597792146.gif)
# Navigation Transition
Navigation transition is a transition animation that runs during the navigation from one view to another. The animation settings of the navigation transition are pre-defined and cannot be modified.
To implement the navigation transition, you are advised to use the [\<Navigation>](../reference/arkui-ts/ts-basic-components-navigation.md) component, complete with the [\<NavRouter>](../reference/arkui-ts/ts-basic-components-navrouter.md) and [\<NavDestination>](../reference/arkui-ts/ts-basic-components-navdestination.md) components.
Below is the complete sample code and effect.
```ts
@Entry
@Component
struct NavigationDemo {
private listArray: Array<number> = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
// Set the title bar menu, if a title bar is involved.
@Builder NavigationMenus() {
Column() {
Text('menu')
.fontColor('#182431')
.fontSize(14)
.lineHeight(19)
.opacity(0.4)
.margin({ top: 70 })
}
.alignItems(HorizontalAlign.Start)
}
build() {
Stack() {
Column() {
// Define the <Navigation> component, setting the display mode and title.
Navigation() {
// An <Input> component is defined here.
TextInput({ placeholder: 'search...' })
.width('90%')
.height(40)
.backgroundColor('#ededed')
.margin({ bottom: 10 })
// Define the level-1 navigation view through <List>.
List({ space: 12, initialIndex: 0 }) {
ForEach(this.listArray, (item) => {
ListItem() {
// Define the navigation transition through <NavRouter> and define the navigation destination through <NavDestination>. Parameters are transferred between views through state variables or regular variables between components.
// <NavRouter> must contain two child components. The first child component is the level-1 navigation view, and the second child component must be <NavDestination>, which specifies the navigation destination.
NavRouter() {
// First component: Level-1 navigation view.
Row() {
Row()
.width(40)
.height(40)
.backgroundColor('#a8a8a8')
.margin({ right: 12 })
.borderRadius(20)
Column() {
Text('Level-1 item')
.fontSize(16)
.lineHeight(21)
.fontWeight(FontWeight.Medium)
Text('Click to go to subitems')
.fontSize(13)
.lineHeight(21)
.fontColor('#a8a8a8')
}
.alignItems(HorizontalAlign.Start)
Blank()
Row()
.width(15)
.height(15)
.margin({ right: 12 })
.border({
width: { top: 2, right: 2 },
color: 0xcccccc
})
.rotate({ angle: 45 })
}
.borderRadius(15)
.shadow({ radius: 100, color: '#ededed' })
.width('90%')
.alignItems(VerticalAlign.Center)
.padding({ left: 16, top: 12, bottom: 12 })
.height(80)
// Second component: navigation destination
NavDestination() {
// Content of the destination view, which is generally a custom component.
Column() {
Text("Destination"+ item +" content ")
.fontSize(20)
.fontColor(Color.Black)
.textAlign(TextAlign.Center)
.width('100%')
.height('100%')
}
.width('100%')
.height('100%')
.backgroundColor(0xf5f5f5)
}
.title('Destination page') // Title of the level-2 page.
}
}
.width('100%')
}, item => item)
}
.listDirection(Axis.Vertical)
.edgeEffect(EdgeEffect.Spring)
.sticky(StickyStyle.Header)
.chainAnimation(false)
.borderRadius(15)
.width('100%')
.height('100%')
}
.width('100%')
.mode(NavigationMode.Auto) // Set the display mode of the navigation bar to Auto.
.title('Navigation transition') // Set the title text.
.titleMode(NavigationTitleMode.Full) // Set the display mode of the page title bar.
.menus(this.NavigationMenus) // Set the title bar menu.
}
.width('100%')
}
}
}
```
![en-us_image_0000001588458252](figures/en-us_image_0000001588458252.gif)
# Page Transition Animation
During page redirection, one page disappears and the other page appears. You can customize the [page transition effects](../reference/arkui-ts/ts-page-transition-animation.md) for these pages through the **pageTransition** API. Specifically, **PageTransitionEnter** defines the page entrance animation, while **PageTransitionExit** defines the page exit animation.
API of **PageTransitionEnter**:
```ts
PageTransitionEnter({type?: RouteType,duration?: number,curve?: Curve | string,delay?: number})
```
API of **PageTransitionExit**:
```ts
PageTransitionExit({type?: RouteType,duration?: number,curve?: Curve | string,delay?: number})
```
Both **PageTransitionEnter** and **PageTransitionExit** contain the **slide**, **translate**, **scale**, and **opacity** attributes. For **PageTransitionEnter**, these attributes indicate the start values for page entrance. For **PageTransitionExit**, these attributes indicate the end values for page exit. In this sense, configuration of page transition is similar to that of component transition. **PageTransitionEnter** provides the **onEnter** callback, and **PageTransitionExit** provides the **onExit** callback.
In the preceding APIs, the **type** parameter indicates the route type used in page navigation. Each page transition involves exit of one page and entrance of the other. If you switch from page A to page B through the **router.pushUrl** operation, page A exits, with the exit animation applied; and page B enters, with the entrance animation applied. If you switch from page B back to page A through the **router.back** operation, page B exits, , with the exit animation applied; and page A enters, with the entrance animation applied. That is, **PageTransitionEnter** of a page may be an entrance animation of a new page (pushed to the stack) or of an existing page (popped from the stack). To distinguish these two types of entrance animations, the **type** parameter is provided.
## Setting type to RouteType.None
When **type** is set to **RouteType.None** (default value), the page transition animations work for both the push and pop operations in the page stack.
```ts
// pageA
pageTransition() {
// Configure the page entrance animation to sliding in from the left, with the duration of 1200 ms. The settings take effect no matter whether the push or pop operation is performed on the page stack.
PageTransitionEnter({ type: RouteType.None, duration: 1200 })
.slide(SlideEffect.Left)
// Configure the page exit animation to sliding out from the left, with the duration of 1000 ms. The settings take effect no matter whether the push or pop operation is performed on the page stack.
PageTransitionExit({ type: RouteType.None, duration: 1000 })
.slide(SlideEffect.Left)
}
```
```ts
// pageB
pageTransition() {
// Configure the page entrance animation to sliding in from the right, with the duration of 1000 ms. The settings take effect no matter whether the push or pop operation is performed on the page stack.
PageTransitionEnter({ type: RouteType.None, duration: 1000 })
.slide(SlideEffect.Right)
// Configure the page exit animation to sliding out from the right, with the duration of 1200 ms. The settings take effect no matter whether the push or pop operation is performed on the page stack.
PageTransitionExit({ type: RouteType.None, duration: 1200 })
.slide(SlideEffect.Right)
}
```
Assume that the page stack is in the multi-instance mode, that is, duplicate pages are allowed in the page stack. There may be four scenarios. The following table lists the page transition effects.
| Route Operation | Page A Transition Effect | Page B Transition Effect |
| ---------------------------- | ---------------------------------- | ---------------------------------- |
| **router.pushUrl** – redirection from page A to new page B.| The page exits. The animation defined by **PageTransitionExit** is applied. In the example, the page slides out from the left of the screen. | The page enters. The animation defined by **PageTransitionEnter** is applied. In the example, the page slides in from the right of the screen.|
| **router.back** – redirection from page B back to page A. | The page enters. The animation defined by **PageTransitionEnter** is applied. In the example, the page slides in from the left of the screen.| The page exits. The animation defined by **PageTransitionExit** is applied. In the example, the page slides out from the right of the screen. |
| **router.pushUrl** – redirection from page B to new page A.| The page enters. The animation defined by **PageTransitionEnter** is applied. In the example, the page slides in from the left of the screen.| The page exits. The animation defined by **PageTransitionExit** is applied. In the example, the page slides out from the right of the screen. |
| **router.back** – redirection from page A back to page B. | The page exits. The animation defined by **PageTransitionExit** is applied. In the example, the page slides out from the left of the screen. | The page enters. The animation defined by **PageTransitionEnter** is applied. In the example, the page slides in from the right of the screen.|
If you want the page accessed by **router.pushUrl** to always slide in from the right and the page exited by **router.back** to always slide out from the right, the third and fourth cases in the preceding table do not meet the requirements. In this case, you need to define four page transition effects.
## Setting type to RouteType.Push or RouteType.Pop
When **type** is set to **RouteType.Push**, the page transition animations work for only both the push operations in the page stack. When **type** is set to **RouteType.Pop**, the page transition animations work for only both the pop operations in the page stack.
```ts
// pageA
pageTransition() {
// Configure the page entrance animation to sliding in from the right, with the duration of 1200 ms. The settings take effect only when the push operation is performed on the page stack.
PageTransitionEnter({ type: RouteType.Push, duration: 1200 })
.slide(SlideEffect.Right)
// Configure the page entrance animation to sliding in from the left, with the duration of 1200 ms. The settings take effect only when the pop operation is performed on the page stack.
PageTransitionEnter({ type: RouteType.Pop, duration: 1200 })
.slide(SlideEffect.Left)
// Configure the page exit animation to sliding out from the left, with the duration of 1000 ms. The settings take effect only when the push operation is performed on the page stack.
PageTransitionExit({ type: RouteType.Push, duration: 1000 })
.slide(SlideEffect.Left)
// Configure the page exit animation to sliding out from the right, with the duration of 1000 ms. The settings take effect only when the pop operation is performed on the page stack.
PageTransitionExit({ type: RouteType.Pop, duration: 1000 })
.slide(SlideEffect.Right)
}
```
```ts
// pageB
pageTransition() {
// Configure the page entrance animation to sliding in from the right, with the duration of 1000 ms. The settings take effect only when the push operation is performed on the page stack.
PageTransitionEnter({ type: RouteType.Push, duration: 1000 })
.slide(SlideEffect.Right)
// Configure the page entrance animation to sliding in from the left, with the duration of 1000 ms. The settings take effect only when the pop operation is performed on the page stack.
PageTransitionEnter({ type: RouteType.Pop, duration: 1000 })
.slide(SlideEffect.Left)
// Configure the page exit animation to sliding out from the left, with the duration of 1200 ms. The settings take effect only when the push operation is performed on the page stack.
PageTransitionExit({ type: RouteType.Push, duration: 1200 })
.slide(SlideEffect.Left)
// Configure the page exit animation to sliding out from the right, with the duration of 1200 ms. The settings take effect only when the pop operation is performed on the page stack.
PageTransitionExit({ type: RouteType.Pop, duration: 1200 })
.slide(SlideEffect.Right)
}
```
The preceding code defines page transition effects for all possibles scenarios. Assume that the page stack is in the multi-instance mode, that is, duplicate pages are allowed in the page stack. There may be four scenarios. The following table lists the page transition effects.
| Route Operation | Page A Transition Effect | Page B Transition Effect |
| ---------------------------- | ---------------------------------------- | ---------------------------------------- |
| **router.pushUrl** – redirection from page A to new page B.| The page exits. The transition style of **PageTransitionExit** whose **type** is **RouteType.Push** takes effect. The page slides out from the left of the screen.| The page enters. The transition style of **PageTransitionEnter** whose **type** is **RouteType.Push** takes effect. The page slides in from the right of the screen.|
| **router.back** – redirection from page B back to page A. | The page enters. The transition style of **PageTransitionEnter** whose **type** is **RouteType.Pop** takes effect. The page slides in from the left of the screen.| The page exits. The transition style of **PageTransitionExit** whose **type** is **RouteType.Pop** takes effect. The page slides out from the right of the screen.|
| **router.pushUrl** – redirection from page B to new page A.| The page enters. The transition style of **PageTransitionEnter** whose **type** is **RouteType.Push** takes effect. The page slides in from the right of the screen.| The page exits. The transition style of **PageTransitionExit** whose **type** is **RouteType.Push** takes effect. The page slides out from the left of the screen.|
| **router.back** – redirection from page A back to page B. | The page exits. The transition style of **PageTransitionExit** whose **type** is **RouteType.Pop** takes effect. The page slides out from the right of the screen.| The page enters. The transition style of **PageTransitionEnter** whose **type** is **RouteType.Pop** takes effect. The page slides in from the left of the screen.|
>**NOTE**
>
> 1. The transition style of each page can be independently configured. However, as each transition involves two pages, take into account the smoothness between page transitions, for example, the transition duration.
>
> 2. If no page transition style is defined, a page uses the default page transition style.
## Disabling Page Transition
```ts
pageTransition() {
PageTransitionEnter({ type: RouteType.None, duration: 0 })
PageTransitionExit({ type: RouteType.None, duration: 0 })
}
```
You can disable the transition animation of a page by setting the page transition duration to 0.
## Example
In the following example, page transition animations are defined for all four page transition scenarios.
```ts
// PageTransitionSrc1
import router from '@ohos.router';
@Entry
@Component
struct PageTransitionSrc1 {
build() {
Column() {
Image($r('app.media.mountain'))
.width('90%')
.height('80%')
.objectFit(ImageFit.Fill)
.syncLoad(true) // Load the image synchronously so that the image has been loaded when the page is displayed.
.margin(30)
Row({ space: 10 }) {
Button("pushUrl")
.onClick(() => {
// Navigate to the next page, which is a push operation.
router.pushUrl({ url: 'pages/myTest/PageTransitionDst1' });
})
Button("back")
.onClick(() => {
// Return to the previous page, which is equivalent to the pop operation.
router.back();
})
}.justifyContent(FlexAlign.Center)
}
.width("100%").height("100%")
.alignItems(HorizontalAlign.Center)
}
pageTransition() {
// Configure the page entrance animation to sliding in from the right, with the duration of 1000 ms. The settings take effect only when the push operation is performed on the page stack.
PageTransitionEnter({ type: RouteType.Push, duration: 1000 })
.slide(SlideEffect.Right)
// Configure the page entrance animation to sliding in from the left, with the duration of 1000 ms. The settings take effect only when the pop operation is performed on the page stack.
PageTransitionEnter({ type: RouteType.Pop, duration: 1000 })
.slide(SlideEffect.Left)
// Configure the page exit animation to sliding out from the left, with the duration of 1000 ms. The settings take effect only when the push operation is performed on the page stack.
PageTransitionExit({ type: RouteType.Push, duration: 1000 })
.slide(SlideEffect.Left)
// Configure the page exit animation to sliding out from the right, with the duration of 1000 ms. The settings take effect only when the pop operation is performed on the page stack.
PageTransitionExit({ type: RouteType.Pop, duration: 1000 })
.slide(SlideEffect.Right)
}
}
```
```ts
// PageTransitionDst1
import router from '@ohos.router';
@Entry
@Component
struct PageTransitionDst1 {
build() {
Column() {
Image($r('app.media.forest'))
.width('90%')
.height('80%')
.objectFit(ImageFit.Fill)
.syncLoad(true) // Load the image synchronously so that the image has been loaded when the page is displayed.
.margin(30)
Row({ space: 10 }) {
Button("pushUrl")
.onClick(() => {
// Navigate to the next page, which is a push operation.
router.pushUrl({ url: 'pages/myTest/PageTransitionSrc1' });
})
Button("back")
.onClick(() => {
// Return to the previous page, which is equivalent to the pop operation.
router.back();
})
}.justifyContent(FlexAlign.Center)
}
.width("100%").height("100%")
.alignItems(HorizontalAlign.Center)
}
pageTransition() {
// Configure the page entrance animation to sliding in from the right, with the duration of 1000 ms. The settings take effect only when the push operation is performed on the page stack.
PageTransitionEnter({ type: RouteType.Push, duration: 1000 })
.slide(SlideEffect.Right)
// Configure the page entrance animation to sliding in from the left, with the duration of 1000 ms. The settings take effect only when the pop operation is performed on the page stack.
PageTransitionEnter({ type: RouteType.Pop, duration: 1000 })
.slide(SlideEffect.Left)
// Configure the page exit animation to sliding out from the left, with the duration of 1000 ms. The settings take effect only when the push operation is performed on the page stack.
PageTransitionExit({ type: RouteType.Push, duration: 1000 })
.slide(SlideEffect.Left)
// Configure the page exit animation to sliding out from the right, with the duration of 1000 ms. The settings take effect only when the pop operation is performed on the page stack.
PageTransitionExit({ type: RouteType.Pop, duration: 1000 })
.slide(SlideEffect.Right)
}
}
```
![pageTransition_PushPop](figures/pageTransition_PushPop.gif)
In the following example, **type** is set to **RouteType.None**.
```ts
// PageTransitionSrc2
import router from '@ohos.router';
@Entry
@Component
struct PageTransitionSrc2 {
build() {
Column() {
Image($r('app.media.mountain'))
.width('90%')
.height('80%')
.objectFit(ImageFit.Fill)
.syncLoad(true) // Load the image synchronously so that the image has been loaded when the page is displayed.
.margin(30)
Row({ space: 10 }) {
Button("pushUrl")
.onClick(() => {
// Navigate to the next page, which is a push operation.
router.pushUrl({ url: 'pages/myTest/pageTransitionDst2' });
})
Button("back")
.onClick(() => {
// Return to the previous page, which is equivalent to the pop operation.
router.back();
})
}.justifyContent(FlexAlign.Center)
}
.width("100%").height("100%")
.alignItems(HorizontalAlign.Center)
}
pageTransition() {
// Configure the page entrance animation to sliding in from the left, with the duration of 1000 ms. The settings take effect no matter whether the push or pop operation is performed on the page stack.
PageTransitionEnter({ duration: 1000 })
.slide(SlideEffect.Left)
// Configure the page exit animation to translating by 100 vp along the x- and y-axes and changing the opacity to 0, with the duration of 1200 ms. The settings take effect no matter whether the push or pop operation is performed on the page stack.
PageTransitionExit({ duration: 1200 })
.translate({ x: 100.0, y: 100.0 })
.opacity(0)
}
}
```
```ts
// PageTransitionDst2
import router from '@ohos.router';
@Entry
@Component
struct PageTransitionDst2 {
build() {
Column() {
Image($r('app.media.forest'))
.width('90%')
.height('80%')
.objectFit(ImageFit.Fill)
.syncLoad(true) // Load the image synchronously so that the image has been loaded when the page is displayed.
.margin(30)
Row({ space: 10 }) {
Button("pushUrl")
.onClick(() => {
// Navigate to the next page, which is a push operation.
router.pushUrl({ url: 'pages/myTest/pageTransitionSrc2' });
})
Button("back")
.onClick(() => {
// Return to the previous page, which is equivalent to the pop operation.
router.back();
})
}.justifyContent(FlexAlign.Center)
}
.width("100%").height("100%")
.alignItems(HorizontalAlign.Center)
}
pageTransition() {
// Configure the page entrance animation to sliding in from the left, with the duration of 1200 ms. The settings take effect no matter whether the push or pop operation is performed on the page stack.
PageTransitionEnter({ duration: 1200 })
.slide(SlideEffect.Left)
// Configure the page exit animation to translating by 100 vp along the x- and y-axes and changing the opacity to 0, with the duration of 1000 ms. The settings take effect no matter whether the push or pop operation is performed on the page stack.
PageTransitionExit({ duration: 1000 })
.translate({ x: 100.0, y: 100.0 })
.opacity(0)
}
}
```
![pageTransition_None](figures/pageTransition_None.gif)
# Shadow Effect
You can use the [shadow](../reference/arkui-ts/ts-universal-attributes-image-effect.md) API to apply a shadow effect to a component. Even better, you can set the parameter of this API to [ShadowOptions](../reference/arkui-ts/ts-universal-attributes-image-effect.md#shadowoptions) to customize the shadow effect. When the radius or color opacity in **ShadowOptions** is set to **0**, there is no shadow effect.
```ts
@Entry
@Component
struct ShadowOptionDemo {
build() {
Row() {
Column() {
Column() {
Text('shadowOption').fontSize(12)
}
.width(100)
.aspectRatio(1)
.margin(10)
.justifyContent(FlexAlign.Center)
.backgroundColor(Color.White)
.borderRadius(20)
.shadow({ radius: 10, color: Color.Gray })
Column() {
Text('shadowOption').fontSize(12)
}
.width(100)
.aspectRatio(1)
.margin(10)
.justifyContent(FlexAlign.Center)
.backgroundColor('#a8a888')
.borderRadius(20)
.shadow({ radius: 10, color: Color.Gray, offsetX: 20, offsetY: 20 })
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
.height('100%')
}
}
```
![en-us_image_0000001598502322](figures/en-us_image_0000001598502322.png)
# Spring Curve Animation
ArkUI provides [preset animation curves](../reference/arkui-ts/ts-appendix-enums.md#curve), which specifies the change rule of animation attributes from the start value to the end value, such as Linear, Ease, and EaseIn. At the same time, ArkUI also provides spring curves generated by the physical model of spring oscillators. Through the spring curve, developers can set the end value to be exceeded and oscillate around the end value until the end value stops. The animation effect of spring curve is more interactive and playable than other curves.
There are two types of interfaces for spring curves: [springCurve](../reference/apis/js-apis-curve.md#curvesspringcurve9) and [springMotion](../reference/apis/js-apis-curve.md#curvesspringmotion9) and [responsiveSpringMotion](../reference/apis/js-apis-curve.md#curvesresponsivespringmotion9). Spring curves can be generated in both modes.
## Using springCurve
The **springCurve** API is as follows:
```ts
springCurve(velocity: number, mass: number, stiffness: number, damping: number)
```
The structural parameters include the initial velocity, the mass, stiffness and damping of the spring system. When building springCurve, you can set the quality to 1. Adjust the stiffness and damping parameters based on the parameter description in springCurve to achieve the desired oscillation effect.
```ts
import curves from '@ohos.curves';
@Entry
@Component
struct SpringTest {
@State translateX: number = 0;
private jumpWithSpeed(speed: number) {
this.translateX = -1;
animateTo({ duration: 2000, curve: curves.springCurve(speed, 1, 1, 1.2) }, () => {
//Spring animation for translation in the x direction at the specified initial speed
this.translateX = 0;
})
}
build() {
Column() {
Button("button")
.fontSize(14)
.width(100)
.height(50)
.margin(30)
.translate({ x: this.translateX })
Row({space:50}) {
Button("jump 50").fontSize(14)
.onClick(() => {
//Translate the spring curve with an initial speed of 50.
this.jumpWithSpeed(50);
})
Button("jump 200").fontSize(14)
.onClick(() => {
//Translate the spring curve with an initial speed of 200.
this.jumpWithSpeed(200);
})
}.margin(30)
}.height('100%').width('100%')
}
}
```
![springCurve](figures/springCurve.gif)
In the preceding example, when different buttons are clicked and different initial speeds of springCurve are given, the buttons reach the specified positions elastically, and the amplitude of the buttons increases with the speed. In addition, you can modify the mass, stiffness, and damping parameters of springCurve to achieve the desired elasticity.
>**NOTE**
>
>The speed only amplifies the oscillation effect, but whether the system can produce the oscillation effect depends on the physical parameters of the spring oscillator, that is, mass, stiffness, and damping. The smaller the stiffness and the larger the damping, the weaker the "elasticity" of springCurve and the weaker the oscillation effect. With the decrease of stiffness or the increase of damping, no matter how large the velocity is, there will be no oscillation near the end value.
## Using springMotion and responsiveSpringMotion
The interfaces of the [springMotion](../reference/apis/js-apis-curve.md#curvesspringmotion9) are as follows:
```ts
springMotion(response?: number, dampingFraction?: number, overlapDuration?: number)
```
The interfaces of the [responsiveSpringMotion](../reference/apis/js-apis-curve.md#curvesresponsivespringmotion9) are as follows:
```ts
responsiveSpringMotion(response?: number, dampingFraction?: number, overlapDuration?: number)
```
Their construction parameters include three optional parameters: spring natural vibration period, damping coefficient, and elastic animation connection duration. For details about the parameters, see their documents.
When the springMotion and responsiveSpringMotion curves are used, the duration parameter does not take effect. This parameter is applicable to hand animation.
```ts
import curves from '@ohos.curves';
@Entry
@Component
struct SpringMotionTest {
@State positionX: number = 100;
@State positionY: number = 100;
diameter: number = 50;
build() {
Column() {
Row() {
Circle({ width: this.diameter, height: this.diameter })
.fill(Color.Blue)
.position({ x: this.positionX, y: this.positionY })
.onTouch((event: TouchEvent) => {
if (event.type === TouchType.Move) {
// Follow-up process. Use the responsiveSpringMotion curve.
animateTo({ curve: curves.responsiveSpringMotion() }, () => {
//subtract the radius so that the center of the ball moves to the finger position
this.positionX = event.touches[0].screenX - this.diameter / 2;
this.positionY = event.touches[0].screenY - this.diameter / 2;
console.info(`move, animateTo x:${this.positionX}, y:${this.positionY}`);
})
} else if (event.type === TouchType.Up) {
//Use the springMotion curve when you leave your hand.
animateTo({ curve: curves.springMotion() }, () => {
this.positionX = 100;
this.positionY = 100;
console.info(`touchUp, animateTo x:100, y:100`);
})
}
})
}
.width("100%").height("80%")
.clip(true) //If the ball exceeds the range of the parent component, the ball is invisible.
.backgroundColor(Color.Orange)
Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Start, justifyContent: FlexAlign.Center }) {
Text("Drag the ball").fontSize(16)
}
.width("100%")
Row() {
Text('Click position: [x:' + Math.round(this.positionX) +', y:' + Math.round(this.positionY) + ']').fontSize(16)
}
.padding(10)
.width("100%")
}.height('100%').width('100%')
}
}
```
The preceding code is an example of hand animation. In the onTouch event, the touch position is captured and the translate or position attribute of the component is changed so that the component moves to the touch position when following the hand. After the hand is released, the component returns to the original position. The following figure shows the effect of the follower animation.
![springMotion](figures/springMotion.gif)
The responsiveSpringMotion curve is recommended for the follow-up process, and the springMotion curve is recommended for the release process. The follow-up process is triggered for multiple times as the position of the hand changes. Therefore, the responsiveSpringMotion animation is started for multiple times. When the hand is released, the springMotion animation is started once. During the follow-up and release processes, an animation is executed on the same attribute of the same object, and the springMotion or responsiveSpringMotion curve is used. Each time the animation is started, the speed used by the last animation is inherited, implementing smooth transition.
>**NOTE**
>
> 1. SpringCurve can be used to set the initial speed. If a single attribute has multiple animations, the animations do not affect each other. The effects of multiple animations are superimposed.
>
> 2. Although springMotion has an internal speed mechanism, it cannot be set by developers. When multiple animations exist for a single attribute, the later animation replaces the previous animation and inherits the speed of the previous animation.
# Spring Curve
In a damped spring system corresponding to a damped spring curve (spring curve for short), an object that deviates from the equilibrium position is forced to oscillate due to a reverse force generated by spring deformation; this oscillation is resisted by the damping force. Except for the special case where the damping is 0, the oscillation gradually decays in amplitude towards 0, and the resultant animation curve is naturally continuous.
An animation using the spring curve slows down toward the end where the velocity of 0, instead of stopping abruptly.
ArkUI provides four types of damped spring curve APIs:
- [springMotion](../reference/apis/js-apis-curve.md#curvesspringmotion9): creates a spring animation curve. The animation duration is automatically calculated based on the curve parameters, attribute change values, and initial spring velocity. Manually set animation duration values do not take effect.
**springMotion** does not provide any API for setting the velocity, as the velocity is obtained through inheritance. For an attribute, if there is already a springMotion or responsiveSpringMotion animation running, a new spring animation will stop the running animation and inherit the attribute values and velocity of that animation as its initial values. This spring curve API provides default parameters, which you can directly use when appropriate.
```ts
function springMotion(response?: number, dampingFraction?: number, overlapDuration?: number): ICurve;
```
- [responsiveSpringMotion](../reference/apis/js-apis-curve.md#curvesresponsivespringmotion9): creates a responsive spring animation curve. It is a special case of **springMotion**, with the only difference in the default values. It is typically used to create an animation with a gesture on the screen. You can use **springMotion** to create an animation for when the user lifts their finger off the screen. The created animation automatically inherits the previous velocity for animation transition.
When the **overlapDuration** parameter of the new animation is not **0** and the previous spring animation of the current attribute is not yet complete, **response** and **dampingFracion** transit from the values of the previous animation to that of the new animation within the period specified by **overlapDuration**.
```ts
function responsiveSpringMotion(response?: number, dampingFraction?: number, overlapDuration?: number): ICurve;
```
- [interpolatingSpring](../reference/apis/js-apis-curve.md#curvesinterpolatingspring10): creates an interpolating spring curve animated from 0 to 1. It applies to scenarios where the initial animation velocity needs to be specified. The animation duration is automatically calculated, and the manually specified animation duration does not take effect.
The actual animation value is calculated based on the curve. Therefore, the velocity should be the normalized speed, which is equal to the absolute speed of the animation attribute change divided by the amount of the animation attribute change. In light of this, this API is not applicable to the scenario where the attribute value of the animation start point is the same as that of the animation end point, since under this scenario, the amount of the animation attribute change is 0, and the normalized speed does not exist.
```ts
function interpolatingSpring(velocity: number, mass: number, stiffness: number, damping: number): ICurve;
```
- [springCurve](../reference/apis/js-apis-curve.md#curvesspringcurve9): creates a spring curve with the specified animation duration. This API is almost the same as **interpolatingSpring**. However, for an animation that uses **springCurve**, the physical duration of the curve is mapped to the specified duration, which is equivalent to stretching or compressing the curve on the time axis and violating the original physical rule of the curve. Whenever possible, avoid using this API.
```ts
function springCurve(velocity: number, mass: number, stiffness: number, damping: number): ICurve;
```
The following shows a complete example and effect of spring curves. For details about how to connect gestures and animations, see [Animation Smoothing](arkts-animation-smoothing.md).
```ts
import curves from '@ohos.curves';
class Spring {
public title: string;
public subTitle: string;
public iCurve: ICurve;
constructor(title: string, subTitle: string, iCurve: ICurve) {
this.title = title;
this.iCurve = iCurve;
this.subTitle = subTitle;
}
}
// Spring component
@Component
struct Motion {
@Prop dRotate: number
private title: string
private subTitle: string
private iCurve: ICurve
build() {
Row() {
Column() {
Text(this.title)
.fontColor(Color.Black)
.fontSize(16)
Text(this.subTitle)
.fontColor(0xcccccc)
.fontSize(12)
}
.width(200)
.alignItems(HorizontalAlign.Start)
.justifyContent(FlexAlign.Center)
.height(100)
Stack({ alignContent: Alignment.Top }) {
// Circle
Column()
.width(100)
.height(100)
.border({
width: 10,
color: 0xf56c6c,
radius: 50
})
.backgroundColor(Color.White)
// Mask layer
Column() {
Row()
.width(100)
.height(100)
.border({
width: 10,
color: 0x909399,
radius: 50
})
.backgroundColor(0xf56c6c)
}
.width(100)
.height(50)
.clip(true)
.rotate({ angle: this.dRotate, centerX: 50, centerY: 50 })
.animation({ curve: this.iCurve, iterations: -1 })
}
.width(100)
.height(100)
}
.height(110)
.borderWidth({ bottom: 1 })
.borderColor(0xf5f5f5)
.margin({ bottom: 5 })
.alignItems(VerticalAlign.Top)
}
}
@Entry
@Component
export struct SpringDemo {
@State dRotate: number = 0;
private springs: Spring[] = [
new Spring('springMotion()', '(springMotion(1, 0.25): \n\nCycle: 2; damping: 0.25)', curves.springMotion(1, 0.25)),
new Spring('responsiveSpringMotion()', 'responsiveSpringMotion(1, 0.25): \n\nDefault responsive spring curve', curves.responsiveSpringMotion(1, 0.25)),
new Spring('interpolatingSpring()', '(interpolatingSpring(10, 1, 228, 30): \n\nInitial velocity: 100; quality: 1; stiffness: 228; damping: 30)', curves.interpolatingSpring(10, 1, 228, 30)),
new Spring('springCurve()', '(springCurve(10, 1, 228, 30): \n\nInitial velocity: 100; quality: 1; stiffness: 228; damping: 30)', curves.springCurve(10, 1, 228, 30))
];
build() {
Column() {
ForEach(this.springs, (item: Spring) => {
Motion({ title: item.title, subTitle: item.subTitle, iCurve: item.iCurve, dRotate: this.dRotate })
})
}
.justifyContent(FlexAlign.Center)
.width('100%')
.height(437)
.margin({ top: 20 })
.onClick(() => {
this.dRotate = 360;
})
}
}
```
![en-us_image_0000001649089041](figures/en-us_image_0000001649089041.gif)
# Traditional Curve
The animation curve, defined by a timing function, is a speed curve that controls the time consumed by an animation on attribute changes. The slope of the animation curve at a certain moment indicates the animation velocity and corresponds to the attribute change speed. A well-designed animation curve should be continuously smooth, in line with the user's intent, and consistent with the real world. When designing animation curves for your animations and transitions, take into account use cases and user intents. Animation curves can be classified into physics-based curves (ArkUI provides multiple physics-based spring curves) and traditional curves based on whether they model the real world. Compared with the traditional curve, the movement trajectory generated by the physics-based curve would feel more natural to users and help create a more engaging experience. In light of this, you are advised to use the physics-based curve when possible.
Traditional curves are mathematically described curves. A typical representative is the cubic Bezier curve. You define the curve shape with a set of control points, to bring out the expected animation effect, such as ease in and ease out. As aforementioned, a traditional curve is not based on the real-world behavior. This means that it does not change its shape to respond to user behavior, and lacks the natural and engaging feel given by a physics-based curve. When creating animations, prioritize physics-based curves and use traditional curves only in rare cases.
ArkUI provides APIs for traditional curves such as Bezier and step curves. For details, see [Interpolation Calculation](../reference/apis/js-apis-curve.md).
The following is an example of a traditional curve.
```ts
class MyCurve {
public title: string;
public curve: Curve;
public color: Color | string;
constructor(title: string, curve: Curve, color: Color | string = '') {
this.title = title;
this.curve = curve;
this.color = color;
}
}
const myCurves: MyCurve[] = [
new MyCurve(' Linear', Curve.Linear, 0x409EFF),
new MyCurve(' Ease', Curve.Ease, 0x67C23A),
new MyCurve(' EaseIn', Curve.EaseIn, 0xE6A23C),
new MyCurve(' EaseOut', Curve.EaseOut, 0xF56C6C),
new MyCurve(' EaseInOut', Curve.EaseInOut, 0xFFB6C1),
new MyCurve(' FastOutSlowIn', Curve.FastOutSlowIn, 0xDC143C),
new MyCurve(' LinearOutSlowIn', Curve.LinearOutSlowIn, 0xFF00FF),
new MyCurve(' FastOutLinearIn', Curve.FastOutLinearIn, 0x00BFFF),
new MyCurve(' ExtremeDeceleration', Curve.ExtremeDeceleration, 0x5F9EA0),
new MyCurve(' Sharp', Curve.Sharp, 0x00FFFF),
new MyCurve(' Rhythm', Curve.Rhythm, 0x66CDAA),
new MyCurve(' Smooth', Curve.Smooth, 0x7CFC00),
new MyCurve(' Friction', Curve.Friction, 0xFFA500)
]
@Entry
@Component
export struct CurveDemo {
@State dRotate: number = 0; // Rotation angle.
build() {
Column() {
// Curve example
Grid() {
ForEach(myCurves, (item: MyCurve) => {
GridItem() {
Column() {
Row()
.width(20)
.height(20)
.borderRadius(10)
.backgroundColor(item.color)
Text(item.title)
.fontSize(10)
.fontColor(0x909399)
}
.width('100%')
}
})
}
.columnsTemplate('1fr 1fr 1fr')
.rowsTemplate('1fr 1fr 1fr 1fr 1fr')
.padding(10)
.width('100%')
.height(300)
Stack() {
// Swing pipe
Row()
.width(290)
.height(290)
.border({
width: 10,
color: 0xE6E8EB,
radius: 145
})
ForEach(myCurves, (item: MyCurve) => {
// Balls
Column() {
Row()
.width(20)
.height(20)
.borderRadius(10)
.backgroundColor(item.color)
}
.width(20)
.height(300)
.rotate({ angle: this.dRotate })
.animation({ duration: 2000, iterations: -1, curve: item.curve, delay: 100 })
})
}
.width('100%')
.height(437)
.onClick(() => {
this.dRotate ? null : this.dRotate = 360;
})
}
.width('100%')
}
}
```
![en-us_image_0000001641260233](figures/en-us_image_0000001641260233.gif)
# Transition Animation Within the Component
The process of inserting and deleting a component is the transition process of the component. The animation of inserting and deleting a component is called the transition animation in the component. You can define the appearance and disappearance effects of a component through the transition animation in the component.
The interface of the transition animation in the component is as follows:
```ts
transition(value: TransitionOptions)
```
The input parameter of the [transition](../reference/arkui-ts/ts-transition-animation-component.md) API defines the transition effect in the component. You can define the transition effect of one or more transition styles such as translation, opacity, rotation, and scaling. **transition** must be used together with [animateTo](arkts-layout-update-animation.md#using-explicit-animation-to-create-layout-update-animation).
## Common Usage of Transition
The type parameter is used to specify the gadget change scenario where the current transition dynamic effect takes effect. The type is [TransitionType](../reference/arkui-ts/ts-appendix-enums.md#transitiontype).
- The same animation effect is used for inserting and deleting components.
```ts
Button()
.transition({ type: TransitionType.All, scale: { x: 0, y: 0 } })
```
When the type attribute is TransitionType.All, the specified transition effect takes effect in all change (insertion and deletion) scenarios of the component. In this case, deleting the animation and inserting the animation are reverse processes, and deleting the animation is reverse playing of inserting the animation. For example, the preceding code defines a Button control. When a component is inserted, the component changes from the state where x and y of the scale are both 0 to the default state where x and y of the scale are both 1 (complete display). The component is gradually zoomed in. When a component is deleted, the component changes from the default state where x and y of the scale are 1 to the state where x and y of the specified scale are 0 and gradually shrinks to 0.
- Different animation effects are used for inserting and deleting components.
```ts
Button()
.transition({ type: TransitionType.Insert, translate: { x: 200, y: -200 }, opacity: 0 })
.transition({ type: TransitionType.Delete, rotate: { x: 0, y: 0, z: 1, angle: 360 } })
```
When different transition animation effects need to be implemented for component insertion and deletion, you can call the transition function twice to set the type attribute to TransitionType.Insert and TransitionType.Delete respectively. For example, the preceding code defines a Button control. When the component is inserted, the component changes from an initial state in which the position is translated by 200 vp in the x direction and the position is translated by –200 vp in the y direction relative to the normal layout position of the component, and the transparency is 0 in the x and y directions to a default state in which the translation amount is 0 and the transparency is 1. The inserted animation is a combination of a translation animation and a transparency animation. When the component is deleted, the component changes from the default state in which the rotation angle is 0 to the end state in which the component rotates 360 degrees around the z axis, that is, rotates around the z axis for one week.
- Define only one of the animation effects of inserting or deleting a component.
```ts
Button()
.transition({ type: TransitionType.Delete, translate: { x: 200, y: -200 } })
```
If only the transition animation effect of component insertion or deletion is required, you only need to set the transition effect whose **type** attribute is **TransitionType.Insert** or **TransitionType.Delete**. For example, the preceding code defines a Button control. When a component is deleted, the component is moved from the normal position without translation to the position of 200 vp in the x direction and -200 vp in the y direction relative to the normal layout position. Inserting the component does not generate a transition animation for the component.
## if/else: generates transition animations in components.
The if/else statement can control the insertion and deletion of components. The following code can be used to control whether the if condition is met through the button click event to control whether to display the Image component under if.
```ts
@Entry
@Component
struct IfElseTransition {
@State flag: boolean = true;
@State show: string = 'show';
build() {
Column() {
Button(this.show).width(80).height(30).margin(30)
.onClick(() => {
if (this.flag) {
this.show = 'hide';
} else {
this.show = 'show';
}
//Click the button to control the display and disappearance of the image.
this.flag = !this.flag;
})
if (this.flag) {
Image($r('app.media.mountain')).width(200).height(200)
}
}.height('100%').width('100%')
}
}
```
No animation is configured in the preceding code. Next, we'll add the in-component transition effect to the above code. The Image component is controlled by if. You need to add the transition parameter to the Image component to specify the transition effect in the component. For example, you can add the translation effect when inserting the file, and add the scaling and transparency effects when deleting the file.
```ts
if (this.flag) {
Image($r('app.media.mountain')).width(200).height(200)
.transition({ type: TransitionType.Insert, translate: { x: 200, y: -200 } })
.transition({ type: TransitionType.Delete, opacity: 0, scale: { x: 0, y: 0 } })
}
```
Although the preceding code specifies the animation style, the animation parameters are not specified. Therefore, you do not know how long and how to use the curve to complete the animation. Transition must be used together with animateTo. In the closure of animateTo, transition controls the insertion and deletion of components. The preceding sample code is used to change the value of flag in the animateTo closure. The code is as follows: Set the animation duration to 1000 ms, use the default curve of the animateTo function, and change the value of flag. Animation is generated based on the animation parameter for all changes caused by flag changes. Here, the flag affects the appearance and disappearance of the image.
```ts
animateTo({ duration: 1000 }, () => {
this.flag = !this.flag;
})
```
After the preceding process, when animateTo and transition are used together, the transition animation in the component is generated. The complete sample code is as follows:
```ts
@Entry
@Component
struct IfElseTransition {
@State flag: boolean = true;
@State show: string = 'show';
build() {
Column() {
Button(this.show).width(80).height(30).margin(30)
.onClick(() => {
if (this.flag) {
this.show = 'hide';
} else {
this.show = 'show';
}
animateTo({ duration: 1000 }, () => {
//Control the appearance and disappearance of the Image component in the animation closure.
this.flag = !this.flag;
})
})
if (this.flag) {
//The appearance and disappearance of the image are configured as different transition effects.
Image($r('app.media.mountain')).width(200).height(200)
.transition({ type: TransitionType.Insert, translate: { x: 200, y: -200 } })
.transition({ type: TransitionType.Delete, opacity: 0, scale: { x: 0, y: 0 } })
}
}.height('100%').width('100%')
}
}
```
![ifElseTransition](figures/ifElseTransition.gif)
>**NOTE**
>
>When the transition effect is set to translate or scale, the animation process may exceed the range of the parent component after the translation or magnification is overlaid on the position. If you want the child component to be completely displayed when the parent component is beyond the range of the parent component, you can set the clip attribute of the parent component to false so that the parent component does not tailor the child component. If you want the excess child components not to be displayed when the parent component is exceeded, you can set the clip attribute of the parent component to true to tailor the excess child components.
## Forach generates transition animations in components.
Similar to if/else, Forach can control the insertion and deletion of components by controlling the number of elements in an array. To use Forach to generate an intra-component transition animation, the following conditions must be met:
- The transition effect is configured for the component in Forach.
- Controls the insertion or deletion of components in the closure of animateTo, that is, controls the addition and deletion of array elements.
The following code is an example of using Forach to generate an in-component transition animation.
```ts
@Entry
@Component
struct ForEachTransition {
@State numbers: string[] = ["1", "2", "3", "4", "5"]
startNumber: number = 6;
build() {
Column({ space: 10 }) {
Column() {
ForEach(this.numbers, (item) => {
// The transition effect needs to be configured for the direct component under Forach.
Text(item)
.width(240)
.height(60)
.fontSize(18)
.borderWidth(1)
.backgroundColor(Color.Orange)
.textAlign(TextAlign.Center)
.transition({ type: TransitionType.All, translate: { x: 200 }, scale: { x: 0, y: 0 } })
}, item => item)
}
.margin(10)
.justifyContent(FlexAlign.Start)
.alignItems(HorizontalAlign.Center)
.width("90%")
.height("70%")
Button ('Add element to header')
.fontSize(16)
.width(160)
.onClick(() => {
animateTo({ duration: 1000 }, () => {
//Insert an element to the array header. As a result, Forach adds the corresponding component to the header.
this.numbers.unshift(this.startNumber.toString());
this.startNumber++;
})
})
Button ('Add element to tail')
.width(160)
.fontSize(16)
.onClick(() => {
animateTo({ duration: 1000 }, () => {
//Insert an element to the end of the array. As a result, Forach adds the corresponding component to the end.
this.numbers.push(this.startNumber.toString());
this.startNumber++;
})
})
Button ('Delete Header Element')
.width(160)
.fontSize(16)
.onClick(() => {
animateTo({ duration: 1000 }, () => {
//Delete the header element of the array. As a result, Forach deletes the header component.
this.numbers.shift();
})
})
Button ('Delete Tail Element')
.width(160)
.fontSize(16)
.onClick(() => {
animateTo({ duration: 1000 }, () => {
//Delete the tail element of the array. As a result, Forach deletes the header component.
this.numbers.pop();
})
})
}
.width('100%')
.height('100%')
}
}
```
The display effect is shown below.
![forEachTransition2](figures/forEachTransition2.gif)
The column layout mode is set to FlexAlign.Start, that is, the vertical layout starts from the head. Therefore, when an element is added to the end of an array, the position of the component corresponding to the existing element in the array is not affected, and only the insertion animation of the new component is triggered. When an element is added to the array header, the subscripts of all elements in the original array are added. Although the addition or deletion of the element is not triggered, the location of the corresponding component is affected. Therefore, in addition to the transition animation for new components, the position animation is also performed for components in Forach.
>**NOTE**
>
>If/else and ForEach are syntax nodes. The component for configuring the transition effect in the component should be directly used as the child of the syntax node. If a component is added or deleted due to the addition or deletion of a syntax node, only the intra-component transition animation of the direct child component can be triggered. Developers should not expect the component transition animation to be generated for deeper components.
# Transition Animation Overview
Compared with [attribute animations](arkts-attribute-animation-overview.md), which work for components that are always present, transition animations are applied to animate the appearing or disappearing components. With transition animations, you are freed from the burdensome tasks of disappearing component management, which are unavoidable if you used attribute animations instead: deleting the component in the callback invoked when the animation is complete; checking the component status in the callback, in case that the component that has been deleted appears again before the animation ends.
Transition animations are classified into basic transition animations and advanced template-based transition animations. They come in any of the following types:
- [Enter/Exit transition](arkts-enter-exit-transition.md): used on appearing and disappearing components. It is a basic type of transition.
- [Navigation transition](arkts-navigation-transition.md): used during page navigation, where one page disappears and another page appears. For example, it can be used for switching from a level-1 menu page to a level-2 details page.
- [Modal transition](arkts-modal-transition.md): achieved by a modal – a view that appears on top of the current view while the current view remains. The dialog box is a typical type of modal.
- [Shared element transition](arkts-shared-element-transition.md): achieved by animating the size and position between styles of the same or similar elements during page switching.
......@@ -84,8 +84,8 @@ The table below lists the main tasks involved in UI development with ArkUI. You
| Develop the layout | Understand the common layouts and how to improve layout performance. | - [Layout Overview](arkts-layout-development-overview.md)<br>- [Improving Layout Performance](arkts-layout-development-performance-boost.md)|
| Add components | Learn the usage of common built-in components, custom components, and GUI elements supported by APIs.| - [Common Components](arkts-common-components-button.md)<br>- [Custom Components](../quick-start/arkts-create-custom-components.md)<br>- [Popup and Menu](arkts-popup-and-menu-components-popup.md)|
| Set page routing and component navigation| Learn how to set page routes and navigation between components. | - [Page Routing](arkts-routing.md)<br>- [Navigation](arkts-navigation-navigation.md)|
| Use graphics | Understand how to display images, draw custom geometry, and make custom graphics on the canvas. | - [Displaying Images](arkts-graphics-display.md)<br>- [Drawing Geometric Shapes](arkts-geometric-shape-drawing.md)<br>- [Drawing Custom Graphics Using the Canvas](arkts-drawing-customization-on-canvas.md) |
| Apply animations | Learn the typical scenarios of applying animations on components and pages. | - [Animation Within a Page](arkts-layout-update-animation.md)<br>- [Animation Between Pages](arkts-zoom-animation.md)|
| Use graphics | Understand how to display images, draw custom geometry, and make custom graphics on the canvas. | - [Displaying Images](arkts-graphics-display.md)<br>- [Drawing Geometric Shapes](arkts-geometric-shape-drawing.md)<br>- [Drawing Custom Graphics Using the Canvas](arkts-drawing-customization-on-canvas.md)|
| Apply animations | Learn the typical scenarios of applying animations on components and pages. | - [Attribute Animation](arkts-attribute-animation-overview.md)<br>- [Transition Animation](arkts-transition-overview.md)<br>- [Component Animation](arkts-component-animation.md)<br>- [Animated Curve](arkts-traditional-curve.md)<br>- [Animation Smoothing](arkts-animation-smoothing.md)<br>- [Animation Effects](arkts-blur-effect.md)|
| Bind events | Learn the basic concepts of events and how to use common events and gesture events. | - [Universal Events](arkts-common-events-touch-screen-event.md)<br>- [Gesture Events](arkts-gesture-events-binding.md)|
##
......
# Zoom Animation
If the same element (for example, the same image) is used on different pages, you can use the Shared Element Transition (../reference/arkui-ts/ts-transition-animation-shared-elements.md) animation to connect the elements. To highlight the association of the same elements between different pages, you can add shared element transition animations for them. If the sizes of the same element on different pages are obviously different, the view can be zoomed in or out.
The interface for shared element transition is as follows:
```ts
sharedTransition(id: string, options?: sharedTransitionOptions)
```
Based on the type parameter in sharedTransitionOptions, shared element transitions are classified into Exchange shared element transitions and Static shared element transitions.
## Transition of Shared Elements of the Exchange Type
The transition of shared elements of the exchange type requires that components with the same ID configured through the sharedTransition function exist on two pages. These components are called shared elements. This type of shared element transition is applicable to the connection of the same element between two pages, and transits from the position and size of the shared element on the start page to the position and size of the shared element on the target page. If type is not specified, the shared element transition of the Exchange type is used by default, which is the most common shared element transition mode. When the shared element transition of the Exchange type is used, the animation parameters of the shared element transition are determined by the animation parameters in options on the target page.
## Transition of Shared Elements of the Static Type
Static shared element transition is usually used in the scenario where the title gradually appears or is hidden during page switching. Static shared elements only need to exist on one page. Static shared elements with the same ID cannot exist on two pages. When the page (target page) is displayed, the transparency of the sharedTransition component of the Static type is changed from 0 to the transparency set by the component, and the position remains unchanged. When the page (that is, the start page) disappears, an animation in which the transparency gradually changes to 0 is performed, and the position remains unchanged.
The animation parameters of the shared element transition are determined by the animation parameters in the sharedTransition attribute of the component.
## Example Scenario
The following describes an example of using a shared element transition to zoom in or zoom out an image.
```ts
// src page
import router from '@ohos.router';
@Entry
@Component
struct SharedTransitionSrc {
build() {
Column() {
//Configure the transition of the shared element of the Exchange type. The ID of the shared element is sharedImage1.
Image($r('app.media.mountain')).width(50).height(50)
.sharedTransition('sharedImage1', { duration: 1000, curve: Curve.Linear })
.onClick(() => {
//When a small image is clicked, the route jumps to the next page.
router.pushUrl({ url: 'pages/myTest/sharedTransitionDst' });
})
}
.padding(10)
.width("100%")
.alignItems(HorizontalAlign.Start)
}
}
```
```ts
// dest page
import router from '@ohos.router';
@Entry
@Component
struct SharedTransitionDest {
build() {
Column() {
//Configure the transition of shared elements of the Static type.
Text("SharedTransition dest page")
.fontSize(16)
.sharedTransition('text', { duration: 500, curve: Curve.Linear, type: SharedTransitionEffectType.Static })
.margin({ top: 10 })
//Configure the transition of the shared element of the Exchange type. The ID of the shared element is sharedImage1.
Image($r('app.media.mountain'))
.width(150)
.height(150)
.sharedTransition('sharedImage1', { duration: 500, curve: Curve.Linear })
.onClick(() => {
// Return to the previous page when a user clicks an image.
router.back();
})
}
.width("100%")
.alignItems(HorizontalAlign.Center)
}
}
```
In the preceding example, the shared element transition whose ID is sharedImage1 is configured on both the first page (src page) and the second page (dest page) so that the two pages can match the group of shared elements. When you jump from the first page to the second page, the first page is the start page, and the second page is the target page. Configure the component whose ID is sharedImage1 to perform shared element transition based on the duration of 500 ms on the target page to zoom in the view. Configure the component whose ID is text to perform shared element transition based on the duration of 500 ms in the sharedTransition parameter of the Static type, the headline gradually appears. When you return from the second page to the first page, the second page is the start page, and the first page is the target page. Configure the component whose ID is sharedImage1 to perform shared element transition based on the duration of 1000 ms on the target page and zoom out to the original view. Configure the component whose ID is text to perform shared element transition based on the duration of 500 ms in the sharedTransition parameter of the Static type, the title is gradually hidden.
![sharedTransition](figures/sharedTransition.gif)
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册