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

Update docs (18613)

Signed-off-by: Nester.zhou <ester.zhou@huawei.com>
上级 ab5c61c6
# UI Development # UI Development
- [ArkUI Overview](arkui-overview.md) - [ArkUI Overview](arkui-overview.md)
- ArkTS-based Declarative Development Paradigm - UI Development (ArkTS-based Declarative Development Paradigm)
- [Overview](ui-ts-overview.md) - [UI Development (ArkTS-based Declarative Development Paradigm) Overview](arkts-ui-development-overview.md)
- [Declarative UI Development Guidelines](ui-ts-developing-intro.md) - Layout Development
- Declarative UI Development Examples - [Layout Overview](arkts-layout-development-overview.md)
- [Creating a Simple Page](ui-ts-creating-simple-page.md) - Building a Layout
- Building a Comprehensive Example - [Linear Layout](arkts-layout-development-linear.md)
- [Building a Food Data Model](ui-ts-building-data-model.md) - [Stack Layout](arkts-layout-development-stack-layout.md)
- [Building a Food Category List Layout](ui-ts-building-category-list-layout.md) - [Flex Layout](arkts-layout-development-flex-layout.md)
- [Building a Food Category Grid Layout](ui-ts-building-category-grid-layout.md) - [Relative Layout](arkts-layout-development-relative-layout.md)
- [Implementing Page Redirection and Data Transmission](ui-ts-page-redirection-data-transmission.md) - [Responsive Grid Layout](arkts-layout-development-grid-layout.md)
- Adding a Splash Screen Animation - [Media Query](arkts-layout-development-media-query.md)
- [Using the Drawing Feature](ui-ts-drawing-feature.md) - [Creating a List](arkts-layout-development-create-list.md)
- [Using the Animation Feature](ui-ts-animation-feature.md) - [Creating a Grid](arkts-layout-development-create-grid.md)
- [Common Components](ui-ts-components-intro.md) - [Creating a Swiper](arkts-layout-development-create-looping.md)
- Common Layout Development - [Improving Layout Performance](arkts-layout-development-performance-boost.md)
- Adaptive Layouts - Adding a Component
- [Linear Layout](ui-ts-layout-linear.md) - Adding a Common Component
- [Statck Layout](ui-ts-layout-stack.md) - [Button](arkts-common-components-button.md)
- [Flex Layout](ui-ts-layout-flex.md) - [Radio Button](arkts-common-components-radio-button.md)
- [Grid Layout](ui-ts-layout-grid.md) - [Toggle](arkts-common-components-switch.md)
- Responsive Layouts - [Progress Indicator](arkts-common-components-progress-indicator.md)
- [Grid Layout](ui-ts-layout-grid-container-new.md) - [Text Display](arkts-common-components-text-display.md)
- [Media Query](ui-ts-layout-mediaquery.md) - [Text Input](arkts-common-components-text-input.md)
- [Custom Component Lifecycle Callbacks](ui-ts-custom-component-lifecycle-callbacks.md) - [Custom Dialog Box](arkts-common-components-custom-dialog.md)
- [Web Component Development](ui-ts-components-web.md) - [Video Playback](arkts-common-components-video-player.md)
- [Recommendations for Improving Performance](ui-ts-performance-improvement-recommendation.md) - [XComponent](arkts-common-components-xcomponent.md)
- JavaScript-compatible Web-like Development Paradigm - Adding a Bubble and Menu
- [Overview](ui-js-overview.md) - [Bubble](arkts-popup-and-menu-components-popup.md)
- Framework Overview - [Menu](arkts-popup-and-menu-components-menu.md)
- [File Organization](js-framework-file.md) - Setting Page Routing and Component Navigation
- [Page Routing](arkts-routing.md)
- Component Navigation
- [Navigation](arkts-navigation-navigation.md)
- [Tabs](arkts-navigation-tabs.md)
- Using Graphics
- [Displaying Images](arkts-graphics-display.md)
- [Drawing Geometric Shapes](arkts-geometric-shape-drawing.md)
- [Drawing Custom Graphics on the 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)
- Using Interaction Events
- [Interaction Event Overview](arkts-event-overview.md)
- Universal Events
- [Touchscreen Event](arkts-common-events-touch-screen-event.md)
- [Keyboard and Mouse Event](arkts-common-events-device-input-event.md)
- [Focus Event](arkts-common-events-focus-event.md)
- Gesture Events
- [Gesture Binding](arkts-gesture-events-binding.md)
- [Single Gesture](arkts-gesture-events-single-gesture.md)
- [Combined Gestures](arkts-gesture-events-combined-gestures.md)
- [Recommendations for Improving Performance](arkts-performance-improvement-recommendation.md)
- UI Development (JavaScript-compatible Web-like Development Paradigm)
- [UI Development (JavaScript-compatible Web-like Development Paradigm) Overview](ui-js-overview.md)
- Framework Overview
- [File Organization](js-framework-file.md)
- ["js" Tag](js-framework-js-tag.md) - ["js" Tag](js-framework-js-tag.md)
- [app.js](js-framework-js-file.md) - [app.js](js-framework-js-file.md)
- Syntax - Syntax
......
# 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.
# Button
The **\<Button>** component is usually activated by user clicks to perform a specific action. Buttons are classified as capsule, circle, or normal buttons. When used as a container, the **\<Button>** component accepts child components such as text and images. For details, see [Button](../reference/arkui-ts/ts-basic-components-button.md).
## Creating a Button
You can create a button that contains or does not contain child components.
- Create a button that does not contain child components.
```ts
Button(label?: string, options?: { type?: ButtonType, stateEffect?: boolean })
```
Creates a button that does not contain child components. In this API, **label** indicates the button text, **type** indicates the button type, and **stateEffect** specifies whether to set pressed effect on the click of the button.
```ts
Button('Ok', { type: ButtonType.Normal, stateEffect: true })
.borderRadius(8)
.backgroundColor(0x317aff)
.width(90)
.height(40)
```
![en-us_image_0000001562820757](figures/en-us_image_0000001562820757.png)
- Create a button that contains child components.
```ts
Button(options?: {type?: ButtonType, stateEffect?: boolean})
```
Creates a button that contains a single child component, which can either be a [basic component](../reference/arkui-ts/ts-basic-components-blank.md) or a [container component](../reference/arkui-ts/ts-container-ability-component.md).
```ts
Button({ type: ButtonType.Normal, stateEffect: true }) {
Row() {
Image($r('app.media.loading')).width(20).height(40).margin({ left: 12 })
Text('loading').fontSize(12).fontColor(0xffffff).margin({ left: 5, right: 12 })
}.alignItems(VerticalAlign.Center)
}.borderRadius(8).backgroundColor(0x317aff).width(90).height(40)
```
![en-us_image_0000001511421216](figures/en-us_image_0000001511421216.png)
## Setting the Button Type
Use the **type** parameter to set the button type to **Capsule**, **Circle**, or **Normal**.
- Capsule button (default type)
Buttons of this type have rounded corners whose radius is automatically set to half of the button height. The rounded corners cannot be reset through the **borderRadius** attribute.
```ts
Button('Disable', { type: ButtonType.Capsule, stateEffect: false })
.backgroundColor(0x317aff)
.width(90)
.height(40)
```
![en-us_image_0000001511421208](figures/en-us_image_0000001511421208.png)
- Circle button
Buttons of this type are round. The rounded corners cannot be reset through the **borderRadius** attribute.
```ts
Button('Circle', { type: ButtonType.Circle, stateEffect: false })
.backgroundColor(0x317aff)
.width(90)
.height(90)
```
![en-us_image_0000001511740428](figures/en-us_image_0000001511740428.png)
- Normal button
Buttons of this type have rounded corners set to 0. The rounded corners can be reset through the **borderRadius** attribute.
```ts
Button('Ok', { type: ButtonType.Normal, stateEffect: true })
.borderRadius(8)
.backgroundColor(0x317aff)
.width(90)
.height(40)
```
![en-us_image_0000001563060641](figures/en-us_image_0000001563060641.png)
## Setting Styles
- Set the border radius:
In general cases, you can use universal attributes to define the button styles. For example, you can use the **borderRadius** attribute to set the border radius.
```ts
Button('circle border', { type: ButtonType.Normal })
.borderRadius(20)
.height(40)
```
![en-us_image_0000001511900392](figures/en-us_image_0000001511900392.png)
- The **Font** type is used to set the text style.
Add a font style for text displayed on the button.
```ts
Button('font style', { type: ButtonType.Normal })
.fontSize(20)
.fontColor(Color.Pink)
.fontWeight(800)
```
![en-us_image_0000001511580828](figures/en-us_image_0000001511580828.png)
- Set the background color:
You can do so by adding the **backgroundColor** attribute.
```ts
Button('background color').backgroundColor(0xF55A42)
```
![en-us_image_0000001562940477](figures/en-us_image_0000001562940477.png)
- Assign a function to the button:
In this example, the delete function is assigned to the button.
```ts
Button({ type: ButtonType.Circle, stateEffect: true }) {
Image($r('app.media.ic_public_delete_filled')).width(30).height(30)
}.width(55).height(55).margin({ left: 20 }).backgroundColor(0xF55A42)
```
![en-us_image_0000001511740436](figures/en-us_image_0000001511740436.png)
## Adding Events
The **\<Button>** component is usually used to trigger actions. You can bind the **onClick** event to the button to have it respond with custom behavior after being clicked.
```ts
Button('Ok', { type: ButtonType.Normal, stateEffect: true })
.onClick(()=>{
console.info('Button onClick')
})
```
## Example Scenario
- Using the Button for Startup
You can use the button for any UI element that involves the startup operation. The button triggers the predefined event based on the user's operation. For example, you can use a button in the **\<List>** container to redirect the user to another page.
```ts
// xxx.ets
import router from '@ohos.router';
@Entry
@Component
struct ButtonCase1 {
build() {
List({ space: 4 }) {
ListItem() {
Button("First").onClick(() => {
router.pushUrl({ url: 'pages/first_page' })
})
.width('100%')
}
ListItem() {
Button("Second").onClick(() => {
router.pushUrl({ url: 'pages/second_page' })
})
.width('100%')
}
ListItem() {
Button("Third").onClick(() => {
router.pushUrl({ url: 'pages/third_page' })
})
.width('100%')
}
}
.listDirection(Axis.Vertical)
.backgroundColor(0xDCDCDC).padding(20)
}
}
```
![en-us_image_0000001562700393](figures/en-us_image_0000001562700393.png)
- Use the button for submitting forms:
On the user login/registration page, you can use a button to submit a login or registration request.
```ts
// xxx.ets
@Entry
@Component
struct ButtonCase2 {
build() {
Column() {
TextInput({ placeholder: 'input your username' }).margin({ top: 20 })
TextInput({ placeholder: 'input your password' }).type(InputType.Password).margin({ top: 20 })
Button('Register').width(300).margin({ top: 20 })
.onClick(() => {
// Operation
})
}.padding(20)
}
}
```
![en-us_image_0000001562940473](figures/en-us_image_0000001562940473.png)
- Configure the button to float:
The button can remain floating when the user swipes on the screen.
```ts
// xxx.ets
@Entry
@Component
struct HoverButtonExample {
private arr: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
build() {
Stack() {
List({ space: 20, initialIndex: 0 }) {
ForEach(this.arr, (item) => {
ListItem() {
Text('' + item)
.width('100%').height(100).fontSize(16)
.textAlign(TextAlign.Center).borderRadius(10).backgroundColor(0xFFFFFF)
}
}, item => item)
}.width('90%')
Button() {
Image($r('app.media.ic_public_add'))
.width(50)
.height(50)
}
.width(60)
.height(60)
.position({x: '80%', y: 600})
.shadow({radius: 10})
.onClick(() => {
// Operation
})
}
.width('100%')
.height('100%')
.backgroundColor(0xDCDCDC)
.padding({ top: 5 })
}
}
```
![GIF](figures/GIF.gif)
# Custom Dialog Box
A custom dialog box is a dialog box you customize by using APIs of the **CustomDialogController** class. It can be used for user interactions, showing an ad, prize, alert, software update message, and more. For details, see [Custom Dialog Box](../reference/arkui-ts/ts-methods-custom-dialog-box.md).
## Creating a Custom Dialog Box
1. Use the \@CustomDialog decorator to create a custom dialog box.
2. Set the content for the \@CustomDialog decorated dialog box.
```ts
@CustomDialog
struct CustomDialogExample {
controller: CustomDialogController
build() {
Column() {
Text ('I am content')
.fontSize(20)
.margin({ top: 10, bottom: 10 })
}
}
}
```
3. Create a builder that is bound to the decorator.
```ts
dialogController: CustomDialogController = new CustomDialogController({
builder: CustomDialogExample({}),
})
```
4. Click the component bound to the **onClick** event to display the dialog box.
```ts
Flex({justifyContent:FlexAlign.Center}){
Button('click me')
.onClick(() => {
this.dialogController.open()
})
}.width('100%')
```
![en-us_image_0000001562700493](figures/en-us_image_0000001562700493.png)
## Interaction with Custom Dialog Box
Custom dialog boxes can be used for data interactions to complete a series of response operations.
1. Add button operations to the \@CustomDialog decorator and add the creation of data functions.
```ts
@CustomDialog
struct CustomDialogExample {
controller: CustomDialogController
cancel: () => void
confirm: () => void
build() {
Column() {
Text('I am content') .fontSize(20).margin({ top: 10, bottom: 10 })
Flex({ justifyContent: FlexAlign.SpaceAround }) {
Button('cancel')
.onClick(() => {
this.controller.close()
this.cancel()
}).backgroundColor(0xffffff).fontColor(Color.Black)
Button('confirm')
.onClick(() => {
this.controller.close()
this.confirm()
}).backgroundColor(0xffffff).fontColor(Color.Red)
}.margin({ bottom: 10 })
}
}
}
```
2. Receive the page in the builder and create corresponding function operations.
```ts
dialogController: CustomDialogController = new CustomDialogController({
builder: CustomDialogExample({
cancel: this.onCancel,
confirm: this.onAccept,
}),
alignment: DialogAlignment.Default, // Set the alignment mode of the dialog box. By default, the dialog box is displayed at the bottom.
})
onCancel() {
console.info('Callback when the first button is clicked')
}
onAccept() {
console.info('Callback when the second button is clicked')
}
```
![en-us_image_0000001511421320](figures/en-us_image_0000001511421320.png)
# Progress Indicator
The **\<Progress>** component is used to provide a progress indicator that displays the progress of an operation. For details, see [Progress](../reference/arkui-ts/ts-basic-components-progress.md).
## Creating a Progress Indicator
You can create a progress indicator by calling the following API:
```ts
Progress(options: {value: number, total?: number, type?: ProgressType})
```
Creates a progress bar indicator. In this API, **value** indicates the initial progress, **total** indicates the total progress, and **type** indicates the style of the progress indicator.
```ts
Progress({ value: 24, total: 100, type: ProgressType.Linear }) // Create a linear progress indicator whose total progress is 100 and initial progress is 24.
```
![create](figures/create.png)
## Setting the Progress Indicator Style
Progress indicators come in five styles. When creating a progress indicator, you can specify its style by setting the **ProgressType** parameter to any of the following: **ProgressType.Linear** (linear style), **ProgressType.Ring** (indeterminate ring style), **ProgressType.ScaleRing** (determinate ring style), **ProgressType.Eclipse** (eclipse style), and **ProgressType.Capsule** (capsule style).
- Linear style (default style)
>**NOTE**
>
> Since API version 9, the progress indicator adaptively switches to the vertical layout if the height is greater than the width and remains the horizontal layout if the height is equal to the width.
```ts
Progress({ value: 20, total: 100, type: ProgressType.Linear }).width(200).height(50)
Progress({ value: 20, total: 100, type: ProgressType.Linear }).width(50).height(200)
```
![en-us_image_0000001562700417](figures/en-us_image_0000001562700417.png)
- Indeterminate ring style
```ts
// The progress indicator in the indeterminate ring style on the left: Retain its default settings for the foreground color (blue) and stroke width (2.0 vp).
Progress({ value: 40, total: 150, type: ProgressType.Ring }).width(100).height(100)
// The right progress indicator in the indeterminate ring style on the right.
Progress({ value: 40, total: 150, type: ProgressType.Ring }).width(100).height(100)
.color(Color.Grey) // Set the foreground color to gray.
.style({ strokeWidth: 15}) // Set the stroke width to 15.0 vp.
```
![progress_ring](figures/progress_ring.png)
- Determinate ring style
```ts
Progress({ value: 20, total: 150, type: ProgressType.ScaleRing }).width(100).height(100)
.backgroundColor(Color.Black)
.style({ scaleCount: 20, scaleWidth: 5 }) // Set the total number of scales to 20 and the scale width to 5 vp.
Progress({ value: 20, total: 150, type: ProgressType.ScaleRing }).width(100).height(100)
.backgroundColor(Color.Black)
.style({ strokeWidth: 15, scaleCount: 20, scaleWidth: 5 }) // Set the stroke width to 15, the total number of scales to 20, and the scale width to 5 vp.
Progress({ value: 20, total: 150, type: ProgressType.ScaleRing }).width(100).height(100)
.backgroundColor(Color.Black)
.style({ strokeWidth: 15, scaleCount: 20, scaleWidth: 3 }) // Set the stroke width to 15, the total number of scales to 20, and the scale width to 3 vp.
```
![progress_scalering](figures/progress_scalering.png)
- Eclipse style
```ts
// The progress indicator in the eclipse style on the left: Retain its default settings for the foreground color (blue).
Progress({ value: 10, total: 150, type: ProgressType.Eclipse }).width(100).height(100)
// The progress indicator in the eclipse style on the right: Set its foreground color to gray.
Progress({ value: 20, total: 150, type: ProgressType.Eclipse }).color(Color.Grey).width(100).height(100)
```
![progress_circle](figures/progress_circle.png)
- Capsule style
>**NOTE**
>
>- At both ends, the progress indicator in the capsule style works in a same manner as that in the eclipse style.
>- In the middle part of the capsule, the progress indicator works in a same manner as the linear style.
>
>- If the height is greater than the width, the progress indicator adaptively switches to the vertical layout.
```ts
Progress({ value: 10, total: 150, type: ProgressType.Capsule }).width(100).height(50)
Progress({ value: 20, total: 150, type: ProgressType.Capsule }).width(50).height(100).color(Color.Grey)
Progress({ value: 50, total: 150, type: ProgressType.Capsule }).width(50).height(100).backgroundColor(Color.Black)
```
![progress_captule](figures/progress_captule.png)
## Example Scenario
In this example, the progress of the **\<Progress>** component is updated by clicking the button. After the button is clicked, the value of **progressValue** is incremented and passed to the **\<Progress>** component, which is then updated accordingly.
```ts
@Entry
@Component
struct ProgressCase1 {
@State progressValue: number = 0 // Set the initial progress of the progress indicator to 0.
build() {
Column() {
Column() {
Progress({value:0, total:100, type:ProgressType.Capsule}).width(200).height(50)
.style({strokeWidth:50}).value(this.progressValue)
Row().width('100%').height(5)
Button ("Progress + 5")
.onClick(()=>{
this.progressValue += 5
if (this.progressValue > 100){
this.progressValue = 0
}
})
}
}.width('100%').height('100%')
}
}
```
![progress](figures/progress.gif)
# Radio Button
The **\<Radio>** component allows users to select from a set of mutually exclusive options. Only one radio button in a given group can be selected at the same time. For details, see [Radio](../reference/arkui-ts/ts-basic-components-radio.md).
## Creating a Radio Button
You can create a radio button by calling the following API:
```ts
Radio(options: {value: string, group: string})
```
Creates a radio button. In this API, **value** indicates the name of the radio button, and **group** indicates the name of the group to which the radio button belongs. You can use the **checked** attribute of the radio button to specify whether it is selected. The value **true** means that the radio button is selected. The color and shape cannot be customized for the radio button.
```ts
Radio({ value: 'Radio1', group: 'radioGroup' })
.checked(false)
Radio({ value: 'Radio2', group: 'radioGroup' })
.checked(true)
```
![en-us_image_0000001562820821](figures/en-us_image_0000001562820821.png)
## Adding Events
The **\<Radio>** component supports the [universal events](../reference/arkui-ts/ts-universal-events-click.md). In addition, it can be bound to the **onChange** event so that it responds with custom behavior after being selected.
```ts
Radio({ value: 'Radio1', group: 'radioGroup' })
.onChange((isChecked: boolean) => {
if(isChecked) {
// Operation
}
})
Radio({ value: 'Radio2', group: 'radioGroup' })
.onChange((isChecked: boolean) => {
if(isChecked) {
// Operation
}
})
```
## Example Scenario
In this example, the **\<Radio>** components are used to switch between sound modes.
```ts
// xxx.ets
import promptAction from '@ohos.promptAction';
@Entry
@Component
struct RadioExample {
build() {
Row() {
Column() {
Radio({ value: 'Radio1', group: 'radioGroup' }).checked(true)
.height(50)
.width(50)
.onChange((isChecked: boolean) => {
if(isChecked) {
// Switch to the ringing mode.
promptAction.showToast({ message: 'Ringing mode.' })
}
})
Text('Ringing')
}
Column() {
Radio({ value: 'Radio2', group: 'radioGroup' })
.height(50)
.width(50)
.onChange((isChecked: boolean) => {
if(isChecked) {
// Switch to the vibration mode.
promptAction.showToast({ message: 'Vibration mode.' })
}
})
Text('Vibration')
}
Column() {
Radio({ value: 'Radio3', group: 'radioGroup' })
.height(50)
.width(50)
.onChange((isChecked: boolean) => {
if(isChecked) {
// Switch to the silent mode.
promptAction.showToast({ message: 'Silent mode.' })
}
})
Text('Silent')
}
}.height('100%').width('100%').justifyContent(FlexAlign.Center)
}
}
```
![en-us_image_0000001562700457](figures/en-us_image_0000001562700457.png)
# Text Display
The **\<Text>** component is used to display a piece of textual information. For details, see [Text](../reference/arkui-ts/ts-basic-components-text.md).
## Creating Text
You can create text in either of the following ways:
- Entering strings
```ts
Text ('I am a piece of text')
```
![en-us_image_0000001563060685](figures/en-us_image_0000001563060685.png)
- Referencing Resource objects
You can use **$r** to create a **Resource** object to reference resources in **/resources/base/element/string.json**.
```ts
Text($r('app.string.module_desc'))
.baselineOffset(0)
.fontSize(30)
.border({ width: 1 })
.padding(10)
.width(300)
```
![en-us_image_0000001511580872](figures/en-us_image_0000001511580872.png)
## Adding Child Components
The **\<Text>** component accepts \<[Span](../reference/arkui-ts/ts-basic-components-span.md)> as its child component. You can add one or more **\<Span>** child components to a **\<Text>** component to display a piece of information, such as the product description and statement of commitment.
- Creating a \<Span> Component
The **\<Span>** component works only when included in a **\<Text>** component. If both the **\<Span>** and **\<Text>** components have text configured, the text of the **\<Span>** overwrites that of the **\<Text>** component.
```ts
Text (' I'm Text') {
Span (' I'm Span')
}
.padding(10)
.borderWidth(1)
```
![en-us_image_0000001562700441](figures/en-us_image_0000001562700441.png)
- Set the text decorative line.
Use the **decoration** attribute to set the style and color of the text decorative line.
```ts
Text() {
Span('I'm Span1,') .fontSize (16).fontColor (Color.Grey)
.decoration({ type: TextDecorationType.LineThrough, color: Color.Red })
Span('I'm Span2').fontColor (Color.Blue).fontSize (16)
.fontStyle(FontStyle.Italic)
.decoration({ type: TextDecorationType.Underline, color: Color.Black })
Span('I'm Span3').fontSize(16).fontColor(Color.Grey)
.decoration({ type: TextDecorationType.Overline, color: Color.Green })
}
.borderWidth(1)
.padding(10)
```
![en-us_image_0000001562700437](figures/en-us_image_0000001562700437.png)
- Use the **textCase** attribute to set the text case.
```ts
Text() {
Span('I am Upper-span').fontSize(12)
.textCase(TextCase.UpperCase)
}
.borderWidth(1)
.padding(10)
```
![en-us_image_0000001562940525](figures/en-us_image_0000001562940525.png)
- Adding Events
The **\<Span>** component does not have size information. Therefore, only the **onClick** event is supported.
```ts
Text() {
Span('I am Upper-span').fontSize(12)
.textCase(TextCase.UpperCase)
.onClick(()=>{
console.info (' I'm Span - onClick')
})
}
```
## Setting Styles
- Use the **textAlign** attribute to set the alignment mode of text.
```ts
Text('Left aligned')
.width(300)
.textAlign(TextAlign.Start)
.border({ width: 1 })
.padding(10)
Text ('Center aligned')
.width(300)
.textAlign(TextAlign.Center)
.border({ width: 1 })
.padding(10)
Text('Right aligned')
.width(300)
.textAlign(TextAlign.End)
.border({ width: 1 })
.padding(10)
```
![en-us_image_0000001511421260](figures/en-us_image_0000001511421260.png)
- Use the **textOverflow** attribute to set the display mode for when the text is too long. This attribute must be used together with **maxLines**. By default, the text is automatically folded.
```ts
Text('This is the setting of textOverflow to Clip text content This is the setting of textOverflow to None text content. This is the setting of textOverflow to Clip text content This is the setting of textOverflow to None text content.')
.width(250)
.textOverflow({ overflow: TextOverflow.None })
.maxLines(1)
.fontSize(12)
.border({ width: 1 }).padding(10)
Text('I am extra long text, with an ellipse displayed for any excess.')
.width(250)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.maxLines(1)
.fontSize(12)
.border({ width: 1 }).padding(10)
```
![en-us_image_0000001563060693](figures/en-us_image_0000001563060693.png)
![en-us_image_0000001563060701](figures/en-us_image_0000001563060701.png)
- Use the **lineHeight** attribute to set the text line height.
```ts
Text('This is the text with the line height set. This is the text with the line height set.')
.width(300).fontSize(12).border({ width: 1 }).padding(10)
Text('This is the text with the line height set. This is the text with the line height set.')
.width(300).fontSize(12).border({ width: 1 }).padding(10)
.lineHeight(20)
```
![en-us_image_0000001511740480](figures/en-us_image_0000001511740480.png)
- Use the **decoration** attribute to set the style and color of the text decorative line.
```ts
Text('This is the text')
.decoration({
type: TextDecorationType.LineThrough,
color: Color.Red
})
.borderWidth(1).padding(10).margin(5)
Text('This is the text')
.decoration({
type: TextDecorationType.Overline,
color: Color.Red
})
.borderWidth(1).padding(10).margin(5)
Text('This is the text')
.decoration({
type: TextDecorationType.Underline,
color: Color.Red
})
.borderWidth(1).padding(10).margin(5)
```
![en-us_image_0000001511580888](figures/en-us_image_0000001511580888.png)
- Use the **baselineOffset** attribute to set the baseline offset of the text.
```ts
Text('This is the text content with baselineOffset 0.')
.baselineOffset(0)
.fontSize(12)
.border({ width: 1 })
.padding(10)
.width('100%')
.margin(5)
Text('This is the text content with baselineOffset 30.')
.baselineOffset(30)
.fontSize(12)
.border({ width: 1 })
.padding(10)
.width('100%')
.margin(5)
Text('This is the text content with baselineOffset -20.')
.baselineOffset(-20)
.fontSize(12)
.border({ width: 1 })
.padding(10)
.width('100%')
.margin(5)
```
![en-us_image_0000001562820789](figures/en-us_image_0000001562820789.png)
- Use the **letterSpacing** attribute to set the letter spacing.
```ts
Text('This is the text content with letterSpacing 0.')
.letterSpacing(0)
.fontSize(12)
.border({ width: 1 })
.padding(10)
.width('100%')
.margin(5)
Text('This is the text content with letterSpacing 3.')
.letterSpacing(3)
.fontSize(12)
.border({ width: 1 })
.padding(10)
.width('100%')
.margin(5)
Text('This is the text content with letterSpacing -1.')
.letterSpacing(-1)
.fontSize(12)
.border({ width: 1 })
.padding(10)
.width('100%')
.margin(5)
```
![en-us_image_0000001562940513](figures/en-us_image_0000001562940513.png)
- Use the **minFontSize** and **maxFontSize** attributes to set the minimum and maximum font size, respectively. For the settings to take effect, these attributes must be used together with **maxLines** or layout constraint settings.
```ts
Text('My maximum font size is 30, minimum font size is 5, width is 250, and maximum number of lines is 1')
.width(250)
.maxLines(1)
.maxFontSize(30)
.minFontSize(5)
.border({ width: 1 })
.padding(10)
.margin(5)
Text('My maximum font size is 30, minimum font size is 5, width is 250, and maximum number of lines is 2')
.width(250)
.maxLines(2)
.maxFontSize(30)
.minFontSize(5)
.border({ width: 1 })
.padding(10)
.margin(5)
Text('My maximum font size is 30, minimum font size is 15, width is 250, and line height is 50')
.width(250)
.height(50)
.maxFontSize(30)
.minFontSize(15)
.border({ width: 1 })
.padding(10)
.margin(5)
Text('My maximum font size is 30, minimum font size is 15, width is 250, and line height is 100')
.width(250)
.height(100)
.maxFontSize(30)
.minFontSize(15)
.border({ width: 1 })
.padding(10)
.margin(5)
```
![en-us_image_0000001511740472](figures/en-us_image_0000001511740472.png)
- Use the **textCase** attribute to set the text case.
```ts
Text('This is the text content with textCase set to Normal.')
.textCase(TextCase.Normal)
.padding(10)
.border({ width: 1 })
.padding(10)
.margin(5)
// The text is displayed in lowercase.
Text('This is the text content with textCase set to LowerCase.')
.textCase(TextCase.LowerCase)
.border({ width: 1 })
.padding(10)
.margin(5)
// The text is displayed in uppercase.
Text('This is the text content with textCase set to UpperCase.')
.textCase(TextCase.UpperCase)
.border({ width: 1 })
.padding(10)
.margin(5)
```
![en-us_image_0000001562940529](figures/en-us_image_0000001562940529.png)
- Use the **copyOption** attribute to set whether copy and paste is allowed.
```ts
Text("This text is copyable")
.fontSize(30)
.copyOption(CopyOptions.InApp)
```
![en-us_image_0000001511580868](figures/en-us_image_0000001511580868.png)
## Adding Events
The **\<Text>** component supports the [universal events](../reference/arkui-ts/ts-universal-events-click.md). It can be bound to the **onClick**, **onTouch**, or other events to respond to user operations.
```ts
Text ('Click Me')
.onClick(()=>{
console.info('I am the response to the click event');
})
```
## Example Scenario
```ts
// xxx.ets
@Entry
@Component
struct TextExample {
build() {
Column() {
Row() {
Text("1").fontSize(14).fontColor(Color.Red).margin({ left: 10, right: 10 })
Text("I am entry 1")
.fontSize(12)
.fontColor(Color.Blue)
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.fontWeight(300)
Text ("Top Hit")
.margin({ left: 6 })
.textAlign(TextAlign.Center)
.fontSize(10)
.fontColor(Color.White)
.fontWeight(600)
.backgroundColor(0x770100)
.borderRadius(5)
.width(15)
.height(14)
}.width('100%').margin(5)
Row() {
Text("2").fontSize(14).fontColor(Color.Red).margin({ left: 10, right: 10 })
Text("I am entry 2")
.fontSize(12)
.fontColor(Color.Blue)
.fontWeight(300)
.constraintSize({ maxWidth: 200 })
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
Text ("Hot")
.margin({ left: 6 })
.textAlign(TextAlign.Center)
.fontSize(10)
.fontColor(Color.White)
.fontWeight(600)
.backgroundColor(0xCC5500)
.borderRadius(5)
.width(15)
.height(14)
}.width('100%').margin(5)
Row() {
Text("3").fontSize(14).fontColor(Color.Orange).margin({ left: 10, right: 10 })
Text("I am entry 3")
.fontSize(12)
.fontColor(Color.Blue)
.fontWeight(300)
.maxLines(1)
.constraintSize({ maxWidth: 200 })
.textOverflow({ overflow: TextOverflow.Ellipsis })
Text ("Hot")
.margin({ left: 6 })
.textAlign(TextAlign.Center)
.fontSize(10)
.fontColor(Color.White)
.fontWeight(600)
.backgroundColor(0xCC5500)
.borderRadius(5)
.width(15)
.height(14)
}.width('100%').margin(5)
Row() {
Text("4").fontSize(14).fontColor(Color.Grey).margin({ left: 10, right: 10 })
Text("I am entry 4")
.fontSize(12)
.fontColor(Color.Blue)
.fontWeight(300)
.constraintSize({ maxWidth: 200 })
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
}.width('100%').margin(5)
}.width('100%')
}
}
```
![en-us_image_0000001562820805](figures/en-us_image_0000001562820805.png)
# Text Input
The **\<TextInput>** and **\<TextArea>** components are input components typically used to accept input from the user, such as comments, chat messages, and table content. They can be used in combination with other components to meet more diversified purposes, for example, login and registration. For details, see [TextInput](../reference/arkui-ts/ts-basic-components-textinput.md) and [TextArea](../reference/arkui-ts/ts-basic-components-textarea.md).
## Creating a Text Box
The **\<TextInput>** component provides single-line text input, while the **\<TextArea>** component provides multi-line text input. To create these components, use the following APIs:
```ts
TextArea(value?:{placeholder?: ResourceStr, text?: ResourceStr, controller?: TextAreaController})
```
```ts
TextInput(value?:{placeholder?: ResourceStr, text?: ResourceStr, controller?: TextInputController})
```
- Single-line text box
```ts
TextInput()
```
![en-us_image_0000001511580844](figures/en-us_image_0000001511580844.png)
- Multi-line text box
```ts
TextArea()
```
![en-us_image_0000001562940481](figures/en-us_image_0000001562940481.png)
The **\<TextArea>** component automatically wraps text so that each line does not have more than the width of the component.
```ts
TextArea({text:"I am TextArea I am TextArea I am TextArea"}).width(300)
```
![en-us_image_0000001511580836](figures/en-us_image_0000001511580836.png)
## Setting the Input Box Type
The **\<TextInput>** component comes in five types. You can specify its type by setting the **type** parameter to any of the following: **Normal**, **Password**, **Email**, **Number**, and **PhoneNumber**.
- Normal type (default type)
```ts
TextInput()
.type(InputType.Normal)
```
![en-us_image_0000001562820765](figures/en-us_image_0000001562820765.png)
- Password type
```ts
TextInput()
.type(InputType.Password)
```
![en-us_image_0000001511580840](figures/en-us_image_0000001511580840.png)
## Setting Styles
- Set the placeholder text displayed when there is no input.
TextInput({placeholder:'I am placeholder text'})
```ts
TextInput({placeholder:'I am placeholder text'})
```
![en-us_image_0000001511900400](figures/en-us_image_0000001511900400.png)
- Set the current text input.
```ts
TextInput({placeholder:'I am placeholder text',text:'I am current text input'})
```
![en-us_image_0000001562820761](figures/en-us_image_0000001562820761.png)
- Use **backgroundColor** to set the background color of the text box.
```ts
TextInput({placeholder:'I am placeholder text',text:'I am current text input'})
.backgroundColor(Color.Pink)
```
![en-us_image_0000001511740444](figures/en-us_image_0000001511740444.png)
More styles can be implemented by leveraging the [universal attributes](../reference/arkui-ts/ts-universal-attributes-size.md).
## Adding Events
You can add the **onChange** event for the text box to obtain its content changes. You can also add the universal events to implement user interactions.
```ts
TextInput()
.onChange((value: string) => {
console.info(value);
})
.onFocus(() => {
console.info ('Get Focus');
})
```
## Example Scenario
In this example, the text box is used to submit forms on the user login or registration page.
```ts
@Entry
@Component
struct TextInputSample {
build() {
Column() {
TextInput({ placeholder: 'input your username' }).margin({ top: 20 })
.onSubmit((EnterKeyType)=>{
console.info(EnterKeyType+'Enter key type')
})
TextInput({ placeholder: 'input your password' }).type(InputType.Password).margin({ top: 20 })
.onSubmit((EnterKeyType)=>{
console.info(EnterKeyType+'Enter key type')
})
Button('Sign in').width(150).margin({ top: 20 })
}.padding(20)
}
}
```
![en-us_image_0000001563060653](figures/en-us_image_0000001563060653.png)
# Video Playback
The **\<Video>** component is used to play a video and control its playback. It is usually used in video players and video list pages within applications. A video automatically plays once fully loaded. When the user clicks the video area, the video is paused and the playback progress bar is displayed. The user can drag the progress bar to the desired position. For details, see [Video](../reference/arkui-ts/ts-media-components-video.md).
## Creating a \<Video> Component
You can create a **\<Video>** component by calling the following API:
```ts
Video(value: {src?: string | Resource, currentProgressRate?: number | string | PlaybackSpeed, previewUri?: string | PixelMap | Resource, controller?: VideoController})
```
Creates a **\<Video>** component. In this API, **src** indicates the path of the video source, **currentProgressRate** indicates the video playback speed, **previewUri** indicates the path of the preview image, and **controller** indicates the video controller . For details about how to load a video, see [Loading Video](#loading-video).
## Loading Video
The **\<Video>** component supports both local and online videos.
### Loading a Local Video
- Common local video
To load a local video, specify the corresponding video file in the local **rawfile** directory, as shown in the following figure.
![en-us_image_0000001562700409](figures/en-us_image_0000001562700409.png)
Use **$rawfile()** to reference the video resource.
```ts
@Component
export struct VideoPlayer{
private controller:VideoController;
private previewUris: Resource = $r ('app.media.preview');
private innerResource: Resource = $rawfile('videoTest.mp4');
build(){
Column() {
Video({
src: this.innerResource,
previewUri: this.previewUris,
controller: this.controller
})
}
}
}
```
- Video provided by a [DataAbility](../application-models/dataability-overview.md), whose path contains the **dataability://** prefix<br>Ensure that the corresponding video resource exists.
```ts
@Component
export struct VideoPlayer{
private controller:VideoController;
private previewUris: Resource = $r ('app.media.preview');
private videosrc: string= 'dataability://device_id/com.domainname.dataability.videodata/video/10'
build(){
Column() {
Video({
src: this.videosrc,
previewUri: this.previewUris,
controller: this.controller
})
}
}
}
```
### Loading a Video in the Application Sandbox
To load a video in the application sandbox, use a string with the **file:///data/storage** prefix. Ensure that the application has the read permission to the files in the specified path.
```ts
@Component
export struct VideoPlayer {
private controller: VideoController;
private videosrc: string = 'file:///data/storage/el2/base/haps/entry/files/show.mp4'
build() {
Column() {
Video({
src: this.videosrc,
controller: this.controller
})
}
}
}
```
### Loading an Online Video
To load online videos, you must apply for the **ohos.permission.INTERNET** permission. For details about how to apply for the permission, see [Declaring Permissions](../security/accesstoken-guidelines.md). In this scenario, the **src** attribute indicates the URL of the online video.
```ts
@Component
export struct VideoPlayer{
private controller:VideoController;
private previewUris: Resource = $r ('app.media.preview');
private videosrc: string= 'https://www.example.com/example.mp4' // Replace the URL with that of the actual video to load.
build(){
Column() {
Video({
src: this.videosrc,
previewUri: this.previewUris,
controller: this.controller
})
}
}
}
```
## Adding Attributes
Use the [attributes](../reference/arkui-ts/ts-media-components-video.md#attributes) of the **\<Video>** component to control video playback. For example, you can set whether to mute the video and whether to display the video playback control bar.
```ts
@Component
export struct VideoPlayer {
private controller: VideoController;
build() {
Column() {
Video({
controller: this.controller
})
.muted(false) // Set whether to mute the video.
.controls(false) // Set whether to display the video playback control bar.
.autoPlay(false) // Set whether to enable auto play.
.loop(false) // Set whether to repeat the video.
.objectFit(ImageFit.Contain) // Set the video scale type.
}
}
}
```
## Adding Events
The **\<Video>** component supports various callback events in addition to the universal events. For details, see [Events](../reference/arkui-ts/ts-media-components-video.md#events).
```ts
@Entry
@Component
struct VideoPlayer{
private controller:VideoController;
private previewUris: Resource = $r ('app.media.preview');
private innerResource: Resource = $rawfile('videoTest.mp4');
build(){
Column() {
Video({
src: this.innerResource,
previewUri: this.previewUris,
controller: this.controller
})
.onUpdate((event) => { // Triggered when the playback progress changes.
console.info("Video update.");
})
.onPrepared((event) => { // Triggered when video preparation is complete.
console.info("Video prepared.");
})
.onError(() => { // Triggered when the video playback fails.
console.info("Video error.");
})
}
}
}
```
## Using the Video Controller
The video controller is used to control video playback. For details, see [VideoController](../reference/arkui-ts/ts-media-components-video.md#videocontroller).
- Default controller
The default controller supports four basic features: start playback, pause playback, set the video playback position, and play the video in full screen.
```ts
@Entry
@Component
struct VideoGuide {
@State videoSrc: Resource = $rawfile('videoTest.mp4')
@State previewUri: string = 'common/videoIcon.png'
@State curRate: PlaybackSpeed = PlaybackSpeed.Speed_Forward_1_00_X
build() {
Row() {
Column() {
Video({
src: this.videoSrc,
previewUri: this.previewUri,
currentProgressRate: this.curRate
})
}
.width('100%')
}
.height('100%')
}
}
```
- Custom controller
To use a custom controller, disable the default controller, and then use components such as \<Button> and \<Slider> to customize the control and display. This type of controller is applicable to scenarios where customization requirements are involved.
```ts
@Entry
@Component
struct VideoGuide {
@State videoSrc: Resource = $rawfile('videoTest.mp4')
@State previewUri: string = 'common/videoIcon.png'
@State curRate: PlaybackSpeed = PlaybackSpeed.Speed_Forward_1_00_X
@State isAutoPlay: boolean = false
@State showControls: boolean = true
@State sliderStartTime: string = '';
@State currentTime: number = 0;
@State durationTime: number = 0;
@State durationStringTime: string ='';
controller: VideoController = new VideoController()
build() {
Row() {
Column() {
Video({
src: this.videoSrc,
previewUri: this.previewUri,
currentProgressRate: this.curRate,
controller: this.controller
}).controls(false).autoPlay(true)
.onPrepared((event)=>{
this.durationTime = event.duration
})
.onUpdate((event)=>{
this.currentTime =event.time
})
Row() {
Text(JSON.stringify(this.currentTime) + 's')
Slider({
value: this.currentTime,
min: 0,
max: this.durationTime
})
.onChange((value: number, mode: SliderChangeMode) => {
this.controller.setCurrentTime(value);
}).width("90%")
Text(JSON.stringify(this.durationTime) + 's')
}
.opacity(0.8)
.width("100%")
}
.width('100%')
}
.height('40%')
}
}
```
## Remarks
The **\<Video>** component has encapsulated the basic capabilities of video playback. You do not need to create video instances or set and obtain video information. Simply set the data source and basic information to play videos. To customize video playback, see [Video Playback](../media/video-playback.md).
# XComponent
As a drawing component, the \<[XComponent](../reference/arkui-ts/ts-basic-components-xcomponent.md)> is usually used to meet relatively complex drawing customization requirements, for example, display of a camera preview stream and drawing of a game image.
You can specify the **type** parameter to implement different features. Two options are mainly available for this parameter: **surface** and **component**.
With the **\<XComponent>** of the **surface** type, you can pass data to the surface independently owned by it to render the image.
With the **\<XComponent>** of the **component** type, you can dynamically load the displayed content.
## surface Type
When the **\<XComponent>** is set to the **surface** type, you can write EGL/OpenGL ES and media data and display it on the **\<XComponent>**.
You can also have the **\<XComponent>** laid out and rendered together with other components.
The **\<XComponent>** has an independent surface, which provides a native window for you to create the EGL/OpenGL ES environment on the native (C/C++) side and use the standard OpenGL ES for development.
In addition, media-related applications (such as videos and cameras) can write data to the surface provided by the **\<XComponent>** to present the corresponding image.
## Using EGL/OpenGL ES for Rendering
### Key Points of Native Code Development
OpenHarmony applications use native APIs to implement interactions between JS and C/C++ code. This is also the case with the **\<XComponent>**. For details, see [Using N-APIs in Application Projects](../napi/napi-guidelines.md).
The type of the file for processing the JS logic on the native side is .so.
- Each module has a .so file.
- The .so file is named in the format of lib{moduleName}.so.
In the scenario where the **\<XComponent>** is used for standard OpenGL ES development, the content of the **CMAKELists.txt** file is as follows:
```
cmake_minimum_required(VERSION 3.4.1)
project(XComponent) # Project name
set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})
# Path for searching for header files
include_directories(${NATIVERENDER_ROOT_PATH}
${NATIVERENDER_ROOT_PATH}/include
)
# Compile the target .so file. SHARED indicates the dynamic library.
add_library(nativerender SHARED
xxx.cpp
)
# Search for related libraries (including OpenGL ES libraries and NDK APIs provided by the <XComponent>).
find_library( EGL-lib
EGL )
find_library( GLES-lib
GLESv3 )
find_library( libace-lib
ace_ndk.z )
# Dependencies required for compiling .so files
target_link_libraries(nativerender PUBLIC ${EGL-lib} ${GLES-lib} ${libace-lib} libace_napi.z.so libc++.a)
```
### Registering the N-API Module
```c++
static napi_value Init(napi_env env, napi_value exports)
{
// Define the API exposed on the module.
napi_property_descriptor desc[] ={
DECLARE_NAPI_FUNCTION("changeColor", PluginRender::NapiChangeColor),
};
// You can mount the native method (PluginRender::NapiChangeColor) to exports through this API. exports is bound to a JS object at the JS layer through the JS engine.
NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc));
return exports;
}
static napi_module nativerenderModule = {
.nm_version = 1,
.nm_flags = 0,
.nm_filename = nullptr,
.nm_register_func = Init, // Specify the callback for when the corresponding module is loaded.
.nm_modname = "nativerender", // Specify the module name. For <XComponent>-related development, the name must be the same as the value of libraryname in the <XComponent> on ArkTS.
.nm_priv = ((void*)0),
.reserved = { 0 },
};
extern "C" __attribute__((constructor)) void RegisterModule(void)
{
// Register the SO module.
napi_module_register(&nativerenderModule);c
}
```
### Parsing the NativeXComponent Instance
**NativeXComponent** provides an instance at the native layer for the **\<XComponent>**, which can be used as a bridge for binding with the **\<XComponent>** at the JS layer. The NDK APIs provided by the **\<XComponent>** depend on this instance. For details about the NDK APIs, see [Native XComponent](../reference/native-apis/_o_h___native_x_component.md).
The **NativeXComponent** instance can be obtained by parsing the callback (that is, the **Init** function in [NAPI module registration](#registering-the-n-api-module)) when the module is loaded.
```c++
{
// ...
napi_status status;
napi_value exportInstance = nullptr;
OH_NativeXComponent *nativeXComponent = nullptr;
// Parse the attribute of the wrapped NativeXComponent pointer.
status = napi_get_named_property(env, exports, OH_NATIVE_XCOMPONENT_OBJ, &exportInstance);
if (status != napi_ok) {
return false;
}
// Use the napi_unwrap API to parse the NativeXComponent instance pointer.
status = napi_unwrap(env, exportInstance, reinterpret_cast<void**>(&nativeXComponent));
// ...
}
```
### Registering XComponent Callback
Based on the NativeXComponent pointer obtained by [parsing the NativeXComponent instance](#parsing-the-nativexcomponent-instance), perform callback registration through the **OH_NativeXComponent_RegisterCallback** API.
```c++
{
...
OH_NativeXComponent *nativeXComponent = nullptr;
// Parse the NativeXComponent instance.
OH_NativeXComponent_Callback callback;
callback->OnSurfaceCreated = OnSurfaceCreatedCB; // Invoked when a surface is successfully created. You can obtain the handle to the native window from this event.
callback->OnSurfaceChanged = OnSurfaceChangedCB; // Invoked when the surface changes. You can obtain the native window handle and XComponent change information from this event.
callback->OnSurfaceDestroyed = OnSurfaceDestroyedCB; // Invoked when the surface is destroyed. You can release resources in this event.
callback->DispatchTouchEvent = DispatchTouchEventCB; // Invoked when a touch event occurs. You can obtain the touch event information from this event.
OH_NativeXComponent_RegisterCallback(nativeXComponent, callback);
...
}
```
### Creating the EGL/OpenGL ES Environment
In the registered **OnSurfaceCreated** callback, you can obtain the handle to the native window (which is essentially the surface independently owned by the **\<XComponent>**). Therefore, you can create the EGL/OpenGL ES environment for your application to start the development of the rendering logic.
```c++
EGLCore* eglCore_; // EGLCore is a class that encapsulates OpenGL-related APIs.
uint64_t width_;
uint64_t height_;
void OnSurfaceCreatedCB(OH_NativeXComponent* component, void* window)
{
int32_t ret = OH_NativeXComponent_GetXComponentSize(component, window, &width_, &height_);
if (ret === OH_NATIVEXCOMPONENT_RESULT_SUCCESS) {
eglCore_->GLContextInit(window, width_, height_); // Initialize the OpenGL environment.
}
}
```
### ArkTS Syntax
You can use the **\<XComponent>** to develop EGL/OpenGL ES rendering by using the following code on the ArkTS side:
```ts
XComponent({ id: 'xcomponentId1', type: 'surface', libraryname: 'nativerender' })
.onLoad((context) => {})
.onDestroy(() => {})
```
- **id**: corresponds to an **\<XComponent>** and must be unique. Generally, you can use the **OH_NativeXComponent_GetXComponentId** API on the native side to obtain the corresponding ID and bind the corresponding **\<XComponent>**.
- **libraryname**: name of the loaded module, which must be the same as the value of **nm_modname** used when the Napi module is registered on the native side.
> **NOTE**
>
> An application loads modules to implement cross-language invoking in either of the following modes:
>
> 1. Use the **import** mode of the NAPI.
>
> ```ts
> import nativerender from "libnativerender.so"
> ```
>
> 2. Use the **\<XComponent>**.
>
> While this mode also uses the NAPI mechanism as the **import** mode, it enables you to use the NDK APIs of the **\<XComponent>**, by having the **NativeXComponent** instance of the **\<XComponent>** exposed to the native layer of the application when the dynamic library is loaded.
- **onLoad** event
- Trigger time: when the surface of the **\<XComponent>** is prepared.
- **context** parameter: where the native API exposed on the module is mounted. Its usage is similar to the usage of the **context2** instance obtained after the module is directly loaded using **import context2 from "libnativerender.so"**.
- Time sequence: When the **onLoad** event is subject to the surface. The following figure shows the time sequence of the **onLoad** event and the **OnSurfaceCreated** event on the native side.
![onLoad](figures/onLoad.png)
- **onDestroy** event
Trigger time: when the **\<XComponent>** is destroyed, in the same manner as that when an ArkUI component is destroyed. The following figure shows the time sequence of the **onDestroy** event and the **OnSurfaceDestroyed** event on the native side.
![onDestroy](figures/onDestroy.png)
### Writing Media Data
The surface held by the **\<XComponent>** complies with the producer-consumer model.
In OpenHarmony, components that comply with the producer design, such as the camera and video player, can write data to the surface held by the **\<XComponent>** and display the data through the **\<XComponent>**.
![picture-1](figures/picture-1.png)
You can bind the **\<XComponent>** to the **XComponentController** to obtain the surface ID (**surfaceId**, which uniquely identifies a surface) and send it to the corresponding component API.
```ts
@State surfaceId:string = "";
mXComponentController: XComponentController = new XComponentController();
XComponent({ id: '', type: 'surface', controller: this.mXComponentController })
.onLoad(() => {
this.surfaceId = this.mXComponentController.getXComponentSurfaceId()
})
```
For details about component APIs, see [AVPlayer](../reference/apis/js-apis-media.md#avplayer9) and [Camera](../reference/apis/js-apis-camera.md).
### component Type
When the **\<XComponent>** is set to the **component** type, you can execute non-UI logic to dynamically load the displayed content.
>**NOTE**
>
> When **type** is set to **component**, the **\<XComponent>** functions as a container, where child components are laid out vertically.
>
> - Vertical alignment: [FlexAlign](../reference/arkui-ts/ts-appendix-enums.md#flexalign).Start
>
> - Horizontal alignment: [FlexAlign](../reference/arkui-ts/ts-appendix-enums.md#flexalign).Center
>
> The component does not respond to any events.
>
> Layout changes and event responses can be set by mounting child components.
>
> The non-UI logic written internally needs to be encapsulated in one or more functions.
### Example Scenario
```ts
@Builder
function addText(label: string): void {
Text(label)
.fontSize(40)
}
@Entry
@Component
struct Index {
@State message: string = 'Hello XComponent'
@State messageCommon: string = 'Hello World'
build() {
Row() {
Column() {
XComponent({ id: 'xcomponentId-container', type: 'component' }) {
addText(this.message)
Divider()
.margin(4)
.strokeWidth(2)
.color('#F1F3F5')
.width("80%")
Column() {
Text(this.messageCommon)
.fontSize(30)
}
}
}
.width('100%')
}
.height('100%')
}
}
```
![en-us_image_0000001511900428](figures/en-us_image_0000001511900428.png)
# Keyboard and Mouse Event
Keyboard and mouse events refer to the input events of the peripheral keyboard and mouse.
## Mouse Event
The supported mouse events include the events triggered by the peripheral mouse and touchpad.
Mouse events can trigger the following callbacks.
| Name | Description |
| ---------------------------------------- | ---------------------------------------- |
| onHover(event:&nbsp;(isHover:&nbsp;boolean)&nbsp;=&gt;&nbsp;void) | Triggered when the mouse cursor enters or leaves the component.<br>**isHover**: whether the mouse cursor hovers over the component. The value **true** means that the mouse cursor enters the component, and the value **false** means that the mouse cursor leaves the component.|
| onMouse(event:&nbsp;(event?:&nbsp;MouseEvent)&nbsp;=&gt;&nbsp;void) | Triggered when the component is clicked by a mouse button or the mouse cursor moves on the component. The **event** parameter indicates the timestamp, mouse button, action, coordinates of the clicked point on the entire screen, and coordinates of the clicked point relative to the component when the event is triggered.|
When the component is bound to the **onHover** callback, you can use the [hoverEffect](../reference/arkui-ts/ts-universal-attributes-hover-effect.md) attribute to set the hover effect of the component in hover state.
**Figure 1** Mouse event data flow
![en-us_image_0000001511900504](figures/en-us_image_0000001511900504.png)
When ArkUI receives the mouse event, it checks whether the mouse event concerns pressing, lifting, or moving of the left mouse button, and then responds accordingly.
- Yes: The mouse event is first converted into a touch event in the same position, and a collision test, gesture judgment, and callback response of the touch event are performed. The collision test and callback response of the mouse event are then performed.
- No: Only the collision test and callback response of the mouse event are performed.
>**NOTE**
>
>All touch events and gesture events that can be responded to by a single finger may be operated and responded by using the left mouse button. For example, to implement page redirection invoked by clicking a button with support for finger touches and left-clicks, you just need to bind one click event (**onClick**). If you want to implement different effects for the finger touch and the left-click, you can use the **source** parameter in the **onClick** callback to determine whether the current event is triggered by a finger or a mouse.
### onHover
```ts
onHover(event: (isHover?: boolean) => void)
```
Triggered when the mouse cursor enters or leaves the component. The **isHover** parameter indicates whether the mouse cursor hovers over the component. This event does not support custom bubbling settings. By default, event bubbling occurs between parent and child components.
If this API is bound to a component, it is triggered when the mouse cursor enters the component from outside and the value of **isHover** is **true**, or when the mouse cursor leaves the component and the value of **isHover** is **false**.
>**NOTE**
>
>Event bubbling is an event propagation in the document object model (DOM) when an event is first handled by an element and then bubbles up to its parent element.
```ts
// xxx.ets
@Entry
@Component
struct MouseExample {
@State isHovered: boolean = false;
build() {
Column() {
Button(this.isHovered ? 'Hovered!' : 'Not Hover')
.width(200).height(100)
.backgroundColor(this.isHovered ? Color.Green : Color.Gray)
.onHover((isHover: boolean) => { // Use the onHover API to listen for whether the mouse cursor is hovered over the component.
this.isHovered = isHover;
})
}.width('100%').height('100%').justifyContent(FlexAlign.Center)
}
}
```
In this example, a **\<Button component>** is created, with the initial background color of gray and the content of **Not Hover**. The component is bound to the **onHover** callback. In the callback, **this.isHovered** is set to the callback parameter **isHover**.
When the cursor moves from outside the button to inside the button, the callback is invoked, the value of **isHover** changes to **true**, the value of **isHovered** changes to **true**, the background color of the component changes to **Color.Green**, and the content changes to **Hovered!**.
When the cursor moves from inside the button to outside the button, the callback is invoked, the value of **isHover** changes to **false**, and the component restores to its initial style.
![onHover](figures/onHover.gif)
### onMouse
```ts
onMouse(event: (event?: MouseEvent) => void)
```
Triggered when a mouse event occurs. It is triggered each time an action by the mouse cursor (**MouseAction**) is detected in the component. The parameter is a [MouseEvent](../reference/arkui-ts/ts-universal-mouse-key.md) object, which indicates the mouse event that triggers the callback. This event supports custom bubbling settings. By default, event bubbling occurs between parent and child components. It is commonly used for customized mouse behavior logic processing.
You can use the **MouseEvent** object in the callback to obtain information about the triggered event, including the coordinates (**screenX**/**screenY**/**x**/**y**), button ([MouseButton](../reference/arkui-ts/ts-appendix-enums.md#mousebutton)), action ([MouseAction](../reference/arkui-ts/ts-appendix-enums.md#mouseaction)), timestamp (**timestamp**), display area of the object that triggers the event ([EventTarget](../reference/arkui-ts/ts-universal-events-click.md)), and event source ([SourceType](../reference/arkui-ts/ts-gesture-settings.md)). The **stopPropagation** callback of **MouseEvent** is used to set whether the current event blocks bubbling.
>**NOTE**
>
>**MouseButton** indicates the physical mouse button being pressed or released that triggers the mouse event. The values are **Left**, **Right**, **Middle**, **Back**, **Forward**, and **None**. **None** indicates that no button is pressed or released, which means that the event is triggered by the mouse cursor moving on the component.
```ts
// xxx.ets
@Entry
@Component
struct MouseExample {
@State isHovered: boolean = false;
@State buttonText: string = '';
@State columnText: string = '';
build() {
Column() {
Button(this.isHovered ? 'Hovered!' : 'Not Hover')
.width(200)
.height(100)
.backgroundColor(this.isHovered ? Color.Green : Color.Gray)
.onHover((isHover: boolean) => {
this.isHovered = isHover
})
.onMouse((event: MouseEvent) => { // Set the onMouse callback for the button.
this.buttonText = 'Button onMouse:\n' + '' +
'button = ' + event.button + '\n' +
'action = ' + event.action + '\n' +
'x,y = (' + event.x + ',' + event.y + ')' + '\n' +
'screenXY=(' + event.screenX + ',' + event.screenY + ')';
})
Divider()
Text(this.buttonText).fontColor(Color.Green)
Divider()
Text(this.columnText).fontColor(Color.Red)
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.borderWidth(2)
.borderColor(Color.Red)
.onMouse((event: MouseEvent) => { // Set the onMouse callback for the column.
this.columnText = 'Column onMouse:\n' + '' +
'button = ' + event.button + '\n' +
'action = ' + event.action + '\n' +
'x,y = (' + event.x + ',' + event.y + ')' + '\n' +
'screenXY=(' + event.screenX + ',' + event.screenY + ')';
})
}
}
```
Bind the **onMouse** API to the button based on the **onHover** example. In the callback, the values of the callback parameters, such as **button** and **action**, are displayed. The same settings are performed on the outer **\<Column>** container. The entire process can be divided into the following two actions:
1. Moving the mouse cursor: When the mouse cursor is moved from outside the button to inside the button, only the **onMouse** callback of the **\<Column>** is triggered. When the mouse cursor is moved to the button, as the **onMouse** event bubbles up by default, both the **onMouse** callbacks of the **\<Column>** and **\<Button>** components are invoked. In this process, the mouse cursor moves, but no mouse button is clicked. Therefore, in the displayed information, the value of **button** is 0 (enumerated value of **MouseButton.None**) and the value of **action** is **3** (enumerated value of **MouseAction.Move**).
2. Clicking the mouse button: After the mouse cursor enters the **\<Button>** component, the **\<Button>** component is clicked twice, namely, left-click and right-click.
Left-clicked: button = 1 (enumerated value of **MouseButton.Left**); action = 1 (enumerated value of **MouseAction.Press**); action = 2 (enumerated value of **MouseAction.Release**).
Right-click: button = 2 (enumerated value of **MouseButton.Right**); action = 1 (enumerated value of **MouseAction.Press**); action = 2 (enumerated value of **MouseAction.Release**)
![onMouse1](figures/onMouse1.gif)
To prevent the mouse event from bubbling, call the **stopPropagation()** API.
```ts
Button(this.isHovered ? 'Hovered!' : 'Not Hover')
.width(200)
.height(100)
.backgroundColor(this.isHovered ? Color.Green : Color.Gray)
.onHover((isHover: boolean) => {
this.isHovered = isHover;
})
.onMouse((event: MouseEvent) => {
event.stopPropagation(); // Prevent the mouse event from bubbling.
this.buttonText = 'Button onMouse:\n' + '' +
'button = ' + event.button + '\n' +
'action = ' + event.action + '\n' +
'x,y = (' + event.x + ',' + event.y + ')' + '\n' +
'screenXY=(' + event.screenX + ',' + event.screenY + ')';
})
```
To prevent the mouse event of the child component (**\<Button>**) from bubbling up to its parent component (**\<Column>**), use the **event** parameter in the **onMouse** callback of **\<Button>** to call the **stopPropagation** API.
```ts
event.stopPropagation()
```
With bubbling prevented, the mouse event on the **\<Button>** component will trigger the **onMouse** callback of the **\<Button>** component, but not the **onMouse** callback of the **\<Column>** component.
### hoverEffect
```ts
hoverEffect(value: HoverEffect)
```
Sets the hover effect of the component in hover state. The parameter value type is **HoverEffect**. The **Auto**, **Scale**, and **Highlight** effects are preset and do not support customization.
**Table 1** HoverEffect
| Enum| Description |
| -------------- | ---------------------------------------- |
| Auto | Default hover effect, which varies by component. |
| Scale | Scale effect. When the mouse cursor is placed over the component, the component is scaled up from 100% to 105%. When the mouse cursor is moved away, the component is scaled down from 105% to 100%.|
| Highlight | Background fade-in and fade-out effect. When the mouse cursor is placed over the component, a white layer with 5% opacity is applied to the background color of the component, resulting in a dimmed background. When the mouse cursor is moved away, the background color of the component is restored to the original style.|
| None | No effect. |
```ts
// xxx.ets
@Entry
@Component
struct HoverExample {
build() {
Column({ space: 10 }) {
Button('Auto')
.width(170).height(70)
Button('Scale')
.width(170).height(70)
.hoverEffect(HoverEffect.Scale)
Button('Highlight')
.width(170).height(70)
.hoverEffect(HoverEffect.Highlight)
Button('None')
.width(170).height(70)
.hoverEffect(HoverEffect.None)
}.width('100%').height('100%').justifyContent(FlexAlign.Center)
}
}
```
![hoverEffect](figures/hoverEffect.gif)
For the **\<Button>** component, **Auto** creates the same effect as **Scale**.
## Key Event
**Figure 2** Key event data flow
![en-us_image_0000001511580944](figures/en-us_image_0000001511580944.png)
The key event is triggered by a device such as a peripheral keyboard, and is sent to the currently focused window after being converted by the driver and multi-mode processing. After obtaining the event, the window distributes the event to the input method (which consumes the key as the input). If the input method does not consume the key event, the window sends the event to the ArkUI framework. Therefore, when an input box component has focus and an input method is enabled, most key events are consumed by the input method. For example, a letter key is used by the input method to enter a letter in the input box, and an arrow key is used by the input method to switch to the desired candidate word.
After the key event is sent to the ArkUI framework, it first identifies the complete focus chain, and then sends the key event one by one from the leaf node to the root node.
### onKeyEvent
```ts
onKeyEvent(event: (event?: KeyEvent) => void)
```
Triggered when the bound component has [focus](arkts-common-events-focus-event.md) and a key event occurs on the component. The callback parameter [KeyEvent](../reference/arkui-ts/ts-universal-events-key.md) can be used to obtain the information about the key event, including [KeyType](../reference/arkui-ts/ts-appendix-enums.md#keytype), [keyCode](../reference/apis/js-apis-keycode.md), keyText, [KeySource](../reference/arkui-ts/ts-appendix-enums.md#keysource), **deviceId**, **metaKey**, **timestamp**, and **stopPropagation**.
```ts
// xxx.ets
@Entry
@Component
struct KeyEventExample {
@State buttonText: string = '';
@State buttonType: string = '';
@State columnText: string = '';
@State columnType: string = '';
build() {
Column() {
Button('onKeyEvent')
.width(140).height(70)
.onKeyEvent((event: KeyEvent) => {// Set the onKeyEvent event for the <Button> component.
if (event.type === KeyType.Down) {
this.buttonType = 'Down';
}
if (event.type === KeyType.Up) {
this.buttonType = 'Up';
}
this.buttonText = 'Button: \n' +
'KeyType:' + this.buttonType + '\n' +
'KeyCode:' + event.keyCode + '\n' +
'KeyText:' + event.keyText;
})
Divider()
Text(this.buttonText).fontColor(Color.Green)
Divider()
Text(this.columnText).fontColor(Color.Red)
}.width('100%').height('100%').justifyContent(FlexAlign.Center)
.onKeyEvent((event: KeyEvent) => {// Set the onKeyEvent event for the parent container <Column>.
if (event.type === KeyType.Down) {
this.columnType = 'Down';
}
if (event.type === KeyType.Up) {
this.columnType = 'Up';
}
this.columnText = 'Column: \n' +
'KeyType:' + this.buttonType + '\n' +
'KeyCode:' + event.keyCode + '\n' +
'KeyText:' + event.keyText;
})
}
}
```
In the preceding example, **onKeyEvent** is bound to the **\<Button>** component and its parent container **\<Column>**. After the application opens and loads a page, the first focusable non-container component in the component tree automatically obtains focus. As the application has only one **\<Button>** component, the component automatically obtains focus. Because the **\<Button>** component is a child node of the **\<Column>** component, the **\<Column>** component also obtains focus. For details about the focus obtaining mechanism, see [Focus Event](arkts-common-events-focus-event.md).
![en-us_image_0000001511421324](figures/en-us_image_0000001511421324.gif)
After the application is opened, press the following keys in sequence: Space, Enter, Left Ctrl, Left Shift, Letter A, and Letter Z.
1. Because the **onKeyEvent** event bubbles by default, the **onKeyEvent** callbacks of both **\<Button>** and **\<Column>** are invoked.
2. Each key has two callbacks, which correspond to **KeyType.Down** and **KeyType.Up** respectively, indicating that the key is pressed and then lifted.
To prevent the key event of the **\<Button>** component from bubbling up to its parent container **\<Column>**, add the **event.stopPropagation()** API to the **onKeyEvent** callback of **\<Button>**.
```ts
Button('onKeyEvent')
.width(140).height(70)
.onKeyEvent((event: KeyEvent) => {
// Use stopPropagation to prevent the key event from bubbling up.
event.stopPropagation();
if (event.type === KeyType.Down) {
this.buttonType = 'Down';
}
if (event.type === KeyType.Up) {
this.buttonType = 'Up';
}
this.buttonText = 'Button: \n' +
'KeyType:' + this.buttonType + '\n' +
'KeyCode:' + event.keyCode + '\n' +
'KeyText:' + event.keyText;
})
```
![en-us_image_0000001511900508](figures/en-us_image_0000001511900508.gif)
# Touchscreen Event
Touchscreen events are events triggered when a finger or stylus is placed on, moved along, or lifted from a component. They can be classified as [click event](#click-event), [drag event](#drag-event), or [touch event](#touch-event).
**Figure 1** Touchscreen event principles
![en-us_image_0000001562700461](figures/en-us_image_0000001562700461.png)
## Click Event
A click event is triggered when a complete press and lift action performed by using a finger or a stylus. When a click event occurs, the following callback is triggered:
```ts
onClick(event: (event?: ClickEvent) => void)
```
The **event** parameter provides the coordinates of the click relative to the window or component as well as the event source where the click occurs, for example, a button, a click on which shows or hides an image.
```ts
@Entry
@Component
struct IfElseTransition {
@State flag: boolean = true;
@State btnMsg: string = 'show';
build() {
Column() {
Button(this.btnMsg).width(80).height(30).margin(30)
.onClick(() => {
if (this.flag) {
this.btnMsg = 'hide';
} else {
this.btnMsg = 'show';
}
// Click the button to show or hide the image.
this.flag = !this.flag;
})
if (this.flag) {
Image($r('app.media.icon')).width(200).height(200)
}
}.height('100%').width('100%')
}
}
```
## Drag Event
A drag event is triggered when a user long presses a component (&gt;=500 ms) using a finger or stylus and drags the component to the drop target. The following figure illustrates the process of triggering a drag event.
![en-us_image_0000001562820825](figures/en-us_image_0000001562820825.png)
Whether a drag event can be triggered depends on the distance of long-pressing and dragging with the finger or stylus on the screen. The drag event is triggered when this distance reaches 5 vp. ArkUI supports intra-application and cross-application drag events.
The drag event provides the following [APIs](../reference/arkui-ts/ts-universal-events-drag-drop.md).
| API | Description |
| ---------------------------------------- | ---------------------------------------- |
| onDragStart(event: (event?: DragEvent, extraParams?: string) =&gt; CustomBuilder \| DragItemInfo) | Triggered when dragging starts. Currently, only custom **pixelmap** objects and custom components are supported. |
| onDragEnter(event: (event?: DragEvent, extraParams?: string) =&gt; void) | Triggered when the dragged item enters a valid drop target.<br/>**DragEvent**: position where the drag occurs.<br>**extraParmas**: custom information about the drag event. |
| onDragLeave(event: (event?: DragEvent, extraParams?: string) =&gt; void) | Triggered when the dragged item leaves a valid drop target.<br/>**DragEvent**: position where the drag occurs.<br>**extraParmas**: custom information about the drag event. |
| onDragMove(event: (event?: DragEvent, extraParams?: string) =&gt; void) | Triggered when the dragged item moves in a valid drop target.<br/>**DragEvent**: position where the drag occurs.<br>**extraParmas**: custom information about the drag event. |
| onDrop(event: (event?: DragEvent, extraParams?: string) =&gt; void) | Triggered when the dragged item is dropped on a valid drop target.<br/>**DragEvent**: position where the drag occurs.<br>**extraParmas**: custom information about the drag event. |
The following is an example of dragging a component out of a window in cross-window dragging:
```ts
import image from '@ohos.multimedia.image';
@Entry
@Component
struct Index {
@State visible: Visibility = Visibility.Visible
private pixelMapReader = undefined
aboutToAppear() {
console.info('begin to create pixmap has info message: ')
this.createPixelMap()
}
createPixelMap() {
let color = new ArrayBuffer(4 * 96 * 96);
var buffer = new Uint8Array(color);
for (var i = 0; i < buffer.length; i++) {
buffer[i] = (i + 1) % 255;
}
let opts = {
alphaType: 0,
editable: true,
pixelFormat: 4,
scaleMode: 1,
size: { height: 96, width: 96 }
}
const promise = image.createPixelMap(color, opts);
promise.then((data) => {
console.info('create pixmap has info message: ' + JSON.stringify(data))
this.pixelMapReader = data;
})
}
@Builder pixelMapBuilder() {
Text('drag item')
.width('100%')
.height(100)
.fontSize(16)
.textAlign(TextAlign.Center)
.borderRadius(10)
.backgroundColor(0xFFFFFF)
}
build() {
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
Text('App1')
.width('40%')
.height(80)
.fontSize(20)
.margin(30)
.textAlign(TextAlign.Center)
.backgroundColor(Color.Pink)
.visibility(Visibility.Visible)
Text('Across Window Drag This')
.width('80%')
.height(80)
.fontSize(16)
.margin(30)
.textAlign(TextAlign.Center)
.backgroundColor(Color.Pink)
.visibility(this.visible)
.onDragStart(() => { // Triggered when cross-window dragging starts.
console.info('Text onDrag start')
return { pixelMap: this.pixelMapReader, extraInfo: 'custom extra info.' }
})
.onDrop((event: DragEvent, extraParams: string) => {
console.info('Text onDragDrop, ')
this.visible = Visibility.None // Make the source invisible after the dragging is complete.
})
}
.width('100%')
.height('100%')
}
}
```
The following is an example of dragging a component into a window in cross-window dragging:
```ts
@Entry
@Component
struct Index {
@State number: string[] = ['drag here']
@State text: string = ''
@State bool1: boolean = false
@State bool2: boolean = false
@State visible: Visibility = Visibility.Visible
@State visible2: Visibility = Visibility.None
scroller: Scroller = new Scroller()
build() {
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
Text('App2')
.width('40%')
.height(80)
.fontSize(20)
.margin(30)
.textAlign(TextAlign.Center)
.backgroundColor(Color.Pink)
.visibility(Visibility.Visible)
List({ space: 20, initialIndex: 0 }) {
ForEach(this.number, (item) => {
ListItem() {
Text('' + item)
.width('100%')
.height(80)
.fontSize(16)
.borderRadius(10)
.textAlign(TextAlign.Center)
.backgroundColor(0xFFFFFF)
}
}, item => item)
ListItem() {
Text('Across Window Drag This')
.width('80%')
.height(80)
.fontSize(16)
.margin(30)
.textAlign(TextAlign.Center)
.backgroundColor(Color.Pink)
.visibility(this.visible2)
}
}
.height('50%')
.width('90%')
.border({ width: 1 })
.divider({ strokeWidth: 2, color: 0xFFFFFF, startMargin: 20, endMargin: 20 })
.onDragEnter((event: DragEvent, extraParams: string) => { // Drag the component into the window.
console.info('List onDragEnter, ' + extraParams)
})
.onDragMove((event: DragEvent, extraParams: string) => { // Move the component during dragging.
console.info('List onDragMove, ' + extraParams)
})
.onDragLeave((event: DragEvent, extraParams: string) => { // Drag the component out of the window.
console.info('List onDragLeave, ' + extraParams)
})
.onDrop((event: DragEvent, extraParams: string) => { // Release the component.
console.info('List onDragDrop, ' + extraParams)
this.visible2 = Visibility.Visible // Make the dragged object visible.
})
}
.width('100%')
.height('100%')
}
}
```
## Touch Event
A touch event is triggered when a finger or stylus is placed on, moved along, or lifted from a component.
```ts
onTouch(event: (event?: TouchEvent) => void)
```
- If **event.type** is **TouchType.Down**, the finger or stylus is placed on the component.
- If **event.type** is **TouchType.Up**, the finger or stylus is lifted from the component.
- If **event.type** is **TouchType.Move**, the finger or stylus is moved along the component.
The touch event supports single and multi-touch interactions. Information about the touch event can be obtained using the **event** parameter, such as the location of the finger that triggers the event, unique identifier of the finger, finger information changed, and the input device source.
```ts
// xxx.ets
@Entry
@Component
struct TouchExample {
@State text: string = '';
@State eventType: string = '';
build() {
Column() {
Button('Touch').height(40).width(100)
.onTouch((event: TouchEvent) => {
if (event.type === TouchType.Down) {
this.eventType = 'Down';
}
if (event.type === TouchType.Up) {
this.eventType = 'Up';
}
if (event.type === TouchType.Move) {
this.eventType = 'Move';
}
this.text = 'TouchType:' + this.eventType + '\nDistance between touch point and touch element:\nx: '
+ event.touches[0].x + '\n' + 'y: ' + event.touches[0].y + '\nComponent globalPos:('
+ event.target.area.globalPosition.x + ',' + event.target.area.globalPosition.y + ')\nwidth:'
+ event.target.area.width + '\nheight:' + event.target.area.height
})
Button('Touch').height(50).width(200).margin(20)
.onTouch((event: TouchEvent) => {
if (event.type === TouchType.Down) {
this.eventType = 'Down';
}
if (event.type === TouchType.Up) {
this.eventType = 'Up';
}
if (event.type === TouchType.Move) {
this.eventType = 'Move';
}
this.text = 'TouchType:' + this.eventType + '\nDistance between touch point and touch element:\nx: '
+ event.touches[0].x + '\n' + 'y: ' + event.touches[0].y + '\nComponent globalPos:('
+ event.target.area.globalPosition.x + ',' + event.target.area.globalPosition.y + ')\nwidth:'
+ event.target.area.width + '\nheight:' + event.target.area.height
})
Text(this.text)
}.width('100%').padding(30)
}
}
```
![en-us_image_0000001511900468](figures/en-us_image_0000001511900468.gif)
# Drawing Custom Graphics Using the Canvas
**Canvas** provides a canvas component for drawing custom graphics. You can use the **CanvasRenderingContext2D** and **OffscreenCanvasRenderingContext2D** objects to draw graphics on the **Canvas** component. The drawing objects can be basic shapes, text, and images.
## Drawing Custom Graphics on the Canvas
You can draw custom graphics on the canvas in any of the following ways:
- Use [CanvasRenderingContext2D](../reference/arkui-ts/ts-canvasrenderingcontext2d.md).
```ts
@Entry
@Component
struct CanvasExample1 {
// Configure the parameters of the CanvasRenderingContext2D object, including whether to enable anti-aliasing. The value true indicates that anti-aliasing is enabled.
private settings: RenderingContextSettings = new RenderingContextSettings(true)
// Create a CanvasRenderingContext2D object by calling CanvasRenderingContext2D object in Canvas.
private context: CanvasRenderingContext2D= new CanvasRenderingContext2D(this.settings)
build() {
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
// Invoke the CanvasRenderingContext2D object in Canvas.
Canvas(this.context)
.width('100%')
.height('100%')
.backgroundColor('#F5DC62')
.onReady(() =>{
// You can draw content here.
this.context.strokeRect(50, 50, 200, 150);
})
}
.width('100%')
.height('100%')
}
}
```
![2023022793003(1)](figures/2023022793003(1).jpg)
- Drawing offscreen onto a canvas is a process where content to draw onto the canvas is first drawn in the buffer, and then converted into a picture, and finally the picture is drawn on the canvas. This process increases the drawing efficiency. Specifically, the implementation is as follows:
1. Use the **transferToImageBitmap** API to create an **ImageBitmap** object for the image that is recently rendered off the screen canvas.
2. Use the **transferFromImageBitmap** API of the **CanvasRenderingContext2D** object to display the created **ImageBitmap** object.
For details, see [OffscreenCanvasRenderingContext2D](../reference/arkui-ts/ts-offscreencanvasrenderingcontext2d.md).
```ts
@Entry
@Component
struct CanvasExample2 {
// Configure the parameters of the CanvasRenderingContext2D and OffscreenCanvasRenderingContext2D objects, including whether to enable anti-aliasing. The value true indicates that anti-aliasing is enabled.
private settings: RenderingContextSettings = new RenderingContextSettings(true)
private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings)
// Create an OffscreenCanvasRenderingContext2D object. width indicates the width of the offscreen canvas, and height indicates the height of the offscreen canvas.
private offContext: OffscreenCanvasRenderingContext2D = new OffscreenCanvasRenderingContext2D(600, 600, this.settings)
build() {
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
Canvas(this.context)
.width('100%')
.height('100%')
.backgroundColor('#F5DC62')
.onReady(() =>{
// You can draw content here.
this.offContext.strokeRect(50, 50, 200, 150);
// Display the image rendered by the offscreen drawing value on the common canvas.
let image = this.offContext.transferToImageBitmap();
this.context.transferFromImageBitmap(image);
})
}
.width('100%')
.height('100%')
}
}
```
![2023022793003(1)](figures/2023022793003(1).jpg)
>**NOTE**
>
>The APIs called for drawing on the canvas through the **CanvasRenderingContext2D** and **OffscreenCanvasRenderingContext2D** objects are the same. Unless otherwise specified, the value unit of the parameters in these APIs is vp.
- Before loading the Lottie animation on the canvas, download the Lottie as follows:
```ts
import lottie from '@ohos/lottie'
```
For details about the APIs, see [Lottie](../reference/arkui-ts/ts-components-canvas-lottie.md).
>**NOTE**
>
>Before using Lottie for the first time, run the **ohpm install \@ohos/lottieETS** command in the Terminal window to download Lottie.
## Initializing the Canvas Component
**onReady(event: () =&gt; void)** is the event callback when the **Canvas** component initialization is complete. After this event is called, the determined width and height of the **Canvas** component can be obtained. The **CanvasRenderingContext2D** and **OffscreenCanvasRenderingContext2D** objects can then be used to call related APIs to draw graphics.
```ts
Canvas(this.context)
.width('100%')
.height('100%')
.backgroundColor('#F5DC62')
.onReady(() => {
this.context.fillStyle = '#0097D4';
this.context.fillRect(50, 50, 100, 100);
})
```
![2023022793350(1)](figures/2023022793350(1).jpg)
## Canvas Component Drawing Modes
Two modes are available for drawing with the **Canvas** component:
- After the **onReady()** callback of the **Canvas** component is invoked, use the **CanvasRenderingContext2D** and **OffscreenCanvasRenderingContext2D** objects to call related APIs for drawing.
```ts
Canvas(this.context)
.width('100%')
.height('100%')
.backgroundColor('#F5DC62')
.onReady(() =>{
this.context.beginPath();
this.context.moveTo(50, 50);
this.context.lineTo(280, 160);
this.context.stroke();
})
```
![2023022793719(1)](figures/2023022793719(1).jpg)
- Define an individual **path2d** object to build an ideal path, and then call the **stroke** or **fill** API of the **CanvasRenderingContext2D** and **OffscreenCanvasRenderingContext2D** objects to draw the path. For details, see [Path2D](../reference/arkui-ts/ts-components-canvas-path2d.md).
```ts
Canvas(this.context)
.width('100%')
.height('100%')
.backgroundColor('#F5DC62')
.onReady(() =>{
let region = new Path2D();
region.arc(100, 75, 50, 0, 6.28);
this.context.stroke(region);
})
```
![2023022794031(1)](figures/2023022794031(1).jpg)
## Common Usage of the Canvas Component
**OffscreenCanvasRenderingContext2D** and **CanvasRenderingContext2D** provide a large number of attributes and methods, which can be used to draw text and graphics and process pixels. They are the core of the **Canvas** component. Common APIs include [fill](../reference/arkui-ts/ts-canvasrenderingcontext2d.md#fill), [clip](../reference/arkui-ts/ts-canvasrenderingcontext2d.md#clip), and [stroke](../reference/arkui-ts/ts-canvasrenderingcontext2d.md#stroke). In addition, [fillStyle](../reference/arkui-ts/ts-canvasrenderingcontext2d.md#fillstyle), [globalAlpha](../reference/arkui-ts/ts-canvasrenderingcontext2d.md#globalalpha), [strokeStyle](../reference/arkui-ts/ts-canvasrenderingcontext2d.md#strokestyle), and more attributes are provided to spruce up the graphics. This topic describes typical usage of the canvas.
- Draw a basic shape.
You can draw a basic shape by calling APIs such as [arc](../reference/arkui-ts/ts-canvasrenderingcontext2d.md#arc), [ellipse](../reference/arkui-ts/ts-canvasrenderingcontext2d.md#ellipse), and [rect](../reference/arkui-ts/ts-canvasrenderingcontext2d.md#rect).
```ts
Canvas(this.context)
.width('100%')
.height('100%')
.backgroundColor('#F5DC62')
.onReady(() =>{
// Draw a rectangle.
this.context.beginPath();
this.context.rect(100, 50, 100, 100);
this.context.stroke();
// Draw a circle on the canvas.
this.context.beginPath();
this.context.arc(150, 250, 50, 0, 6.28);
this.context.stroke();
// Draw an oval on the canvas.
this.context.beginPath();
this.context.ellipse(150, 450, 50, 100, Math.PI * 0.25, Math.PI * 0, Math.PI * 2);
this.context.stroke();
})
```
![2023022794521(1)](figures/2023022794521(1).jpg)
- Draw text.
You can use APIs such as [fillText](../reference/arkui-ts/ts-canvasrenderingcontext2d.md#filltext) and [strokeText](../reference/arkui-ts/ts-canvasrenderingcontext2d.md#stroketext) to draw text.
```ts
Canvas(this.context)
.width('100%')
.height('100%')
.backgroundColor('#F5DC62')
.onReady(() =>{
// Draw filled text on the canvas.
this.context.font = '50px sans-serif';
this.context.fillText("Hello World!", 50, 100);
// Draw a text stroke on the canvas.
this.context.font = '55px sans-serif';
this.context.strokeText("Hello World!", 50, 150);
})
```
![2023022795105(1)](figures/2023022795105(1).jpg)
- Draw images and processes image pixel information.
You can draw an image by calling APIs such as [drawImage](../reference/arkui-ts/ts-canvasrenderingcontext2d.md#drawimage) and [putImageData](../reference/arkui-ts/ts-canvasrenderingcontext2d.md#putimagedata). You can also process image pixel information by calling APIs such as [createImageData](../reference/arkui-ts/ts-canvasrenderingcontext2d.md#createimagedata), [getPixelMap](../reference/arkui-ts/ts-canvasrenderingcontext2d.md#getpixelmap), and [getImageData](../reference/arkui-ts/ts-canvasrenderingcontext2d.md#getimagedata).
```ts
@Entry
@Component
struct GetImageData {
private settings: RenderingContextSettings = new RenderingContextSettings(true)
private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings)
private offContext: OffscreenCanvasRenderingContext2D = new OffscreenCanvasRenderingContext2D(600, 600, this.settings)
private img:ImageBitmap = new ImageBitmap("/common/images/1234.png")
build() {
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
Canvas(this.context)
.width('100%')
.height('100%')
.backgroundColor('#F5DC62')
.onReady(() =>{
// Use the drawImage API to draw an image in the area with the width and height of 130 starting from (0, 0).
this.offContext.drawImage(this.img,0,0,130,130);
// Use the getImageData API to obtain the image data with the width and height of 130 starting from (50, 50).
let imagedata = this.offContext.getImageData(50,50,130,130);
// Use the putImageData API to draw the obtained image data in the area starting from (150, 150).
this.offContext.putImageData(imagedata,150,150);
// Draw the offscreen drawing content to the canvas.
let image = this.offContext.transferToImageBitmap();
this.context.transferFromImageBitmap(image);
})
}
.width('100%')
.height('100%')
}
}
```
![drawimage](figures/drawimage.PNG)
- Other usage
**Canvas** also provides other usage. For example, regarding [CanvasGradient](../reference/arkui-ts/ts-components-canvas-canvasgradient.md), you can create a linear gradient with [createLinearGradient](../reference/arkui-ts/ts-canvasrenderingcontext2d.md#createlineargradient) or create a radial gradient with [createRadialGradient](../reference/arkui-ts/ts-canvasrenderingcontext2d.md#createradialgradient), among others.
```ts
Canvas(this.context)
.width('100%')
.height('100%')
.backgroundColor('#F5DC62')
.onReady(() =>{
// Create a CanvasGradient object with radial gradient colors.
let grad = this.context.createRadialGradient(200,200,50, 200,200,200)
// Set the gradient color stop for the CanvasGradient object, including the offset and colors.
grad.addColorStop(0.0, '#E87361');
grad.addColorStop(0.5, '#FFFFF0');
grad.addColorStop(1.0, '#BDDB69');
// Fill the rectangle with the CanvasGradient object.
this.context.fillStyle = grad;
this.context.fillRect(0, 0, 400, 400);
})
```
![2023022700701(1)](figures/2023022700701(1).jpg)
## Example Scenario
- Draw a basic shape.
```ts
@Entry
@Component
struct ClearRect {
private settings: RenderingContextSettings = new RenderingContextSettings(true);
private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);
build() {
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
Canvas(this.context)
.width('100%')
.height('100%')
.backgroundColor('#F5DC62')
.onReady(() =>{
// Set the fill color to blue.
this.context.fillStyle = '#0097D4';
// Take (50, 50) as the upper left corner and draw a rectangle with the width and height of 200.
this.context.fillRect(50,50,200,200);
// Use (70, 70) as the upper left corner and clear the area with the width of 150 and height of 100.
this.context.clearRect(70,70,150,100);
})
}
.width('100%')
.height('100%')
}
}
```
![2023022701120(1)](figures/2023022701120(1).jpg)
- Draw an irregular shape.
```ts
@Entry
@Component
struct Path2d {
private settings: RenderingContextSettings = new RenderingContextSettings(true);
private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);
build() {
Row() {
Column() {
Canvas(this.context)
.width('100%')
.height('100%')
.backgroundColor('#F5DC62')
.onReady(() =>{
// Use the Path2D API to create a pentagon.
let path = new Path2D();
path.moveTo(150, 50);
path.lineTo(50, 150);
path.lineTo(100, 250);
path.lineTo(200, 250);
path.lineTo(250, 150);
path.closePath();
// Set the fill color to blue.
this.context.fillStyle = '#0097D4';
// Draw the pentagon described by Path2D in the canvas in fill mode.
this.context.fill(path);
})
}
.width('100%')
}
.height('100%')
}
}
```
![2023032422159](figures/2023032422159.jpg)
# Event Overview
Interaction events are classified into touchscreen events, keyboard and mouse events, and focus events based on trigger types.
- [Touchscreen event](arkts-common-events-touch-screen-event.md): single-finger or single-stroke operation performed by a finger or stylus on the touchscreen.
- [Keyboard and mouse event](arkts-common-events-device-input-event.md): includes operation events of the peripheral mouse or touchpad and key events of the peripheral keyboard.
- The mouse event refers to an event responded to when an operation is performed with a peripheral mouse/touchpad.
- The key event refer to an event responded to when an operation is performed with a peripheral keyboard.
- [Focus event](arkts-common-events-focus-event.md): controls the focusability and response events of the component in the preceding ways.
The gesture event includes the gesture binding method and the bound gesture. The bound gesture may be classified into two types: a single gesture and a combined gesture, and is distinguished according to complexity of the gesture.
- [Gesture binding](arkts-gesture-events-binding.md): binds a single gesture or a combination of gestures to a component and declares the response priority of the bound gesture.
- [Single gesture](arkts-gesture-events-single-gesture.md): basic unit of a gesture, which is a part of all complex gestures.
- [Combined gesture](arkts-gesture-events-combined-gestures.md): a combination of multiple single gestures. Multiple single gestures can be combined into a combined gesture according to a declared type and a certain rule, and the combined gesture can be used.
# Drawing Geometric Shapes
The drawing components are used to draw graphs on the page. The **\<Shape>** component is the parent component of the drawing components. The attributes of **\<Shape>** are universal attributes supported by all the drawing components. For details, see [Shape](../reference/arkui-ts/ts-drawing-components-shape.md).
## Creating a Drawing Component
A drawing component can be created in either of the following ways:
- Create a drawing component with **\<Shape>** as their parent to implement the effect similar to SVG. The API used is as follows:
```ts
Shape(value?: PixelMap)
```
In the API, the **value** parameter sets the drawing target. You can draw a graph in the specified **PixelMap** object. If the **value** parameter is not set, the graph is drawn in the current drawing target.
```ts
Shape() {
Rect().width(300).height(50)
}
```
- Create an independent drawing component to draw a specific shape. Seven shapes are supported: [Circle](../reference/arkui-ts/ts-drawing-components-circle.md), [Ellipse](../reference/arkui-ts/ts-drawing-components-ellipse.md), [Line](../reference/arkui-ts/ts-drawing-components-line.md), [Polyine](../reference/arkui-ts/ts-drawing-components-polyline.md), [Polygon](../reference/arkui-ts/ts-drawing-components-polygon.md), [Path](../reference/arkui-ts/ts-drawing-components-path.md), and [Rect](../reference/arkui-ts/ts-drawing-components-rect.md). The following uses the **Circle** API as an example:
```ts
Circle(options?: {width?: string | number, height?: string | number}
```
This API draws a circle on a page. The **width** parameter indicates the width of the circle, and the **height** parameter indicates the height of the circle. The diameter of the circle is determined by the minimum width and height.
```ts
Circle({ width: 150, height: 150 })
```
![creation-2](figures/creation-2.jpg)
## Viewport
```ts
viewPort{ x?: number | string, y?: number | string, width?: number | string, height?: number | string }
```
Creates a viewport, which is a rectangle in the user space that maps to the view boundary established for the associated SVG element. The value of the **viewport** attribute contains four optional parameters: **x**, **y**, **width**, and **height**. **x** and **y** indicate the coordinates of the upper left corner of the viewport, and **width** and **height** indicate the size of the viewport.
The following three examples describe how to use the viewport:
- Zoom in or zoom out a graph through the shape viewport.
```ts
// Draw a circle whose width and height are both 150.
Text ('Original Size Circle')
Circle({width: 75, height: 75}).fill('#E87361')
Row({space:10}) {
Column() {
// Create a shape component whose width and height are both 150, the background color is yellow, and a viewport whose width and height are both 75. Fill the viewport with a blue rectangle and draw a circle with a diameter of 75 in the viewport.
// The drawing is complete. The viewport is zoomed in twice based on the width and height of the component.
Text ('Enlarged Circle')
Shape() {
Rect().width('100%').height('100%').fill('#0097D4')
Circle({width: 75, height: 75}).fill('#E87361')
}
.viewPort({x: 0, y: 0, width: 75, height: 75})
.width(150)
.height(150)
.backgroundColor('#F5DC62')
}
Column() {
// Create a shape component whose width and height are both 150, the background color is yellow, and a viewport whose width and height are both 300. Fill the viewport with a green rectangle and draw a circle with a diameter of 75 in the viewport.
// After the drawing is complete, the viewport is zoomed out by twice based on the width and height of the component.
Text ('Shrunk Circle')
Shape() {
Rect().width('100%').height('100%').fill('#BDDB69')
Circle({width: 75, height: 75}).fill('#E87361')
}
.viewPort({x: 0, y: 0, width: 300, height: 300})
.width(150)
.height(150)
.backgroundColor('#F5DC62')
}
}
```
![2023032401632](figures/2023032401632.jpg)
- Create a shape component whose width and height are both 300, with a yellow background and a viewport whose width and height are both 300. Fill the viewport with a blue rectangle and draw a circle with a radius of 75 in the viewport.
```ts
Shape() {
Rect().width("100%").height("100%").fill("#0097D4")
Circle({ width: 150, height: 150 }).fill("#E87361")
}
.viewPort({ x: 0, y: 0, width: 300, height: 300 })
.width(300)
.height(300)
.backgroundColor("#F5DC62")
```
![viewport-2](figures/viewport-2.jpg)
- Create a shape component whose width and height are both 300, with a yellow background and a viewport whose width and height are both 300. Fill the viewport with a blue rectangle, draw a circle with a radius of 75 in the viewport, and move the viewport 150 to the right and below respectively.
```ts
Shape() {
Rect().width("100%").height("100%").fill("#0097D4")
Circle({ width: 150, height: 150 }).fill("#E87361")
}
.viewPort({ x: -150, y: -150, width: 300, height: 300 })
.width(300)
.height(300)
.backgroundColor("#F5DC62")
```
![viewport-3](figures/viewport-3.jpg)
## Setting Styles
The drawing component allows you to change the component style through various attributes.
- You can use **fill** to set the color of the filling area of the component.
```ts
Path()
.width(100)
.height(100)
.commands('M150 0 L300 300 L0 300 Z')
.fill("#E87361")
```
![2023022792216(1)](figures/2023022792216(1).jpg)
- You can use **stroke** to set the stroke color of a component.
```ts
Path()
.width(100)
.height(100)
.fillOpacity(0)
.commands('M150 0 L300 300 L0 300 Z')
.stroke(Color.Red)
```
![stroke](figures/stroke.jpg)
- You can use **strokeOpacity** to set the stroke opacity.
```ts
Path()
.width(100)
.height(100)
.fillOpacity(0)
.commands('M150 0 L300 300 L0 300 Z')
.stroke(Color.Red)
.strokeWidth(10)
.strokeOpacity(0.2)
```
![strokeopacity](figures/strokeopacity.jpg)
- You can use **strokeLineJoin** to set the join style of the stroke. Options include **Bevel**, **Miter**, and **Round**.
```ts
Polyline()
.width(100)
.height(100)
.fillOpacity(0)
.stroke(Color.Red)
.strokeWidth(8)
.points([[20, 0], [0, 100], [100, 90]])
// Set the join style of the stroke to Round.
.strokeLineJoin(LineJoinStyle.Round)
```
![strokeLineJoin](figures/strokeLineJoin.jpg)
- **strokeMiterLimit** places a limit on the ratio of the miter length to the value of **strokeWidth** used to draw a miter join.
The miter length indicates the distance from the outer tip to the inner corner of the miter. This attribute must be set to a value greater than or equal to 1 and takes effect when **strokeLineJoin** is set to **LineJoinStyle.Miter**.
```ts
Polyline()
.width(100)
.height(100)
.fillOpacity(0)
.stroke(Color.Red)
.strokeWidth(10)
.points([[20, 0], [20, 100], [100, 100]])
// Set the join style of the stroke to Miter.
.strokeLineJoin(LineJoinStyle.Miter)
// Set the limit on the ratio of the miter length to the value of strokeWidth used to draw a miter join.
.strokeMiterLimit(1/Math.sin(45))
Polyline()
.width(100)
.height(100)
.fillOpacity(0)
.stroke(Color.Red)
.strokeWidth(10)
.points([[20, 0], [20, 100], [100, 100]])
.strokeLineJoin(LineJoinStyle.Miter)
.strokeMiterLimit(1.42)
```
![2023032405917](figures/2023032405917.jpg)
- Use the **antiAlias** attribute to set whether to enable anti-aliasing. The default value is true, indicating that anti-aliasing is enabled.
```ts
// Enable anti-aliasing.
Circle()
.width(150)
.height(200)
.fillOpacity(0)
.strokeWidth(5)
.stroke(Color.Black)
```
![untitled](figures/untitled.png)
```ts
// Disable anti-aliasing.
Circle()
.width(150)
.height(200)
.fillOpacity(0)
.strokeWidth(5)
.stroke(Color.Black)
.antiAlias(false)
```
![2023032411518](figures/2023032411518.jpg)
## Example Scenario
- Draw a closed path at (-80, -5). The fill color is 0x317AF7, the stroke width is 10, the stroke color is red, and the Join style of the stroke is miter (default value).
```ts
@Entry
@Component
struct ShapeExample {
build() {
Column({ space: 10 }) {
Shape() {
Path().width(200).height(60).commands('M0 0 L400 0 L400 150 Z')
}
.viewPort({ x: -80, y: -5, width: 500, height: 300 })
.fill(0x317AF7)
.stroke(Color.Red)
.strokeWidth(3)
.strokeLineJoin(LineJoinStyle.Miter)
.strokeMiterLimit(5)
}.width('100%').margin({ top: 15 })
}
}
```
![scenario-1](figures/scenario-1.jpg)
- Draw a circle with a diameter of 150 mm and a ring with a diameter of 150 mm and a red dotted line (use the shorter side as the diameter if the width and height are different).
```ts
@Entry
@Component
struct CircleExample {
build() {
Column({ space: 10 }) {
// Draw a circle whose diameter is 150.
Circle({ width: 150, height: 150 })
// Draw a ring with a diameter of 150 mm and a red dotted line.
Circle()
.width(150)
.height(200)
.fillOpacity(0)
.strokeWidth(3)
.stroke(Color.Red)
.strokeDashArray([1, 2])
}.width('100%')
}
}
```
![scenario-2](figures/scenario-2.jpg)
# Gesture Binding
You can bind to each component different gesture events and design the logic for responding to these events. When a gesture is successfully recognized, the ArkUI framework notifies the component of the gesture recognition result through event callback.
## gesture (Common Gesture Binding Method)
```ts
.gesture(gesture: GestureType, mask?: GestureMask)
```
**gesture** is a frequently used API for binding a gesture to a component.
For example, you can use it to bind the tap gesture to the **\<Text>** component.
```ts
// xxx.ets
@Entry
@Component
struct Index {
build() {
Column() {
Text('Gesture').fontSize(28)
// Use the gesture API to bind the tap gesture.
.gesture(
TapGesture()
.onAction(() => {
console.info('TapGesture is onAction');
}))
}
.height(200)
.width(250)
}
}
```
## priorityGesture (Gesture Binding Method with Priority)
```ts
.priorityGesture(gesture: GestureType, mask?: GestureMask)
```
The **priorityGesture** API binds gestures that are preferentially recognized to a component.
By default, the child component preferentially recognizes the gesture specified by **gesture**, and the parent component preferentially recognizes the gesture specified by **priorityGesture** (if set).
In the following example, the parent component **\<Column>** and child component **\<Text>** are both bound to the tap gesture. As the **\<Column>** is bound to the gesture through **priorityGesture**, the tap gesture recognized by the parent component is preferentially responded to.
```ts
// xxx.ets
@Entry
@Component
struct Index {
build() {
Column() {
Text('Gesture').fontSize(28)
.gesture(
TapGesture()
.onAction(() => {
console.info('Text TapGesture is onAction');
}))
}
.height(200)
.width(250)
// When the tap gesture is bound to the parent <Column> component through priorityGesture, the tap gesture event of the <Text> component is ignored when the text area is tapped, and the tap gesture event of the<Column> component is preferentially responded to.
.priorityGesture(
TapGesture()
.onAction(() => {
console.info('Column TapGesture is onAction');
}), GestureMask.IgnoreInternal)
}
}
```
## parallelGesture (Parallel Gesture Binding Method)
```ts
.parallelGesture(gesture: GestureType, mask?: GestureMask)
```
The **parallelGesture** API binds to a component the gesture that can be triggered together with the child component gesture.
By default, the gesture event does not bubble up. When a parent component and a child component are bound to a same gesture, the gesture events bound to the parent component and the child component compete with each other, and a gesture event of at most one component can be responded to. When **parallelGesture** is set, the same gesture events can be triggered for the parent and child components, thereby implementing a bubbling effect.
```ts
// xxx.ets
@Entry
@Component
struct Index {
build() {
Column() {
Text('Gesture').fontSize(28)
.gesture(
TapGesture()
.onAction(() => {
console.info('Text TapGesture is onAction');
}))
}
.height(200)
.width(250)
// When parallelGesture is set, the tap gestures on the <Column> component and on the child <Text> component are both recognized.
.parallelGesture(
TapGesture()
.onAction(() => {
console.info('Column TapGesture is onAction');
}), GestureMask.IgnoreInternal)
}
}
```
>**NOTE**
>
>When the parent component and the child component are bound to both the click gesture and the double-click gesture, both the parent component and the child component respond only to the click gesture.
# Combined Gestures
A combined gesture consists of multiple single gestures. Different GestureModes are used in GestureGroup to declare the type of the combined gesture. [Continuous recognition](#continuous-recognition), [parallel recognition](#parallel-recognition), and [exclusive recognition](#exclusive-recognition) are supported for a group of gestures.
```ts
GestureGroup(mode:GestureMode, ...gesture:GestureType[])
```
- **mode**: recognition mode of combined gestures. This parameter is mandatory and belongs to the **GestureMode** enumeration class.
- **gesture**: array consisting of multiple gestures. This parameter is mandatory. .
## Continuous Recognition
For continuous recognition, the value of **GestureMode** is **Sequence**. In this gesture mode, gestures registered in the combined gestures will be recognized according to the registration sequence until they are all recognized successfully. If any of the registered gestures fails to be recognized, all gestures fail to be recognized.
In the following example, the combined gestures for continuous recognition are the long press gesture and pan gesture.
The **translate** attribute is bound to a **\<Column>** component. You can set the attribute to translate the component. Then, bind **LongPressGesture** and **PanGesture** to the component in the **Sequence** gesture mode. When a long press gesture is recognized, the displayed number is updated. When the user drags the component after the long press gesture, the component is dragged based on the callback function of the pan gesture.
```ts
// xxx.ets
@Entry
@Component
struct Index {
@State offsetX: number = 0;
@State offsetY: number = 0;
@State count: number = 0;
@State positionX: number = 0;
@State positionY: number = 0;
@State borderStyles: BorderStyle = BorderStyle.Solid
build() {
Column() {
Text('sequence gesture\n' + 'LongPress onAction:' + this.count + '\nPanGesture offset:\nX: ' + this.offsetX + '\n' + 'Y: ' + this.offsetY)
.fontSize(28)
}
// Bind the translate attribute to translate the component.
.translate({ x: this.offsetX, y: this.offsetY, z: 0 })
.height(250)
.width(300)
// The following combined gestures are recognized in sequence. When the long press gesture event is not triggered correctly, the pan gesture event is not triggered.
.gesture(
// Set the gesture mode to Sequence.
GestureGroup(GestureMode.Sequence,
// The first gesture recognized in the combined gestures is the long press gesture, which can be responded to for multiple times.
LongPressGesture({ repeat: true })
// When the long press gesture is successfully recognized, the value of count displayed on the <Text> component is increased.
.onAction((event: GestureEvent) => {
if (event.repeat) {
this.count++;
}
console.info('LongPress onAction');
})
.onActionEnd(() => {
console.info('LongPress end');
}),
// The pan gesture is triggered when the component is dragged after the long press gesture is recognized.
PanGesture()
.onActionStart(() => {
this.borderStyles = BorderStyle.Dashed;
console.info('pan start');
})
// When the gesture is triggered, the pan distance is obtained based on the callback, and the displacement distance of the component is modified. In this way, the component is translated.
.onActionUpdate((event: GestureEvent) => {
this.offsetX = this.positionX + event.offsetX;
this.offsetY = this.positionY + event.offsetY;
console.info('pan update');
})
.onActionEnd(() => {
this.positionX = this.offsetX;
this.positionY = this.offsetY;
this.borderStyles = BorderStyle.Solid;
})
)
)
}
}
```
![sequence](figures/sequence.gif)
>**NOTE**
>
>The drag event is a typical use case of continuous recognition with the long press gesture and pan gesture combined. It is triggered only when the user performs the pan gesture within the preset time frame after a long press gesture is recognized. If the long press gesture is not recognized or the pan gesture is not performed within the preset time frame, the drag event will not be triggered.
## Parallel Recognition
For parallel recognition, the value of **GestureMode** is **Parallel**. In this gesture mode, gestures registered in the combined gestures will be recognized at the same time until they are all recognized successfully. The gestures are recognized in parallel without affecting each other.
For example, if the tap gesture and the double-tap gesture are bound to the \**<Column>** component in parallel recognition mode, they can be recognized at the same time, and the recognition of these two gestures does not interfere with each other.
```ts
// xxx.ets
@Entry
@Component
struct Index {
@State count1: number = 0;
@State count2: number = 0;
build() {
Column() {
Text('parallel gesture\n' + 'tapGesture count is 1:' + this.count1 + '\ntapGesture count is 2:' + this.count2 + '\n')
.fontSize(28)
}
.height(200)
.width(250)
// The following combined gestures are recognized in parallel mode. After a tap gesture is recognized successfully, if another tap gesture is recognized within the specified time frame, a double-tap gesture will also be recognized.
.gesture(
GestureGroup(GestureMode.Parallel,
TapGesture({ count: 1 })
.onAction(() => {
this.count1++;
}),
TapGesture({ count: 2 })
.onAction(() => {
this.count2++;
})
)
)
}
}
```
![parallel](figures/parallel.gif)
>**NOTE**
>
>After a tap gesture and a double-tap gesture are combined for parallel recognition, when taps are performed in an area, the tap gesture and the double-tap gesture are recognized at the same time.
>
>When there is only a single tap, the tap gesture is recognized, but the double-tap gesture fails to be recognized.
>
>When there are two taps and the interval between the two taps is within a specified period (300 ms by default), two tap events and one double-tap event are triggered.
>
>When there are two taps, but the interval between the two taps exceeds the specified time, two tap events are triggered but the double-tap event is not triggered.
## Exclusive Recognition
For exclusive recognition, the value of **GestureMode** is **Exclusive**. In this gesture mode, gestures registered in the combined gesture are recognized at the same time. If one gesture is recognized successfully, the gesture recognition ends, and all other gestures fail to be recognized.
For example, if the tap gesture and the double-tap gesture are bound to the \**<Column>** component in exclusive recognition mode, only a tap gesture event can be triggered. This is because a tap gesture requires a single tap to be triggered, and a double-tap gesture event requires two taps to be triggered; each tap event is consumed by the tap gesture and cannot be accumulated into a double-tap gesture.
```ts
// xxx.ets
@Entry
@Component
struct Index {
@State count1: number = 0;
@State count2: number = 0;
build() {
Column() {
Text('parallel gesture\n' + 'tapGesture count is 1:' + this.count1 + '\ntapGesture count is 2:' + this.count2 + '\n')
.fontSize(28)
}
.height(200)
.width(250)
// The following combined gestures are mutually exclusive. After the tap gesture is recognized successfully, the double-tap gesture fails to be recognized.
.gesture(
GestureGroup(GestureMode.Exclusive,
TapGesture({ count: 1 })
.onAction(() => {
this.count1++;
}),
TapGesture({ count: 2 })
.onAction(() => {
this.count2++;
})
)
)
}
}
```
![exclusive](figures/exclusive.gif)
>**NOTE**
>
>After a tap gesture and a double-tap gesture are combined for exclusive recognition, when taps are performed in an area, the tap gesture and the double-tap gesture are recognized at the same time.
>
>When there is only a single tap, the tap gesture is recognized, but the double-tap gesture fails to be recognized.
>
>When there are two taps, the gesture recognition is declared as successful when the first tap gesture is recognized. In this case, the double-tap gesture fails to be recognized. Even if the second tap is performed within the specified time, the double-tap gesture event is not responded to. Instead, another tap gesture event is triggered.
# Single Gesture
## TapGesture
```ts
TapGesture(value?:{count?:number; fingers?:number})
```
Triggers a tap gesture with one or more taps. This API has two optional parameters:
- **count**: number of consecutive taps required for gesture recognition. The default value is 1. A value less than 1 evaluates to the default value **1**. If multi-tap is configured, the timeout interval between a lift and the next tap is 300 ms.
- **fingers**: number of fingers required for gesture recognition. The value ranges from 1 to 10. The default value is **1**. If the number of fingers used for the tap is less than the specified one within 300 ms after the first finger is tapped, the gesture fails to be recognized. Gesture recognition also fails if the number of fingers used for the tap exceeds the value of **fingers**.
The following example binds a double-tap gesture (a tap gesture whose **count** value is **2**) to the **\<Text>** component:
```ts
// xxx.ets
@Entry
@Component
struct Index {
@State value: string = "";
build() {
Column() {
Text('Click twice').fontSize(28)
.gesture(
// Bind a tap gesture whose count value is 2.
TapGesture({ count: 2 })
.onAction((event: GestureEvent) => {
this.value = JSON.stringify(event.fingerList[0]);
}))
Text(this.value)
}
.height(200)
.width(250)
.padding(20)
.border({ width: 3 })
.margin(30)
}
}
```
![tap](figures/tap.gif)
## LongPressGesture
```ts
LongPressGesture(value?:{fingers?:number; repeat?:boolean; duration?:number})
```
Triggers a long press gesture, which requires one or more fingers with a minimum 500 ms hold-down time. This API has three optional parameters:
- **fingers**: minimum number of fingers required for gesture recognition. The value ranges from 1 to 10. The default value is **1**.
- **repeat**: whether to continuously trigger the event callback. The default value is **false**.
- **duration**: minimum hold-down time, in ms. The default value is **500**.
The following exemplifies how to bind a long press gesture that can be repeatedly triggered to the **\<Text>** component:
```ts
// xxx.ets
@Entry
@Component
struct Index {
@State count: number = 0;
build() {
Column() {
Text('LongPress OnAction:' + this.count).fontSize(28)
.gesture(
// Bind the long press gesture that can be triggered repeatedly.
LongPressGesture({ repeat: true })
.onAction((event: GestureEvent) => {
if (event.repeat) {
this.count++;
}
})
.onActionEnd(() => {
this.count = 0;
})
)
}
.height(200)
.width(250)
.padding(20)
.border({ width: 3 })
.margin(30)
}
}
```
![longPress](figures/longPress.gif)
## PanGesture
```ts
PanGestureOptions(value?:{ fingers?:number; direction?:PanDirection; distance?:number})
```
Triggers a pan gesture, which requires the minimum movement distance (5 vp by default) of a finger on the screen. This API has three optional parameters:
- **fingers**: minimum number of fingers required for gesture recognition. The value ranges from 1 to 10. The default value is **1**.
- **direction**: pan direction. The enumerated value supports the AND (&amp;) and OR (\|) operations. The default value is **Pandirection.All.**
- **distance**: minimum pan distance required for gesture recognition, in vp. The default value is **5**.
The following exemplifies how to bind a pan gesture to the **\<Text>** component. You can pan a component by modifying the layout and position information of the component in the **PanGesture** callback.
```ts
// xxx.ets
@Entry
@Component
struct Index {
@State offsetX: number = 0;
@State offsetY: number = 0;
@State positionX: number = 0;
@State positionY: number = 0;
build() {
Column() {
Text('PanGesture Offset:\nX: ' + this.offsetX + '\n' + 'Y: ' + this.offsetY)
.fontSize(28)
.height(200)
.width(300)
.padding(20)
.border({ width: 3 })
// Bind the layout and position information to the component.
.translate({ x: this.offsetX, y: this.offsetY, z: 0 })
.gesture(
// Bind the pan gesture to the component.
PanGesture()
.onActionStart((event: GestureEvent) => {
console.info('Pan start');
})
// When the drag gesture is triggered, modify the layout and position information of the component based on the callback.
.onActionUpdate((event: GestureEvent) => {
this.offsetX = this.positionX + event.offsetX;
this.offsetY = this.positionY + event.offsetY;
})
.onActionEnd(() => {
this.positionX = this.offsetX;
this.positionY = this.offsetY;
})
)
}
.height(200)
.width(250)
}
}
```
![pan](figures/pan.gif)
>**NOTE**
>
>Most slidable components, such as **\<List>**, **\<Grid>**, **\<Scroll>**, and **\<Tab>**, slide through the pan gesture. Therefore, binding the [pan gesture](#pangesture) or [swipe gesture](#swipegesture) to child components will cause gesture competition.
>
>When a child component is bound to the pan gesture, sliding in the child component area triggers only the pan gesture of the child component. If the parent component needs to respond, you need to modify the gesture binding method or transfer messages from the child component to the parent component, or modify the **PanGesture** parameter distance of the parent and child components to make the panning more sensitive. When a child component is bound to the swipe gesture, you need to modify the parameters of **PanGesture** and **SwipeGesture** to achieve the required effect because the triggering conditions of **PanGesture** and **SwipeGesture** are different.
## PinchGesture
```ts
PinchGesture(value?:{fingers?:number; distance?:number})
```
The pinch gesture is used to trigger a pinch gesture event. A minimum quantity of fingers that trigger the pinch gesture is two fingers, a maximum quantity of fingers that trigger the pinch gesture is five fingers, a minimum recognition distance is 3vp, and there are two optional parameters:
- fingers: specifies the minimum number of fingers required to trigger a pinch gesture. This parameter is optional. The minimum value is 2 and the maximum value is 5. The default value is 2.
- distance: specifies the minimum distance for triggering the pinch gesture. This parameter is optional. The unit is vp. The default value is 3.
For example, to bind a three-finger pinch gesture to the Column component, you can obtain the zoom ratio from the function callback of the pinch gesture to zoom out or zoom in the component.
```ts
// xxx.ets
@Entry
@Component
struct Index {
@State scaleValue: number = 1;
@State pinchValue: number = 1;
@State pinchX: number = 0;
@State pinchY: number = 0;
build() {
Column() {
Column() {
Text('PinchGesture scale:\n' + this.scaleValue)
Text('PinchGesture center:\n(' + this.pinchX + ',' + this.pinchY + ')')
}
.height(200)
.width(300)
.border({ width: 3 })
.margin({ top: 100 })
// Bind the zoom ratio to the component. You can change the zoom ratio to zoom out or zoom in the component.
.scale({ x: this.scaleValue, y: this.scaleValue, z: 1 })
.gesture(
// Bind the pinch gesture triggered by three fingers to the widget.
PinchGesture({ fingers: 3 })
.onActionStart((event: GestureEvent) => {
console.info('Pinch start');
})
// When the pinch gesture is triggered, the callback function can be used to obtain the zoom ratio to change the zoom ratio of the component.
.onActionUpdate((event: GestureEvent) => {
this.scaleValue = this.pinchValue * event.scale;
this.pinchX = event.pinchCenterX;
this.pinchY = event.pinchCenterY;
})
.onActionEnd(() => {
this.pinchValue = this.scaleValue;
console.info('Pinch end');
})
)
}
}
}
```
![pinch](figures/pinch.png)
## RotationGesture
```ts
RotationGesture(value?:{fingers?:number; angle?:number})
```
The rotation gesture is used to trigger a rotation gesture event. A minimum quantity of fingers that trigger the rotation gesture is two fingers, a maximum quantity of fingers that trigger the rotation gesture is five fingers, a minimum change degree is one degree, and there are two optional parameters:
- **fingers**: minimum number of fingers required to trigger a rotation gesture. This parameter is optional. The minimum value is 2 and the maximum value is 5. The default value is 2.
- **angle**: minimum change degree for triggering the rotation gesture. This parameter is optional. The unit is deg. The default value is 1.
For example, a rotation gesture is bound to a **\<Text>** component to implement rotation of the component. A rotation angle may be obtained from a callback function of the rotation gesture, so as to implement rotation of the component:
```ts
// xxx.ets
@Entry
@Component
struct Index {
@State angle: number = 0;
@State rotateValue: number = 0;
build() {
Column() {
Text('RotationGesture angle:' + this.angle).fontSize(28)
// Bind the rotation layout to the component. You can change the rotation angle to rotate the component.
.rotate({ angle: this.angle })
.gesture(
RotationGesture()
.onActionStart((event: GestureEvent) => {
console.info('RotationGesture is onActionStart');
})
// When the rotation gesture takes effect, the rotation angle is obtained by using the callback function of the rotation gesture, so as to modify the rotation angle of the component.
.onActionUpdate((event: GestureEvent) => {
this.angle = this.rotateValue + event.angle;
console.info('RotationGesture is onActionEnd');
})
// Angle of the fixed component at the end of the rotation when the rotation ends and the handle is raised
.onActionEnd(() => {
this.rotateValue = this.angle;
console.info('RotationGesture is onActionEnd');
})
.onActionCancel(() => {
console.info('RotationGesture is onActionCancel');
})
)
}
.height(200)
.width(250)
}
}
```
![rotation](figures/rotation.png)
## SwipeGesture
```ts
SwipeGesture(value?:{fingers?:number; direction?:SwipeDirection; speed?:number})
```
Swipe gestures are used to trigger swipe events. A swipe gesture is recognized when the swipe speed is 100 vp/s or higher. There are three optional parameters:
- **fingers**: minimum number of fingers required to trigger a swipe gesture. TThe minimum value is 1 and the maximum value is 10. The default value is 1.
- **direction**: swipe direction. The enumerated values support the AND and OR operations. The default value is **SwipeDirection.All**.
- **speed**: minimum speed of the swipe gesture, in vp/s. The default value is **100**.
The following describes how to bind a sliding gesture to the Column component to rotate the component:
```ts
// xxx.ets
@Entry
@Component
struct Index {
@State rotateAngle: number = 0;
@State speed: number = 1;
build() {
Column() {
Column() {
Text("SwipeGesture speed\n" + this.speed)
Text("SwipeGesture angle\n" + this.rotateAngle)
}
.border({ width: 3 })
.width(300)
.height(200)
.margin(100)
// Bind rotation to the Column component and change the rotation angle based on the sliding speed and angle of the sliding gesture.
.rotate({ angle: this.rotateAngle })
.gesture(
// Bind the sliding gesture and restrict it to be triggered only when the user slides in the vertical direction.
SwipeGesture({ direction: SwipeDirection.Vertical })
// When the swipe gesture is triggered, the swipe speed and angle are obtained, which can be used to modify the layout parameters.
.onAction((event: GestureEvent) => {
this.speed = event.speed;
this.rotateAngle = event.angle;
})
)
}
}
}
```
![swipe](figures/swipe.gif)
>**NOTE**
>
>When SwipeGesture and PanGesture are bound at the same time, competition occurs if they are bound in default mode or mutually exclusive mode. The trigger condition of SwipeGesture is that the sliding speed reaches 100 vp/s. The trigger condition of PanGesture is that the sliding distance reaches 5 vp and the trigger condition is met first. You can modify the parameters of SwipeGesture and PanGesture to achieve different effects.
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册