提交 32253489 编写于 作者: G ge-yafang

update docs

Signed-off-by: Nge-yafang <geyafang@huawei.com>
上级 2e990e24
# UI
- [ArkUI Overview](arkui-overview.md)
- JavaScript-based Web-like Development Paradigm
- [Overview](ui-js-overview.md)
- Framework
- [File Organization](js-framework-file.md)
- ["js" Tag](js-framework-js-tag.md)
- [app.js](js-framework-js-file.md)
- Syntax
- [HML](js-framework-syntax-hml.md)
- [CSS](js-framework-syntax-css.md)
- [JavaScript](js-framework-syntax-js.md)
- [Lifecycle](js-framework-lifecycle.md)
- [Resource Limitations and Access](js-framework-resource-restriction.md)
- [Multi-Language Capability](js-framework-multiple-languages.md)
- Building the UI
- [Component Overview](ui-js-building-ui-component.md)
- Building the Layout
- [Layout Description](ui-js-building-ui-layout-intro.md)
- [Adding Title and Paragraph Text](ui-js-building-ui-layout-text.md)
- [Adding an Image](ui-js-building-ui-layout-image.md)
- [Adding a Comment](ui-js-building-ui-layout-comment.md)
- [Adding a Container](ui-js-building-ui-layout-external-container.md)
- [Adding Interactions](ui-js-building-ui-interactions.md)
- [Developing Animations](ui-js-building-ui-animation.md)
- [Defining Events](ui-js-building-ui-event.md)
- [Defining Page Routes](ui-js-building-ui-routes.md)
- Common Component Development Guidelines
- [&lt;text&gt; Development](ui-js-components-text.md)
- [&lt;input&gt; Development](ui-js-components-input.md)
- [&lt;button&gt; Development](ui-js-components-button.md)
- [&lt;list&gt; Development](ui-js-components-list.md)
- [&lt;picker&gt; Development](ui-js-components-picker.md)
- [&lt;dialog&gt; Development](ui-js-components-dialog.md)
- [&lt;form&gt; Development](ui-js-components-form.md)
- [&lt;stepper&gt; Development](ui-js-components-stepper.md)
- [&lt;tabs&gt; Development](ui-js-component-tabs.md)
- [&lt;image&gt; Development](ui-js-components-images.md)
- Animation Development Guidelines
- CSS Animation
- [Defining Attribute Style Animations](ui-js-animate-attribute-style.md)
- [Defining Animations with the transform Attribute](ui-js-animate-transform.md)
- [Defining Animations with the background-position Attribute](ui-js-animate-background-position-style.md)
- [Defining Animations for SVG Components](ui-js-animate-svg.md)
- JS Animation
- [Component Animation](ui-js-animate-component.md)
- Interpolator Animation
- [Animation Effect](ui-js-animate-dynamic-effects.md)
- [Animation Frame](ui-js-animate-frame.md)
- [Custom Components](ui-js-custom-components.md)
- TypeScript-based Declarative Development Paradigm
- [Overview](ui-ts-overview.md)
- Framework Overview
- File Organization
- [Directory Structure](ts-framework-directory.md)
- [Rules for Accessing Application Code Files](ts-framework-file-access-rules.md)
- ["js" Tag](ts-framework-js-tag.md)
- Resource Access
- [Accessing Application Resources](ts-application-resource-access.md)
- [Accessing System Resources](ts-system-resource-access.md)
- [Media Resource Types](ts-media-resource-type.md)
- [Pixel Units](ts-pixel-units.md)
- [Types](ts-types.md)
- Declarative Syntax
- [Overview](ts-syntax-intro.md)
- General UI Description Specifications
- [Basic Concepts](ts-general-ui-concepts.md)
- Declarative UI Description Specifications
- [Configuration Without Parameters](ts-parameterless-configuration.md)
- [Configuration with Mandatory Parameters](ts-configuration-with-mandatory-parameters.md)
- [Attribute Configuration](ts-attribution-configuration.md)
- [Event Configuration](ts-event-configuration.md)
- [Child Component Configuration](ts-child-component-configuration.md)
- Componentization
- [@Component](ts-component-based-component.md)
- [@Entry](ts-component-based-entry.md)
- [@Preview](ts-component-based-preview.md)
- [@Builder](ts-component-based-builder.md)
- [@Extend](ts-component-based-extend.md)
- [@CustomDialog](ts-component-based-customdialog.md)
- [@Styles](ts-component-based-styles.md)
- About UI State Management
- [Basic Concepts](ts-ui-state-mgmt-concepts.md)
- Managing Component States
- [@State](ts-component-states-state.md)
- [@Prop](ts-component-states-prop.md)
- [@Link](ts-component-states-link.md)
- Managing Application States
- [AppStorage](ts-application-states-appstorage.md)
- [PersistentStorage](ts-application-states-apis-persistentstorage.md)
- [Environment](ts-application-states-apis-environment.md)
- Managing Other States
- [@Observed and @ObjectLink](ts-other-states-observed-objectlink.md)
- [@Consume and @Provide](ts-other-states-consume-provide.md)
- [@Watch](ts-other-states-watch.md)
- About Rendering Control Syntax
- [if/else](ts-rending-control-syntax-if-else.md)
- [ForEach](ts-rending-control-syntax-foreach.md)
- [LazyForEach](ts-rending-control-syntax-lazyforeach.md)
- About @Component
- [build Function](ts-function-build.md)
- [Initialization of Custom Components' Member Variables](ts-custom-component-initialization.md)
- [Custom Component Lifecycle Callbacks](ts-custom-component-lifecycle-callbacks.md)
- [Component Creation and Re-initialization](ts-component-creation-re-initialization.md)
- [About Syntactic Sugar](ts-syntactic-sugar.md)
- Common Component Development Guidelines
- [&lt;web&gt; Development](ui-ts-components-web.md)
- Experiencing the Declarative UI
- [Creating a Declarative UI Project](ui-ts-creating-project.md)
- [Getting to Know Components](ui-ts-components.md)
- [Creating a Simple Page](ui-ts-creating-simple-page.md)
- Defining Page Layout and Connection
- [Building a Food Data Model](ui-ts-building-data-model.md)
- [Building a Food Category List Layout](ui-ts-building-category-list-layout.md)
- [Building a Food Category Grid Layout](ui-ts-building-category-grid-layout.md)
- [Implementing Page Redirection and Data Transmission](ui-ts-page-redirection-data-transmission.md)
# ArkUI Overview
## Introduction
ArkUI is a UI development framework that provides what you'll need to develop application UIs.
## Basic Concepts
- Component: the smallest unit for UI building and display. You build a UI that meets your needs through flexible combinations of components.
- Page: the smallest unit for ArkUI application scheduling. You can design multiple pages for your application, manage their files on a per-page basis, and schedule page redirection through routing APIs, so as to implement decoupling of application functions.
## Key Capabilities
- Diverse components: In addition to a wide range of basic components, such as components for text display, image display, and keypress interaction, ArkUI provides media components that support video playback. Better yet, it also provides polymorphic components, which can adapt to styles of different devices.
- Flexible layouts: Creating a responsive UI in ArkUI is easy, with our carefully-designed layout approaches: Besides the classic flexible layout capability, you also have access to the list, grid, and atomic layouts that auto-adapt to screen resolutions.
- Animation: Apart from animations embedded in components, ArkUI offers additional animation features: attribute animation, transition animation, and custom animation.
- UI interaction: ArkUI allows users to interact with your application UI properly, regardless of the system platform and input device. By default, the UI accepts input from touch gestures, remote controls, and mouse devices, with support for the event notification capability.
- Drawing: ArkUI offers advanced drawing capabilities that allow you to draw custom shapes with a range of editors, from images to fill colors and texts.
- Platform API channel: ArkUI provides an API extension mechanism through which platform capabilities are encapsulated to produce JavaScript APIs in a unified style.
## Development Paradigms
ArkUI comes with two development paradigms: JavaScript-based web-like development paradigm (web-like development paradigm for short) and TypeScript-based declarative development paradigm (declarative development paradigm for short). You can choose whichever development paradigm that aligns with your practice.
### Web-like Development Paradigm
The web-like development paradigm uses the classical three-stage programming model, in which OpenHarmony Markup Language (HML) is used for building layouts, CSS for defining styles, and JavaScript for adding processing logic. UI components are associated with data through one-way data-binding. This means that when data changes, the UI automatically updates with the new data. This development paradigm has a low learning curve for frontend web developers, allowing them to quickly transform existing web applications into ArkUI applications. It could be helpful if you are developing small- and medium-sized applications with simple UIs.
### Declarative Development Paradigm
The declarative development paradigm uses the TypeScript programming language and extends the declarative UI syntax, providing UI drawing capabilities from three dimensions: component, animation, and state management. The programming mode used is closer to natural semantics. You can intuitively describe the UI without caring about how the framework implements UI drawing and rendering, leading to simplified and efficient development. With type annotations in TypeScript, you can enforce type checking at compile time. If you are developing large applications, the declarative development paradigm is more applicable.
### Web-like Development Paradigm vs. Declarative Development Paradigm
| Development Paradigm | Language | UI Update Mode | Applicable To | Intended Audience |
| -------- | -------- | -------- | -------- | -------- |
| Web-like development paradigm | JavaScript | Data-driven | Applets and service widgets with simple UIs | Frontend web developers |
| Declarative development paradigm | Extended TypeScript (eTS) | Data-driven | Applications involving technological sophistication and teamwork | Mobile application and system application developers |
### Framework Structure
![en-us_image_0000001267647869](figures/en-us_image_0000001267647869.png)
As shown above, the two development paradigms share the UI backend engine and language runtime. The UI backend engine implements the six basic capabilities of the ArkUI framework. The declarative development paradigm does not require the JS Framework for managing the page DOM. As such, it has more streamlined rendering and update links and less memory usage. This makes the declarative development paradigm a better choice for building application UIs.
# File Organization
## Directory Structure
The following figure shows the typical directory structure of the JavaScript module \(entry/src/main/js/module\) for an application with feature abilities \(FA\) using JavaScript APIs.
**Figure1** Directory structure
![](figures/unnaming-(1).png)
**Figure2** Directory structure for resource sharing <sup>5+</sup>
![](figures/directory-structure-for-resource-sharing-5+.png "directory-structure-for-resource-sharing-5+")
Functions of the files are as follows:
- .hml files describe the page layout.
- .css files describe the page style.
- .js files process the interactions between pages and users.
Functions of the folders are as follows:
- The app.js file manages global JavaScript logics and application lifecycle. For details, see [app.js](js-framework-js-file.md).
- The pages directory stores all component pages.
- The common directory stores public resource files, such as media resources, custom components, and .js files.
- The resources directory stores resource configuration files, for example, for multi-resolution loading. For details, see [Resource Limitations and Access](js-framework-resource-restriction.md).
- The share directory<sup>5+</sup> is used to configure resources shared by multiple instances. For example, images and JSON files in this directory can be shared by default1 and default2 instances.
>![](public_sys-resources/icon-note.gif) **NOTE**:
>- Reserved folders \(i18n and resources\) cannot be renamed.
>- If the same resource name and directory are used under the share directory and the instance \(default\) directory, the resource in the instance directory will be used when you reference the directory.
>- The share directory does not support i18n.
>- You should create the optional folders \(shown in the directory structure\) as needed after you create the project in DevEco Studio.
## File Access Rules
Application resources can be accessed via an absolute or relative path. In the JS UI framework, an absolute path starts with a slash \(/\), and a relative path starts with ./ or ../. The rules are as follows:
- To reference a code file, use a relative path, for example, ../common/utils.js.
- To reference a resource file, use an absolute path, for example, /common/xxx.png.
- Store code files and resource files in the common directory and access the files in a required fashion.
- In a .css file, use the url\(\) function to create a URL, for example, url\(/common/xxx.png\).
>![](public_sys-resources/icon-note.gif) **NOTE**:
>When code file A needs to reference code file B:
>
>- If code files A and B are in the same directory, you can use either a relative or absolute path in code file B to reference resource files.
>- If code files A and B are in different directories, you must use an absolute path in code file B to reference resource files because the directory of code file B changes during Webpack packaging.
>- Use an absolute path if you want to dynamically change the resource file path through data binding in a .js file.
## Media File Formats
Table1 Supported image formats
| Image Format | File Format |
| ------------ | ----------- |
| BMP | .bmp |
| GIF | .gif |
| JPEG | .jpg |
| PNG | .png |
| WebP | .webp |
Table2 Supported video formats
| Video Formats | File Format |
| ------------------------------- | ----------- |
| H.264 AVC Baseline Profile (BP) | .3gp .mp4 |
# app.js
## Application Lifecycle
You can customize the [lifecycle](js-framework-lifecycle.md) implementation logic on an application-by-application basis in app.js. The following example only prints the corresponding logs in the lifecycle function:
```
// app.js
export default {
onCreate() {
console.info('Application onCreate');
},
onDestroy() {
console.info('Application onDestroy');
},
}
```
## Application Object<sup>6+</sup>
| Attribute | Data Type | Description |
| -------- | -------- | -------- |
| getApp | Function | Obtains the object exposed in the app.js file from the custom .js file. |
The following is a sample code snippet:
```
// app.js
export default {
data: {
test: "by getAPP"
},
onCreate() {
console.info('AceApplication onCreate');
},
onDestroy() {
console.info('AceApplication onDestroy');
},
};
```
```
// test.js Customize the logic code.
export var appData = getApp().data;
```
# "js" Tag
The "js" tag contains the instance name, window style, and page route information.
| Tag | Data Type | Default Value | Mandatory | Description |
| -------- | -------- | -------- | -------- | -------- |
| name | string | default | Yes | Name of the JavaScript instance. |
| pages | Array | - | Yes | Route information. For details, see ["pages"](#pages). |
| window | Object | - | No | Window information. For details, see ["window"](#window). |
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:
> The "name", "window", and "pages" tags are configured in the "js" tag of the config.json file.
## "pages"
The "pages" defines the route information of each page. Each page consists of the page path and page name. The following is an example:
```
{
...
"pages": [
"pages/index/index",
"pages/detail/detail"
]
...
}
```
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:
>
> - The first page in the pages list is the home page, also referred to as the entry, of the application.
>
>
> - The page name should not be a component name, for example, text.hml or button.hml.
## window
The "window" defines window-related configurations. To solve the screen adaptation problem, you can use either of the following methods:
- Specify designWidth, which is the logical screen width. All size styles, such as width and font-size, are scaled at the ratio of designWidth to the physical screen width. For example, when designWidth is 720 px and if you set width to 100 px, the actual display width is scaled to 200 physical px on the screen whose physical width is 1440 px.
- Set autoDesignWidth to true, the designWidth field will be ignored, and the component and layout will be scaled automatically based on the screen density. The logical screen width is automatically calculated based on the physical screen width and screen density. The logical screen width may vary depending on the device. Use the relative layout to adapt to different devices. For example, on a device with a resolution of 466x466 and 320 DPI (a screen density of 2x, with 160 DPI as the base), 1 px is equivalent to 2 physical px.
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:
> 1. The default &lt;length&gt; value in the current style is calculated based on the screen density. For example, if the screen density is x2 (with 160 DPI as the baseline) and the default &lt;length&gt; value is 1 px, the actual length rendered on the device is 2 physical px.
>
> 2. Values of autoDesignWidth and designWidth do not affect how the default &lt;length&gt; value is calculated and the final effect.
| Attribute | Type | Mandatory | Default Value | Description |
| -------- | -------- | -------- | -------- | -------- |
| | | | | |
| designWidth | number | No | 720<br/> | Logical screen width, which is a reference value for page design. The actual display width is scaled at the ratio of the value to the device width. |
| autoDesignWidth | boolean | No | false | Whether to automatically calculate the baseline width. If autoDesignWidth is set to true, designWidth is ignored. The baseline width is calculated based on the physical screen width and screen density. |
The following is a sample code snippet:
```
{
...
"window": {
"designWidth": 720,
"autoDesignWidth": false
}
...
}
```
## Example
```
{
"app": {
"bundleName": "com.example.player",
"version": {
"code": 1,
"name": "1.0"
},
"vendor": "example"
}
"module": {
...
"js": [
{
"name": "default",
"pages": [
"pages/index/index",
"pages/detail/detail"
], "window": { "designWidth": 720, "autoDesignWidth": false }
}
],
"abilities": [
{
...
}
]
}
}
```
# Lifecycle
## Application Lifecycle
You can define the following application lifecycle functions in the app.js file.
| Attribute | Type | Description | Called When |
| -------- | -------- | -------- | -------- |
| onCreate | () => void | Listens for application creation. | The application is created. |
| onShow<sup>6+</sup> | () => void | Listens for whether the application is running in the foreground. | The application is running in the foreground. |
| onHide<sup>6+</sup> | () => void | Listens for whether the application is running in the background. | The application is running in the background. |
| onDestroy | () => void | Listens for application uninstallation. | The application exits. |
## Page Lifecycle
You can define the following page lifecycle functions in the .js file of the page.
| Attribute | Type | Description | Called When |
| -------- | -------- | -------- | -------- |
| onInit | () => void | Listens for page initialization. | Page initialization is complete. This function is called only once in the page lifecycle. |
| onReady | () => void | Listens for page creation. | A page is created. This function is called only once in the page lifecycle. |
| onShow | () => void | Listens for page display. | The page is displayed. |
| onHide | () => void | Listens for page disappearance. | The page disappears. |
| onDestroy | () => void | Listens for page destruction. | The page is destroyed. |
| onBackPress | () => boolean | Listens for the back button action. | The back button is touched.<br/>- true means that the page processes the return logic.<br/>- false means that the default return logic is used.<br/>- If no value is returned, the default return logic is used. |
| onActive()<sup>5+</sup> | () => void | Listens for page activation. | The page is activated. |
| onInactive()<sup>5+</sup> | () => void | Listens for page suspension. | The page is suspended. |
| onNewRequest()<sup>5+</sup> | () => void | Listens for a new FA request. | The FA has been started and a new request is received. |
The lifecycle functions of page A are called in the following sequence:
- Open page A: onInit() -> onReady() -> onShow()
- Open page B on page A: onHide()
- Go back to page A from page B: onShow()
- Exit page A: onBackPress() -> onHide() -> onDestroy()
- Hide page A: onInactive() -> onHide()
- Show background page A on the foreground: onShow() -> onActive()
![en-us_image_0000001267887881](figures/en-us_image_0000001267887881.png)
# Multi-Language Capability
Applications designed based on the JS UI framework apply to different countries and regions. With the multi-language capability, you do not need to develop application versions in different languages, and your users can switch between various locales. This also facilitates project maintenance.
You only need to perform operations in [Resource Files](#resource-files) and [Resource Reference](#resource-reference) to use the multi-language capability of this framework. For details about how to obtain the current system language, see [Language Acquisition](#language-acquisition).
## Resource Files
Resource files store application content in multiple languages. This framework uses JSON files to store resource definitions. Place the resource file of each locale in the i18n directory described in [File Organization](js-framework-file.md).
Resource files should be named in _language-script-region_.json format. For example, the resource file for Hong Kong Chinese in the traditional script is named zh-Hant-HK. You can omit the region, for example, zh-CN for simplified Chinese, or omit both the script and region, for example, zh for Chinese.
```
language[-script-region].json
```
The following table describes the requirements for the qualifiers of resource file names.
Table1 Requirements for qualifier values
| Qualifier Type | Description and Value Range |
| -------- | -------- |
| Language | Indicates the language used by the device. The value consists of two or three lowercase letters, for example, zh indicates Chinese, en indicates English, and mai indicates Maithili.<br/>For details about the value range, refer to ISO 639 (codes for the representation of names of languages). |
| Script | Indicates the script type used by the device. The value starts with one uppercase letter followed by three lowercase letters, for example, Hans indicates simplified Chinese and Hant indicates traditional Chinese.<br/>For details about the value range, refer to ISO 15924 (codes for the representation of names of scripts). |
| Country/Region | Indicates the country or region where a user is located. The value consists of two or three uppercase letters or three digits, for example, CN indicates China and GB indicates the United Kingdom.<br/>For details about the value range, refer to ISO 3166-1 (codes for the representation of names of countries and their subdivisions). |
If there is no resource file of the locale that matches the system language, content in the en-US.json file will be used by default.
The format of the resource file content is as follows:
en-US.json
```
{
"strings": {
"hello": "Hello world!",
"object": "Object parameter substitution-{name}",
"array": "Array type parameter substitution-{0}",
"symbol": "@#$%^&*()_+-={}[]\\|:;\"'<>,./?"
},
"files": {
"image": "image/en_picture.PNG"
}
}
```
Different languages have different matching rules for singular and plural forms. In the resource file, zero, one, two, few, many, and other are used to define the string content in different singular and plural forms. For example, there is only the other scenario in Chinese since the language does not have singular and plural forms. one and other scenarios are applicable to English. All six scenarios are needed for Arabic.
The following example takes en-US.json and ar-AE.json as examples:
en-US.json
```
{
"strings": {
"people": {
"one": "one person",
"other": "{count} people"
}
}
}
```
ar-AE.json
```
{
"strings": {
"people": {
"zero": "لا أحد",
"one": "وحده",
"two": "اثنان",
"few": "ستة اشخاص",
"many": "خمسون شخص",
"other": "مائة شخص"
}
}
}
```
## Resource Reference
Multi-language syntax used on application development pages (including simple formatting and singular-plural formatting) can be used in .hml or .js files.
- Simple formatting
Use the $t function to reference to resources of different locales. The $t function is available for both .hml and .js files. The system displays content based on a resource file path specified via $t and the specified resource file whose locale matches the current system language.
Table2 Simple formatting
| Attribute | Type | Parameter | Mandatory | Description |
| -------- | -------- | -------- | -------- | -------- |
| $t | Function | See Table3 | Yes | Sets the parameters based on the system language, for example, this.$t('strings.hello'). |
Table3 $t function parameters
| Parameter | Type | Mandatory | Description |
| -------- | -------- | -------- | -------- |
| path | string | Yes | Path of the language resource key |
| params | Array\|Object | No | Content used to replace placeholders during runtime. There are two types of placeholders available:<br/>- Named placeholder, for example, {name}. The actual content must be of the object type, for example, $t('strings.object', { name: 'Hello world' }).<br/>- Digit placeholder, for example, {0}. The actual content must be of the array type, for example, $t('strings.array', ['Hello world']. |
- Example code for simple formatting
```
<!-- xxx.hml -->
<div>
<!-- Display Hello world! without using a placeholder. -->
<text>{{ $t('strings.hello') }}</text>
<!-- Replace named placeholder {name} with Hello world and display it. -->
<text>{{ $t('strings.object', { name: 'Hello world' }) }}</text>
<!-- Replace digit placeholder {0} with Hello world and display it. -->
<text>{{ $t('strings.array', ['Hello world']) }}</text>
<!-- Obtain the resource content from the .js file and display Hello world. -->
<text>{{ hello }}</text>
<!-- Obtain the resource content from the .js file, replace named placeholder {name} with Hello world, and display Substitution in an object: Hello world. -->
<text>{{ replaceObject }}</text>
<!-- Obtain the resource content from the .js file, replace digit placeholder {0} with Hello world, and display Substitution in an array: Hello world. -->
<text>{{ replaceArray }}</text>
<!-- Display the image in the specified file path. -->
<image src="{{ $t('files.image') }}" class="image"></image>
<!-- Obtain the image file path from the .js file and display the image. -->
<image src="{{ replaceSrc }}" class="image"></image>
</div>
```
```
// xxx.js
// The following example uses the $t function in the .js file.
export default {
data: {
hello: '',
replaceObject: '',
replaceArray: '',
replaceSrc: '',
},
onInit() {
this.hello = this.$t('strings.hello');
this.replaceObject = this.$t('strings.object', { name: 'Hello world' });
this.replaceArray = this.$t('strings.array', ['Hello world']);
this.replaceSrc = this.$t('files.image');
},
}
```
- Singular-plural formatting
Table4 Singular-plural formatting
| Attribute | Type | Parameter | Mandatory | Description |
| -------- | -------- | -------- | -------- | -------- |
| $tc | Function | See Table 5. | Yes | Converts between the singular and plural forms based on the system language, for example, this.$tc('strings.people').<br/>> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:<br/>> The resource content is distinguished by the following JSON keys: zero, one, two, few, many, and other. |
Table5 $tc function parameters
| Parameter | Type | Mandatory | Description |
| -------- | -------- | -------- | -------- |
| path | string | Yes | Path of the language resource key |
| count | number | Yes | Number |
- Sample code for singular-plural formatting
```
<!--xxx.hml-->
<div>
<!-- When the value 0 is passed, "0 people" matches the Arabic string whose key is zero. -->
<text>{{ $tc('strings.people', 0) }}</text>
<!-- When the value 1 is passed, "1 person" matches the Arabic string whose key is one. -->
<text>{{ $tc('strings.people', 1) }}</text>
<!-- When the value 2 is passed, "2 people" matches the Arabic string whose key is two. -->
<text>{{ $tc('strings.people', 2) }}</text>
<!-- When the value 6 is passed, "6 people" matches the Arabic string whose key is few. -->
<text>{{ $tc('strings.people', 6) }}</text>
<!-- When the value 50 is passed, "50 people" matches the Arabic string whose key is many. -->
<text>{{ $tc('strings.people', 50) }}</text>
<!-- When the value 100 is passed, "100 people" matches the Arabic string whose key is other. -->
<text>{{ $tc('strings.people', 100) }}</text>
</div>
```
## Language Acquisition
For details about how to obtain the language, see the Application Configuration section.
# Resource Limitations and Access
## Resource Qualifiers
The name of a resource qualifier consists of one or more qualifiers that represent the application scenarios or device characteristics, covering the screen density, and more. The qualifiers are separated using hyphens (-). When creating a qualifiers file under resources, you need to understand the file naming conventions and the rules for matching qualifiers files and the device status.
## Naming Conventions for Resource Qualifiers
- Qualifiers are ordered in the following sequence: screen density. You can select one or multiple qualifiers to name your file based on your application scenarios and device characteristics, while following the preceding sequence.
- The qualifiers are separated using hyphens (-), for example, res-dark-ldpi.json.
- Value range of qualifiers: The value of each qualifier must meet the requirements specified in the following table. Otherwise, the resource files in the resources directory cannot be matched. The qualifiers are case sensitive.
- Qualifier prefix: The name of a qualifier file in the resources file has the prefix res, for example, res-ldpi.json.
- Default resource qualifier file: By default, the resource qualifier file in resources is res-defaults.json.
- In the resource qualifier file, color enumeration cannot be used to set resources.
Table1 Resource qualifiers
| Data Type | Description and Value Range |
| -------- | -------- |
| Screen density | Indicates the screen density of the device, in dpi. The value can be:<br/>- ldpi: low-density screen (~120 dpi) (0.75 x Reference density)<br/>- mdpi: medium-density screen (~160 dpi) (reference density)<br/>- hdpi: high-density screen (~240 dpi) (1.5 x Reference density)<br/>- xhdpi: extra high-density screen (~320 dpi) (2.0 x Reference density)<br/>- xxhdpi: extra extra high-density screen (~480 dpi) (3.0 x Reference density)<br/>- xxxhdpi: extra extra extra high-density screen (~640 dpi) (4.0 x Reference density) |
## Rules for Matching Qualifiers Files and Device Resources
- Qualifiers are matched with the device resources in the following priorities: screen orientation > dark mode > screen density. If no matching resource qualifier file is found, the default resource qualifier file is used.
- If a qualifier file contains resource qualifiers, their values must be consistent with the current device status so that the file can be used for matching the device status. For example, the res-hdpi.json qualifier file does not match the device density xhdpi.
## Referencing Resources in the JS Module
You can use the $r syntax in the application development files .hml and .js to format the JSON resources in the resources directory of the JS module and obtain the corresponding resources.
| Attribute | Type | Description |
| -------- | -------- | -------- |
| $r | (key: string) => string | Obtains the resource content that matches the specific qualifiers, for example, this.$r('strings.hello loaded).<br/>Parameter description:<br/>- key: key value defined in the resource qualifier file, for example, strings.hello.<br/> |
Example of res-defaults.json:<br/>
```
{
strings: {
hello: 'hello world'
}
}
```
## Example
resources/res-dark.json:
```
{
"image": {
"clockFace": "common/dark_face.png"
},
"colors": {
"background": "#000000"
}
}
```
resources/res-defaults.json:
```
{
"image": {
"clockFace": "common/face.png"
},
"colors": {
"background": "#ffffff"
}
}
```
```
<!-- xxx.hml -->
<div style="background-color: {{ $r('colors.background') }}">
<image src="{{ $r('image.clockFace') }}"></image>
</div>
```
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:
> The resource qualifier file does not support color enumeration.
# CSS
Cascading Style Sheets (CSS) is a language used to describe the HML page structure. All HML components have default styles. You can customize styles for these components using CSS to design various pages.
## Size Unit
1. Logical px set by &lt;length&gt;:
1. The default logical screen width is 720 px (for details, see the "window" section in [config.json](js-framework-js-tag.md)). Your page will be scaled to fit the actual width of the screen. For example, on a screen with the actual width of 1440 physical px, 100 px is displayed on 200 physical px, with all sizes doubled from 720 px to 1440 px.
2. If you set autoDesignWidth to true (for details, see the "window" section in [config.json](js-framework-js-tag.md)), the logical px are scaled based on the screen density. For example, if the screen density is 3x, 100 px will be rendered on 300 physical px. This approach is recommended when your application needs to adapt to multiple devices.
2. Percentage set by &lt;percentage&gt;: The component size is represented by its percentage of the parent component size. For example, if the width &lt;percentage&gt; of a component is set to 50%, the width of the component is half of its parent component's width.
## Style Import
CSS files can be imported using the @import statement. This facilitates module management and code reuse.
## Style Declaration
The .css file with the same name as the .hml file in each page directory describes the styles of components on the HML page, determining how the components will be displayed.
1. Internal style: The style and class attributes can be used to specify the component style. Example:
```
<!-- index.hml -->
<div class="container">
<text style="color: red">Hello World</text>
</div>
```
```
/* index.css */
.container {
justify-content: center;
}
```
2. External style files: You need to import the files. For example, create a style.css file in the common directory and import the file at the beginning of index.css.
```
/* style.css */
.title {
font-size: 50px;
}
```
```
/* index.css */
@import '../../common/style.css';
.container {
justify-content: center;
}
```
## Selectors
A CSS selector is used to select elements for which styles need to be added to. The following table lists the supported selectors.
| Selector | Example | Description |
| -------- | -------- | -------- |
| .class | .container | Selects all components whose class is container. |
| \#id | \#titleId | Selects all components whose id is titleId. |
| tag | text | Selects all &lt;text&gt; components. |
| , | .title, .content | Selects all components whose class is title or content. |
| \#id .class tag | \#containerId .content text | Selects all grandchild &lt;text&gt; components whose grandparent components are identified as containerId and whose parent components are of the content class. To select child components, use > to replace the space between \#id and .class, for example, \#containerId>.content. |
The following is an example:
```
<!-- Page layoutxxx.hml -->
<div id="containerId" class="container">
<text id="titleId" class="title">Title</text>
<div class="content">
<text id="contentId">Content</text>
</div>
</div>
```
```
/* Page style xxx.css */
/* Set the style for all <div> components. */
div {
flex-direction: column;
}
/* Set the style for the component whose class is title. */
.title {
font-size: 30px;
}
/* Set the style for the component whose id is contentId. */
#contentId {
font-size: 20px;
}
/* Set padding for all components of the title or content class to 5 px. */
.title, .content {
padding: 5px;
}
/* Set the style for all texts of components whose class is container.
*/
.container text {
color: #007dff;
}
/* Set the style for direct descendant texts of components whose class is container.
*/
.container > text {
color: #fa2a2d;
}
```
The above style works as follows:
![en-us_image_0000001267607889](figures/en-us_image_0000001267607889.png)
In the preceding example, .container text sets title and content to blue, and .container > text sets title to red. The two styles have the same priority, but .container > text declares the style later and overwrites the former style. (For details about the priority, see [Selector Specificity](#selector-specificity).)
## Selector Specificity
The specificity rule of the selectors complies with the W3C rule, which is only available for inline styles, id, class, tag, grandchild components, and child components. (Inline styles are those declared in the style attribute.)
When multiple selectors point to the same element, their priorities are as follows (in descending order): inline style > id > class > tag.
## Pseudo-classes
A CSS pseudo-class is a keyword added to a selector that specifies a special state of the selected element(s). For example, :disabled can be used to select the element whose disabled attribute is true.
In addition to a single pseudo-class, a combination of pseudo-classes is supported. For example, :focus:checked selects the element whose focus and checked attributes are both set to true. The following table lists the supported single pseudo-class in descending order of priority.
| Pseudo-class | Available Components | Description |
| -------- | -------- | -------- |
| :disabled | Components that support the disabled attribute | Selects the element whose disabled attribute is changed to true (unavailable for animation attributes). |
| :focus | Components that support the focusable attribute | Selects the element that takes focus (unavailable for animation attributes). |
| :active | Components that support the click event<br/> | Selects the element activated by a user. For example, a pressed button or a selected tab-bar (unavailable for animation attributes). |
| :waiting | button | Selects the element whose waiting attribute is true (unavailable for animation attributes). |
| :checked | input[type="checkbox", type="radio"], and switch | Selects the element whose checked attribute is true (unavailable for animation attributes). |
| :hover<sup>6+</sup> | Components that support the mouseover event | Selects the element that the cursor is on. |
The following is an example for you to use the :active pseudo-class to control the style when a user presses the button.
```
<!-- index.hml -->
<div class="container">
<input type="button" class="button" value="Button"></input>
</div>
```
```
/* index.css */
.button:active {
background-color: #888888;/* After the button is activated, the background color is changed to #888888. */
}
```
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:
> Pseudo-classes are not supported for the <popup> component and its child components, including, <dialog>, <menu>, <option>, and <picker>.
## Precompiled Styles
Precompilation is a program that uses specific syntax to generate CSS files. It provides variables and calculation, helping you define component styles more conveniently. Currently, Less, Sass, and Scss are supported. To use precompiled styles, change the suffix of the original .css file. For example, change index.css to index.less, index.sass, or index.scss.
- The following index.less file is changed from index.css.
```
/* index.less */
/* Define a variable. */
@colorBackground: #000000;
.container {
background-color: @colorBackground; /* Use the variable defined in the .less file. */
}
```
- Reference a precompiled style file. For example, if the style.scss file is located in the common directory, change the original index.css file to index.scss and import style.scss.
```
/* style.scss */
/* Define a variable. */
$colorBackground: #000000;
```
Reference the precompiled style file in index.scss:
```
/* index.scss */
/* Import style.scss. */
@import '../../common/style.scss';
.container {
background-color: $colorBackground; /* Use the variable defined in style.scss. */
}
```
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:
> Place precompiled style files in the common directory.
## CSS Style Inheritance<sup>6+</sup>
CSS style inheritance enables a child node to inherit the style of its parent node. The inherited style has the lowest priority when multiple style selectors are involved. Currently, the following styles can be inherited:
- font-family
- font-weight
- font-size
- font-style
- text-align
- line-height
- letter-spacing
- color
- visibility
# HML
The OpenHarmony Markup Language (HML) is an HTML-like language that allows you to build pages based on components and events. Pages built using HML have advanced capabilities such as logic control, data binding, event binding, loop rendering, and conditional rendering.
## HML Page Structure
```
<!-- xxx.hml -->
<div class="item-container">
<text class="item-title">Image Show</text>
<div class="item-content">
<image src="/common/xxx.png" class="image"></image>
</div>
</div>
```
## Data Binding
```
<!-- xxx.hml -->
<div onclick="changeText">
<text> {{content[1]}} </text>
</div>
```
```
// xxx.js
export default {
data: {
content: ['Hello World!', 'Welcome to my world!']
},
changeText: function() {
this.content.splice(1, 1, this.content[0]);
}
}
```
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:
> - To make the array data modification take effect, use the splice method to change array items.
>
> - ECMAScript 6 (ES6) syntax is not supported in HML.
## Common Event Binding
Events are bound to components through 'on' or '@'. When a component triggers an event, the corresponding event processing function in the .js file is executed.
Events can be written in the following formats:
- funcName: name of the event callback, which is implemented by defining the corresponding function in the .js file.
- funcName(a,b): function parameters, such as a and b, which can be constants, or variables defined in data in the .js file. Do not add the prefix this. to variables.
- Example
```
<!-- xxx.hml -->
<div class="container">
<text class="title">{{count}}</text>
<div class="box">
<input type="button" class="btn" value="increase" onclick="increase" />
<input type="button" class="btn" value="decrease" @click="decrease" />
<!-- Pass additional parameters. -->
<input type="button" class="btn" value="double" @click="multiply(2)" />
<input type="button" class="btn" value="decuple" @click="multiply(10)" />
<input type="button" class="btn" value="square" @click="multiply(count)" />
</div>
</div>
```
```
// xxx.js
export default {
data: {
count: 0
},
increase() {
this.count++;
},
decrease() {
this.count--;
},
multiply(multiplier) {
this.count = multiplier * this.count;
}
};
```
```
/* xxx.css */
.container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
left: 0px;
top: 0px;
width: 454px;
height: 454px;
}
.title {
font-size: 30px;
text-align: center;
width: 200px;
height: 100px;
}
.box {
width: 454px;
height: 200px;
justify-content: center;
align-items: center;
flex-wrap: wrap;
}
.btn {
width: 200px;
border-radius: 0;
margin-top: 10px;
margin-left: 10px;
}
```
## Bubbling Event Binding<sup>5+</sup>
Bubbling event binding covers the following:
- Bind an event callback for event bubbling: on:{event}.bubble. on:{event} is equivalent to on:{event}.bubble.
- Bind an event callback, but stop the event from bubbling upwards: grab:{event}.bubble. grab:{event} is equivalent to grab:{event}.bubble.
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:
> For details about bubbling events, see [Universal Events](../reference/arkui-js/js-components-common-events.md)
- Example
```
<!-- xxx.hml -->
<div>
<!-- Bind an event callback for event bubbling.5+ -->
<div on:touchstart.bubble="touchstartfunc"></div>
<div on:touchstart="touchstartfunc"></div>
<!-- Bind an event callback, but stop the event from bubbling upwards. 5+ -->
<div grab:touchstart.bubble="touchstartfunc"></div>
<div grab:touchstart="touchstartfunc"></div>
<!-- Bind an event callback for event bubbling.6+ -->
<div on:click.bubble="clickfunc"></div>
<div on:click="clickfunc"></div>
<!-- Bindan event callback, but stop the event from bubbling upwards.6+ -->
<div grab:click.bubble="clickfunc"></div>
<div grab:click="clickfunc"></div>
</div>
```
```
// xxx.js
export default {
clickfunc: function(e) {
console.log(e);
},
touchstartfuc: function(e) {
console.log(e);
},
}
```
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:
> Events bound using a traditional statement (such as onclick) will bubble only when the API version in use is 6 or later.
## Capturing Event Binding<sup>5+</sup>
Touch events can be captured. In the capture phase, which precedes the bubbling phase, an event starts from the parent component to the child component.
Event capturing binding includes:
- Bind an event callback for event capturing: on:{event}.capture.
- Bind an event callback, but stop the event from being captured during downward transfer: grab:{event}.capture.
- Example
```
<!-- xxx.hml -->
<div>
<!-- Bind an event callback for event capturing.5+ --> <div on:touchstart.capture="touchstartfunc"></div>
<!-- Bind an event callback, but stop the event from being captured during downward transfer.5+ -->
<div grab:touchstart.capture="touchstartfunc"></div>
</div>
```
```
// xxx.js
export default {
touchstartfuc: function(e) {
console.log(e);
},
}
```
## Loop Rendering
```
<!-- xxx.hml -->
<div class="array-container">
<!-- div loop rendering -->
<!-- By default, $item indicates the element in the array, and $idx indicates the index of the element in the array. -->
<div for="{{array}}" tid="id" onclick="changeText">
<text>{{$idx}}.{{$item.name}}</text>
</div>
<!-- Define the name for an element variable. -->
<div for="{{value in array}}" tid="id" onclick="changeText">
<text>{{$idx}}.{{value.name}}</text>
</div>
<!-- Define an element variable and its index name. -->
<div for="{{(index, value) in array}}" tid="id" onclick="changeText">
<text>{{index}}.{{value.name}}</text>
</div>
</div>
```
```
// xxx.js
export default {
data: {
array: [
{id: 1, name: 'jack', age: 18},
{id: 2, name: 'tony', age: 18},
],
},
changeText: function() {
if (this.array[1].name === "tony"){
this.array.splice(1, 1, {id:2, name: 'Isabella', age: 18});
} else {
this.array.splice(2, 1, {id:3, name: 'Bary', age: 18});
}
},
}
```
The tid attribute accelerates the for loop and improves the re-rendering efficiency when data in a loop changes.
The tid attribute specifies the unique ID of each element in the array. If it is not specified, the index of each element in the array is used as the ID. For example, tid="id" indicates that the id attribute of each element is its unique ID.
The for loop supports the following statements:
- for="array": array is an array object, whose element variable is $item by default.
- for="v in array": v is a custom element variable, whose index is $idx by default.
- for="(i, v) in array": i indicates the element index, and v indicates the element variable. All elements of the array object will be looped through.
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:
> - Each element in the array must have the data attribute specified by tid. Otherwise, an exception may occur.
>
> - The attribute specified by tid in the array must be unique. Otherwise, performance loss occurs. In the above example, only id and name can be used as tid because they are unique fields.
>
> - The tid field does not support expressions.
## Conditional Rendering
There are two ways to implement conditional rendering: if-elif-else or show. In if-elif-else, when the if statement evaluates to false, the component is not built in the VDOM and is not rendered. For show, when show is false, the component is not rendered but is built in the VDOM. In addition, the if-elif-else statements must be used in sibling nodes. Otherwise, the compilation fails. The following example uses both ways to implement conditional rendering:
```
<!-- xxx.hml -->
<div class="container">
<button class="btn" type="capsule" value="toggleShow" onclick="toggleShow"></button>
<button class="btn" type="capsule" value="toggleDisplay" onclick="toggleDisplay"></button>
<text if="{{visible}}"> Hello-TV </text>
<text elif="{{display}}"> Hello-Wearable </text>
<text else> Hello-World </text>
</div>
```
```
/* xxx.css */
.container{
flex-direction: column;
align-items: center;
}
.btn{
width: 280px;
font-size: 26px;
margin: 10px 0;
}
```
```
// xxx.js
export default {
data: {
visible: false,
display: true,
},
toggleShow: function() {
this.visible = !this.visible;
},
toggleDisplay: function() {
this.display = !this.display;
}
}
```
In the optimized rendering (show), if show is true, the node is rendered properly; if it is false, the display style will be none.
```
<!-- xxx.hml -->
<div class="container">
<button class="btn" type="capsule" value="toggle" onclick="toggle"></button>
<text show="{{visible}}" > Hello World </text>
</div>
```
```
/* xxx.css */
.container{
flex-direction: column;
align-items: center;
}
.btn{
width: 280px;
font-size: 26px;
margin: 10px 0;
}
```
```
// xxx.js
export default {
data: {
visible: false,
},
toggle: function() {
this.visible = !this.visible;
},
}
```
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:
> Do not use for and if attributes at the same time in an element.
## Logic Control Block
<block> makes loop rendering and conditional rendering more flexible. A <block> will not be compiled as a real component. **NOTE** that the <block> tag supports only the for and if attributes.
```
<!-- xxx.hml -->
<list>
<block for="glasses">
<list-item type="glasses">
<text>{{$item.name}}</text>
</list-item>
<block for="$item.kinds">
<list-item type="kind">
<text>{{$item.color}}</text>
</list-item>
</block>
</block>
</list>
```
```
// xxx.js
export default {
data: {
glasses: [
{name:'sunglasses', kinds:[{name:'XXX',color:'XXX'},{name:'XXX',color:'XXX'}]},
{name:'nearsightedness mirror', kinds:[{name:'XXX',color:'XXX'}]},
],
},
}
```
## Template Reference
HML supports using elements to reference template files. For details, see Custom Components.
```
<!-- template.hml -->
<div class="item">
<text>Name: {{name}}</text>
<text>Age: {{age}}</text>
</div>
```
```
<!-- index.hml -->
<element name='comp' src='../../common/template.hml'></element>
<div>
<comp name="Tony" age="18"></comp>
</div>
```
# JavaScript
You can use a .js file in the ECMAScript compliant JavaScript language to define the service logic of an HML page. With dynamic typing, JavaScript can make your application more expressive with a flexible design. The following describes the JavaScript compilation and running.
## Syntax
The ES6 syntax is supported.
- Module declaration
Import functionality modules.
```
import router from '@system.router';
```
- Code reference
Import JavaScript code.
```
import utils from '../../common/utils.js';
```
## Objects
- Application Object
| Attribute | Type | Description |
| -------- | -------- | -------- |
| $def | Object | Object that is exposed in the app.js file and obtained by this.$app.$def.<br/>> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:<br/>> Application objects do not support data binding. Data update should be triggered on the UI. |
Example Code
```
// app.js
export default {
onCreate() {
console.info('AceApplication onCreate');
},
onDestroy() {
console.info('AceApplication onDestroy');
},
globalData: {
appData: 'appData',
appVersion: '2.0',
},
globalMethod() {
console.info('This is a global method!');
this.globalData.appVersion = '3.0';
}
};
```
```
// index.js
export default {
data: {
appData: 'localData',
appVersion:'1.0',
},
onInit() {
this.appData = this.$app.$def.globalData.appData;
this.appVersion = this.$app.$def.globalData.appVersion;
},
invokeGlobalMethod() {
this.$app.$def.globalMethod();
},
getAppVersion() {
this.appVersion = this.$app.$def.globalData.appVersion;
}
}
```
- Page objects
| Attribute | Type | Description |
| -------- | -------- | -------- |
| data | Object/Function | Data model of the page. If the attribute is of the function type, the return value must be of the object type. The attribute name cannot start with a dollar sign ($) or underscore (_). Do not use reserved words (for, if, show, and tid).<br/>Do not use this attribute and private or public at the same time. |
| $refs | Object | DOM elements or child component instances that have registered the ref attribute. For example code, see [Obtaining a DOM element](#obtaining-a-dom-element). |
| private | Object | Data model of the page. Private data attribute can be modified only on the current page. |
| public | Object | Data model of the page. Behaviors of public data attributes are the same as those of the data attribute. |
| props | Array/Object | Used for communication between components. This attribute can be transferred to components via &lt;tag xxxx='value'&gt;. A props name must be in lowercase and cannot start with a dollar sign ($) or underscore (_). Do not use reserved words (for, if, show, and tid). Currently, props does not support functions. For details, see [Custom Component](../reference/arkui-js/js-components-custom-props.md). |
| computed | Object | Used for pre-processing an object for reading and setting. The result is cached. The name cannot start with a dollar sign ($) or underscore (_). Do not use reserved words. For details, see [Custom Component](../reference/arkui-js/js-components-custom-props.md). |
## Functions
- Data functions
| Function | Parameter | Description |
| -------- | -------- | -------- |
| $set | key: string, value: any | Adds an attribute or modifies an existing attribute.<br/>Usage:<br/>this.$set('_key_',_value_): Add an attribute. |
| $delete | key: string | Deletes an attribute.<br/>Usage:<br/>this.$delete('_key_'): Delete an attribute. |
Example Code
```
// index.js
export default {
data: {
keyMap: {
OS: 'OpenHarmony',
Version: '2.0',
},
},
getAppVersion() {
this.$set('keyMap.Version', '3.0');
console.info("keyMap.Version = " + this.keyMap.Version); // keyMap.Version = 3.0
this.$delete('keyMap');
console.info("keyMap.Version = " + this.keyMap); // log print: keyMap.Version = undefined
}
}
```
- Public functions
| Function | Parameter | Description |
| -------- | -------- | -------- |
| $element | id: string | Obtains the component with a specified ID. If no ID is specified, the root component is returned. For example code, see [Obtaining a DOM element](#obtaining-a-dom-element).<br/>Usage:<br/>```<div id='_xxx_'></div>```<br/>- this.$element('_xxx_'): Obtain the component whose ID is _xxx_.<br/>- this.$element(): Obtain the root component. |
| $rootElement | None | Obtains the root element.<br/>Usage: this.$rootElement().scrollTo({ duration: 500, position: 300 }), which scrolls the page by 300 px within 500 ms. |
| $root | N/A | Obtains the root ViewModel instance. For example code, see [Obtaining the ViewModel](#obtaining-the-viewmodel). |
| $parent | N/A | Obtains the parent ViewModel instance. For example code, see [Obtaining the ViewModel](#obtaining-the-viewmodel). |
| $child | id: string | Obtains the ViewModel instance of a custom child component with a specified ID. For example code, see [Obtaining the ViewModel](#obtaining-the-viewmodel).<br/>Usage:<br/>this.$child('_xxx_'): Obtain the ViewModel instance of a custom child component whose ID is _xxx_. |
- Event function
| Function | Parameter | Description |
| -------- | -------- | -------- |
| $watch | data: string, callback: string \| Function | Listens for attribute changes. If the value of the data attribute changes, the bound event is triggered. For details, see [Custom Component](../reference/arkui-js/js-components-custom-props.md)<br/>Usage:<br/>this.$watch('_key_',_callback_) |
- Page function
| Function | Parameter | Description |
| -------- | -------- | -------- |
| scrollTo<sup>6+</sup> | scrollPageParam: ScrollPageParam | Scrolls the page to the target position. You can specify the position using the ID selector or scrolling distance. |
Table1 ScrollPageParam<sup>6+</sup>
| Name | Type | Default Value | Description |
| -------- | -------- | -------- | -------- |
| position | number | - | Position to scroll to. |
| id | string | - | ID of the element to be scrolled to. |
| duration | number | 300 | Scrolling duration, in milliseconds. |
| timingFunction | string | ease | Animation curve for scrolling. Available option:<br/>[Animation Styles](../reference/arkui-js/js-components-common-animation.md) |
| complete | () => void | - | Callback to be invoked when the scrolling is complete. |
Example:
```
this.$rootElement.scrollTo({position: 0})
this.$rootElement.scrollTo({id: 'id', duration: 200, timingFunction: 'ease-in', complete: ()=>void})
```
## Obtaining a DOM Element
1. Use $refs to obtain a DOM element.
```
<!-- index.hml -->
<div class="container">
<image-animator class="image-player" ref="animator" images="{{images}}" duration="1s" onclick="handleClick"></image-animator>
</div>
```
```
// index.js
export default {
data: {
images: [
{ src: '/common/frame1.png' },
{ src: '/common/frame2.png' },
{ src: '/common/frame3.png' },
],
},
handleClick() {
const animator = this.$refs.animator; // Obtain the DOM element whose $refs attribute is animator.
const state = animator.getState();
if (state === 'paused') {
animator.resume();
} else if (state === 'stopped') {
animator.start();
} else {
animator.pause();
}
},
};
```
2. Call $element to obtain a DOM element.
```
<!-- index.hml -->
<div class="container">
<image-animator class="image-player" id="animator" images="{{images}}" duration="1s" onclick="handleClick"></image-animator>
</div>
```
```
// index.js
export default {
data: {
images: [
{ src: '/common/frame1.png' },
{ src: '/common/frame2.png' },
{ src: '/common/frame3.png' },
],
},
handleClick() {
const animator = this.$element('animator'); // Obtain the DOM element whose ID is animator.
const state = animator.getState();
if (state === 'paused') {
animator.resume();
} else if (state === 'stopped') {
animator.start();
} else {
animator.pause();
}
},
};
```
## Obtaining the ViewModel
The following shows files of the root page:
```
<!-- root.hml -->
<element name='parentComp' src='../../common/component/parent/parent.hml'></element>
<div class="container">
<div class="container">
<text>{{text}}</text>
<parentComp></parentComp>
</div>
</div>
```
```
// root.js
export default {
data: {
text: 'I am a root!',
},
}
```
Customize the parent component.
```
<!-- parent.hml -->
<element name='childComp' src='../child/child.hml'></element>
<div class="item" onclick="textClicked">
<text class="text-style" onclick="parentClicked">Click this parent component</text>
<text class="text-style" if="{{show}}">Hello parent component!</text>
<childComp id = "selfDefineChild"></childComp>
</div>
```
```
// parent.js
export default {
data: {
show: false,
text: 'I am the parent component!',
},
parentClicked () {
this.show = !this.show;
console.info('parent component get parent text');
console.info(`${this.$parent().text}`);
console.info("The parent component gets the child function.");
console.info(`${this.$child('selfDefineChild').childClicked()}`);
},
}
```
Customize the child component.
```
<!-- child.hml -->
<div class="item" onclick="textClicked">
<text class="text-style" onclick="childClicked">Child component clicked</text>
<text class="text-style" if="{{show}}">Hello child component</text>
</div>
```
```
// child.js
export default {
data: {
show: false,
text: 'I am the child component!',
},
childClicked () {
this.show = !this.show;
console.info('child component get parent text');
console.info('${this.$parent().text}');
console.info('child component get root text');
console.info('${this.$root().text}');
},
}
```
# Accessing Application Resources
## Resource Definition
Application resources are defined by in the project's resources directory, which is organized as follows:
- Level-1: base sub-directory, qualifiers sub-directories, and rawfile sub-directory
- The base sub-directory is a default directory. If no qualifiers sub-directories in the resources directory of the application match the device status, the resource file in the base sub-directory will be automatically referenced.
- You need to create qualifiers sub-directories on your own. The name of a qualifiers sub-directory consists of one or more qualifiers that represent the application scenarios or device characteristics, covering the mobile country code (MCC), mobile network code (MNC), language, script, country or region, screen orientation, device type, color mode, and screen density. The qualifiers are separated using underscores (_) or hyphens (-).
- When the resources in the rawfile sub-directory are referenced, resource files will not be matched based on the device status. You can directly store resource files in the rawfile sub-directory.
- Level-2: resource sub-directories
- Resource sub-directories store basic elements such as character strings, colors, and floating point numbers, and resource files such as media files.
- Supported files and resource types are listed in the table below:
| File Name | Resource Type |
| -------- | -------- |
| color.json | Color resource. |
| float.json | Resources such as spacing, rounded corners, and fonts. |
| string.json | String resource. |
| plural.json | String resource. |
| media directory | Image resource. |
## Referencing Resources
To reference an application resource in a project, use the "$r('app.type.name')" format. app indicates the resource defined in the resources directory of the application. type indicates the resource type (or the location where the resource is stored). The value can be color, float, string, plural, or media. name indicates the resource name, which you set when defining the resource.
When referencing resources in the rawfile sub-directory, use the "$rawfile('filename')" format. Currently, $rawfile allows only the <Image> component to reference image resources. In the format, filename indicates the relative path of a file in the rawfile directory, and the file name must contain the file name extension. **NOTE** that the relative path cannot start with a slash (/).
## Example
Some custom resources in the base sub-directory are as follows:
```
resources
├─ base
│ ├─ element
│ │ ├─ color.json
│ │ ├─ string.json
│ │ └─ float.json
│ └─ media
│ └─ my_background_image.png
└─ rawfile
├─ test.png
└─ newDir
└─ newTest.png
```
The content of the color.json file is as follows:
```
{
"color": [
{
"name": "color_hello",
"value": "#ffff0000"
},
{
"name": "color_world",
"value": "#ff0000ff"
}
]
}
```
The content of the float.json file is as follows:
```
{
"float":[
{
"name":"font_hello",
"value":"28.0fp"
},
{
"name":"font_world",
"value":"20.0fp"
}
]
}
```
The content of the string.json file is as follows:
```
{
"string":[
{
"name":"string_hello",
"value":"Hello"
},
{
"name":"string_world",
"value":"World"
},
{
"name":"message_arrive",
"value":"We will arrive at %s."
}
]
}
```
The content of the plural.json file is as follows:
```
{
"plural":[
{
"name":"eat_apple",
"value":[
{
"quantity":"one",
"value":"%d apple"
},
{
"quantity":"other",
"value":"%d apples"
}
]
}
]
}
```
In the ets file, you can use the resources defined in the resources directory.
```
Text($r('app.string.string_hello'))
.fontColor($r('app.color.color_hello'))
.fontSize($r('app.float.font_hello'))
}
Text($r('app.string.string_world'))
.fontColor($r('app.color.color_world'))
.fontSize($r('app.float.font_world'))
}
Text($r('app.string.message_arrive', "five of the clock")) // Reference string resources. The second parameter of $r is used to replace %s.
.fontColor($r('app.color.color_hello'))
.fontSize($r('app.float.font_hello'))
}
Text($r('app.plural.eat_apple', 5, 5)) // Reference plural resources. The first parameter specifies the plural resource, and the second parameter specifies the number of plural resources. The third number indicates the substitute of %d.
.fontColor($r('app.color.color_world'))
.fontSize($r('app.float.font_world'))
}
Image($r(?app.media.my_background_image creation)) // Reference media resources.
Image($rawfile( Femaletest.png loaded)) // Reference an image in the rawfile directory.
Image($rawfile(newDir/newTest.png loaded)) // Reference an image in the rawfile directory.
```
# Environment
Environment is a singleton object created by the framework when the application is started. It provides the AppStorage with a series of environment state attributes required by the application. These attributes describe the device environment where the application runs. Environment and its attributes are immutable, and all attribute values are of the simple type. The following example shows how to obtain the semantic environment from Environment:
```
Environment.EnvProp("accessibilityEnabled", "default");
var enable = AppStorage.Get("accessibilityEnabled");
```
accessibilityEnabled is the default system variable identifier provided by Environment. You need to bind the corresponding system attribute to the AppStorage. Then, you can use the methods or decorators in the AppStorage to access the corresponding system attribute data.
## Environment APIs
| key | Parameter | Return Value | Description |
| -------- | -------- | -------- | -------- |
| EnvProp | key: string,<br/>defaultValue: any | boolean | Binds this system attribute to the AppStorage. You are advised to use this API during application startup. If the attribute already exists in the AppStorage, false is returned. Do not use the variables in the AppStorage. Instead, call this method to bind environment variables. |
| EnvProps | keys: {<br/>key: string,<br/>defaultValue: any<br/>}[] | void | Associates this system item array with the AppStorage. |
| Keys | Array&lt;string&gt; | number | Returns the associated system item array. |
## Built-in Environment Variables
| key | Type | Description |
| -------- | -------- | -------- |
| accessibilityEnabled | boolean | Whether to enable accessibility. |
| colorMode | ColorMode | Color mode. The options are as follows:<br/>- ColorMode.LIGHT: light mode.<br/>- ColorMode.DARK: dark mode. |
| fontScale | number | Font scale. The value range is [0.85, 1.45]. |
| fontWeightScale | number | Font weight scale. The value range is [0.6, 1.6]. |
| layoutDirection | LayoutDirection | Layout direction. The options are as follows:<br/>- LayoutDirection.LTR: The direction is from left to right.<br/>- LayoutDirection.RTL: The direction is from right to left. |
| languageCode | string | Current system language. The value is in lowercase, for example, zh. |
# PersistentStorage
ArkUI provides some static methods in the PersistentStorage class for managing persistent data of applications. Persistent data with specific tags can be linked to the AppStorage, and then the persistent data can be accessed through the AppStorage APIs. Alternatively, the @StorageLink decorator can be used to access the variable of the specific key.
| Name | Type | Return Value | Definition |
| -------- | -------- | -------- | -------- |
| PersistProp | key : string<br/>defaultValue: T | void | Changes the associated named attribute to persistent data in the AppStorage. The value overwriting sequence is as follows:<br/>- If the attribute exists in the AppStorage, use it to overwrite the value in Persistent.<br/>- If Persistent contains the specified attribute, use the attribute value in Persistent.<br/>- If the preceding conditions are not met, use defaultValue. The values null and undefined are not supported. |
| DeleteProp | key: string | void | Cancels two-way binding. The value of this attribute will be deleted from the persistent storage. |
| PersistProps | keys: {<br/>key: string,<br/>defaultValue: any<br/>}[] | void | Associates multiple named attribute bindings. |
| Keys | void | Array &lt;string&gt; | Returns the flags of all persistent attributes. |
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:
> - When using PersistProp, ensure that the input key exists in the AppStorage.
>
> - DeleteProp takes effect only for the data that has been linked during the current startup.
```
PersistentStorage.PersistProp("highScore", "0");
@Entry
@Component
struct PersistentComponent {
@StorageLink('highScore') highScore: string = '0'
@State currentScore: number = 0
build() {
Column() {
if (this.currentScore === Number(this.highScore)) {
Text(`new highScore : ${this.highScore}`)
}
Button() {
Text(`goal!, currentScore : ${this.currentScore}`)
.fontSize(10)
}.onClick(() => {
this.currentScore++
if (this.currentScore > Number(this.highScore)) {
this.highScore = this.currentScore.toString()
}
})
}
}
}
```
# AppStorage
AppStorage is a singleton object in an application, which is created by the UI framework when the application is started and destroyed when the application exits. It is used to provide central storage for changing state attributes of an application. AppStorage contains all the state attributes that need to be accessed throughout the application. The AppStorage retains all attributes and their values as long as the application remains running, and the attribute values can be accessed through unique key values.
The UI component can synchronize the application state data with the AppStorage through the decorators. The application service logic can also be implemented by accessing the AppStorage through APIs.
The selection state attribute of the AppStorage can be synchronized with different data sources or data sinks. These data sources and data sinks can be local or remote devices and provide different functions, such as data persistence. Such data sources and data sinks can be implemented independently of the UI in the service logic.
By default, the attributes in the AppStorage are changeable. If needed, AppStorage can also use immutable (read-only) attributes.
## AppStorage APIs
| Name | Type | Return Value | Definition |
| -------- | -------- | -------- | -------- |
| SetAndLink | key: string,<br/>defaultValue: T | @Link | Works in a way similar to the Link API. If the current key is stored in the AppStorage, the value corresponding to the key is returned. If the key has not been created, a Link instance corresponding to the default value is created and returned. |
| Set | key: string,<br/>newValue: T | void | Replaces the value of a saved key. |
| Link | key: string | @Link | Returns two-way binding to this attribute if there is data with a given key. This means that attribute changes made by a variable or component will be synchronized to the AppStorage, and attribute changes made through the AppStorage will be synchronized to the variable or component. If the attribute with this key does not exist or is read-only, undefined is returned. |
| SetAndProp | propName: string,<br/>defaultValue: S | @Prop | Works in a way similar to the Prop API. If the current key is stored in the AppStorage, the value corresponding to the key is returned. If the key has not been created, a Prop instance corresponding to the default value is created and returned. |
| Prop | key: string | @Prop | Returns one-way binding to an attribute with a given key if the attribute exists. This means that attribute changes made through the AppStorage will be synchronized to the variable or component, but attribute changes made by the variable or component will be synchronized to the AppStorage. The variable returned by this method is an immutable one, which is applicable both to the variable and immutable state attributes. If the attribute with the specified key does not exist, undefined is returned.<br/>> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:<br/>> The attribute value used in the prop method must be of a simple type. |
| SetOrCreate | key: string,<br/>newValue: T | boolean | If an attribute that has the same name as the specified key exists: replaces the value of the attribute and returns true when the attribute can be modified; retains the original value of the attribute and returns false otherwise.<br/>If an attribute that has the same name as the specified key does not exist: creates an attribute whose key is key and value is newValue. The values null and undefined are not supported. |
| Get | key: string | T or undefined | Obtains the value of the specified key. |
| Has | propName: string | boolean | Checks whether the attribute corresponding to the specified key value exists. |
| Keys | void | array&lt;string&gt; | Returns an array of strings containing all keys. |
| Delete | key: string | boolean | Deletes the key-value pair for the specified key. Returns true if the key-value pair exists and is successfully deleted; returns false otherwise. |
| Clear | void | boolean | Deletes all attributes. If any of the attributes is being referenced by a state variable, false is returned. |
| IsMutable | key: string | boolean | Specifies whether the attribute exists and can be changed. |
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:
> Currently, the API can process only basic data and cannot modify a value in an object.
## Synchronization Between AppStorage and Components
In [Managing Component States](ts-component-states-state.md), we have defined how to synchronize the state variables of child components with the @State decorated variables in the parent component or ancestor component, including @Prop, @Link, and @Consume.
In this section, we'll describe how to synchronize component variables with the AppStorage through the @StorageLink and @StorageProp decorators.
### @StorageLink Decorator
Two-way data binding can be established between components and the AppStorage through state variables decorated by @StorageLink(_key_). Wherein, key is the attribute key value in the AppStorage. When a component containing the @StorageLink decorated variable is created, the variable is initialized using the value in the AppStorage. Changes made to this variable in the component will be first synchronized to the AppStorage, and then to other bound instances, such as PersistentStorage or other bound UI components.
### @StorageProp Decorator
One-way data binding can be established between components and the AppStorage through state variables decorated by @StorageProp(_key_). Wherein, key is the attribute key value in the AppStorage. When a component containing the @StorageProp decorated variable is created, the variable is initialized using the value in the AppStorage. Changes made to the value in the AppStorage will cause the bound UI component to update the state.
## Example
```
let varA = AppStorage.Link('varA')
let envLang = AppStorage.Prop('languageCode')
@Entry
@Component
struct ComponentA {
@StorageLink('varA') varA: number = 2
@StorageProp('languageCode') lang: string = 'en'
private label: string = 'count'
private aboutToAppear() {
this.label = (this.lang === 'en') ? 'Number' : 'Count'
}
build() {
Row({ space: 20 }) {
Button(`${this.label}: ${this.varA}`)
.onClick(() => {
AppStorage.Set<number>('varA', AppStorage.Get<number>('varA') + 1)
})
Button(`lang: ${this.lang}`)
.onClick(() => {
if (this.lang === 'zh') {
AppStorage.Set<string>('languageCode', 'en')
} else {
AppStorage.Set<string>('languageCode', 'en')
}
this.label = (this.lang === 'en') ? 'Number' : 'Count'
})
}
}
}
```
Each time the user clicks the Count button, the value of this.varA will increase by 1. This variable is synchronized with varA in the AppStorage. Each time the user clicks the language icon, the value of languageCode in the AppStorage will be changed, and the change will be synchronized to the this.lang variable.
# Attribute Configuration
Use attribute methods to configure component attributes. An attribute method follows the corresponding component and is bound to the component using the "." operator.
- The following is an example of configuring the font size attribute of the Text component:
```
Text('123')
.fontSize(12)
```
- Use the "." operator to implement chain call to configure multiple attributes at the same time, as shown below:
```
Image('a.jpg')
.alt('error.jpg') .width(100) .height(100)
```
- In addition to constants, you can also pass variables or expressions, as shown below:
```
// Size, count, and offset are private variables defined in the component.
Text('hello')
.fontSize(this.size)
Image('a.jpg')
.width(this.count % 2 === 0 ? 100 : 200) .height(this.offset + 100)
```
- For attributes of preset components, the framework also provides some predefined enumeration types, which you can pass as parameters to methods. Enumeration types must meet the parameter type requirements on the enumeration type definitions for specific attributes. You can configure the font color and weight attributes of the Text component as follows:
```
Text('hello')
.fontSize(20)
.fontColor(Color.Red)
.fontWeight(FontWeight.Bold)
```
# Child Component Configuration
For a component that supports child components, for example, a container component, add the UI descriptions of the child components inside "{ ... }". The &lt;Column&gt;, &lt;Row&gt;, &lt;Stack&gt;, &lt;Button&gt;, &lt;Grid&gt;, and &lt;List&gt; components are container components.
- The following is a simple example of the &lt;Column&gt; component:
```
Column() {
Text('Hello')
.fontSize(100)
Divider()
Text(this.myText)
.fontSize(100)
.fontColor(Color.Red)
}
```
- Multiple child components can be nested in the &lt;Column&gt; component, as shown below:
```
Column() {
Column() {
Button() {
Text('+ 1')
}.type(ButtonType.Capsule)
.onClick(() =&gt; console.log ('+1 clicked!'))
Image('1.jpg')
}
Divider()
Column() {
Button() {
Text('+ 2')
}.type(ButtonType.Capsule)
.onClick(() =&gt; console.log ('+2 clicked!'))
Image('2.jpg')
}
Divider()
Column() {
Button() {
Text('+ 3')
}.type(ButtonType.Capsule)
.onClick(() =&gt; console.log('+3 clicked!'))
Image('3.jpg')
}
}.alignItems(HorizontalAlign.Center) // center align components inside Column
```
# @Builder
The @Builder decorated method is used to define the declarative UI description of a component and quickly generate multiple layouts in a custom component. The functionality and syntax of the @Builder decorator are the same as those of the [build Function](ts-function-build.md).
```
@Entry
@Component
struct CompA {
size : number = 100;
@Builder SquareText(label: string) {
Text(label)
.width(1 * this.size)
.height(1 * this.size)
}
@Builder RowOfSquareTexts(label1: string, label2: string) {
Row() {
this.SquareText(label1)
this.SquareText(label2)
}
.width(2 * this.size)
.height(1 * this.size)
}
build() {
Column() {
Row() {
this.SquareText("A")
this.SquareText("B")
// or as long as tsc is used
}
.width(2 * this.size)
.height(1 * this.size)
this.RowOfSquareTexts("C", "D")
}
.width(2 * this.size)
.height(2 * this.size)
}
}
```
## @BuilderParam<sup>8+</sup>
The @BuilderParam decorator is used to modify the function type attributes (for example, @BuilderParam content: () => any) in a custom component. When the custom component is initialized, the attributes modified by the @BuilderParam decorator must be assigned values.
### Background
In certain circumstances, you may need to add a specific function, such as a click-to-jump action, to a custom component. However, embedding an event method directly inside of the component can add the function to all places where the component is initialized. This is where the @BuilderParam decorator come into the picture. When initializing a custom component, you can assign a @Builder decorated method to the @BuilderParam decorated attribute, thereby adding the specific function to the custom component.
### Component Initialization Through Parameters
When initializing a custom component through parameters, assign a @Builder decorated method to the @BuilderParam decorated attribute —content, and call the value of content in the custom component.
```
@Component
struct CustomContainer {
header: string = "";
@BuilderParam content: () => any;
footer: string = "";
build() {
Column() {
Text(this.header)
.fontSize(50)
this.content()
Text(this.footer)
.fontSize(50)
}
}
}
@Entry
@Component
struct CustomContainerUser {
@Builder specificParam(label: string) {
Column() {
Text(label).fontSize(50)
}
}
build() {
Column() {
CustomContainer({
header: "Header",
content: this.specificParam("111")
footer: "Footer",
})
}
}
}
```
### Component Initialization Through Trailing Closure
In a custom component, use the @BuilderParam decorated attribute to receive a trailing closure. When the custom component is initialized, the component name is followed by a pair of braces ({}) to form a trailing closure (CustomComponent(){}). You can consider a trailing closure as a container and add content to it. For example, you can add a component ({Column(){Text("content")}) to the closure. The syntax of the closure is the same as that of [build](ts-function-build.md). In this scenario, the custom component has one and only one @BuilderParam decorated attribute.
Example: Add a &lt;Column> component and a click event to the closure, and call the specificParam method decorated by @Builder in the new &lt;Column&gt; component. After the &lt;Column&gt; component is clicked, the value of the component's header attribute will be changed to changeHeader. In addition, when the component is initialized, the content of the trailing closure will be assigned to the closer attribute decorated by @BuilderParam.
```
@Component
struct CustomContainer {
header: string = "";
@BuilderParam closer: () => any;
build() {
Column() {
Text(this.header)
.fontSize(50)
this.closer()
}
}
}
@Builder function specificParam(label1: string, label2: string) {
Column() {
Text(label1)
.fontSize(50)
Text(label2)
.fontSize(50)
}
}
@Entry
@Component
struct CustomContainerUser {
@State text: string = "header"
build() {
Column() {
CustomContainer({
header: this.text,
}){
Column(){
specificParam("111", "22")
}.onClick(()=>{
this.text = "changeHeader"
})
}
}
}
}
```
# @Component
A struct decorated by @Component has the componentization capability and can serve as an independent component. This type of component is also called a custom component, and its UI structure is described in the build method. Custom components have the following features:
- Composability: Custom components can be used with preset or other components, as well as common attributes and methods.
- Reusable: Custom components can be reused by other components and used as different instances in different parent components or containers.
- Lifecycle: Custom components provide callbacks for service logic processing throughout the lifecycle.
- Data-driven update: The UI of custom components can be automatically updated based on the data of state variables.
For details about componentization, see [About @Component](ts-function-build.md).
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:
> - The build method must be defined for a custom component.
>
> - Custom constructors are prohibited for custom components.
The following code illustrates how to create a custom component named MyComponent:
```
@Component
struct MyComponent {
build() {
Column() {
Text('my component')
.fontColor(Color.Red)
}.alignItems(HorizontalAlign.Center) // center align Text inside Column
}
}
```
The build method of MyComponent is executed during initial rendering. When the component status changes, the build method will be executed again.
The following code illustrates how to use MyComponent:
```
@Component
struct ParentComponent {
build() {
Column() {
MyComponent()
Text('we use component')
.fontSize(20)
}
}
}
```
MyComponent can be applied multiple times and reused in different components, as shown in the code below:
```
@Component
struct ParentComponent {
build() {
Row() {
Column() {
MyComponent()
Text('first column')
.fontSize(20)
}
Column() {
MyComponent()
Text('second column')
.fontSize(20)
}
}
}
private aboutToAppear() {
console.log('ParentComponent: Just created, about to become rendered first time.')
}
private aboutToDisappear() {
console.log('ParentComponent: About to be removed from the UI.')
}
}
```
# @CustomDialog
The @CustomDialog decorator is used to decorate custom pop-up dialog boxes.
```
// custom-dialog-demo.ets
@CustomDialog
struct DialogExample {
controller: CustomDialogController;
action: () => void;
build() {
Row() {
Button ("Close CustomDialog")
.onClick(() => {
this.controller.close();
this.action();
})
}.padding(20)
}
}
@Entry
@Component
struct CustomDialogUser {
dialogController : CustomDialogController = new CustomDialogController({
builder: DialogExample({action: this.onAccept}),
cancel: this.existApp,
autoCancel: true
});
onAccept() {
console.log("onAccept");
}
existApp() {
console.log("Cancel dialog!");
}
build() {
Column() {
Button("Click to open Dialog")
.onClick(() => {
this.dialogController.open()
})
}
}
}
```
# @Entry
The custom component decorated by @Entry functions as the default entry component of the respective page. When the page is loaded, the custom component decorated by @Entry is created and displayed first.
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:
> A source file can contain at most one custom component decorated by @Entry.
Example of using @Entry:
```
// Only MyComponent decorated by @Entry is rendered and displayed. "hello world" is displayed, but "goodbye" is not displayed.
@Entry
@Component
struct MyComponent {
build() {
Column() {
Text('hello world')
.fontColor(Color.Red)
}
}
}
@Component
struct HideComponent {
build() {
Column() {
Text('goodbye')
.fontColor(Color.Blue)
}
}
}
```
# @Extend
The @Extend decorator adds new attribute functions to preset components, such as &lt;Text&gt;, &lt;Column&gt;, and &lt;Button&gt;. You can use the @Extend decorator to quickly define and reuse the custom styles of a component.
```
@Extend(Text) function fancy(fontSize: number) {
.fontColor(Color.Red)
.fontSize(fontSize)
.fontStyle(FontStyle.Italic)
}
@Entry
@Component
struct FancyUse {
build() {
Row({ space: 10 }) {
Text("Fancy")
.fancy(16)
Text("Fancy")
.fancy(24)
}
}
}
```
> ![img](public_sys-resources/icon-note.gif) **NOTE**: The @Extend decorator cannot be used in the struct definition of a custom component.
\ No newline at end of file
# @Preview
Custom components decorated by @Preview can be previewed in the Previewer of DevEco Studio. When the page is loaded, the custom components decorated by @Preview are created and displayed.
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:
> In a source file, at most one custom component can be decorated by @Preview.
Example of using @Preview:
```
// Display only Hello Component1 on the PC preview. The content under MyComponent is displayed on the real device.
@Entry
@Component
struct MyComponent {
build() {
Column() {
Row() {
Text('Hello World!')
.fontSize("50lpx")
.fontWeight(FontWeight.Bold)
}
Row() {
Component1()
}
Row() {
Component2()
}
}
}
}
@Preview
@Component
struct Component1 {
build() {
Column() {
Row() {
Text('Hello Component1')
.fontSize("50lpx")
.fontWeight(FontWeight.Bold)
}
}
}
}
@Component
struct Component2 {
build() {
Column() {
Row() {
Text('Hello Component2')
.fontSize("50lpx")
.fontWeight(FontWeight.Bold)
}
}
}
}
```
# @Styles
The @Styles decorator adds new attribute functions to basic components, such as &lt;Text&gt;, &lt;Column&gt;, and &lt;Button&gt;. Currently, @Styles supports only universal attributes. You can use the @Styles decorator to quickly define and reuse the custom styles of a component.
@Styles can be defined inside or outside a component. When it is defined outside a component, the keyword function must be included.
```
@Styles function globalFancy() {
.backgroundColor(Color.Red)
}
@Entry
@Component
struct FancyUse {
@Styles componentFancy() {
.backgroundColor(Color.Blue)
}
build() {
Column({ space: 10 }) {
Text("Fancy")
.globalFancy()
.width(100)
.height(100)
.fontSize(30)
Text("Fancy")
.componentFancy()
.width(100)
.height(100)
.fontSize(30)
}
}
}
```
@Styles can also be used inside the StateStyles attribute to assign state-specific attributes to components.
In StateStyles, styles defined outside the component can be directly called. However, the keyword this must be used to call styles defined in the component.
```
@Styles function globalFancy() {
.width(100)
.height(100)
}
@Entry
@Component
struct FancyUse {
@Styles function componentFancy() {
.width(50)
.height(50)
}
build() {
Row({ space: 10 }) {
Button() {
Text("Fancy")
}
.stateStyles({
normal: {
.width(80)
.height(80)
},
disabled: this.componentFancy,
pressed: globalFancy
})
}
}
}
```
# Component Creation and Re-initialization
## Initial Creation and Rendering
1. Create the parent component ParentComp.
2. Locally initialize the state variable isCountDown of ParentComp.
3. Execute the build function of ParentComp.
4. Create a preset &lt;Column&gt; component.
1. Create a preset &lt;Text&gt; component, set the text content to be displayed, and add the &lt;Text&gt; component instance to the &lt;Column&gt; component.
2. Create the component on the true branch based on the if condition.
1. Create a preset &lt;Image&gt; component and set the image source address.
2. Create a TimerComponent using the given constructor.
1. Create a TimerComponent object.
2. Initialize the values of member variables locally.
3. Use the parameters provided by the TimerComponent constructor to update the values of member variables.
4. Execute the aboutToAppear function of TimerComponent.
5. Execute the build function of TimerComponent to create the corresponding UI description structure.
3. Create a preset &lt;Button&gt; component and set the corresponding content.
## Status Update
When a user clicks a button:
1. The value of the isCountDown state variable of ParentComp is changed to false.
2. The build function of ParentComp is executed.
3. The preset &lt;Column&gt; component is reused by the framework and reinitialized.
4. The child components of &lt;Column&gt; reuse and reinitialize the objects in the memory.
1. Reuse the preset &lt;Text&gt; component after re-initializing the component using new text content.
2. Reuse the component on the false branch based on the if condition.
1. Destroy the components on the original true branch as these components are no longer used.
1. Destroy the created preset &lt;image&gt; component instance.
2. Destroy the TimerComponent component instance, and call the aboutToDisappear function.
2. Create components on the false branch.
1. Create a preset &lt;Image&gt; component and set the image source address.
2. Create a TimerComponent again using the given constructor.
3. Initialize the newly created TimerComponent and call the aboutToAppear and build functions.
3. Reuse the preset &lt;Button&gt; component, with the new image source address.
## Example
```
@Entry
@Component
struct ParentComp {
@State isCountDown: boolean = true
build() {
Column() {
Text(this.isCountDown ? 'Count Down' : 'Stopwatch')
if (this.isCountDown) {
Image('countdown.png')
TimerComponent({counter: 10, changePerSec: -1, showInColor: Color.Red})
} else {
Image('stopwatch.png')
TimerComponent({counter: 0, changePerSec: +1, showInColor: Color.Black })
}
Button(this.isCountDown ? 'Swtich to Stopwatch' : 'Switch to Count Down')
.onClick(() =&gt; {this.isCountDown = !this.isCountDown})
}
}
}
// Manage and display a count down / stop watch timer
@Component
struct TimerComponent {
@State counter: number = 0
private changePerSec: number = -1
private showInColor: Color = Color.Black
private timerId : number = -1
build() {
Text(`${this.counter}sec`)
.fontColor(this.showInColor)
}
aboutToAppear() {
this.timerId = setInterval(() =&gt; {this.counter += this.changePerSec}, 1000)
}
aboutToDisappear() {
if (this.timerId &gt; 0) {
clearTimeout(this.timerId)
this.timerId = -1
}
}
}
```
# @Link
Two-way binding can be established between the @Link decorated variable and the @State decorated variable of the parent component. The @Link data has the following features:
- Support for multiple types: The value of the @Link decorated variable can be of the same type as the @State decorated variable; that is, the value can be of the following types: class, number, string, boolean, or arrays of these types.
- Private: Data is accessed only within the component.
- Single data source: The variable of the parent component for initializing the @Link decorated variable must be the @State decorated variable.
- Two-way binding: When a child component changes the @Link decorated variable, the @State decorated variable of its parent component is also changed.
- Support for initialization with the variable reference passed to the @Link decorated variable: When creating a new instance of the component, you must use the naming parameter to initialize all @Link decorated variables. The @Link decorated variable can be initialized by using the reference of the @State or @Link decorated variable. Wherein, the @State decorated variable can be referenced using the '$' operator.
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:
> The @Link decorated variable cannot be initialized within the component.
## Simple Type Example
```
@Entry
@Component
struct Player {
@State isPlaying: boolean = false
build() {
Column() {
PlayButton({buttonPlaying: $isPlaying})
Text(`Player is ${this.isPlaying? '':'not'} playing`)
}
}
}
@Component
struct PlayButton {
@Link buttonPlaying: boolean
build() {
Column() {
Button() {
Image(this.buttonPlaying? 'play.png' : 'pause.png')
}.onClick(() => {
this.buttonPlaying = !this.buttonPlaying
})
}
}
}
```
The @Link semantics are derived from the '$' operator. In other words, $isPlaying is the two-way binding of the internal state this.isPlaying. When you click PlayButton, the &lt;Image&gt; and &lt;Text&gt; components of PlayButton are refreshed at the same time.
## Complex Type Example
```
@Entry
@Component
struct Parent {
@State arr: number[] = [1, 2, 3]
build() {
Column() {
Child({items: $arr})
ForEach(this.arr,
item => Text(`${item}`),
item => item.toString())
}
}
}
@Component
struct Child {
@Link items: number[]
build() {
Column() {
Button() {
Text('Button1: push')
}.onClick(() => {
this.items.push(100)
})
Button() {
Text('Button2: replace whole item')
}.onClick(() => {
this.items = [100, 200, 300]
})
}
}
}
```
In the example above, click Button1 and Button2 to change the list of text items displayed in the parent component.
## Example of Using @Link, @State, and @Prop Together
```
@Entry
@Component
struct ParentView {
@State counter: number = 0
build() {
Column() {
ChildA({counterVal: this.counter}) // pass by value
ChildB({counterRef: $counter}) // $ creates a Reference that can be bound to counterRef
}
}
}
@Component
struct ChildA {
@Prop counterVal: number
build() {
Button() {
Text(`ChildA: (${this.counterVal}) + 1`)
}.onClick(() => {this.counterVal+= 1})
}
}
@Component
struct ChildB {
@Link counterRef: number
build() {
Button() {
Text(`ChildB: (${this.counterRef}) + 1`)
}.onClick(() => {this.counterRef+= 1})
}
}
```
In the preceding example, ParentView contains two child components: ChildA and ChildB. They are initialized by the state variable counter of ParentView.
- ChildB uses @Link to establish two-way state binding. When the value of the counterRef state variable is changed in ChildB, the change is synchronized to ParentView and ChildA.
- ChildA uses @Prop to establish one-way state binding from ParentView to itself. When ChildA changes the state, it is re-rendered, but the change is not updated to ParentView or ChildB.
# @Prop
@Prop and @State have the same semantics but different initialization modes. Variables decorated by @Prop must be initialized using the @State decorated variable provided by their parent components. The @Prop decorated variable can be modified in the component, but the modification is not updated to the parent component; that is, @Prop uses one-way data binding.
The @Prop state data has the following features:
- Support for simple types: The number, string, and boolean types are supported.
- Private: Data is accessed only within the component.
- Support for multiple instances: A component can have multiple attributes decorated by @Prop.
- Support for initialization with a value passed to the @Prop decorated variable: When a new instance of the component is created, all @Prop decorated variables must be initialized. Initialization inside the component is not supported.
## Example
```
@Entry
@Component
struct ParentComponent {
@State countDownStartValue: number = 10 // 10 Nuggets default start value in a Game
build() {
Column() {
Text(`Grant ${this.countDownStartValue} nuggets to play.`)
Button() {
Text('+1 - Nuggets in New Game')
}.onClick(() => {
this.countDownStartValue += 1
})
Button() {
Text('-1 - Nuggets in New Game')
}.onClick(() => {
this.countDownStartValue -= 1
})
// When creating a child component, you must provide the initial value of its @Prop decorated variable in the constructor parameter and initialize the regular variable CostOfOneAttump (not Prop).
CountDownComponent({ count: this.countDownStartValue, costOfOneAttempt: 2})
}
}
}
@Component
struct CountDownComponent {
@Prop count: number
private costOfOneAttempt: number
build() {
Column() {
if (this.count > 0) {
Text(`You have ${this.count} Nuggets left`)
} else {
Text('Game over!')
}
Button() {
Text('Try again')
}.onClick(() => {
this.count -= this.costOfOneAttempt
})
}
}
}
```
In the preceding example, when you press +1 or -1, the status of the parent component changes and the build method is executed again. In this case, a new CountDownComponent is created. The countDownStartValue property of the parent component is used to initialize the @Prop decorated variable of the child component. When you tap the Try again button of the child component, the value of the @Prop decorated variable count is changed. As a result, the CountDownComponent is rendered again. However, the change of the count value does not affect the countDownStartValue value of the parent component.
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:
> When a new component instance is created, all its @Prop decorated variables must be initialized.
# @State
The @State decorated variable is the internal state data of the component. When the state data is modified, the build method of the component is called to refresh the UI.
The @State data has the following features:
- Support for multiple types: The following types are supported: strong types by value and by reference, including class, number, boolean, string, as well as arrays of these types, that is, Array&lt;class&gt;, Array&lt;string&gt;, Array&lt;boolean&gt;, and Array&lt;number>. object and any are not allowed.
- Support for multiple instances: Multiple instances can coexist in a component. The internal state data of different instances is independent.
- Private: An attribute marked with @State can only be accessed within the component.
- Local initialization required: Initial values must be allocated to all @State decorated variables through the initialization process. Otherwise, they may become undefined in the framework.
- Support for setting of initial attribute values based on the state variable name: When creating a component instance, you can explicitly specify the initial value of the @State decorated attribute based on the variable name.
## Simple Example of @State Decorated Attribute
```
@Entry
@Component
struct MyComponent {
@State count: number = 0
// MyComponent provides a method for modifying the @State status data member.
private toggleClick() {
this.count += 1
}
build() {
Column() {
Button() {
Text(`click times: ${this.count}`)
.fontSize(10)
}.onClick(this.toggleClick.bind(this))
}
}
}
```
## Complex Example of @State Decorated Variable
```
// Customize the status data class.
class Model {
value: string
constructor(value: string) {
this.value = value
}
}
@Entry
@Component
struct EntryComponent {
build() {
Column() {
MyComponent({count: 1, increaseBy: 2}) // MyComponent1 in this document
MyComponent({title: {value: 'Hello, World 2'}, count: 7}) //MyComponent2 in this document
}
}
}
@Component
struct MyComponent {
@State title: Model = {value: 'Hello World'}
@State count: number = 0
private toggle: string = 'Hello World'
private increaseBy: number = 1
build() {
Column() {
Text(`${this.title.value}`).fontSize(30)
Button() {
Text(`Click to change title`).fontSize(20).fontColor(Color.White)
}.onClick(() => {
this.title.value = (this.toggle == this.title.value) ? 'Hello World' : 'Hello UI'
}) // Modify the internal state of MyComponent using the anonymous method.
Button() {
Text(`Click to increase count=${this.count}`).fontSize(20).fontColor(Color.White)
}.onClick(() => {
this.count += this.increaseBy
}) // Modify the internal state of MyComponent using the anonymous method.
}
}
}
```
In the preceding example:
- Two @State decorated variables, count and title, have been defined for MyComponent. If the value of count or title changes, the build method of MyComponent needs to be called to render the component again.
- The EntryComponent has multiple MyComponent instances. The internal status change of the first MyComponent does not affect the second MyComponent.
- When creating a MyComponent instance, initialize the variables in the component based on the variable name. For example:
```
MyComponent({title: {value: 'Hello, World 2'}, count: 7})
```
# Configuration with Mandatory Parameters
If the API definition of a component contains any mandatory parameter, set the parameters in the parentheses next to the component. Use constants to assign values to the parameters.
Examples:
- Set the mandatory parameter src of the &lt;Image&gt; component as follows:
```
Image('http://xyz/a.jpg')
```
- Set the mandatory parameter content of the &lt;Text&gt; component as follows:
```
Text('123')
```
You can also use variables or expressions to assign values to parameters. The result type returned by an expression must meet the parameter type requirements. For example, to pass a variable or expression to construct the Image and Text components:
```
// imagePath, where imageUrl is a private data variable defined in the component.
Image(this.imagePath)
Image('http://' + this.imageUrl)
// count is a private data variable defined in the component.
// (``) and (${}) are the template character string features supported by the TS language and comply with the
// features of the corresponding language. This specification is not limited.
Text(`count: ${this.count}`)
```
# Initialization of Custom Components' Member Variables
The member variables of a component can be initialized in either of the following ways:
- Local initialization. For example:
```
@State counter: Counter = new Counter()
```
- Initialization using constructor parameters. For example:
```
MyComponent(counter: $myCounter)
```
The allowed method depends on the decorator of the state variable, as shown in the following table.
| Decorator Type | Local Initialization | Initialization Using Constructor Parameters |
| -------- | -------- | -------- |
| @State | Mandatory | Optional |
| @Prop | Forbidden | Mandatory |
| @Link | Forbidden | Mandatory |
| @StorageLink | Mandatory | Forbidden |
| @StorageProp | Mandatory | Forbidden |
| @Provide | Mandatory | Optional |
| @Consume | Forbidden | Forbidden |
| @ObjectLink | Forbidden | Mandatory |
| Normal member variable | Recommended | Optional |
As indicated by the preceding table:
- The @State decorated variables need to be initialized locally. The initial value can be overwritten by the constructor parameter.
- The @Prop and @Link decorated variables must be initialized only by constructor parameters.
Comply with the following rules when using constructors to initialize member variables:
| From the Variable in the Parent Component (Below) to the Variable in the Child Component (Right) | @State | @Link | @Prop | Normal Variable |
| -------- | -------- | -------- | -------- | -------- |
| @State | Not allowed | Allowed | Allowed | Allowed |
| @Link | Not allowed | Allowed | Not recommended | Allowed |
| @Prop | Not allowed | Not allowed | Allowed | Allowed |
| @StorageLink | Not allowed | Allowed | Not allowed | Allowed |
| @StorageProp | Not allowed | Not allowed | Not allowed | Allowed |
| Normal variable | Allowed | Not allowed | Not allowed | Allowed |
As indicated by the preceding table:
- The normal variables of the parent component can be used to initialize the @State decorated variables of the child component, but not the @Link or @Prop decorated variables.
- The @State decorated variable of the parent component can be used to initialize the @Prop, @Link (using $), or normal variables of the child component, but not the @State decorated variables of the child component.
- The @Link decorated variables of the parent component can be used to initialize the @Link decorated or normal variables of the child component. However, initializing the @State decorated members of the child component can result in a syntax error. In addition, initializing the @Prop decorated variables is not recommended.
- The @Prop decorated variables of the parent component can be used to initialize the normal variables or @Prop decorated variables of the child component, but not the @State or @Link decorated variables.
- Passing @StorageLink and @StorageProp from the parent component to the child component is prohibited.
- In addition to the preceding rules, the TypeScript strong type rules need to be followed.
## Example
```
@Entry
@Component
struct Parent {
@State parentState: ClassA = new ClassA()
build() {
Row() {
CompA({aState: new ClassA, aLink: $parentState}) // valid
CompA({aLink: $parentState}) // valid
CompA() // invalid, @Link aLink remains uninitialized
CompA({aLink: new ClassA}) // invalid, @Link aLink must be a reference ($) to either @State or @Link variable
}
}
}
@Component
struct CompA {
@State aState: boolean = false // must initialize locally
@Link aLink: ClassA // must not initialize locally
build() {
Row() {
CompB({bLink: $aLink, // valid init a @Link with reference of another @Link,
bProp: this.aState}) // valid init a @Prop with value of a @State
CompB({aLink: $aState, // invalid: type missmatch expected ref to ClassA, provided reference to boolean
bProp: false}) // valid init a @Prop by constants value
}
}
}
@Component
struct CompB {
@Link bLink: ClassA = new ClassA() // invalid, must not initialize locally
@Prop bProp: boolean = false // invalid must not initialize locally
build() {
...
}
}
```
# Custom Component Lifecycle Callbacks
The lifecycle callbacks of a custom component are used to notify users of the lifecycle of the component. These callbacks are private and are invoked by the development framework at a specified time at runtime. They cannot be manually invoked from applications.
## Lifecycle Callback Definition
| Function | Description |
| -------- | -------- |
| aboutToAppear | Invoked after a new instance of the custom component is created and before its build function is executed. You can change state variables in the aboutToAppear function. The change will take effect when you execute the build function next time. |
| aboutToDisappear | Invoked before the destructor of the custom component is consumed. Do not change state variables in the aboutToDisappear function as doing this can cause unexpected errors. For example, the modification of the @Link decorated variable may cause unstable application running. |
| onPageShow | Invoked when a page is displayed. This callback is used in the routing process or scenarios where the application is switched to the foreground or background. Only the custom components decorated by @Entry take effect. |
| onPageHide | Invoked when a page is hidden. This callback is used in the routing process or scenarios where the application is switched to the foreground or background. Only the custom components decorated by @Entry take effect. |
| onBackPress | Invoked when a user clicks the back button. Only the custom components decorated by @Entry take effect.<br/>- The value true is returned if the page processes the return logic instead of performing page routing.<br/>- The value false is returned if the default return logic is used.<br/>- If no value is returned, the default return logic is used. |
## Example
```
@Component
struct CountDownTimerComponent {
@State countDownFrom: number = 10
private timerId: number = -1
private aboutToAppear(): void {
this.timerId = setInterval(() => {
if (this.countDownFrom <= 1) {
clearTimeout(this.timerId)
}
this.countDownFrom -= 1
}, 1000) // decr counter by 1 every second
}
private aboutToDisappear(): void {
if (this.timerId > 0) {
clearTimeout(this.timerId)
this.timerId = -1
}
}
build() {
Text(`${this.countDownFrom} sec left`)
}
}
```
The example above shows that lifecycle functions are critical for CountDownTimerComponent to manage its timer resources. Similar functions include loading resources asynchronously from the network.
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:
> - Promise and asynchronous callback functions can be used in lifecycle functions, for example, network resource getters and timer setters.
>
> - Do not use async await in lifecycle functions.
# Event Configuration
You can use event methods to configure events supported by components.
- Example of using a lambda expression to configure the event of a component:
```
// Counter is a private data variable defined in the component.
Button('add counter')
.onClick(() => {
this.counter += 2
})
```
- When using an anonymous function expression to configure the event of a component, bind must be used to ensure that the contained components are referenced by this in the function body.
```
// Counter is a private data variable defined in the component.
Button('add counter')
.onClick(function () {
this.counter += 2
}.bind(this))
```
- Example of using a component's member function to configure the event of the component:
```
myClickHandler(): void {
// do something
}
...
Button('add counter')
.onClick(this.myClickHandler)
```
# Directory Structure
The following figure shows the typical directory structure of the eTS module (entry/src/main) for an application with feature abilities (FAs).
![en-us_image_0000001222967752](figures/en-us_image_0000001222967752.png)
Functions of the files are as follows:
- The Extended TypeScript (eTS) files that end with the .ets extension describe the UI layouts, styles, event interactions, and page logics.
Functions of the folders and files are as follows:
- The app.ets file manages global application logics and lifecycles.
- The pages directory stores all component pages.
- The common directory stores common code files, such as custom components and public methods.
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:
>
> TypeScript and JavaScript files can be imported as page files.
# Rules for Accessing Application Code Files
The application code files can be accessed in the following ways:
- Use a relative path to reference the code file. For example, if the upper-level directory is ../common/utils/utils.ets, use ./common/utils/utils.ets for the current directory.
- Use the root path of the current module to reference the code file, for example, common/utils/utils.ets.
- Store common code files in the common directory.
## Example
```
import { FoodData, FoodList } from "../common/utils/utils.ets";
@Entry
@Component
struct FoodCategoryList {
private foodItems: FoodData[] = [
new FoodData("Tomato"),
new FoodData("Strawberry"),
new FoodData("Cucumber")
]
build() {
Column() {
FoodList({ foodItems: this.foodItems })
}
}
}
```
Example for importing a code file:
```
//common/utils/utils.ets
export class FoodData {
name: string;
constructor(name: string) {
this.name = name;
}
}
@Component
export struct FoodList {
private foodItems: FoodData[]
build() {
Column() {
Flex({justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center}) {
Text('Food List')
.fontSize(20)
}
.width(200)
.height(56)
.backgroundColor('#FFf1f3f5')
List() {
ForEach(this.foodItems, item => {
ListItem() {
Text(item.name)
.fontSize(14)
}
}, item => item.toString())
}
}
}
}
```
# "js" Tag
Configure the "js" tag in the config.json file of your application. The "js" tag contains the instance name, page route, and window configuration information.
| Tag | Type | Default Value | Mandatory | Description |
| -------- | -------- | -------- | -------- | -------- |
| name | string | default | Yes | Name of the eTS instance. |
| pages | Array | - | Yes | Page route information. For details, see ["pages"](#pages). |
| window | Object | - | No | Window configuration information. For details, see ["window"](#window). |
| mode | Object | - | No | Running type and syntax style of the JS component. For details, see ["mode"](#mode). |
## pages
The "pages" defines the route information of each page's entry component. Each page consists of the page path and page name. The following is an example:
```
{
"pages": [
"pages/index",
"pages/detail"
]
}
```
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:
> - The first page in the "pages" list is the home page of the application.
>
> - The page name must not be a component name, for example, Text.ets or Button.ets.
>
> - Each page file must contain the [page entry component](ts-component-based-entry.md) (with the @Entry decoration).
## window
The "window" configures the view window. The following attributes can be configured:
| Type | Default Value | Description |
| -------- | -------- | -------- |
| designWidth | - | Logical width of the view. The default value is 720. (The default value is 454 for wearables.) The logical width of the view determines the unit size of lpx. For example, if designWidth is 720 and the view width is 1440 physical pixels, 1 lpx is 2 physical pixels. For details, see [lpx](ts-pixel-units.md). |
```
{
...
"window": {
"designWidth": 720
}
...
}
```
## mode
The "mode" configures the running type and syntax style of a JS component. The following attributes are supported:
| Type | Default Value | Description |
| -------- | -------- | -------- |
| type | - | Running type of the JS component. The options are as follows:<br/>- pageAbility: Run the JS component in ability mode.<br/>- form: Run the JS component as a service widget. |
| syntax | - | Syntax type of the JS component. The options are as follows:<br/>- hml: compiled in the .hml, .css, or .js style.<br/>- ets: compiled in the declarative syntax style. |
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:
> If type is set to form, syntax cannot be ets.
## Example
config.json:
```
{
"app": {
"bundleName": "com.example.player",
"version": {
"code": 1,
"name": "1.0"
},
"vendor": "example"
},
"module": {
"js": [{
"name": "default",
"pages": [
"pages/index",
"pages/detail"
],
"window": {
"designWidth": 720
},
"mode": {
"type": "pageAbility",
"syntax": "ets"
},
}],
"abilities": [{
...
}]
}
}
```
# build Function
The build function meets the definition of the Builder API and is used to define the declarative UI description of components. Components must comply with the preceding Builder API constraints. Custom or preset components are combined in declarative mode in the build method. The build method is called when a component is created or updated.
```
interface Builder {
build: () => void
}
```
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:
> The build method supports only composite components and uses the rendering control syntax.
# Basic Concepts
The TypeScript-based declarative development paradigm provides a wide array of basic components, which can be combined and extended in a declarative manner to describe the UI of an application. It also provides basic data binding and event processing mechanisms to help you implement the application interaction logic.
## HelloWorld Example
```
// An example of displaying Hello World. After you click the button, Hello UI is displayed.
@Entry
@Component
struct Hello {
@State myText: string = 'World'
build() {
Column() {
Text('Hello')
.fontSize(30)
Text(this.myText)
.fontSize(32)
Divider()
Button() {
Text('Click me')
.fontColor(Color.Red)
}.onClick(() =&gt; {
this.myText = 'UI'
})
.width(500)
.height(200)
}
}
}
```
## Basic Concepts
The preceding sample code shows the structure of a simple page. It involves the following basic concepts:
- Decorator: a special kind of declaration that can be applied to classes, structures, methods, and variables. In the sample code, @Entry, @Component, and @State are decorators.
- Custom component: a reusable UI unit, which can be combined with other components. In the sample code, struct Hello decorated by @Component is a custom component.
- UI description: declaratively describes the UI structure. In the sample code, the block of code in the build() method provides the UI description.
- Built-in component: the default basic or layout component preset in the framework. You can directly invoke these components, such as &lt;Column&gt;, &lt;Text&gt;, &lt;Divider&gt;, and &lt;Button&gt; components in the sample code.
- Attribute method: a method used to configure component attributes, such as fontSize(), width(), height(), and color().
- Event method: a method used to add the component response logic to an event. In the sample code, the onClick method is added for the Button component for defining the click response logic.
# Media Resource Types
- The following table describes the image resource types supported by the development framework.
| Image Format | File Name Extension |
| -------- | -------- |
| JPEG | .jpg |
| PNG | .png |
| GIF | .gif |
| SVG | .svg |
| WEBP | .webp |
| BMP | .bmp |
# @Consume and @Provide
As the data provider, @Provide can update the data of child nodes and trigger page rendering. After @Consume detects that the @Provide data is updated, it will initiate re-rendering of the current view.
Table1 @Provide
| Name | Description |
| -------- | -------- |
| Decorator parameter | A constant of the string type, which is used to set an alias for a decorated variable. If an alias is specified, implement the data update for this alias. If there is no alias, use the variable name as the alias. @Provide("_alias_") is recommended. |
| Synchronization mechanism | The @Provide decorated variable is similar to the @state variable. You can modify the variable to re-render the page. You can also modify the @Consume decorated variable to modify the @State decorated variable reversely. |
| Initial value | The initial value must be set. |
| Page re-rendering scenarios | The following will trigger page re-rendering:<br/>- Changes of variables in primitive types (boolean, string, and number)<br/>- Changes of the @Observed decorated classes or their attributes<br/>- Adding, deleting, or updatingelements in an array |
Table2 @Consume
| Type | Description |
| -------- | -------- |
| Initial value | No default value can be set. |
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:
> To avoid infinite loops caused by circular reference, exercise caution when using @Provide and @Consume.
The description of other attributes is the same as that of @Provide.
```
@Entry
@Component
struct CompA {
@Provide("reviewVote") reviewVotes : number = 0;
build() {
Column() {
CompB()
Button() {
Text(`${this.reviewVotes}`)
.fontSize(30)
}
.onClick(() => {
this.reviewVotes += 1;
})
}
}
}
@Component
struct CompB {
build() {
Column() {
CompC()
}
}
}
@Component
struct CompC {
@Consume("reviewVote") reviewVotes : number;
build() {
Column() {
Button() {
Text(`${this.reviewVotes}`)
.fontSize(30)
}
.onClick(() => {
this.reviewVotes += 1;
})
}
}
}
```
# @Observed and @ObjectLink
This section introduces to you two new decorators: @Observed and @ObjectLink.
- @Observed applies to a class, indicating that the data changes in the class are managed by the UI page, for example, @Observed class ClassA {}.
- @ObjectLink applies to an object decorated by @Observed, for example, @ObjectLink a: ClassA.
## Background
When you need to set bidirectional synchronization in a child component for a variable (parent_a) of its parent component, you can use @State to decorate the variable (parent_a) in the parent component and use @Link to decorate the corresponding variable (child_a) in the child component. In this way, data can be synchronized between the parent component and the specific child component, and between the parent component and its other child components. As shown below, bidirectional synchronization is configured for variables of ClassA in the parent and child components. If attribute c of the variable in child component 1 has its value changed, the parent component will be notified to synchronize the change. If attribute c in the parent component has its value changed, all child components will be notified to synchronize the change.
![en-us_image_0000001267647861](figures/en-us_image_0000001267647861.png)
In the preceding example, full synchronization is performed for a data object. If you want to synchronize partial information of a data object in a parent component, and if the information is a class object, use @ObjectLink and @Observed instead, as shown below.
![en-us_image_0000001267607881](figures/en-us_image_0000001267607881.png)
## Configuration Requirement
- @Observed applies to classes, and @ObjectLink applies to variables.
- The variables decorated by @ObjectLink must be of the class type.
- The classes must be decorated by @Observed.
- Parameters of the primitive types are not supported. You can use @Prop to perform unidirectional synchronization.
- @ObjectLink decorated variables are immutable.
- Attribute changes are allowed. If an object is referenced by multiple @ObjectLink decorated variables, all custom components that have these variables will be notified for re-rendering.
- Default values cannot be set for @ObjectLink decorated variables.
- The parent component must be initialized with a TypeScript expression that involves variables decorated by @State, @Link, @StorageLink, @Provide, or @Consume.
- @ObjectLink decorated variables are private variables and can be accessed only within the component.
## Examples
### Example 1
```
@Observed
class ClassA {
public name : string;
public c: number;
constructor(c: number, name: string = 'OK') {
this.name = name;
this.c = c;
}
}
class ClassB {
public a: ClassA;
constructor(a: ClassA) {
this.a = a;
}
}
@Component
struct ViewA {
label : string = "ep1";
@ObjectLink a : ClassA;
build() {
Column() {
Text(`ViewA [${this.label}]: a.c=${this.a.c}`)
.fontSize(20)
Button(`+1`)
.width(100)
.margin(2)
.onClick(() => {
this.a.c += 1;
})
Button(`reset`)
.width(100)
.margin(2)
.onClick(() => {
this.a = new ClassA(0); // ERROR, this.a is immutable
})
}
}
}
@Entry
@Component
struct ViewB {
@State b : ClassB = new ClassB(new ClassA(10));
build() {
Flex({direction: FlexDirection.Column, alignItems: ItemAlign.Center}) {
ViewA({label: "ViewA #1", a: this.b.a})
ViewA({label: "ViewA #2", a: this.b.a})
Button(`ViewB: this.b.a.c += 1` )
.width(320)
.margin(4)
.onClick(() => {
this.b.a.c += 1;
})
Button(`ViewB: this.b.a = new ClassA(0)`)
.width(240)
.margin(4)
.onClick(() => {
this.b.a = new ClassA(0);
})
Button(`ViewB: this.b = new ClassB(ClassA(0))`)
.width(240)
.margin(4)
.onClick(() => {
this.b = new ClassB(new ClassA(0));
})
}
}
}
```
### Example 2
```
var nextID: number = 0;
@Observed
class ClassA {
public name : string;
public c: number;
public id : number;
constructor(c: number, name: string = 'OK') {
this.name = name;
this.c = c;
this.id = nextID++;
}
}
@Component
struct ViewA {
label : string = "ViewA1";
@ObjectLink a: ClassA;
build() {
Row() {
Button(`ViewA [${this.label}] this.a.c= ${this.a.c} +1`)
.onClick(() => {
this.a.c += 1;
})
}
}
}
@Entry
@Component
struct ViewB {
@State arrA : ClassA[] = [ new ClassA(0), new ClassA(0) ];
build() {
Column() {
ForEach (this.arrA, (item) => {
ViewA({label: `#${item.id}`, a: item})
},
(item) => item.id.toString()
)
ViewA({label: `ViewA this.arrA[first]`, a: this.arrA[0]})
ViewA({label: `ViewA this.arrA[last]`, a: this.arrA[this.arrA.length-1]})
Button(`ViewB: reset array`)
.onClick(() => {
this.arrA = [ new ClassA(0), new ClassA(0) ];
})
Button(`ViewB: push`)
.onClick(() => {
this.arrA.push(new ClassA(0))
})
Button(`ViewB: shift`)
.onClick(() => {
this.arrA.shift()
})
}
}
}
```
# @Watch
@Watch is used to listen for changes of state variables. The syntax structure is as follows:
```
@State @Watch("onChanged") count : number = 0
```
As shown above, add an @Watch decorator to the target state variable to register an onChanged callback. When the state variable count is changed, the onChanged callback will be triggered.
@Watch can be used to listen for changes of variables decorated by @State, @Prop, @Link, @ObjectLink, @Provide, @Consume, @StorageProp, or @StorageLink.
```
@Entry
@Component
struct CompA {
@State @Watch("onBasketUpdated") shopBasket : Array<number> = [ 7, 12, 47, 3 ];
@State totalPurchase : number = 0;
updateTotal() : number {
let sum = 0;
this.shopBasket.forEach((i) => { sum += i; });
// Calculate the total amount of items in the shopping basket. If the amount exceeds CNY100, the specified discount will be applied.
this.totalPurchase = (sum < 100) ? sum : 0.9 * sum;
return this.totalPurchase;
}
// @Watch cb
onBasketUpdated(propName: string) : void {
this.updateTotal();
}
build() {
Column() {
Button("add to basket").onClick(() => { this.shopBasket.push(Math.round(100 * Math.random())) })
Text(`${this.totalPurchase}`)
.fontSize(30)
}
}
}
```
# Configuration Without Parameters
If the API definition of a component does not contain mandatory parameters, you do not need to configure any content in the parentheses next to the component. For example, the Divider component does not contain parameters:
```
Column() {
Text('item 1')
Divider() // No parameter configuration of the divider component
Text('item 2')
}
```
# Pixel Units
The framework provides four pixel units, with vp as the reference data unit.
| Name | Description |
| -------- | -------- |
| px | Physical pixel unit of the screen. |
| vp | Pixels specific to the screen density, which are converted into physical pixels of the screen based on the screen pixel density. |
| fp | Font pixel, which is similar to vp and varies according to the system font size. |
| lpx | Logical pixel unit of the window. It is the ratio of the actual screen width to the logical width (configured by [designWidth](ts-framework-js-tag.md)). For example, if designWidth is set to 720, then 1lpx is equal to 2px for a screen with an actual width of 1440 physical pixels. |
## Pixel Unit Conversion
Conversion from other pixel units to px is supported.
| API | Description |
| -------- | -------- |
| vp2px(value : number) : number | Converts a value in units of vp to a value in units of px. |
| px2vp(value : number) : number | Converts a value in units of px to a value in units of vp. |
| fp2px(value : number) : number | Converts a value in units of fp to a value in units of px. |
| px2fp(value : number) : number | Converts a value in units of px to a value in units of fp. |
| lpx2px(value : number) : number | Converts a value in units of lpx to a value in units of px. |
| px2lpx(value : number) : number | Converts a value in units of px to a value in units of lpx. |
## Example
```
@Entry
@Component
struct Example {
build() {
Column() {
Flex({ wrap: FlexWrap.Wrap }) {
Column() {
Text("width(220)")
.width(220).height(40).backgroundColor(0xF9CF93)
.textAlign(TextAlign.Center).fontColor(Color.White).fontSize('12vp')
}.margin(5)
Column() {
Text("width('220px')")
.width('220px').height(40).backgroundColor(0xF9CF93)
.textAlign(TextAlign.Center).fontColor(Color.White)
}.margin(5)
Column() {
Text("width('220vp')")
.width('220vp').height(40).backgroundColor(0xF9CF93)
.textAlign(TextAlign.Center).fontColor(Color.White).fontSize('12vp')
}.margin(5)
Column() {
Text("width('220lpx') designWidth:720")
.width('220lpx').height(40).backgroundColor(0xF9CF93)
.textAlign(TextAlign.Center).fontColor(Color.White).fontSize('12vp')
}.margin(5)
Column() {
Text("width(vp2px(220) + 'px')")
.width(vp2px(220) + 'px').height(40).backgroundColor(0xF9CF93)
.textAlign(TextAlign.Center).fontColor(Color.White).fontSize('12vp')
}.margin(5)
Column() {
Text("fontSize('12fp')")
.width(220).height(40).backgroundColor(0xF9CF93)
.textAlign(TextAlign.Center).fontColor(Color.White).fontSize('12fp')
}.margin(5)
}.width('100%')
}
}
}
```
![en-us_image_0000001267607893](figures/en-us_image_0000001267607893.gif)
# ForEach
The development framework provides ForEach to iterate arrays and create components for each array item. ForEach is defined as follows:
```
ForEach(
arr: any[], // Array to be iterated
itemGenerator: (item: any, index?: number) => void, // child component generator
keyGenerator?: (item: any, index?: number) => string // (optional) Unique key generator, which is recommended.
)
```
## ForEach
ForEach(arr: any[],itemGenerator: (item: any, index?: number) => void, keyGenerator?: (item: any, index?: number) => string):void
Table1 Parameters
| Name | Type | Mandatory | Default Value | Description |
| -------- | -------- | -------- | -------- | -------- |
| arr | any[] | Yes | - | Must be an array. An empty array is allowed. If an array is empty, no child component is created. You can set the functions whose return values are of the array type, for example, arr.slice (1, 3). The set functions cannot change any state variables including the array itself, such as Array.splice, Array.sort, and Array.reverse. |
| itemGenerator | (item: any, index?: number) => void | Yes | - | Used to generate the lambda function of the child components. It generates one or more child components for a given array item. A single component and its child component list must be contained in braces ({...}) |
| keyGenerator | (item: any, index?: number) => string | No | - | Used as an anonymous parameter for generating a unique and stable key value for a given array item. When the position of a subitem in the array is changed, the key value of the subitem cannot be changed. When a subitem in the array is replaced with a new item, the key value of the current item must be different from that of the new item. This key-value generator is optional. However, for performance reasons, it is strongly recommended that the key-value generator be provided, so that the development framework can better identify array changes. If the array is reversed while no key-value generator is provided, all nodes in ForEach will be rebuilt. |
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:
> - ForEach must be used in container components.
>
> - The generated child components are allowed in the parent container component of ForEach. The child component generator function can contain the if/else conditional statement, and the if/else conditional statement can contain ForEach.
>
> - The calling sequence of subitem generator functions may be different from that of the data items in the array. During the development, do not assume whether the subitem generator and key value generator functions are executed and the execution sequence. The following is an example of incorrect usage:
>
> ```
> ForEach(anArray, item => {Text(`${++counter}. item.label`)})
> ```
>
> Below is an example of correct usage:
>
>
> ```
> ForEach(anArray.map((item1, index1) => { return { i: index1 + 1, data: item1 }; }),
> item => Text(`${item.i}. item.data.label`),
> item => item.data.id.toString())
> ```
## Example
The following is an example of a simple-type array:
```
@Entry
@Component
struct MyComponent {
@State arr: number[] = [10, 20, 30]
build() {
Column() {
Button() {
Text('Reverse Array')
}.onClick(() => {
this.arr.reverse()
})
ForEach(this.arr, // Parameter 1: array to be iterated
(item: number) => { // Parameter 2: item generator
Text(`item value: ${item}`)
Divider()
},
(item: number) => item.toString() // Parameter 3: unique key generator, which is optional but recommended.
)
}
}
}
```
The following is an example of a complex-type array:
```
class Month {
year: number
month: number
days: Array<number>
constructor(year, month, days) {
this.year = year;
this.month = month;
this.days = days;
}
}
@Entry
@Component
struct Calendar1 {
// simulate with 6 months
@State calendar: Month[] = [
new Month(2020, 1, [...Array(31).keys()]),
new Month(2020, 2, [...Array(28).keys()]),
new Month(2020, 3, [...Array(31).keys()]),
new Month(2020, 4, [...Array(30).keys()]),
new Month(2020, 5, [...Array(31).keys()]),
new Month(2020, 6, [...Array(30).keys()]),
]
build() {
Column() {
Button('next month')
.onClick(() => {
this.calendar.shift()
this.calendar.push({
year: 2020,
month: 7,
days: [...Array(31)
.keys()]
})
})
ForEach(this.calendar,
(item: Month) => {
Text('month:' + item.month)
.fontSize(30)
.padding(20)
Grid() {
ForEach(item.days,
(day: number) => {
GridItem() {
Text((day + 1).toString())
.fontSize(30)
}
},
(day: number) => day.toString())
}
.columnsTemplate('1fr 1fr 1fr 1fr 1fr 1fr 1fr')
.rowsGap(20)
},
// field is used together with year and month as the unique ID of the month.
(item: Month) => (item.year * 12 + item.month).toString())
}
}
}
```
# if/else
Use if/else for conditional rendering.
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:
> - State variables can be used in the if conditional statement.
>
> - You can use the if conditional statement to implement rendering of child components.
>
> - The if conditional statement must be used in container components.
>
> - Some container components limit the type or number of child components. When if is placed in these components, the limitation applies to components created in if and else statements. For example, when if is used in the &lt;Grid&gt; component, whose child components can only be &lt;GridItem&gt;, only the &lt;GridItem> component can be used in the if conditional statement.
## Example
Example of using the if conditional statement:
```
Column() {
if (this.count > 0) {
Text('count is positive')
}
}
```
Example of using the if, else if, and else conditional statements:
```
Column() {
if (this.count < 0) {
Text('count is negative')
} else if (this.count % 2 === 0) {
Divider()
Text('even')
} else {
Divider()
Text('odd')
}
}
```
# LazyForEach
The development framework provides LazyForEach to iterate data from provided data sources and create corresponding components during each iteration. LazyForEach is defined as follows:
```
LazyForEach(
dataSource: IDataSource, // Data source to be iterated
itemGenerator: (item: any) => void, // child component generator
keyGenerator?: (item: any) => string // (optional) Unique key generator, which is recommended.
): void
interface IDataSource {
totalCount(): number; // Get total count of data
getData(index: number): any; // Get single data by index
registerDataChangeListener(listener: DataChangeListener): void; // Register listener to listening data changes
unregisterDataChangeListener(listener: DataChangeListener): void; // Unregister listener
}
interface DataChangeListener {
onDataReloaded(): void; // Called while data reloaded
onDataAdded(index: number): void; // Called while single data added
onDataMoved(from: number, to: number): void; // Called while single data moved
onDataDeleted(index: number): void; // Called while single data deleted
onDataChanged(index: number): void; // Called while single data changed
}
```
## APIs
### LazyForEach
LazyForEach(dataSource: IDataSource, itemGenerator: (item: any) => void, keyGenerator?: (item: any) => string):void
Table1 Parameters
| Name | Type | Mandatory | Default Value | Description |
| -------- | -------- | -------- | -------- | -------- |
| dataSource | IDataSource | Yes | - | Object used to implement the IDataSource API. You need to implement related APIs. |
| itemGenerator | (item: any) => void | Yes | - | Used to generate the lambda function of the child components. It generates one or more child components for a given array item. A single component and its child component list must be contained in the braces ({...}) |
| keyGenerator | (item: any) => string | No | - | Used as an anonymous parameter for generating a unique and stable key value for a given array item. When the position of a subitem in the array is changed, the key value of the subitem cannot be changed. When a subitem in the array is replaced with a new item, the key value of the current item must be different from that of the new item. This key-value generator is optional. However, for performance reasons, it is strongly recommended that the key-value generator be provided, so that the development framework can better identify array changes. If the array is reversed while no key-value generator is provided, all nodes in LazyForEach will be rebuilt. |
Table2 Description of IDataSource
| Name | Description |
| -------- | -------- |
| totalCount(): number | Obtains the total number of data records. |
| getData(index: number): any | Obtains the data corresponding to the specified index. |
| registerDataChangeListener(listener: DataChangeListener): void | Registers the data change listener. |
| unregisterDataChangeListener(listener: DataChangeListener): void | Unregisters the data change listener. |
Table3 Description of DataChangeListener
| Name | Description |
| -------- | -------- |
| onDataReloaded(): void | Reloads all data. |
| onDataAdded(index: number): void | Notifies the component that data is added to the position indicated by the specified index. |
| onDataMoved(from: number, to: number): void | Notifies the component that data is moved from the from position to the to position. |
| onDataDeleted(index: number): void | Notifies the component that data is deleted from the position indicated by the specified index. |
| onDataChanged(index: number): void | Notifies the component that data in the position indicated by the specified index is changed. |
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:
> - LazyForEach must be used in the container component. Only the &lt;List&gt;, &lt;Grid&gt;, and &lt;Swiper&gt; components support LazyForEach (that is, only the visible part and a small amount of data before and after the visible part are loaded for caching). For other components, all data is loaded at a time.
>
> - LazyForEach must create one and only one child component in each iteration.
>
> - The generated child component must be in the parent container component of LazyForEach.
>
> - LazyForEach can be included in an if/else conditional statement, but cannot contain an if/else conditional statement.
>
> - For the purpose of high-performance rendering, when the onDataChanged method of the DataChangeListener object is used to update the UI, the component update is triggered only when the state variable is used in the component specified in the UI description of itemGenerator.
>
> - The calling sequence of the subitem generator function may be different from that of the data items in the data source. During the development, do not assume whether the subitem generator and key value generator functions are executed and the execution sequence. The following is an example of incorrect usage:
>
> ```
> LazyForEach(dataSource, item => {Text(`${++counter}. item.label`)})
> ```
>
> Below is an example of correct usage:
>
>
> ```
> LazyForEach(dataSource,
> item => Text(`${item.i}. item.data.label`)),
> item => item.data.id.toString())
> ```
## Example
```
// Basic implementation of IDataSource to handle data listener
class BasicDataSource implements IDataSource {
private listeners: DataChangeListener[] = []
public totalCount(): number {
return 0
}
public getData(index: number): any {
return undefined
}
registerDataChangeListener(listener: DataChangeListener): void {
if (this.listeners.indexOf(listener) < 0) {
console.info('add listener')
this.listeners.push(listener)
}
}
unregisterDataChangeListener(listener: DataChangeListener): void {
const pos = this.listeners.indexOf(listener);
if (pos >= 0) {
console.info('remove listener')
this.listeners.splice(pos, 1)
}
}
notifyDataReload(): void {
this.listeners.forEach(listener => {
listener.onDataReloaded()
})
}
notifyDataAdd(index: number): void {
this.listeners.forEach(listener => {
listener.onDataAdded(index)
})
}
notifyDataChange(index: number): void {
this.listeners.forEach(listener => {
listener.onDataChanged(index)
})
}
notifyDataDelete(index: number): void {
this.listeners.forEach(listener => {
listener.onDataDeleted(index)
})
}
notifyDataMove(from: number, to: number): void {
this.listeners.forEach(listener => {
listener.onDataMoved(from, to)
})
}
}
class MyDataSource extends BasicDataSource {
private dataArray: string[] = ['/path/image0', '/path/image1', '/path/image2', '/path/image3']
public totalCount(): number {
return this.dataArray.length
}
public getData(index: number): any {
return this.dataArray[index]
}
public addData(index: number, data: string): void {
this.dataArray.splice(index, 0, data)
this.notifyDataAdd(index)
}
public pushData(data: string): void {
this.dataArray.push(data)
this.notifyDataAdd(this.dataArray.length - 1)
}
}
@Entry
@Component
struct MyComponent {
private data: MyDataSource = new MyDataSource()
build() {
List({space: 3}) {
LazyForEach(this.data, (item: string) => {
ListItem() {
Row() {
Image(item).width("30%").height(50)
Text(item).fontSize(20).margin({left:10})
}.margin({left: 10, right: 10})
}
.onClick(()=>{
this.data.pushData('/path/image' + this.data.totalCount())
})
}, item => item)
}
}
}
```
# About Syntactic Sugar
## Decorators
A decorator @Decorator can decorate a class, structure, or class attribute. Multiple decorators can be applied to the same target element and defined on a single line or multiple lines. It is recommended that the decorators be defined on multiple lines.
In the example below, the elements decorated by @Component take on the form of a component, and the variables decorated by @State can be used to represent states.
```
@Component
struct MyComponent {
@State count: number = 0
}
```
Multiple decorators can be defined on a single line, as shown below:
```
@Entry @Component struct MyComponent {
}
```
However, you are advised to define the decorators on multiple lines, as shown below:
```
@Entry
@Component
struct MyComponent {
}
```
### Supported Decorators
| Decorator | Decorates... | Description |
| -------- | -------- | -------- |
| @Component | struct | The decorated structure has the component-based capability. The build method must be implemented to update the UI. |
| @Entry | struct | The decorated component is used as the entry of a page. The component is rendered and displayed when the page is loaded. |
| @Preview | struct | Custom components decorated by @Preview can be previewed in the Previewer of DevEco Studio. When the page is loaded, the custom components decorated by @Preview are created and displayed. |
| @Builder | Methods | In the decorated method, you can use the declarative UI description to quickly generate multiple layouts in a custom component. |
| @Extend | Methods | This decorator adds new attribute functions to a preset component, allowing you to quickly define and reuse the custom style of the component. |
| @CustomDialog | struct | This decorator is used to decorate custom pop-up dialog boxes. |
| @State | Primitive data types, classes, and arrays | If the decorated state data is modified, the build method of the component will be called to update the UI. |
| @Prop | Primitive data types | This decorator is used to establish one-way data binding between the parent and child components. When the data associated with the parent component is modified, the UI of the current component is updated. |
| @Link | Primitive data types, classes, and arrays | This decorator is used to establish two-way data binding between the parent and child components. The internal state data of the parent component is used as the data source. Any changes made to one component will be reflected to the other. |
| @Observed | Classes | This decorator is used to indicate that the data changes in the class will be managed by the UI page. |
| @ObjectLink | Objects of @Observed decorated classes | When the decorated state variable is modified, the parent and sibling components that have the state variable will be notified for UI re-rendering. |
| @Consume | Primitive data types, classes, and arrays | When the @Consume decorated variable detects the update of the @Provide decorated variable, the re-rendering of the current custom component is triggered. |
| @Provide | Primitive data types, classes, and arrays | As the data provider, @Provide can update the data of child nodes and trigger page rendering. |
| @Watch | Variables decorated by @State, @Prop, @Link, @ObjectLink, @Provide, @Consume, @StorageProp, or @StorageLink | This decorator is used to listen for the changes of the state variables. The application can register a callback method through @Watch. |
## Chain Call
You can configure the UI structure and its attributes and events and separate them with a dot(.) to implement chain call.
```
Column() {
Image('1.jpg')
.alt('error.jpg')
.width(100)
.height(100)
}.padding(10)
```
## struct
Components can be implemented based on structs. Components cannot inherit from each other. The structs implemented components can be created and destroyed more quickly than class implemented components.
```
@Component
struct MyComponent {
@State data: string = ''
build() {
}
}
```
## Instantiating a struct Without the new Keyword
You can omit the new keyword when instantiating a struct.
```
// Definition
@Component
struct MyComponent {
build() {
}
}
// Use
Column() {
MyComponent()
}
// Equivalent to
new Column() {
new MyComponent()
}
```
## Restrictions on Using TypeScript in Generators
TypeScript has the following restrictions on generators:
- Expressions can be used only in character strings (${expression}), if conditions, ForEach parameters, and component parameters.
- No expressions should cause any application state variables (@State, @Link, and @Prop) to change. Otherwise, undefined and potentially unstable framework behavior may occur.
- The generator function cannot contain local variables.
None of the above restrictions apply to anonymous function implementations of event-handling functions (such as onClick)
Incorrect:
```
build() {
let a: number = 1 // invalid: variable declaration not allowed
Column() {
Text('Hello ${this.myName.toUpperCase()}') // ok.
ForEach(this.arr.reverse(), ..., ...) // invalid: Array.reverse modifies the @State array varible in place
}
buildSpecial() // invalid: no function calls
Text(this.calcTextValue()) // this function call is ok.
}
```
## $$
$$ supports two-way binding for simple variables and @State, @Link, and @Prop decorated variables.
Currently, $$ supports only the rendering between the show parameter of the bindPopup attribute and the @State decorated variable, and the checked attribute of the <Radio> component.
```
@Entry
@Component
struct bindPopup {
@State customPopup: boolean = false
build() {
Column() {
Button(){
Text('Popup')
}
.onClick(()=>{
this.customPopup = !this.customPopup
})
.bindPopup(
$$this.customPopup, {
message: "showPopup"
}
)
}
}
}
```
# Overview
This section defines the core mechanism and functions of the TypeScript-based declarative development paradigm. It acquaints you with the declarative UI descriptions, componentization mechanisms, UI state management, rendering control syntax, and syntactic sugar.
Follow the provided guidelines for UI development. For details about the components, see components.
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:
> - All examples use the TypeScript (TS) language. If you are using another language, comply with the syntax requirements for that language.
>
> - The components used in the examples are preset in the UI framework and are used only to explain the UI description specifications.
>
> - Universal attribute and event methods generally apply to all components, and the attribute and event methods within a component apply only to this component.
# Accessing System Resources
System resources include colors, rounded corners, fonts, spacing, character strings, and images. By using system resources, you can develop different applications with the same visual style.
To reference a system resource, use the "$r('sys.type.resource_id')" format. Wherein: sys indicates a system resource; type indicates the resource type, which can be color, float, string, or media; resource_id indicates the resource ID, which is determined when the system resource is provided. For details about available system resource IDs.
```
Text('Hello')
.fontColor($r('sys.color.id_color_emphasize'))
.fontSize($r('sys.float.id_text_size_headline1'))
.fontFamily($r('sys.string.id_text_font_family_medium'))
.backgroundColor($r('sys.color.id_color_palette_aux1'))
Image($r('sys.media.ic_app'))
.border({color: $r('sys.color.id_color_palette_aux1'), radius: $r('sys.float.id_corner_radius_button'), width: 2})
.margin({top: $r('sys.float.id_elements_margin_horizontal_m'), bottom: $r('sys.float.id_elements_margin_horizontal_l')})
.height(200)
.width(300)
```
# Types
## Length Type
| Name| Type| Description|
| -------- | -------- | -------- |
| Length | string \| number | Length unit. If the input is a number, use vp. If the input is a string, explicitly specify the unit, for example, '10px', or specify the length in percentage, for example, '100%'.|
## Angle Type
| Name| Type| Description|
| -------- | -------- | -------- |
| Angle | string \| number | Angle unit. If the input is a number, use deg. If the input is a string, use either of the following angle units:<br>- deg: for example, '100deg'<br>- rad: for example, '3.14rad' |
## Point Type
| Name| Type| Description|
| -------- | -------- | -------- |
| Point | [Length, Length] | Coordinates of a point. The first value is the x-axis coordinate, and the second value is the y-axis coordinate.|
## Color Type
The Color type used by component attribute methods is described as follows:
| Name| Type| Description|
| -------- | -------- | -------- |
| Color | string \| number \| Color | Color information. If the input is a string, use rgb or rgba to specify the color. If the input is a number, use Hex format to specify the color. If the input is a Color enum, use a color value to specify the color.<br>- 'rgb(255, 255, 255)'<br>- 'rgba(255, 255, 255, 1.0)'<br>- Hex format: 0xrrggbb, 0xaarrggbb, '\#FFFFFF'<br>- Enum: Color.Black, Color.White |
The supported Color enums are described as follows:
| Color| Value| Illustration|
| -------- | -------- | -------- |
| Black | 0x000000 | ![en-us_image_0000001219864153](figures/en-us_image_0000001219864153.png) |
| Blue | 0x0000ff | ![en-us_image_0000001174104404](figures/en-us_image_0000001174104404.png) |
| Brown | 0xa52a2a | ![en-us_image_0000001219744201](figures/en-us_image_0000001219744201.png) |
| Gray | 0x808080 | ![en-us_image_0000001174264376](figures/en-us_image_0000001174264376.png) |
| Green | 0x008000 | ![en-us_image_0000001174422914](figures/en-us_image_0000001174422914.png) |
| Orange | 0xffa500 | ![en-us_image_0000001219662661](figures/en-us_image_0000001219662661.png) |
| Pink | 0xffc0cb | ![en-us_image_0000001219662663](figures/en-us_image_0000001219662663.png) |
| Red | 0xff0000 | ![en-us_image_0000001219662665](figures/en-us_image_0000001219662665.png) |
| White | 0xffffff | ![en-us_image_0000001174582866](figures/en-us_image_0000001174582866.png) |
| Yellow | 0xffff00 | ![en-us_image_0000001174582864](figures/en-us_image_0000001174582864.png) |
## ColorStop Type
ColorStop is used to describe the progressive color stop.
| Name| Type| Description|
| -------- | -------- | -------- |
| ColorStop | [Color, number] | Type of the progressive color stop. The first parameter specifies the color value, and the second parameter specifies the ratio of 0 to 1.|
## Resource Type
The Resource type is used to reference resources for setting the value of a component attribute.
You can use $r or $rawfile to create a Resource object. For details, see [Resource Access](ts-application-resource-access.md).
- $r('belonging.type.name')
belonging: system or application resource. The value can be 'sys' or 'app'.
type: resource type, which can be 'color', 'float', 'string', or 'media'.
name: resource name, which is determined during resource definition.
- $rawfile('filename')
filename: name of the file in resources/rawfile of the project.
| Name| Type| Description|
| -------- | -------- | -------- |
| Resource | {<br>readonly id: [number];<br>readonly type: [number];<br>readonly params?: any[];<br>} | id: resource ID.<br>type: resource type (enumerated value).<br>params: optional parameters.<br>After a Resource object is created using $r or $rawfile, modifying attribute values of the object is prohibited.|
## ResourceStr Type<sup>8+</sup>
| Name| Type| Description|
| -------- | -------- | -------- |
| ResourceStr | string \| Resource| Resource string.|
## ResourceColor Type<sup>8+</sup>
| Name| Type| Description|
| -------- | -------- | -------- |
| ResourceColor | Color \| number \| string \| Resource | Resource color.|
## Font Type<sup>8+</sup>
| Name| Type| Description|
| -------- | -------- | -------- |
| Font | {<br>size?: Length;<br>weight?: FontWeight \| number \| string;<br>family?: string \| Resource;<br>style?: FontStyle;<br>} | Text style.<br>size: font size. For the number type, use the unit fp.<br>weight: font weight. For the number type, the value ranges from 100 to 900, at an interval of 100. The default value is 400. A larger value indicates a larger font weight.<br>family: font family. Use commas (,) to separate multiple fonts. The priority of the fonts is the sequence in which they are placed. An example value is 'Arial, sans-serif'.<br>style: font style.|
## CustomBuilder Type<sup>8+</sup>
You can use CustomBuilder to define custom UI descriptions in component attribute methods.
| Name| Type| Description|
| -------- | -------- | -------- |
| CustomBuilder | () => any | Builder of component attribute methods for defining custom UI descriptions. This type of method must be decorated by @Builder. For details, see [@Builder](ts-component-based-builder.md).|
## Example
```
@Entry
@Component
struct dataTypeExample {
build() {
Column({ space: 5 }) {
Text('Length').fontColor(0xCCCCCC).fontSize(9).width('90%')
Text('90%').width('90%').height(40).backgroundColor(0xF9CF93)
.textAlign(TextAlign.Center).fontColor(Color.White)
Text('320').width(320).height(40).backgroundColor(0xF9CF93)
.textAlign(TextAlign.Center).fontColor(Color.White)
Text('1000px').width('1000px').height(40).backgroundColor(0xF9CF93)
.textAlign(TextAlign.Center).fontColor(Color.White)
Text('Angle').fontColor(0xCCCCCC).fontSize(9).width('90%')
Text('45deg')
.width(40).height(40)
.rotate({ x: 0, y: 0, z: 1, angle: 45, centerX: '50%', centerY: '50%' })
.fontColor(Color.White)
.backgroundColor(0xF9CF93).textAlign(TextAlign.Center)
Text('45rad')
.width(40).height(40)
.rotate({ x: 0, y: 0, z: 1, angle: '45rad', centerX: '50%', centerY: '50%' })
.fontColor(Color.White)
.backgroundColor(0xF9CF93).textAlign(TextAlign.Center).margin({ top: 30 })
Text('Point').fontColor(0xCCCCCC).fontSize(9).width('90%')
Line().width(300).height(40).startPoint([0, 20]).endPoint([300, 20])
Text('Color').fontColor('#CCCCCC').fontSize(9).width('90%')
Text('0xF9CF93')
.fontColor(Color.White).textAlign(TextAlign.Center)
.width('90%').height(40).backgroundColor(0xF9CF93)
Text('#F9CF93')
.fontColor(Color.White).textAlign(TextAlign.Center)
.width('90%').height(40).backgroundColor('#F9CF93')
Text('rgb(249, 207, 147)')
.fontColor(Color.White).textAlign(TextAlign.Center)
.width('90%').height(40).backgroundColor('rgb(249, 207, 147)')
Text('rgba(249, 207, 147, 1.0)')
.fontColor(Color.White).textAlign(TextAlign.Center)
.width('90%').height(40).backgroundColor('rgba(249, 207, 147, 1.0)')
Text('Color.Yellow')
.textAlign(TextAlign.Center)
.width('90%').height(40).backgroundColor(Color.Yellow)
}
.width('100%').margin({ top: 5 })
}
}
```
![en-us_image_0000001219982719](figures/en-us_image_0000001219982719.png)
# Basic Concepts
In the declarative UI programming paradigm, the UI is a function in the specific application state, and you update a UI by modifying the current application state. The development framework provides comprehensive application state management capabilities, as shown in the figure below.
![en-us_image_0000001222967768](figures/en-us_image_0000001222967768.png)
## State Variable Decorators
- @State: state attribute of the component. Each time the @State decorated variable changes, the component re-renders and updates the UI.
- @Link: allows a component to depend on some state attributes of its parent component. Each time the data in one component is updated, the state of the other component is updated, and the parent and child components are rendered again.
- @Prop: works in a way similar to that of @Link. The difference is that the changes made by a child component are not synchronized to the parent component.
## Application State Data
AppStorage is the central store of the application states in the entire UI. The UI framework creates a singleton AppStorage object for the application and provides the corresponding decorators and APIs for the application.
- @StorageLink: works in a way similar to that of @Consume. The difference is that the link object with the specified name is obtained from the AppStorage. It establishes two-way binding between the UI component and AppStorage to synchronize data.
- @StorageProp: synchronizes UI component attributes with the AppStorage unidirectionally. That is, the value change in the AppStorage will trigger an update of the corresponding UI component, but the change of the UI component will not cause an update of the attribute value in the AppStorage.
- Service logic implementation API: adds, reads, modifies, or deletes the state attributes of applications. The changes made by this API will be synchronized to the UI component for UI update.
# Defining Attribute Style Animations
Keyframes is used to scale a component by dynamically setting the width and height of its parent component. Set the scale attribute for child components to scale the child and parent components at the same time. Then, set the opacity attribute to display or hide the child and parent components.
```
<!-- xxx.hml -->
<div class="container">
<div class="fade">
<text>fading away</text>
</div>
<div class="bigger">
<text>getting bigger</text>
</div>
</div>
```
```
/* xxx.css */
.container {
background-color:#F1F3F5;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.fade{
width: 30%;
height: 200px;
left: 35%;
top: 25%;
position: absolute;
animation: 2s change infinite friction;
}
.bigger{
width: 20%;
height: 100px;
background-color: blue;
animation: 2s change1 infinite linear-out-slow-in;
}
text{
width: 100%;
height: 100%;
text-align: center;
color: white;
font-size: 35px;
animation: 2s change2 infinite linear-out-slow-in;
}
/* Color change */
@keyframes change{
from {
background-color: #f76160;
opacity: 1;
}
to {
background-color: #09ba07;
opacity: 0;
}
}
/* Scaling of the parent component */
@keyframes change1{
0% {
width: 20%;
height: 100px;
}
100% {
width: 80%;
height: 200px;
}
}
/* Text scaling of the child component */
@keyframes change2{
0%{
transform: scale(0);
}
100% {
transform: scale(1.5);
}
}
```
![en-us_image_0000001267647889](figures/en-us_image_0000001267647889.gif)
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:
> 1. The values of animation attributes are not sequenced. However, the values of duration and delay are parsed based on the sequence in which they are displayed.
>
> 2. The animation-duration attribute must be set. Otherwise, the duration is 0, which means there is no animation effect. When animation-fill-mode is set to forwards, the component directly displays the style of the last frame.
# Defining Animations with the background-position Attribute
By changing the background-position attribute (where the first value is the position on the x-axis and the second value is the position on the y-axis), you move a background image. If the background image goes beyond the respective component boundaries, the excess parts will not be displayed.
```
<!-- xxx.hml -->
<div class="container">
<div class="content"></div>
<div class="content1"></div>
</div>
```
```
/* xxx.css */
.container {
background-color:#F1F3F5;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%;
}
.content{
width: 400px;
height: 400px;
background-image: url('common/images/bg-tv.jpg');
background-size: 100%;
background-repeat: no-repeat;
animation: change 3s infinite;
border: 1px solid black;
}
.content1{
margin-top:50px;
width: 400px;
height: 400px;
background-image: url('common/images/bg-tv.jpg');
background-size: 50%;
background-repeat: no-repeat;
animation: change1 5s infinite;
border: 1px solid black;
}
/* Move the background image out of the component. */
@keyframes change{
0%{
background-position:0px top;
}
25%{
background-position:400px top;
}
50%{
background-position:0px top;
}
75%{
background-position:0px bottom;
}
100%{
background-position:0px top;
}
}
/* Move the background image within the component. */
@keyframes change1{
0%{
background-position:left top;
}
25%{
background-position:50% 50%;
}
50%{
background-position:right bottom;
}
100%{
background-position:left top;;
}
}
```
![en-us_image_0000001267607873](figures/en-us_image_0000001267607873.gif)
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:
> The background-position attribute can only be used to move background images, but not the background color (background-color).
# Component Animation
Create and run an animation shortcut on the component. For details, see [Universal Methods](../reference/arkui-js/js-components-common-methods.md).
## Obtaining an Animation Object
Call the animate method to obtain an animation object, which supports animation attributes, methods, and events.
```
<!-- xxx.hml -->
<div class="container">
<div id="content" class="box" onclick="Show"></div>
</div>
```
```
/* xxx.css */
.container {
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%;
}
.box{
width: 200px;
height: 200px;
background-color: #ff0000;
margin-top: 30px;
}
```
```
/* xxx.js */
export default {
data: {
animation: '',
},
onInit() {
},
onShow() {
var options = {
duration: 1500,
};
var frames = [
{
width:200,height:200,
},
{
width:300,height:300,
}
];
this.animation = this.$element('content').animate(frames, options); // Obtain the animation object.
},
Show() {
this.animation.play();
}
}
```
![en-us_image_0000001222807812](figures/en-us_image_0000001222807812.gif)
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:
> - When using the animate method, you must pass the keyframes and options parameters.
> - If animate is called multiple times and the replace policy is used, parameters passed to the last call will take effect.
## Setting Animation Parameters
After obtaining an animation object, you can set its style working on the component by using the keyframes parameter.
```
<!-- xxx.hml -->
<div class="container">
<div id="content" class="box" onclick="Show"></div>
</div>
```
```
/* xxx.css */
.container {
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%;
}
.box{
width: 200px;
height: 200px;
background-color: #ff0000;
margin-top: 30px;
}
```
```
/* xxx.js */
export default {
data: {
animation: '',
keyframes:{},
options:{}
},
onInit() {
this.options = {
duration: 4000,
};
this.keyframes = [
{
transform: {
translate: '-120px -0px',
scale: 1,
rotate: 0
},
transformOrigin: '100px 100px',
offset: 0.0,
width: 200,
height: 200
},
{
transform: {
translate: '120px 0px',
scale: 1.5,
rotate: 90
},
transformOrigin: '100px 100px',
offset: 1.0,
width: 300,
height: 300
}
];
},
Show() {
this.animation = this.$element('content').animate(this.keyframes, this.options);
this.animation.play();
}
}
```
![en-us_image_0000001267647897](figures/en-us_image_0000001267647897.gif)
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:
> - The sequence of translate, scale, and rotate affects the animation effect.
>
> - transformOrigin works only for scale and rotate.
Set the animation attributes by using the options parameter.
```
<!-- xxx.hml -->
<div class="container">
<div id="content" class="box" onclick="Show"></div>
</div>
```
```
/* xxx.css */
.container {
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%;
}
.box{
width: 200px;
height: 200px;
background-color: #ff0000;
margin-top: 30px;
}
```
```
/* xxx.js */
export default {
data: {
animation: '',
},
onInit() {
},
onShow() {
var options = { duration: 1500, easing: 'ease-in', delay: 5, iterations: 2, direction: 'normal', };
var frames = [
{
transform: {
translate: '-150px -0px'
}
},
{
transform: {
translate: '150px 0px'
}
}
];
this.animation = this.$element('content').animate(frames, options);
},
Show() {
this.animation.play();
}
}
```
![en-us_image_0000001222967796](figures/en-us_image_0000001222967796.gif)
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:
> direction: mode of playing the animation.
>
> normal: plays the animation in forward loop mode.
>
> reverse: plays the animation in reverse loop mode.
>
> alternate: plays the animation in alternating loop mode. When the animation is played for an odd number of times, the playback is in forward direction. When the animation is played for an even number of times, the playback is in reverse direction.
>
> alternate-reverse: plays the animation in reverse alternating loop mode. When the animation is played for an odd number of times, the playback is in reverse direction. When the animation is played for an even number of times, the playback is in forward direction.
## Adding an Event and Calling a Method
Animation objects support animation events and methods. You can achieve the intended animation by adding start and cancel events and calling the play, pause, rewind, and stop methods.
```
<!-- xxx.hml -->
<div class="container">
<div id="content" style="width: 350px;height: 350px;margin-top: 100px;background: linear-gradient(pink, purple);">
</div>
<div class="row">
<button type="capsule" value="play" onclick="playAnimation"></button>
<button type="capsule" value="pause" onclick="pauseAnimation"></button>
</div>
<div class="row1">
<button type="capsule" value="reverse" onclick="reverseAnimation"></button>
<button type="capsule" value="cancel" onclick="cancelAnimation"></button>
</div>
</div>
```
```
/* xxx.css */
.container {
flex-direction: column;
align-items: center;
justify-content: center;
}
button{
width: 200px;
}
.row{
width: 65%;
height: 100px;
align-items: center;
justify-content: space-between;
margin-top: 40px;
position: fixed;
top: 65%;
left: 120px;
}
.row1{
width: 65%;
height: 100px;
align-items: center;
justify-content: space-between;
margin-top: 30px;
position: fixed;
top: 75%;
left: 120px;
}
```
```
/* xxx.js */
import prompt from '@system.prompt';
export default {
data: {
animation: '',
},
onInit() {
},
onShow() {
var options = {
duration: 1500,
easing:'ease-in',
elay:5,
direction:'normal',
iterations:2
};
var frames = [
{
transform: {
translate: '-150px -0px'
},
opacity: 0.1,
offset: 0.0,
width: 200,
height: 200,
},
{
transform: {
translate: '150px 0px'
},
opacity: 1.0,
offset: 1.0,
width: 300,
height: 300,
}
];
this.animation = this.$element('content').animate(frames, options);
this.animation.onstart = function(){
prompt.showToast({
message: "start"
});
}; // Add a start event.
this.animation.onrepeat = function(){
prompt.showToast({
message: " repeated"
});
};// Add a repeat event.
this.animation.oncancel = function(){
prompt.showToast({
message: "canceled"
});
};// Add a cancellation event.
this.animation.onfinish = function(){
prompt.showToast({
message: "finish"
});
};// Add a finish event.
},
playAnimation() {
this.animation.play();// Start this animation.
},
pauseAnimation() {
this.animation.pause();// Pause this animation.
},
reverseAnimation() {
this.animation.reverse();// Reverse this animation.
},
cancelAnimation() {
this.animation.cancel();// Cancel this animation.
}
}
```
![en-us_image_0000001223127752](figures/en-us_image_0000001223127752.gif)
Change the animation status by changing the playStat attribute.
```
<!-- xxx.hml -->
<div class="container">
<div id="content" style="width: 350px;height: 350px;margin-top: 100px;background: linear-gradient(pink, purple);">
</div>
<div class="row">
<button type="capsule" value="{{state}}" onclick="playStateClick"></button>
</div>
<div class="row1">
<button type="capsule" value="{{state1}}" onclick="playStateClick1"></button>
</div>
</div>
```
```
/* xxx.css */
.container {
flex-direction: column;
align-items: center;
justify-content: center;
}
button{
width: 200px;
}
.row{
width: 65%;
height: 100px;
align-items: center;
justify-content: space-between;
margin-top: 50px;
margin-left: 260px;
position: fixed;
top: 65%;
}
.row1{
width: 65%;
height: 100px;
align-items: center;
justify-content: space-between;
margin-top: 50px;
margin-left: 260px;
position: fixed;
top: 75%;
}
```
```
/* xxx.js */
import prompt from '@system.prompt';
export default {
data: {
animation: '',
state:'play',
state1:'play'
},
onInit() {
},
onShow() {
var options = {
duration: 1500,
easing:'ease-in',
elay:5,
direction:'normal',
iterations:2,
};
var frames = [
{
transform: {
translate: '-150px -0px'
},
opacity: 0.1,
offset: 0.0,
width: 200,
height: 200,
},
{
transform: {
translate: '150px 0px'
},
opacity: 1.0,
offset: 1.0,
width: 300,
height: 300,
}
];
this.animation = this.$element('content').animate(frames, options);
this.animation.onstart = function(){
prompt.showToast({
message: "start"
});
};
this.animation.onrepeat = function(){
prompt.showToast({
message: " repeated"
});
};
this.animation.onfinish = function(){
prompt.showToast({
message: " finished"
});
};
},
playStateClick(){
if(this.animation.playState != 'running'){
this.animation.playState = 'running';// Set playState to running to run the animation.
this.state = 'pause'
}else{
this.animation.playState = 'paused';// Set playState to paused to pause the animation.
this.state = 'play'
}
},
playStateClick1(){
if(this.animation.playState != 'running'){
this.animation.playState = 'running';
this.state1 = 'finish'
}else{
this.animation.playState = 'finished';// Set playState to finished to stop the animation.
this.state1 = 'play'
}
}
}
```
![en-us_image_0000001267607921](figures/en-us_image_0000001267607921.gif)
# Animation Effect
You can set the interpolator to implement the animation effect. For details, see [Animation](../reference/apis/js-apis-basic-features-animator.md).
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:
> This feature is supported since API version 6.
## Creating an Animation Object
Use createAnimator to create an animation object and set the animation attributes by using the options parameter.
```
<!-- xxx.hml -->
<div class="container">
<div style="width: 300px;height: 300px;margin-top: 100px;background: linear-gradient(pink, purple);transform: translate({{translateVal}});">
</div>
<div class="row">
<button type="capsule" value="play" onclick="playAnimation"></button>
</div>
</div>
```
```
/* xxx.css */
.container {
flex-direction: column;
align-items: center;
justify-content: center;
}
button{
width: 200px;
}
.row{
width: 65%;
height: 100px;
align-items: center;
justify-content: space-between;
margin-top: 50px;
margin-left: 260px;
}
```
```
/* xxx.js */
import animator from '@ohos.animator';
export default {
data: {
translateVal: 0,
animation: null
},
onInit() {},
onShow(){
var options = {
duration: 3000,
easing:"friction",
delay:"1000",
fill: 'forwards',
direction:'alternate',
iterations: 2,
begin: 0,
end: 180
};// Set attributes.
this.animation = animator.createAnimator(options)// Create an animation.
},
playAnimation() {
var _this = this;
this.animation.onframe = function(value) {
_this.translateVal= value
};
this.animation.play();
}
}
```
![en-us_image_0000001267887885](figures/en-us_image_0000001267887885.gif)
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:
> - When you use createAnimator to create an animation object, you must pass the options parameter.
>
> - begin indicates the start point of the animation interpolation. If it is not set, the default value 0 is used.
>
> - end indicates the end point of the animation interpolation. If it is not set, the default value 1 is used.
## Adding Animation Events and Calling Methods
The animator supports events and methods, which you can use to customize the animation effect. Events include frame, cancel, repeat, and finish. Methods include update, play, pause, cancel, reverse, and finish. For details about the supported events and methods, see [animator supported events and animator supported APIs](../reference/apis/js-apis-basic-features-animator.md).
```
<!-- xxx.hml -->
<div style="flex-direction: column;align-items: center;">
<div style="width:200px;height: 200px;margin-top: 100px;background: linear-gradient(#b30d29, #dcac1b);
transform: scale({{scaleVal}});"></div>
<div style="width: {{DivWidth}};height: {{DivHeight}};margin-top: 200px;
background: linear-gradient(pink, purple);margin-top: 200px;transform:translateY({{translateVal}});">
</div>
<div class="row">
<button type="capsule" value="play" onclick="playAnimation"></button>
<button type="capsule" value="update" onclick="updateAnimation"></button>
</div>
<div class="row1">
<button type="capsule" value="pause" onclick="pauseAnimation"></button>
<button type="capsule" value="finish" onclick="finishAnimation"></button>
</div>
<div class="row2">
<button type="capsule" value="cancel" onclick="cancelAnimation"></button>
<button type="capsule" value="reverse" onclick="reverseAnimation"></button>
</div>
</div>
```
```
/* xxx.css */
.container {
flex-direction: column;
align-items: center;
justify-content: center;
}
button{
width: 200px;
}
.row{
width: 65%;
height: 100px;
align-items: center;
justify-content: space-between;
margin-top: 150px;
position: fixed;
top: 55%;
left: 120px;
}
.row1{
width: 65%;
height: 100px;
align-items: center;
justify-content: space-between;
margin-top: 120px;
position: fixed;
top: 65%;
left: 120px;
}
.row2{
width: 65%;
height: 100px;
align-items: center;
justify-content: space-between;
margin-top: 100px;
position: fixed;
top: 75%;
left: 120px;
}
```
```
/* xxx.js */
import animator from '@ohos.animator';
import prompt from '@system.prompt';
export default {
data: {
scaleVal:1,
DivWidth:200,
DivHeight:200,
translateVal:0,
animation: null
},
onInit() {
var options = {
duration: 3000,
fill: 'forwards',
begin: 200,
end: 270
};
this.animation = animator.createAnimator(options);
},
onShow() {
var _this= this;
// Add an animation repeat event.
this.animation.onrepeat = function() {
prompt.showToast({
message: 'repeat'
});
var repeatoptions = {
duration: 2000,
iterations: 1,
direction: 'alternate',
begin: 180,
end: 240
};
_this.animation.update(repeatoptions);
_this.animation.play();
};
},
playAnimation() {
var _this= this;
// Add the frame-by-frame interpolation callback event for the animation.
this.animation.onframe = function(value) {
_this. scaleVal= value/150,
_this.DivWidth = value,
_this.DivHeight = value,
_this.translateVal = value-180
};
this.animation.play();
},
updateAnimation() {
var newoptions = {
duration: 5000,
iterations: 2,
begin: 120,
end: 180
};
this.animation.update(newoptions);
this.animation.play();// Play this animation.
},
pauseAnimation() {
this.animation.pause();// Pause this animation.
},
finishAnimation() {
var _this= this;
// Add an animation completion event.
this.animation.onfinish = function() {
prompt.showToast({
message: 'finish'
})
};
this.animation.finish(); // Finish this animation.
},
cancelAnimation() {
this.animation.cancel(); // Cancel this animation.
},
reverseAnimation() {
this.animation.reverse(); // Reverse this animation.
}
}
```
![en-us_image_0000001223287724](figures/en-us_image_0000001223287724.gif)
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:
> When calling the update method, you can use it to update the animation parameters. The input parameters are the same as those of createAnimator.
# Animation Frame
## Requesting an Animation Frame
Use the requestAnimationFrame method to request frames on a one-by-one basis. This method accepts a callback as an argument.
When runframe calls requestAnimationFrame, the step callback with the timestamp parameter is passed, and this timestamp iss assigned to startTime. When the difference between the timestamp and startTime is less than the specified value, requestAnimationFrame is called again, and the animation stops.
```
<!-- xxx.hml -->
<div class="container">
<tabs onchange="changecontent">
<tab-content>
<div class="container">
<stack style="width: 300px;height: 300px;margin-top: 100px;margin-bottom: 100px;">
<canvas id="mycanvas" style="width: 100%;height: 100%;background-color: coral;">
</canvas>
<div style="width: 50px;height: 50px;border-radius: 25px;background-color: indigo;position: absolute;left: {{left}};top: {{top}};">
</div>
</stack>
<button type="capsule" value="play" onclick="runframe"></button>
</div>
</tab-content>
</tabs>
</div>
```
```
/* xxx.css */
.container {
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
}
button{
width: 300px;
}
```
```
/* xxx.js */
export default {
data: {
timer: null,
left: 0,
top: 0,
flag: true,
animation: null,
startTime: 0,
},
onShow() {
var test = this.$element("mycanvas");
var ctx = test.getContext("2d");
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(300, 300);
ctx.lineWidth = 5;
ctx.strokeStyle = "red";
ctx.stroke();
},
runframe() {
this.left = 0;
this.top = 0;
this.flag = true;
this.animation = requestAnimationFrame(this.step);
},
step(timestamp) {
if (this.flag) {
this.left += 5;
this.top += 5;
if (this.startTime == 0) {
this.startTime = timestamp;
}
var elapsed = timestamp - this.startTime;
if (elapsed < 500) {
console.log('callback step timestamp: ' + timestamp);
this.animation = requestAnimationFrame(this.step);
}
} else {
this.left -= 5;
this.top -= 5;
this.animation = requestAnimationFrame(this.step);
}
if (this.left == 250 || this.left == 0) {
this.flag = !this.flag
}
},
onDestroy() {
cancelAnimationFrame(this.animation);
}
}
```
![en-us_image_0000001267767877](figures/en-us_image_0000001267767877.gif)
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:
> When invoking the callback, the requestAnimationFrame method passes the timestamp as the first parameter, which indicates the time when requestAnimationFrame starts to execute the callback.
## Canceling an Animation Frame
Use the cancelAnimationFrame method to cancel frames on a one-by-one basis. When this method is called, the animation frame request sent through requestAnimationFrame will be canceled.
```
<!-- xxx.hml -->
<div class="container">
<tabs onchange="changecontent">
<tab-content>
<div class="container">
<stack style="width: 300px;height: 300px;margin-top: 100px;margin-bottom: 100px;">
<canvas id="mycanvas" style="width: 100%;height: 100%;background-color: coral;">
</canvas>
<div style="width: 50px;height: 50px;border-radius: 25px;background-color: indigo;position: absolute;left: {{left}};top: {{top}};">
</div>
</stack>
<button type="capsule" value="play" onclick="runframe"></button>
</div>
</tab-content>
</tabs>
</div>
```
```
/* xxx.css */
.container {
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
}
button{
width: 300px;
}
```
```
/* xxx.js */
export default {
data: {
timer: null,
left: 0,
top: 0,
flag: true,
animation: null
},
onShow() {
var test = this.$element("mycanvas");
var ctx = test.getContext("2d");
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(300, 300);
ctx.lineWidth = 5;
ctx.strokeStyle = "red";
ctx.stroke();
},
runframe() {
this.left = 0;
this.top = 0;
this.flag = true;
this.animation = requestAnimationFrame(this.step);
},
step(timestamp) {
if (this.flag) {
this.left += 5;
this.top += 5;
this.animation = requestAnimationFrame(this.step);
} else {
this.left -= 5;
this.top -= 5;
this.animation = requestAnimationFrame(this.step);
}
if (this.left == 250 || this.left == 0) {
this.flag = !this.flag
}
},
onDestroy() {
cancelAnimationFrame(this.animation);
}
}
```
![en-us_image_0000001223127740](figures/en-us_image_0000001223127740.gif)
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:
> When cancelAnimationFrame is called, a parameter that indicates an ID must be passed.
# Defining Animations for SVG Components
You can use child components in the &lt;svg&gt; component to animate attributes over time.
#### Attribute Style Animation
In the [animate](../reference/arkui-js/js-components-svg-animate.md) child component of the &lt;svg> component, set attributeName to the attribute you want to animate, set from to the animation start value, and set to to the animation end value.
```
<!-- xxx.hml -->
<div class="container">
<svg>
<text x="300" y="300" fill="blue">
Hello
<animate attributeName="font-size" from="30" to="60" dur="3s" repeatCount="indefinite">
</animate>
<animate attributeName="fill" from="red" to="blue" dur="3s" repeatCount="indefinite">
</animate>
<animate attributeName="opacity" from="1" to="0.3" dur="3s" repeatCount="indefinite">
</animate>
</text>
<text x="300" y="600" fill="blue">
World
<animate attributeName="font-size" from="30" to="60" values="30;80" dur="3s" repeatCount="indefinite">
</animate>
<animate attributeName="fill" from="red" to="blue" dur="3s" repeatCount="indefinite">
</animate>
<animate attributeName="opacity" from="0.3" to="1" dur="3s" repeatCount="indefinite">
</animate>
</text>
</svg>
</div>
```
![en-us_image_0000001183871404.gif](figures/en-us_image_0000001183871404.gif)
> ![icon-note.gif](public_sys-resources/icon-note.gif) NOTE:
> When values is also set, the from and to settings do not take effect.
#### Motion Path Animation
In the [animateMotion](../reference/arkui-js/js-components-svg-animatemotion.md) child component of the &lt;svg&gt; component, set path to define a shape for the animation.
```
<!-- xxx.hml -->
<div class="container">
<svg fill="white" width="800" height="900">
<path d="M300,200 h-150 a150 150 0 1 0 150 -150 z" fill="white" stroke="blue" stroke-width="5" >
</path>
<path fill="red" d="M-5,-5 L10,0 L-5,5 L0,0 Z" >
<animateMotion dur="2000" repeatCount="indefinite" rotate="auto-reverse"path="M300,200 h-150 a150 150 0 1 0 150 -150 z">
</animateMotion>
</path>
</svg>
</div>
```
![en-us_image_0000001229510983.gif](figures/en-us_image_0000001229510983.gif)
#### animateTransform Animation
In the [animateTransform](../reference/arkui-js/js-components-svg-animatetransform.md) child component of the &lt;svg&gt; component, set attributeName to bind the corresponding attribute to the transform attribute, and set type to the animation type, from to the start value, and to to the end value.
```
<!-- xxx.hml -->
<div class="container" style="">
<svg>
<line x1="90" y1="300" x2="90" y2="730" stroke-width="10" stroke="black" stroke-linecap="round">
<animateTransform attributeName="transform" attributeType="XML" type="translate" dur="3s" values="0;30;10;30;20;30;25;30" keyTimes="0;0.3;0.5;0.7;0.8;0.9;1.0;1.1"
fill="freeze">
</animateTransform>
</line>
<circle cx="500" cy="500" r="50" stroke-width="15" fill="red" stroke="#e70d0d">
<animateTransform attributeName="transform" attributeType="XML" type="rotate" dur="3s" values="0;30;10;30;20;30;25;30" keyTimes="0;0.3;0.5;0.7;0.8;0.9;1.0;1.1" fill="freeze">
</animateTransform>
<animateTransform attributeName="transform" attributeType="XML" type="scale" dur="6s" values="1;1;1.3" keyTimes="0;0.5;1" fill="freeze"></animateTransform>
<animateTransform attributeName="transform" attributeType="XML" type="translate" dur="9s" values="0;0;300 7" keyTimes="0;0.6;0.9" fill="freeze"></animateTransform>
</circle>
<rect width="500" height="200" x="90" y="840">
<animateTransform attributeName="transform" attributeType="XML" type="skewY" dur="6s" values="0;0;30" keyTimes="0;0.5;1" fill="freeze"></animateTransform>
</rect>
<line x1="650" y1="300" x2="650" y2="600" stroke-width="20" stroke="blue" stroke-linecap="round">
<animateTransform attributeName="transform" attributeType="XML" type="translate" dur="9s" values="0;0;0 800" keyTimes="0;0.6;1" fill="freeze"></animateTransform>
</line>
</svg>
</div>
```
```
/* xxx.css */
.container {
flex-direction: column;
align-items: center;
width: 100%;
height: 100%;
background-color: #F1F3F5;
}
```
![en-us_image_0000001182832088.gif](figures/en-us_image_0000001182832088.gif)
\ No newline at end of file
# Defining Animations with the transform Attribute
Set the transform attribute for component rotation, scaling, translation, and skewing.
## Designing Static Animation
Create a square and rotate it by 90 degrees to form a rhombus. Cover the lower part of the rhombus with a rectangle to form a roof. Set the translate attribute of the rectangle to the coordinate (150px, -150px) to form a door, use the position attribute to translate the horizontal and vertical axes to the specified coordinates of the parent component (square), set the scale attribute to scale up the parent and child components together to determine the window size, and use the skewX attribute to skew the component and set the coordinate translate(200px,-830px) to form a chimney.
```
<!-- xxx.hml -->
<div class="container">
<div class="top"></div>
<div class="content"></div>
<div class="door"></div>
<!-- Window -->
<div class="window">
<div class="horizontal"></div>
<div class="vertical"></div>
</div>
<div class="chimney"></div>
</div>
```
```
/* xxx.css */
.container {
background-color:#F1F3F5;
align-items: center;
flex-direction: column;
}
.top{
z-index: -1;
position: absolute;
width: 428px;
height: 428px;
background-color: #860303;
transform: rotate(45deg);
margin-top: 230px;
margin-left: 266px;
}
.content{
margin-top: 500px;
width: 600px;
height: 400px;
background-color: white;
border: 1px solid black;
}
.door{
width: 100px;
height: 150px;
background-color: #1033d9;
transform: translate(150px,-150px);
}
.window{
z-index: 1;
position: relative;
width: 100px;
height: 100px;
background-color: white;
border: 1px solid black;
transform: translate(-150px,-400px) scale(1.5);
}
/* Horizontal axis of the window */
.horizontal{
position: absolute;
top: 50%;
width: 100px;
height: 5px;
background-color: black;
}
/* Vertical axis of the window */
.vertical{
position: absolute;
left: 50%;
width: 5px;
height: 100px;
background-color: black;
}
.chimney{
z-index: -2;
width: 40px;
height: 100px;
border-radius: 15px;
background-color: #9a7404;
transform: translate(200px,-830px) skewX(-5deg);
}
```
![en-us_image_0000001267887841](figures/en-us_image_0000001267887841.png)
## Designing Translation Animation
Decrease the y-coordinate over a time frame to make the ball bounce back. Gradually decrease the bounce height until it drops to 0. An animation where the ball falls is hereby created.
```
<!-- xxx.hml -->
<div class="container">
<div class="circle"></div>
<div class="flower"></div>
</div>
```
```
/* xxx.css */
.container {
background-color:#F1F3F5;
display: flex;
justify-content: center;
}
.circle{
width: 100px;
height: 100px;
border-radius: 50px;
background-color: red;
/* Use forwards to enable the animation stop at the last frame. */
animation: down 3s fast-out-linear-in forwards;
}
.flower{
position: fixed;
width: 80%;
margin-left: 10%;
height: 5px;
background-color: black;
top: 1000px;
}
@keyframes down {
0%{
transform: translate(0px,0px);
}
/* Start ball falling. */
15%{
transform: translate(10px,900px);
}
/* Start bouncing back. */
25%{
transform: translate(20px,500px);
}
/* Let the ball fall. */
35%{
transform: translate(30px,900px);
}
/* Let the ball bounce back. */
45%{
transform: translate(40px,700px);
}
55%{
transform: translate(50px,900px);
}
65%{
transform: translate(60px,800px);
}
80%{
transform: translate(70px,900px);
}
90%{
transform: translate(80px,850px);
}
/* Stop falling. */
100%{
transform: translate(90px,900px);
}
}
```
![en-us_image_0000001222967760](figures/en-us_image_0000001222967760.gif)
## Designing Rotation Animation
Set the rotation center around an element in different transform-origin positions. Of the rotate3d values, the first three values are the rotation vectors of the x-axis, y-axis, and z-axis, respectively; the fourth value is the rotation angle, which can be a negative value to indicate that the rotation is performed counterclockwise.
```
<!-- xxx.hml -->
<div class="container">
<div class="rotate">
<div class="rect rect1"></div>
<div class="rect rect2"></div>
<div class="rect rect3"></div>
</div>
<!-- 3D attributes -->
<div class="rotate3d">
<div class="content">
<div class="rect4"></div>
<div class="rect5"> </div>
</div>
<div class="mouse"></div>
</div>
</div>
```
```
/* xxx.css */
.container {
flex-direction: column;
background-color:#F1F3F5;
display: flex;
align-items: center;
justify-content: center;
}
.rect{
width: 100px;
height: 100px;
animation: rotate 3s infinite;
margin-left: 100px;
}
.rect1{
background-color: #f76160;
}
.rect2{
background-color: #60f76f;
/* Change the origin position.*/
transform-origin: 10% 10px;
}
.rect3{
background-color: #6081f7;
/* Change the origin position.*/
transform-origin: right bottom;
}
@keyframes rotate {
from {
transform: rotate(0deg)
}
to {
transform: rotate(360deg);
}
}
/* 3D sample style */
.rotate3d{
margin-top: 150px;
flex-direction: column;
background-color:#F1F3F5;
display: flex;
align-items: center;
width: 80%;
height: 600px;
border-radius: 300px;
border: 1px solid #ec0808;
}
.content{
padding-top: 150px;
display: flex;
align-items: center;
justify-content: center;
}
/* Use react4 and react5 to shape eyes. */
.rect4{
width: 100px;
height: 100px;
animation: rotate3d1 17ms infinite;
background: linear-gradient(#e6c4ec, #be15d9)
}
.rect5{
width: 100px;
height: 100px;
animation: rotate3d1 17ms infinite;
margin-left: 100px;
background: linear-gradient(#e6c4ec, #be15d9)
}
.mouse{
margin-top: 150px;
width: 200px;
height: 100px;
border-radius: 50px;
border: 1px solid #e70303;
animation: rotate3d2 17ms infinite;
}
/* Eye animation */
@keyframes rotate3d1{
0% {
transform:rotate3d(0,0,0,0deg)
}
50% {
transform:rotate3d(20,20,20,360deg);
}
100% {
transform:rotate3d(0,0,0,0deg);
}
}
/* Mouth animation */
@keyframes rotate3d2{
0% {
transform:rotate3d(0,0,0,0deg)
}
33% {
transform:rotate3d(0,0,10,30deg);
}
66% {
transform:rotate3d(0,0,10,-30deg);
}
100% {
transform:rotate3d(0,0,0,0deg);
}
}
```
![en-us_image_0000001222807776](figures/en-us_image_0000001222807776.gif)
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:
> transform-origin specifies the origin of an element's transformation. If only one value is set, the other value is 50%. If both values are set, the first value indicates the position on the x-axis, and the second value indicates the position on the y-axis.
## Designing Scaling Animation
This example implements a ripple animation with the scale attribute. Here is the overall procedure: First, use the positioning function to determine the coordinates of the element's position. Then, create multiple components to achieve the overlapping effect. After that, set the opacity attribute to hide or display the components. To scale and hide/display a component at the same time, set both the scale and opacity attributes. Finally, set different animation durations for different components to achieve the diffusion effect.
Set the scaling values for the x-axis, y-axis, and z-axis in scale3d to implement the animation.
```
<!-- xxx.hml -->
<div class="container">
<div class="circle">
<text>ripple</text>
</div>
<div class="ripple"></div>
<div class="ripple ripple2"></div>
<!-- 3d -->
<div class="content">
<text>spring</text>
</div>
</div>
```
```
/* xxx.css */
.container {
flex-direction: column;
background-color:#F1F3F5;
width: 100%;
position: relative;
}
.circle{
margin-top: 400px;
margin-left: 40%;
width: 100px;
height: 100px;
border-radius: 50px;
background:linear-gradient(#dcaec1, #d3a8e3);
z-index: 1; position: absolute;
}
.ripple{
margin-top: 400px;
margin-left: 40%;
position: absolute; z-index: 0;
width: 100px;
height: 100px;
border-radius: 50px;
background:linear-gradient(#dcaec1,#d3a8e3);
animation: ripple 5s infinite;
}
/* Set different animation durations for different components. */
.ripple2{
animation-duration: 2.5s;
}
@keyframes ripple{
0%{
transform: scale(1);
opacity: 0.5;
}
50%{
transform: scale(3);
opacity: 0;
}
100%{
transform: scale(1);
opacity: 0.5;
}
}
text{
color: white;
text-align: center;
height: 100%;
width: 100%;
}
.content {
margin-top: 700px;
margin-left: 33%;
width: 200px;
height: 100px;
animation:rubberBand 1s infinite;
/* Set the gradient.*/
background:linear-gradient(#e276aa,#ec0d66);
position: absolute;
}
@keyframes rubberBand {
0% {
transform: scale3d(1, 1, 1);
}
30% {
transform: scale3d(1.25, 0.75, 1.1);
}
40% {
transform: scale3d(0.75, 1.25, 1.2);
}
50% {
transform: scale3d(1.15, 0.85, 1.3);
}
65% {
transform: scale3d(.95, 1.05, 1.2);
}
75% {
transform: scale3d(1.05, .95, 1.1);
}
100%{
transform: scale3d(1, 1, 1);
}
}
```
![en-us_image_0000001267887837](figures/en-us_image_0000001267887837.gif)
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:
> After the transform attributes are set, the child element changes with the parent element. Value changing of other attributes (such as height and width) of the parent element will not affect the child element.
## Setting matrix
The matrix attribute defines a transformation matrix with six input parameters: scaleX, skewY, skewX, scaleY, translateX, and translateY. In the following example, matrix is set to matrix(1,0,0,1,0,200) to skew and translate the component.
```
<!-- xxx.hml -->
<div class="container">
<div class="rect"> </div>
</div>
```
```
/* xxx.css */
.container{
background-color:#F1F3F5;
display: flex;
justify-content: center;
}
.rect{
width: 100px;
height: 100px;
background-color: red;
animation: down 3s infinite forwards;
}
@keyframes down{
0%{
transform: matrix(1,0,0,1,0,0);
}
10%{
transform: matrix(1,0,0,1,0,200);
}
60%{
transform: matrix(2,1.5,1.5,2,0,700);
}
100%{
transform: matrix(1,0,0,1,0,0);
}
}
```
![en-us_image_0000001267767853](figures/en-us_image_0000001267767853.gif)
## Integrating transform Attributes
You can set multiple transform attributes at the same time to apply different transformations to a component. The following example applies the scale, translate, and rotate attributes simultaneously.
```
<!-- xxx.hml -->
<div class="container">
<div class="rect1"></div>
<div class="rect2"></div>
<div class="rect3"></div>
<div class="rect4"></div>
<div class="rect5"></div>
</div>
```
```
/* xxx.css */
.container{
flex-direction:column;
background-color:#F1F3F5;
padding:50px;
}
.rect1{
width: 100px;
height: 100px;
background:linear-gradient(#e77070,#ee0202);
animation: change1 3s infinite forwards;
}
.rect2{
margin-top: 50px;
width: 100px;
height: 100px;
background:linear-gradient(#95a6e8, #2739de);
animation: change2 3s infinite forwards;
}
.rect3{
margin-top: 50px;
width: 100px;
height: 100px;
background:linear-gradient(#142ee2, #8cb1e5);
animation: change3 3s infinite;
}
.rect4{
align-self: center;
margin-left: 50px;
margin-top: 200px;
width: 100px;
height: 100px;
background:linear-gradient(#e2a8df, #9c67d4,#8245d9,#e251c3);
animation: change4 3s infinite;
}
.rect5{
margin-top: 300px;
width: 100px;
height: 100px;
background:linear-gradient(#e7ded7, #486ccd, #94b4d2);
animation: change5 3s infinite;
}
/* Use change1 and change2 for comparison. */
@keyframes change1{
0%{
transform: translate(0,0); transform: rotate(0deg)
}
100%{
transform: translate(0,500px);
transform: rotate(360deg)
}
}
/*change2 and change3 compare the animation effects with different attribute sequences.*/
@keyframes change2{
0%{
transform:translate(0,0) rotate(0deg) ;
}
100%{
transform: translate(300px,0) rotate(360deg);
}
}
@keyframes change3{
0%{
transform:rotate(0deg) translate(0,0);
}
100%{
transform:rotate(360deg) translate(300px,0);
}
}
/* Where the attribute values do not match. */
@keyframes change4{
0%{
transform: scale(0.5);
}
100%{
transform:scale(2) rotate(45deg);
}
}
/* Multi-attribute format */
@keyframes change5{
0%{
transform:scale(0) translate(0,0) rotate(0);
}
100%{
transform: scale(1.5) rotate(360deg) translate(200px,0);
}
}
```
![en-us_image_0000001223127712](figures/en-us_image_0000001223127712.gif)
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:
> 1. When multiple transform attributes are set, the later one overwrites the previous one. To apply multiple transform styles at the same time, use the shorthand notation; that is, write multiple style values in one transform, for example, transform: scale(1) rotate(0) translate(0,0).
>
> 2. When using the shorthand notion, **NOTE** that the animation effect varies according to the sequence of the style values.
>
> 3. The style values in the transform attribute used when the animation starts and ends must be in one-to-one mapping. Only the styles that have value mappings are played.
# Developing Animations
Animations are classified into [Static Animation](#static-animation) and [Continuous Animation](#continuous-animation).
## Static Animation
The transform attributes are the core of the static animation. A static animation can transform in the following three ways and only once in each way at a time:
- translate: Moves a specified component horizontally or vertically.
- scale: Scales a specified component horizontally or vertically to the required scale.
- rotate: Rotates a specified component by a specified angle along the horizontal axis, vertical axis, or center point.
For more information, see [Component Methods](../reference/arkui-js/js-components-common-methods.md). The following is an example:
```
<!-- xxx.hml -->
<div class="container">
<text class="translate">hello</text>
<text class="rotate">hello</text>
<text class="scale">hello</text>
</div>
```
```
/* xxx.css */
.container {
flex-direction: column;
align-items: center;
}
.translate {
height: 150px;
width: 300px;
font-size: 50px;
background-color: #008000;
transform: translate(200px);
}
.rotate {
height: 150px;
width: 300px;
font-size: 50px;
background-color: #008000;
transform-origin: 200px 100px;
transform: rotateX(45deg);
}
.scale {
height: 150px;
width: 300px;
font-size: 50px;
background-color: #008000;
transform: scaleX(1.5);
}
```
figure1 Static animation
![en-us_image_0000001223127724](figures/en-us_image_0000001223127724.png)
## Continuous Animation
The static animation has only the start and end states. To set the transition state and conversion effect, use continuous animations.
The core of a continuous animation is animation attributes, which define the start and end states of the animation and the curve of time and speed. Animation attributes can implement the following effects:
- animation-name: Background color, opacity, width, height, and transformation type applied to the element after the animation is executed
- animation-delay and animation-duration: Element delay and duration after the animation is executed
- animation-timing-function: Speed curve of an animation, which makes the animation more fluent
- animation-iteration-count: Number of animation playback times
- animation-fill-mode: Whether to restore the initial state after the animation is executed
To use the animation attributes, you need to define a @keyframes rule in the .css file, set the animation transition effect in @keyframes, and invoke the effect through a style class in the .hml file. The following is an example for animation-name:
```
<!-- xxx.hml -->
<div class="item-container">
<text class="header">animation-name</text>
<div class="item {{colorParam}}">
<text class="txt">color</text>
</div>
<div class="item {{opacityParam}}">
<text class="txt">opacity</text>
</div>
<input class="button" type="button" name="" value="show" onclick="showAnimation"/>
</div>
```
```
/* xxx.css */
.item-container {
margin-right: 60px;
margin-left: 60px;
flex-direction: column;
}
.header {
margin-bottom: 20px;
}
.item {
background-color: #f76160;
}
.txt {
text-align: center;
width: 200px;
height: 100px;
}
.button {
width: 200px;
font-size: 30px;
background-color: #09ba07;
}
.color {
animation-name: Color;
animation-duration: 8000ms;
}
.opacity {
animation-name: Opacity;
animation-duration: 8000ms;
}
@keyframes Color {
from {
background-color: #f76160;
}
to {
background-color: #09ba07;
}
}
@keyframes Opacity {
from {
opacity: 0.9;
}
to {
opacity: 0.1;
}
}
```
```
// xxx.js
export default {
data: {
colorParam: '',
opacityParam: '',
},
showAnimation: function () {
this.colorParam = '';
this.opacityParam = '';
this.colorParam = 'color';
this.opacityParam = 'opacity';
},
}
```
figure2 Continuous animation effect
![en-us_image_0000001223287696](figures/en-us_image_0000001223287696.gif)
# Component Overview
Components are the core of a UI page. Each component can provide visible and interactive functional units that are independent from each other. This is achieved by data and method encapsulation. You can use and reuse any component anywhere as needed. For details about how to use the components, see [Universal Attributes](../reference/arkui-js/js-components-common-attributes.md).
You can also customize a new component through proper combination of components to make the development simple and easy. For details about how to customize components, see [Custom Components](ui-js-custom-components.md).
## Classification
Components can be classified into the following types based on their functions.
| Type | Components |
| -------- | -------- |
| Container | badge, dialog, div, form, list, list-item, list-item-group, panel, popup, refresh, stack, stepper, stepper-item, swiper, tabs, tab-bar, tab-content |
| Basic | button, chart, divider, image, image-animator, input, label, marquee, menu, option, picker, picker-view, piece, progress, qrcode, rating, richtext, search, select, slider, span, switch, text, textarea, toolbar, toolbar-item, toggle |
| Media | video |
| Canvas | canvas |
| Grid | grid-container, grid-row, grid-col |
| SVG | svg, rect, circle, ellipse, path, line, polyline, polygon, text, tspan, textPath, animate, animateMotion, animateTransform |
# Defining Events
Events mainly include gesture events for touchscreen devices.
## Gesture Events
A gesture represents a semantic action (for example, tap, drag, or longpress) that can trigger one or more events. A gesture lifecycle may consist of multiple events from the start to the end of the gesture. Supported events:
Touch
- touchstart: Triggered when the touch starts
- touchmove: Triggered when the touch moves
- touchcancel: Triggered when the touch is interrupted, for example, by an incoming call notification or pop-up message
- touchend: Triggered when the touch ends
Click
click: Triggered when a user taps the screen quickly.
Longpress
longpress: Triggered when a user keeps tapping the screen at the same position for a while.
The following is an example:
```
<!-- xxx.hml -->
<div class="container">
<div class="text-container" onclick="click">
<text class="text-style">{{onClick}}</text>
</div>
<div class="text-container" ontouchstart="touchStart">
<text class="text-style">{{touchstart}}</text>
</div>
<div class="text-container" ontouchmove="touchMove">
<text class="text-style">{{touchmove}}</text>
</div>
<div class="text-container" ontouchend="touchEnd">
<text class="text-style">{{touchend}}</text>
</div>
<div class="text-container" ontouchcancel="touchCancel">
<text class="text-style">{{touchcancel}}</text>
</div>
<div class="text-container" onlongpress="longPress">
<text class="text-style">{{onLongPress}}</text>
</div>
</div>
```
```
/* xxx.css */
.container {
flex-direction: column;
justify-content: center;
align-items: center;
}
.text-container {
margin-top: 10px;
flex-direction: column;
width: 750px;
height: 50px;
background-color: #09ba07;
}
.text-style {
width: 100%;
line-height: 50px;
text-align: center;
font-size: 24px;
color: #ffffff;
}
```
```
// xxx.js
export default {
data: {
touchstart: 'touchstart',
touchmove: 'touchmove',
touchend: 'touchend',
touchcancel: 'touchcancel',
onClick: 'onclick',
onLongPress: 'onlongpress',
},
touchCancel: function (event) {
this.touchcancel = 'canceled';
},
touchEnd: function(event) {
this.touchend = 'ended';
},
touchMove: function(event) {
this.touchmove = 'moved';
},
touchStart: function(event) {
this.touchstart = 'touched';
},
longPress: function() {
this.onLongPress = 'longpressed';
},
click: function() {
this.onClick = 'clicked';
},
}
```
# Adding Interactions
You can make the UI interactive by binding events to components. This section describes how to bind &lt;div&gt;, &lt;text&gt;, and &lt;image&gt; components to click events to build a thumb up button, as shown in the following figure.
figure1 Thumb up button effect
![en-us_image_0000001267647901](figures/en-us_image_0000001267647901.gif)
The thumb up button is implemented by binding a click event to a &lt;div&gt; component. The &lt;div&gt; component contains an &lt;image&gt; component and a &lt;text&gt; component.
- The &lt;image&gt; component is used to display unselected and selected (highlighted) thumbs up images. The click event function alternately updates the paths of the images that are liked and not liked.
- The &lt;text&gt; component is used to display the number of thumbs up. The number is updated in the function of the click event.
The click event calls the likeClick() function defined in the .js file. You can change the value of isPressed to update the image component. If the value of isPressed is true, the number of thumbs up is incremented by 1. The likeClick() function takes effect on the &lt;div&gt; component in the .hml file. The style of each child component for the thumbs up button is set in the .css file. The following is an example:
```
<!-- xxx.hml -->
<!-- Thumb up button -->
<div>
<div class="like" onclick="likeClick">
<image class="like-img" src="{{likeImage}}" focusable="true"></image>
<text class="like-num" focusable="true">{{total}}</text>
</div>
</div>
```
```
/* xxx.css */
.like {
width: 104px;
height: 54px;
border: 2px solid #bcbcbc;
justify-content: space-between;
align-items: center;
margin-left: 72px;
border-radius: 8px;
}
.like-img {
width: 33px;
height: 33px;
margin-left: 14px;
}
.like-num {
color: #bcbcbc;
font-size: 20px;
margin-right: 17px;
}
```
```
// xxx.js
export default {
data: {
likeImage: '/common/unLike.png',
isPressed: false,
total: 20,
},
likeClick() {
var temp;
if (!this.isPressed) {
temp = this.total + 1;
this.likeImage = '/common/like.png';
} else {
temp = this.total - 1;
this.likeImage = '/common/unLike.png';
}
this.total = temp;
this.isPressed = !this.isPressed;
},
}
```
ArkUI also provides many form components, such as switches, tags, and pickers, for you to flexibly lay out pages and improve their interactions with users. For details, see Container Components.
# Adding a Comment
After a user enters a comment and clicks the submit button, the content is displayed in the comment area. The user can click the delete button to delete the current comment and enter another comment again.
To set such a comment area on a page, you need to associate a click event with &lt;div&gt;, &lt;text&gt;, and &lt;input&gt;. You can use the &lt;input&gt; component to obtain the comment entered by a user, use the &lt;text&gt; component to display the comment, and use commentText to mark the &lt;text&gt; component (controlled by the if attribute). Associate the click event with the &lt;text&gt; component that contains Done and Delete to update the commentText and inputValue. The following is an example:
```
<!-- xxx.hml -->
<div class="container">
<text class="comment-title">Comment</text>
<div if="{{!commentText}}">
<input class="comment" value="{{inputValue}}" onchange="updateValue()"></input>
<text class="comment-key" onclick="update" focusable="true">Done</text>
</div>
<div if="{{commentText}}">
<text class="comment-text" focusable="true">{{inputValue}}</text>
<text class="comment-key" onclick="update" focusable="true">Delete</text>
</div>
</div>
```
```
/* xxx.css */
.container {
margin-top: 24px;
background-color: #ffffff;
}
.comment-title {
font-size: 40px;
color: #1a1a1a;
font-weight: bold;
margin-top: 40px;
margin-bottom: 10px;
}
.comment {
width: 550px;
height: 100px;
background-color: lightgrey;
}
.comment-key {
width: 150px;
height: 100px;
margin-left: 20px;
font-size: 32px;
color: #1a1a1a;
font-weight: bold;
}
.comment-key:focus {
color: #007dff;
}
.comment-text {
width: 550px;
height: 100px;
text-align: left;
line-height: 35px;
font-size: 30px;
color: #000000;
border-bottom-color: #bcbcbc;
border-bottom-width: 0.5px;
}
```
```
// xxx.js
export default {
data: {
inputValue: '',
commentText: false,
},
update() {
this.commentText = !this.commentText;
},
updateValue(e) {
this.inputValue = e.text;
},
}
```
# Adding a Container
To assemble the basic elements of a page, you need a container component. The &lt;div&gt;, &lt;list&gt;, and &lt;tabs&gt; components are commonly used for laying out page elements. You can use &lt;div&gt; as the container in a page with simple layout. &lt;div&gt; supports a variety of child components required to build the page.
## &lt;List&gt;
If you use &lt;div&gt; repeatedly to render a complex page, frame freezing may occur. In this case, use the &lt;list&gt; component instead of &lt;div&gt; to lay out list items, which provides a smooth list scrolling. **NOTE** that &lt;list&gt; supports only &lt;list-item&gt; as it child components. The following is an example:
```
<!-- xxx.hml -->
<list class="list">
<list-item type="listItem" for="{{textList}}">
<text class="desc-text">{{$item.value}}</text>
</list-item>
</list>
```
```
/* xxx.css */
.desc-text {
width: 683.3px;
font-size: 35.4px;
}
```
```
// xxx.js
export default {
data: {
textList: [{value: 'JS FA'}],
},
}
```
To shorten the sample code, the list contains only one &lt;list-item&gt; component that holds only one &lt;text&gt; component. In practice, a &lt;list&gt; has multiple &lt;list-item&gt; components, and a &lt;list-item&gt; has multiple child components.
## &lt;Tabs&gt;
If your page needs to be dynamically loaded, use the &lt;tabs&gt; component. This component supports the change event, which is triggered after tab switching. A &lt;tabs&gt; component can hold only one &lt;tab-bar&gt; and one &lt;tab-content&gt;. The following is an example:
```
<!-- xxx.hml -->
<tabs>
<tab-bar>
<text>Home</text>
<text>Index</text>
<text>Detail</text>
</tab-bar>
<tab-content>
<image src="{{homeImage}}"></image>
<image src="{{indexImage}}"></image>
<image src="{{detailImage}}"></image>
</tab-content>
</tabs>
```
```
// xxx.js
export default {
data: {
homeImage: '/common/home.png',
indexImage: '/common/index.png',
detailImage: '/common/detail.png',
},
}
```
The &lt;tab-content&gt; component is used to display the tab content, which vertically fills the remaining space of the &lt;tabs&gt; component by default.
# Adding an Image
Generally, the [&lt;image&gt;](../reference/arkui-js/js-components-basic-image.md)component is used to add images on a page. The method of using this component is similar to that of using the &lt;text&gt; component.
To reference images via the &lt;image&gt; component, you must create the common directory under jsdefault, and then store the image files in the common directory. For details about the directory structure, see [Directory Structure](js-framework-file.md). The following sample code shows you how to add an image and set its style.
```
<!-- xxx.hml -->
<image class="img" src="{{middleImage}}"></image>
```
```
/* xxx.css */
.img {
margin-top: 30px;
margin-bottom: 30px;
height: 385px;
}
```
```
// xxx.js
export default {
data: {
middleImage: '/common/ice.png',
},
}
```
# Layout Description
The baseline width for page design is 720 logical pixels. The display width of a page element depends on the ratio of the screen width to the baseline width.
For example, when the width of a component is 100 px, its display width is converted as follows:
On a screen with the width of 720 physical pixels, the display width is 100 physical pixels. On a screen with the width of 1440 physical pixels, the display width is 200 physical pixels.
Basic page elements include title, text, and image areas. Each basic element may contain multiple sub-elements. You can add components, such as buttons, switches, and progress bars, to these elements and sub-elements as required. When setting the layout, you need to consider the following for each basic element:
- Size and arrangement
- Overlapping with other elements
- Alignment, padding, and margin
- Sub-elements and their positions
- Container components and their types
You can disassemble elements on the page first and then implement them in sequence. This reduces visual confusion and logical conflicts caused by element nesting and improves code readability for easier modification. For example, as shown below, you disassemble the page elements and elements in the comment area.
figure1 Page layout
![en-us_image_0000001222967792](figures/en-us_image_0000001222967792.png)
figure2 Layout of the comment area
![en-us_image_0000001267767889](figures/en-us_image_0000001267767889.png)
# Adding Title and Paragraph Text
The &lt;text&gt; component is most commonly used to display text in title and paragraph areas. You can set attributes and styles for a &lt;text&gt; component and add the text to be displayed between the &lt;text&gt; and &lt;/text&gt; tags. For details about the attributes and styles, see [text](../reference/arkui-js/js-components-basic-text.md). The following is an example of adding title and paragraph text on a page:
```
<!-- xxx.hml -->
<div class="container">
<text class="title-text">{{headTitle}}</text>
<text class="paragraph-text">{{paragraphFirst}}</text>
<text class="paragraph-text">{{paragraphSecond}}</text>
</div>
```
```
/* xxx.css */
.container {
flex-direction: column;
margin-top: 20px;
margin-left: 30px;
}
.title-text {
color: #1a1a1a;
font-size: 50px;
margin-top: 40px;
margin-bottom: 20px;
}
.paragraph-text {
color: #000000;
font-size: 35px;
line-height: 60px;
}
```
```
// xxx.js
export default {
data: {
headTitle: 'Capture the Beauty in This Moment',
paragraphFirst: 'Capture the beauty of light during the transition and fusion of ice and water. At the instant of movement and stillness, softness and rigidity, force and beauty, condensing moving moments.',
paragraphSecond: 'Reflecting the purity of nature, the innovative design upgrades your visual entertainment and ergonomic comfort. Effortlessly capture what you see and let it speak for what you feel.',
},
}
```
# Defining Page Routes
An application generally consist of more than one page. For example, in a music application, a user taps a song on a music list page to jump to the playback page of the song. You need to link these pages through the page router to implement redirection as required.
The page router finds the target page based on the page URI. The following describes how to implement redirection between two pages:
1. In the “Project“ window of DevEco Studio, choose entry > src > mainjsdefault. Right-click the pages folder and choose NewJS Page from the shortcut menu to create the detail page.
2. Call router.push() to navigate users to the detail page.
3. Call router.back() to navigate users to the index page.
## Building the Page Layout
The index and detail pages each contains a &lt;text> component that specifies the current page, and a &lt;button&gt; component that implements the switching between two pages. Example code in .hml files is as follows:
```
<!-- index.hml -->
<div class="container">
<text class="title">This is the index page.</text>
<button type="capsule" value="Go to the second page" class="button" onclick="launch"></button>
</div>
```
```
<!-- detail.hml -->
<div class="container">
<text class="title">This is the detail page.</text>
<button type="capsule" value="Go back" class="button" onclick="launch"></button>
</div>
```
## Setting Page Styles
Set styles for the index and detail pages. Center the &lt;text&gt; and &lt;button&gt; components and space the two components with 50 pixels. The CSS code for the two pages is as follows:
```
/* index.css */
/* detail.css */
.container {
flex-direction: column;
justify-content: center;
align-items: center;
}
.title {
font-size: 50px;
margin-bottom: 50px;
}
```
## Implementing Redirection
To make the launch method of the &lt;button&gt; component take effect, the redirection logic needs to be implemented in the .js file of the page. Call router.push() to add the page URI to the route stack, that is, to jump to the page specified by the URI. You need to import the router module before calling the router method. The sample code is as follows:
```
// index.js
import router from '@system.router';
export default {
launch() {
router.push ({
uri: 'pages/detail/detail',
});
},
}
```
```
// detail.js
import router from '@system.router';
export default {
launch() {
router.back();
},
}
```
The figure below shows the effect.
figure1 Page routing
![en-us_image_0000001222967784](figures/en-us_image_0000001222967784.png)
# &lt;tabs&gt; Development
The &lt;tabs&gt; component is a common UI component for navigation. It allows quick access to different functions of an app. For details, see [tabs](../reference/arkui-js/js-components-container-tabs.md).
## Creating Tabs
Create a &lt;tabs&gt; component in the .hml file under pages/index.
```
<!-- index.hml -->
<div class="container" >
<tabs>
<tab-bar>
<text>item1</text>
<text>item2</text>
</tab-bar>
<tab-content>
<div class="text">
<text>content1</text>
</div>
<div class="text">
<text>content2</text>
</div>
</tab-content>
</tabs>
</div>
```
```
/* xxx.css */
.container {
flex-direction: column;
justify-content: center;
align-items: center;
background-color: #F1F3F5;
}
.text{
width: 100%;
height: 100%;
justify-content: center;
align-items: center;
}
```
![en-us_image_0000001223287676](figures/en-us_image_0000001223287676.gif)
## Setting the Tabs Orientation
By default, the active tab of a &lt;tabs&gt; component is the one with the specified index. To show the &lt;tabs&gt; vertically, set the vertical attribute to true.
```
<!-- index.hml -->
<div class="container" style="background-color:#F1F3F5;">
<tabs index="1" vertical="true">
<tab-bar >
<text>item1</text>
<text style="margin-top: 50px;">item2</text>
</tab-bar>
<tab-content>
<div>
<image src="common/images/bg-tv.jpg" style="object-fit: contain;"> </image>
</div>
<div>
<image src="common/images/img1.jpg" style="object-fit: contain;"> </image>
</div>
</tab-content>
</tabs>
</div>
```
![en-us_image_0000001222967756](figures/en-us_image_0000001222967756.gif)
Set the mode attribute to enable the child components of the <tab-bar> to be evenly distributed. Set the scrollable attribute to disable scrolling of the <tab-content>.
```
<!-- index.hml -->
<div class="container" style="background-color:#F1F3F5;">
<tabs style="margin-top: 30px;">
<tab-bar mode="fixed">
<text>item1</text>
<text>item2</text>
</tab-bar>
<tab-content scrollable="false">
<div>
<image src="common/images/bg-tv.jpg" style="object-fit: contain;"> </image>
</div>
<div>
<image src="common/images/img2.jpg" style="object-fit: contain;"> </image>
</div>
</tab-content>
</tabs>
</div>
```
![en-us_image_0000001267647857](figures/en-us_image_0000001267647857.gif)
## Setting the Style
Set the background color, border, and tab-content layout of the &lt;tabs&gt; component.
```
<!-- index.hml -->
<div class="container">
<tabs class="tabs">
<tab-bar class="tabBar">
<text class="tabBarItem">item1</text>
<text class="tabBarItem">item2</text>
</tab-bar>
<tab-content class="tabContent">
<div class="tabContent">
<text>content1</text>
</div>
<div class="tabContent" >
<text>content2</text>
</div>
</tab-content>
</tabs>
</div>
```
```
/* xxx.css */
.container {
flex-direction: column;
justify-content: flex-start;
align-items: center;
background-color:#F1F3F5;
}
.tabs{
margin-top: 20px;
border: 1px solid #2262ef;
width: 99%;
padding: 10px;
}
.tabBar{
width: 100%;
border: 1px solid #78abec;
}
.tabContent{
width: 100%;
margin-top: 10px;
height: 300px;
color: blue;
justify-content: center; align-items: center;
}
```
![en-us_image_0000001267767857](figures/en-us_image_0000001267767857.gif)
## Displaying the Tab Index
Add the change event for the &lt;tabs&gt; component to display the index of the current tab after tab switching.
```
<!-- index.hml -->
<div class="container" style="background-color:#F1F3F5;">
<tabs class="tabs" onchange="tabChange">
<tab-bar class="tabBar">
<text class="tabBarItem">item1</text>
<text class="tabBarItem">item2</text>
</tab-bar>
<tab-content class="tabContent">
<div>
<image src="common/images/bg-tv.jpg" style="object-fit: contain;"> </image>
</div>
<div>
<image src="common/images/img1.jpg" style="object-fit: contain;"> </image>
</div>
</tab-content>
</tabs>
</div>
```
```
/* index.js */
import prompt from '@system.prompt';
export default {
tabChange(e){
prompt.showToast({
message: "Tab index: " + e.index
})
}
}
```
![en-us_image_0000001222807772](figures/en-us_image_0000001222807772.gif)
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:
> - A &lt;tabs&gt; can wrap at most one [<tab-bar>](../reference/arkui-js/js-components-container-tab-bar.md) and at most one [<tab-content>](../reference/arkui-js/js-components-container-tab-content.md).
## Example Scenario
In this example, you can switch between tabs and the active tab has the title text in red with an underline below.
Use the &lt;tabs&gt;, <tab-bar>, and <tab-content> components to implement tab switching. Then define the arrays and attributes. Add the change event to change the attribute values in the arrays so that the active tab has a different font color and an underline.
```
<!-- index.hml -->
<div class="container">
<tabs onchange="changeTabactive">
<tab-content>
<div class="item-container" for="datas.list">
<div if="{{$item.title=='List1'?true:false}}">
<image src="common/images/bg-tv.jpg" style="object-fit: contain;"> </image>
</div>
<div if="{{$item.title=='List2'?true:false}}">
<image src="common/images/img1.jpg" style="object-fit: none;"> </image>
</div>
<div if="{{$item.title=='List3'?true:false}}">
<image src="common/images/img2.jpg" style="object-fit: contain;"> </image>
</div>
</div>
</tab-content>
<tab-bar class="tab_bar mytabs" mode="scrollable">
<div class="tab_item" for="datas.list">
<text style="color: {{$item.color}};">{{$item.title}}</text>
<div class="underline-show" if="{{$item.show}}"></div>
<div class="underline-hide" if="{{!$item.show}}"></div>
</div>
</tab-bar>
</tabs>
</div>
```
```
/* xxx.css */
.container{
background-color:#F1F3F5;
}
.tab_bar {
width: 100%;
}
.tab_item {
flex-direction: column;
align-items: center;
}
.tab_item text {
font-size: 32px;
}
.item-container {
justify-content: center;
flex-direction: column;
}
.underline-show {
height: 2px;
width: 160px;
background-color: #FF4500;
margin-top: 7.5px;
}
.underline-hide {
height: 2px;
margin-top: 7.5px;
width: 160px;
}
```
```
/* index.js */
import prompt from '@system.prompt';
export default {
data() {
return {
datas: {
color_normal: '#878787',
color_active: '#ff4500',
show: true,
list: [{
i: 0,
color: '#ff4500',
show: true,
title: 'List1'
}, {
i: 1,
color: '#878787',
show: false,
title: 'List2'
}, {
i: 2,
color: '#878787',
show: false,
title: 'List3'
}]
}
}
},
changeTabactive (e) {
for (let i = 0; i < this.datas.list.length; i++) {
let element = this.datas.list[i];
element.show = false;
element.color = this.datas.color_normal;
if (i === e.index) {
element.show = true;
element.color = this.datas.color_active;
}
}
}
}
```
![en-us_image_0000001267607885](figures/en-us_image_0000001267607885.gif)
# &lt;button&gt; Development
The&lt;button&gt;component can be used to set a capsule, circle, text, arc, or download button. For details, see [button](../reference/arkui-js/js-components-basic-button.md).
## Creating a &lt;button&gt; Component
Create a &lt;button&gt; component in the .hml file under pages/index.
```
<!-- xxx.hml -->
<div class="container">
<button type="capsule" value="Capsule button"></button>
</div>
```
```
/* xxx.css */
.container {
flex-direction: column;
justify-content: center;
align-items: center;
background-color: #F1F3F5;
}
```
![en-us_image_0000001267887821](figures/en-us_image_0000001267887821.png)
## Setting the Button Type
Set the type attribute of the &lt;input&gt; component to button, date, or any of the supported values.
```
<!-- xxx.hml -->
<div class="container">
<button class="circle" type="circle" >+</button>
<button class="text" type="text"> button</button>
</div>
```
```
/* xxx.css */
.container {
background-color: #F1F3F5;
flex-direction: column;
align-items: center;
justify-content: center;
}
.circle {
font-size: 120px;
background-color: blue;
radius: 72px;
}
.text {
margin-top: 30px;
text-color: white;
font-size: 30px;
font-style: normal;
background-color: blue;
width: 50%;
height: 100px;
}
```
![en-us_image_0000001222967744](figures/en-us_image_0000001222967744.png)
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:
> - For capsule buttons, border-related styles are not supported.
>
> - For circle buttons, text-related styles are not supported.
>
> - For text buttons, the text size is adaptive, and radius, width, and height cannot be set. The background-color style is not supported when the background is completely transparent.
>
> - If the icon used by the&lt;button&gt;component is from the cloud, you must declare the ohos.permission.INTERNET permission in the config.json file under the resources folder.
Sample code for declaring the ohos.permission.INTERNET permission in the config.json file under the resources folder:
```
<!-- config.json -->
"module": {
"reqPermissions": [{
"name": "ohos.permission.INTERNET"
}],
}
```
## Showing the Download Progress
Add the progress method to the&lt;button&gt;component to display the download progress in real time.
```
<!-- xxx.hml -->
<div class="container">
<button class="button download" type="download" id="download-btn" onclick="setProgress">{{downloadText}}</button>
</div>
```
```
/* xxx.css */
.container {
background-color: #F1F3F5;
flex-direction: column;
align-items: center;
justify-content: center;
}
.download {
width: 280px;
text-color: white;
background-color: #007dff;
}
```
```
// xxx.js
import prompt from '@system.prompt';
export default {
data: {
percent: 0,
downloadText: "Download",
isPaused: true,
intervalId : null,
},
star(){
this.intervalId = setInterval(()=>{
if(this.percent <100){
this.percent += 1;
this.downloadText = this.percent+ "%";
} else{
prompt.showToast({
message: "Download succeeded."
})
this.paused()
this.downloadText = "Download";
this.percent = 0;
this.isPaused = true;
}
},100)
},
paused(){
clearInterval(this.intervalId);
this.intervalId = null;
},
setProgress(e) {
if(this.isPaused){
prompt.showToast({
message: "Download started"
})
this.star();
this.isPaused = false;
}else{
prompt.showToast({
message: "Paused."
})
this.paused();
this.isPaused = true;
}
}
}
```
![en-us_image_0000001223287652](figures/en-us_image_0000001223287652.gif)
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:
>
> The setProgress method supports only buttons of the download type.
## Example Scenario
Switch between the button types for different types of text.
```
<!-- xxx.hml -->
<div class="container">
<div class="input-item">
<input class="input-text" id="change" type="{{mytype}}" placeholder="{{myholder}}"
style="background-color:{{mystyle1}};
placeholder-color:{{mystyle2}};flex-grow:{{myflex}};"name="{{myname}}" value="{{myvalue}}"></input>
</div>
<div class="input-item">
<div class="doc-row">
<input type="button" class="select-button color-3" value="text" onclick="changetype3"></input>
<input type="button" class="select-button color-3" value="data" onclick="changetype4"></input>
</div>
</div>
</div>
```
```
/* xxx.css */
.container {
flex-direction: column;
align-items: center;
background-color: #F1F3F5;
}
.input-item {
margin-bottom: 80px;
flex-direction: column;
}
.doc-row {
justify-content: center;
margin-left: 30px;
margin-right: 30px;
justify-content: space-around;
}
.input-text {
height: 80px;
line-height: 80px;
padding-left: 30px;
padding-right: 30px;
margin-left: 30px;
margin-right: 30px;
margin-top:100px;
border: 3px solid;
border-color: #999999;
font-size: 30px;
background-color: #ffffff;
font-weight: 400;
}
.select-button {
width: 35%;
text-align: center;
height: 70px;
padding-top: 10px;
padding-bottom: 10px;
margin-top: 30px;
font-size: 30px;
color: #ffffff;
}
.color-3 {
background-color: #0598db;;
}
```
```
// xxx.js
export default {
data: {
myflex: '',
myholder: 'Enter text.',
myname: '',
mystyle1: "#ffffff",
mystyle2: "#ff0000",
mytype: 'text',
myvalue: '',
},
onInit() {
},
changetype3() {
this.myflex = '';
this.myholder = 'Enter text.';
this.myname = '';
this.mystyle1 = "#ffffff";
this.mystyle2 = "#FF0000";
this.mytype = 'text';
this.myvalue = '';
},
changetype4() {
this.myflex = '';
this.myholder = 'Enter a date.';
this.myname = '';
this.mystyle1 = "#ffffff";
this.mystyle2 = "#FF0000";
this.mytype = 'date';
this.myvalue = '';
},
}
```
![en-us_image_0000001222967740](figures/en-us_image_0000001222967740.gif)
# &lt;dialog&gt; Development
The &lt;dialog&gt; component is custom pop-up container for showing critical information or calling for an action. For details, see [dialog](../reference/arkui-js/js-components-container-dialog.md).
## Creating a &lt;dialog&gt; Component
Create a &lt;dialog&gt; component in the .hml file under pages/index and add &lt;button&gt; components to trigger the &lt;dialog&gt;. The &lt;dialog&gt; component supports only the width, height, margin, margin-[left|top|right|bottom], and margin-[start|end] styles.
```
<!-- xxx.hml -->
<div class="doc-page">
<dialog class="dialogClass" id="dialogId"><div class="content">
<text>this is a dialog</text>
</div>
</dialog>
<button value="click me" onclick="openDialog"></button>
</div>
```
```
/* xxx.css */
.doc-page {
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #F1F3F5;
}
.dialogClass{
width: 80%;
height: 250px;
margin-start: 1%;
}
.content{
width: 100%;
height: 250px;
justify-content: center;
background-color: #e8ebec;
border-radius: 20px;
}
text{
width: 100%;
height: 100%;
text-align: center;
}
button{
width: 70%;
height: 60px;
}
```
```
/* xxx.js */
export default {
//Touch to open the dialog box.
openDialog(){
this.$element('dialogId').show()
},
}
```
![en-us_image_0000001267767893](figures/en-us_image_0000001267767893.gif)
## Setting Dialog Box Response
Add a cancel event that is triggered when a user touches a non-dialog area to cancel the pop-up dialog box. Add the show and close methods to display and close the dialog box, respectively.
```
<!-- xxx.hml -->
<div class="doc-page">
<dialog class="dialogClass" id="dialogId">
<div class="dialogDiv">
<text>dialog</text>
<button value="confirm" onclick="confirmClick"></button>
</div>
</dialog>
<button value="click me" onclick="openDialog"></button>
</div>
```
```
/* xxx.css */
.doc-page {
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #F1F3F5;
}
.dialogClass{
width: 80%;
height: 300px;
margin-start: 1%;
}
.dialogDiv{
width: 100%;
flex-direction: column;
justify-content: center;
align-self: center;
}
text{
height: 100px;
align-self: center;
}
button{
align-self: center;
margin-top: 20px;
width: 60%;
height: 80px;
}
```
```
/* xxx.js */
import prompt from '@system.prompt';
export default {
openDialog(){
this.$element('dialogId').show()
},
confirmClick(e) {
this.$element('dialogId').close()
prompt.showToast({
message: 'Confirmed.'
})
},
}
```
![en-us_image_0000001223287720](figures/en-us_image_0000001223287720.gif)
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:
> - This component supports only one child component.
>
> - Attributes and styles of a &lt;dialog&gt; component cannot be dynamically updated.
>
> - The &lt;dialog&gt; component does not support the focusable and click-effect attributes.
## Example Scenario
Use the &lt;dialog&gt; component to implement a schedule. When the dialog box is open, use the [&lt;textarea&gt;](../reference/arkui-js/js-components-basic-textarea.md) component to add an event and touch the OK button to obtain the current time and save the input text. The events in the calendar are displayed in a list.
```
<!-- xxx.hml -->
<div class="doc-page">
<text style="margin-top: 60px;margin-left: 30px;">
<span>{{date}} events</span>
</text>
<div class="btndiv">
<button type="circle" class="btn" onclick="addschedule">+</button>
</div>
<!-- for Render events data -->
<list style="width: 100%;">
<list-item type="item" for="schedulelist" style="width:100%;height: 200px;">
<div class="schedulediv">
<text class="text1">{{date}} event</text>
<text class="text2">{{$item.schedule}}</text>
</div>
</list-item>
</list>
<dialog id="datedialog" oncancel="canceldialog" >
<div class="dialogdiv">
<div class="innertxt">
<text class="text3">{{date}}</text>
<text class="text4">New event</text>
</div>
<textarea placeholder="Event information" onchange="getschedule" class="area" extend="true"></textarea>
<div class="innerbtn">
<button type="text" value="Cancel" onclick="cancelschedule" class="btntxt"></button>
<button type="text" value="OK" onclick="setschedule" class="btntxt"></button>
</div>
</div>
</dialog>
</div>
```
```
/* xxx.css */
.doc-page {
flex-direction: column;
background-color: #F1F3F5;
}
.btndiv {
width: 100%;
height: 200px;
flex-direction: column;
align-items: center;
justify-content: center;
}
.btn {
radius:60px;
font-size: 100px;
background-color: #1E90FF;
}
.schedulediv {
width: 100%;
height: 200px;
flex-direction: column;
justify-content: space-around;
padding-left: 55px;
}
.text1 {
color: #000000;
font-weight: bold;
font-size: 39px;
}
.text2 {
color: #a9a9a9;
font-size: 30px;
}
.dialogdiv {
flex-direction: column;
align-items: center;
}
.innertxt {
width: 320px;
height: 160px;
flex-direction: column;
align-items: center;
justify-content: space-around;
}
.text3 {
font-family: serif;
color: #1E90FF;
font-size: 38px;
}
.text4 {
color: #a9a9a9;
font-size: 33px;
}
.area {
width: 320px;
border-bottom: 1px solid #1E90FF;
}
.innerbtn {
width: 320px;
height: 120px;
justify-content: space-around;
}
.btntxt {
text-color: #1E90FF;
}
```
```
/* xxx.js */
var info = null;
import prompt from '@system.prompt';
import router from '@system.router';
export default {
data: {
curYear:'',
curMonth:'',
curDay:'',
date:'',
schedule:'',
schedulelist:[]
},
onInit() {
// Obtain the current date.
var date = new Date();
this.curYear = date.getFullYear();
this.curMonth = date.getMonth() + 1;
this.curDay = date.getDate();
this.date = this.curYear + '-' + this.curMonth + '-' + this.curDay;
this.schedulelist = []
},
addschedule(e) {
this.$element('datedialog').show()
},
canceldialog(e) {
prompt.showToast({
message: 'Event setting canceled.'
})
},
getschedule(e) {
info = e.value
},
cancelschedule(e) {
this.$element('datedialog').close()
prompt.showToast({
message: 'Event setting canceled.'
})
},
// Touch OK to save the data.
setschedule(e) {
if (e.text === '') {
this.schedule = info
} else {
this.schedule = info
var addItem = {schedule: this.schedule,}
this.schedulelist.push(addItem)
}
this.$element('datedialog').close()
}
}
```
![en-us_image_0000001223127756](figures/en-us_image_0000001223127756.gif)
# &lt;form&gt; Development
The &lt;form&gt; component allows the content in [&lt;input&gt;](../reference/arkui-js/js-components-basic-input.md)components to be submitted and reset. For details, see [form](../reference/arkui-js/js-components-container-form.md).
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:
> This component is supported since API version 6.
## Creating a &lt;form&gt; Component
Create a &lt;form&gt; component in the .hml file under pages/index.
```
<!-- xxx.hml -->
<div class="container">
<form> <input type="text" style="width:80%"></input>
</form>
</div>
```
```
/* xxx.css */
.container {
flex-direction: column;
justify-content: center;
align-items: center;
background-color: #F1F3F5;
}
```
![en-us_image_0000001267887873](figures/en-us_image_0000001267887873.png)
## Zooming In or Out on a Form
To implement the zoom effect after a form is clicked, add the click-effect attribute to the &lt;form&gt; component. For values of click-effect, see [Universal Attributes](../reference/arkui-js/js-components-common-attributes.md).
```
<!-- xxx.hml -->
<div class="container">
<form id="formId" class="formClass" click-effect="spring-large">
<input type="text"></input>
</form>
</div>
```
## Setting the Form Style
Add the background-color and border attributes.
```
/* xxx.css */
.container {
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #F1F3F5;
}
.formClass{
width: 80%;
padding: 10px;
border: 1px solid #c3d3e7;
}
```
![en-us_image_0000001267607913](figures/en-us_image_0000001267607913.gif)
## Adding Response Events
To submit or reset a form, add the submit and reset events.
```
<!-- xxx.hml -->
<div class="container" style="background-color:#F1F3F5;">
<form onsubmit='onSubmit' onreset='onReset' style="justify-content: center; align-items: center;text-align: center;">
<div style="flex-direction: column;justify-content: center;align-self: center;">
<div style="justify-content: center; align-items: center;">
<label>Option 1</label>
<input type='radio' name='radioGroup' value='radio1'></input>
<label>Option 2</label>
<input type='radio' name='radioGroup' value='radio2'></input>
</div>
<div style="margin-top: 30px;justify-content: center; align-items: center;">
<input type="submit" value="Submit" style="width:100px; margin-right:20px;" ></input>
<input type="reset" value="Reset" style="width:100px;"></input>
</div>
</div>
</form>
</div>
```
```
/* xxx.js */
import prompt from '@system.prompt';
export default{
onSubmit(result) {
prompt.showToast({
message: result.value.radioGroup
})
},
onReset() {
prompt.showToast({
message: 'Reset All'
})
}
}
```
![en-us_image_0000001267767885](figures/en-us_image_0000001267767885.gif)
## Example Scenario
Select an option and submit or reset the form data.
Create [&lt;input&gt;](../reference/arkui-js/js-components-basic-input.md) (en-us_topic_0000001173324647.xml) components, set their type attribute to checkbox and radio, and use the onsubmit and onreset events of the &lt;form&gt; component to submit and reset the form data.
```
<!-- xxx.hml -->
<div class="container">
<form onsubmit="formSubmit" onreset="formReset">
<text style="font-size: 30px; margin-bottom: 20px; margin-top: 100px;">
<span > Form </span>
</text>
<div style="flex-direction: column;width: 90%;padding: 30px 0px;">
<text class="txt">Select 1 or more options</text>
<div style="width: 90%;height: 150px;align-items: center;justify-content: space-around;">
<label target="checkbox1">Option 1</label>
<input id="checkbox1" type="checkbox" name="checkbox1"></input>
<label target="checkbox2">Option 2</label>
<input id="checkbox2" type="checkbox" name="checkbox2"></input>
</div>
<divider style="margin: 20px 0px;color: pink;height: 5px;"></divider>
<text class="txt">Select 1 option</text>
<div style="width: 90%;height: 150px;align-items: center;justify-content: space-around;">
<label target="radio1">Option 1</label>
<input id="radio1" type="radio" name="myradio"></input>
<label target="radio2">Option 2</label>
<input id="radio2" type="radio" name="myradio"></input>
</div>
<divider style="margin: 20px 0px;color: pink;height: 5px;"></divider>
<text class="txt">Text box</text>
<input type="text" placeholder="Enter content." style="margin-top: 50px;"></input>
<div style="width: 90%;align-items: center;justify-content: space-between;margin: 40px;">
<input type="submit">Submit</input>
<input type="reset">Reset</input>
</div>
</div>
</form>
</div>
```
```
/* index.css */
.container {
flex-direction:column;
align-items:center;
background-color:#F1F3F5;
}
.txt {
font-size:33px;
font-weight:bold;
color:darkgray;
}
label{
font-size: 20px;
}
```
```
/* xxx.js */
import prompt from '@system.prompt';
export default {
formSubmit() {
prompt.showToast({
message: 'Submitted.'
})
},
formReset() {
prompt.showToast({
message: 'Reset.'
})
}
}
```
![en-us_image_0000001222967788](figures/en-us_image_0000001222967788.gif)
# &lt;image&gt; Development
The &lt;image&gt; component is used to render and display images. For details, see [image](../reference/arkui-js/js-components-basic-image.md).
## Creating an &lt;image&gt; Component
Create an &lt;image&gt; component in the .hml file under pages/index.
```
<!-- index.hml -->
<div class="container">
<image src="common/images/bg-tv.jpg"> </image>
</div>
```
```
/* xxx.css */
.container {
flex-direction: column;
justify-content: center;
align-items: center;
background-color: #F1F3F5;
}
```
![en-us_image_0000001223127736](figures/en-us_image_0000001223127736.png)
## Setting the Image Style
Set the width, height, and object-fit attributes to define the width, height, and scale type of an image.
```
<!-- index.hml -->
<div class="container">
<image src="common/images/bg-tv.jpg"> </image>
</div>
```
```
/* xxx.css */
.container {
flex-direction: column;
align-items: center;
justify-content: center;
background-color:#F1F3F5;
}
image{
width: 80%; height: 500px;
border: 5px solid saddlebrown;
border-radius: 20px;
object-fit: contain;
match-text-direction:true;
}
```
![en-us_image_0000001222807796](figures/en-us_image_0000001222807796.png)
## Display Multiple Images
Define a for loop to display multiple images cyclically. Set option to specify the image scale style. For details about the scale styles, see the description of object-fit.
```
<!-- index.hml -->
<div class="page-container">
<list>
<list-item class="item-container" for="{{list}}">
<image class="testimage" src="{{url[$idx]}}" style="object-fit: {{fit}};"></image>
<div class="text-container">
<text style="font-size: 32px;color:#7b68ee;">image{{$idx}}</text>
<text style="font-size: 23px;color: orange;font-style: italic;">content</text>
</div>
</list-item>
</list>
<div style="width: 100%;height:100px;justify-content: center;margin-top: 100px;flex-shrink: 0;">
<select id="slt2" style="border: 3px solid orange;color: orange;font-size: 40px;width:300px;height:160px;" onchange="setfit">
<option for="{{fit_list}}" value="{{$item}}" style="font-size: 36px;">{{$item}}</option>
</select>
</div>
</div>
```
```
/* xxx.css */
.page-container {
flex-direction:column;
background-color:#F1F3F5;
}
.text-container {
width: 300px;
flex-direction: column;
justify-content: center;
}
.item-container {
flex-direction: row;
align-items: center;
justify-content:center;
margin-top:200px;
}
.testimage {
width: 175px;
height: 220px;
border: 5px solid #add8e6;
padding: 5px 5px 5px 5px;
margin: 5px 5px 5px 5px;
}
.testicon {
width: 50px;
height: 50px;
margin-left: 150px;
border-radius: 25px;
background-color: orange;
}
```
```
/* index.js */
export default {
data: {
url:['common/images/bg-tv.jpg','common/images/img2.jpg'],
list:[0,1],
fit:'cover',
fit_list:['cover','contain','fill','none','scale-down']
},
setfit(e) {
this.fit = e.newValue
}
}
```
![en-us_image_0000001267767873](figures/en-us_image_0000001267767873.gif)
## Loading Images
When an image is successfully loaded, the complete event is triggered, and the loaded image is returned. If an exception occurs during image loading, the error event is triggered, and the image loading failure is printed.
```
<!-- index.hml -->
<div class="container" >
<div>
<image src="common/images/bg-tv.jpg" oncomplete="imageComplete(1)" onerror="imageError(1)"> </image>
</div>
<div>
<image src="common/images/bg-tv1.jpg" oncomplete="imageComplete(2)" onerror="imageError(2)"> </image>
</div>
</div>
```
```
/* xxx.css */
.container{
flex-direction: column;
justify-content: center;
align-self: center;
background-color: #F1F3F5;
}
.container div{
margin-left: 10%;
width: 80%;
height: 300px;
margin-bottom: 40px;
}
```
```
/* index.js */
import prompt from '@system.prompt';
export default {
imageComplete(i,e){
prompt.showToast({
message: "Image "+i+"'s width"+ e.width+"----Image "+i+"'s height"+e.height,
duration: 3000,
})
},
imageError(i,e){
setTimeout(()=>{
prompt.showToast({
message: "Failed to load image "+i+".",
duration: 3000,
})
},3000)
}
}
```
![en-us_image_0000001267887865](figures/en-us_image_0000001267887865.gif)
## Example Scenario
In this example you touch and hold an image to gradually hide it. After the image is completely hidden, it will show in its original setting. Set a setInterval timer to change the image opacity at a specified interval so that it is hidden gradually. When the opacity changes to 0, the timer is cleared and the opacity is set to 1.
```
<!-- index.hml -->
<div class="page-container">
<div class="content">
<div class="image-container">
<image class="testimage" src="{{testuri}}" style="display:{{displaytype}};opacity:{{imageopacity}};" onclick="changedisplaytype" onlongpress="changeopacity"> </image>
</div>
<div class="text-container">
<text style="font-size: 37px;font-weight:bold;color:orange;text-align: center;width: 100%;">Touch and hold the image</text>
</div>
</div>
</div>
```
```
/* xxx.css */
.page-container {
flex-direction:column;
align-self: center;
justify-content: center;
background-color:#F1F3F5;
background-color: #F1F3F5;
}
.content{
flex-direction:column;
}
.image-container {
width: 100%;
height: 300px;
align-items: center;
justify-content: center;
}
.text-container {
margin-top:50px;
width: 100%;
height: 60px;
flex-direction: row;
justify-content: space-between;
}
.testimage {
width: 100%; height: 400px; object-fit: scale-down; border-radius: 20px;}
```
```
/* index.js */
import prompt from '@system.prompt';
export default {
data: {
testuri: 'common/images/bg-tv.jpg',
imageopacity:1,
timer: null
},
changeopacity: function () {
prompt.showToast({
message: 'Touch and hold the image.'
})
var opval = this.imageopacity * 20
clearInterval(this.timer);
this.timer = setInterval(()=>{
opval--;
this.imageopacity = opval / 20
if (opval===0) {
clearInterval(this.timer)
this.imageopacity = 1
}
},100);
}
}
```
![en-us_image_0000001267607905](figures/en-us_image_0000001267607905.gif)
# <input> Development
The <input> component provides an interactive way to receive user input of various types, including date, checkbox, and button. For details, see [input](../reference/arkui-js/js-components-basic-input.md).
## Creating an <input> Component
Create an <input> component in the .hml file under pages/index.
```
<!-- xxx.hml -->
<div class="container">
<input type="text"> Please enter the content </input>
</div>
```
```
/* xxx.css */
.container {
flex-direction: column;
justify-content: center;
align-items: center;
background-color: #F1F3F5;
}
```
![en-us_image_0000001222807768](figures/en-us_image_0000001222807768.png)
## Setting the Input Type
Set the type attribute of the <input> component to button, date, or any of the supported values.
```
<!-- xxx.hml -->
<div class="container">
<div class="div-button">
<dialog class="dialogClass" id="dialogId">
<div class="content">
<text>this is a dialog</text>
</div>
</dialog>
<input class="button" type="button" value="click" onclick="btnclick"></input>
</div>
<div class="content">
<input onchange="checkboxOnChange" checked="true" type="checkbox"></input>
</div>
<div class="content">
<input type="date" class="flex" placeholder="Enter data"></input>
</div>
</div>
```
```
/* xxx.css */
.container {
align-items: center;
flex-direction: column;
justify-content: center;
background-color: #F1F3F5 ;
}
.div-button {
flex-direction: column;
align-items: center;
}
.dialogClass{
width:80%;
height: 200px;
}
.button {
margin-top: 30px;
width: 50%;
}
.content{
width: 90%;
height: 150px;
align-items: center;
justify-content: center;
}
.flex {
width: 80%;
margin-bottom:40px;
}
```
```
// xxx.js
export default {
btnclick(){
this.$element('dialogId').show()
},
}
```
![en-us_image_0000001223287672](figures/en-us_image_0000001223287672.gif)
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:
> - For wearables, the input type can only be button, radio, or checkbox.
>
> - The settings of checked take effect only when the input type is set to checkbox or radio. The default value of checked is false.
## Event Binding
Add the search and translate events to the <input> component.
```
<!-- xxx.hml -->
<div class="content">
<text style="margin-left: -7px;">
<span>Enter text and then touch and hold what you've entered</span>
</text>
<input class="input" type="text" onsearch="search" placeholder="search"> </input>
<input class="input" type="text" ontranslate="translate" placeholder="translate"> </input>
</div>
```
```
/* xxx.css */
.content {
width: 100%;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #F1F3F5;
}
.input {
margin-top: 50px;
width: 60%;
placeholder-color: gray;
}
text{
width:100%;
font-size:25px;
text-align:center;
}
```
```
// xxx.js
import prompt from '@system.prompt'
export default {
search(e){
prompt.showToast({
message: e.value,
duration: 3000,
});
},
translate(e){
prompt.showToast({
message: e.value,
duration: 3000,
});
}
}
```
![en-us_image_0000001267647853](figures/en-us_image_0000001267647853.gif)
## Setting the Input Error Message
Add the showError method to the <input> component to display an error message in the event of incorrect input.
```
<!-- xxx.hml -->
<div class="content">
<input id="input" class="input" type="text" maxlength="20" placeholder="Please input text" onchange="change">
</input>
<input class="button" type="button" value="Submit" onclick="buttonClick"></input>
</div>
```
```
/* xxx.css */
.content {
width: 100%;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #F1F3F5;
}
.input {
width: 80%;
placeholder-color: gray;
}
.button {
width: 30%;
margin-top: 50px;
}
```
```
// xxx.js
import prompt from '@system.prompt'
export default {
data:{
value:'',
},
change(e){
this.value = e.value;
prompt.showToast({
message: "value: " + this.value,
duration: 3000,
});
},
buttonClick(e){
if(this.value.length > 6){
this.$element("input").showError({ error: 'Up to 6 characters are allowed.' });
}else if(this.value.length == 0){
this.$element("input").showError({ error:this.value + 'This field cannot be left empty.' });
}else{
prompt.showToast({
message: "success "
});
}
},
}
```
![en-us_image_0000001223127708](figures/en-us_image_0000001223127708.gif)
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:
> - This method is available when the input type is set to text, email, date, time, number, or password.
## Example Scenario
Enter information by using the <input> component of the type that suits your needs.
```
<!-- xxx.hml -->
<div class="container">
<div class="label-item">
<label>memorandum</label>
</div>
<div class="label-item">
<label class="lab" target="input1">content:</label>
<input class="flex" id="input1" placeholder="Enter content" />
</div>
<div class="label-item">
<label class="lab" target="input3">date:</label>
<input class="flex" id="input3" type="date" placeholder="Enter data" />
</div>
<div class="label-item">
<label class="lab" target="input4">time:</label>
<input class="flex" id="input4" type="time" placeholder="Enter time" />
</div>
<div class="label-item">
<label class="lab" target="checkbox1">Complete:</label>
<input class="flex" type="checkbox" id="checkbox1" style="width: 100px;height: 100px;" />
</div>
<div class="label-item">
<input class="flex" type="button" id="button" value="save" onclick="btnclick"/>
</div>
</div>
```
```
/* xxx.css */
.container {
flex-direction: column;
background-color: #F1F3F5;
}
.label-item {
align-items: center;
border-bottom-width: 1px;border-color: #dddddd;
}
.lab {
width: 400px;}
label {
padding: 30px;
font-size: 30px;
width: 320px;
font-family: serif;
color: #9370d8;
font-weight: bold;
}
.flex {
flex: 1;
}
.textareaPadding {
padding-left: 100px;
}
```
```
// xxx.js
import prompt from '@system.prompt';
export default {
data: {
},
onInit() {
},
btnclick(e) {
prompt.showToast({
message:'Saved successfully!'
})
}
}
```
![en-us_image_0000001222807760](figures/en-us_image_0000001222807760.gif)
# &lt;list&gt; Development
The &lt;list&gt; component provides a list container that presents a series of list items arranged in a column with the same width. Lists can be used for presenting the same type of data in a multiple and coherent row style. For details, see [list](../reference/arkui-js/js-components-container-list.md).
## Creating a &lt;list&gt; Component
Create a &lt;list&gt; component in the .hml file under pages/index.
```
<!-- index.hml -->
<div class="container">
<list> <list-item class="listItem"></list-item>
<list-item class="listItem"></list-item>
<list-item class="listItem"></list-item>
<list-item class="listItem"></list-item>
</list>
</div>
```
```
/* xxx.css */
.container {
flex-direction: column;
align-items: center;
background-color: #F1F3F5;
}
.listItem{
height: 20%;
background-color:#d2e0e0;
margin-top: 20px;
}
```
![en-us_image_0000001223287680](figures/en-us_image_0000001223287680.png)
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:
> - &lt;list-item-group&gt; is a child component of the &lt;list&gt; component and is used to group items in a list. It can have a &lt;list-item&gt; nested inside, but not &lt;list&gt;.
>
> - &lt;list-item&gt; is a child component of the &lt;list&gt; component and is used to display items in a list.
## Adding a Scrollbar
To display a scrollbar on the right side of the screen, set scrollbar to on. The side scrollbar can be used to scroll a long list or the screen up or down.
```
<!-- index.hml -->
<div class="container">
<list class="listCss" scrollbar="on" >
<list-item class="listItem"></list-item>
<list-item class="listItem"></list-item>
<list-item class="listItem"></list-item>
<list-item class="listItem"></list-item>
<list-item class="listItem"></list-item>
<list-item class="listItem"></list-item>
</list>
</div>
```
```
/* index.css */
.container {
flex-direction: column;
background-color: #F1F3F5;
}
.listItem{
height: 20%;
background-color:#d2e0e0;
margin-top: 20px;
}
.listCss{
height: 100%;
scrollbar-color: #8e8b8b;
scrollbar-width: 50px;
}
```
![en-us_image_0000001223287684](figures/en-us_image_0000001223287684.gif)
## Adding a Side Index Bar
Set a custom indexer component to add an index bar at the right boundary of a list. By default, an alphabetical indexer is used.
```
<!-- index.hml -->
<div class="container">
<list class="listCss" indexer="{{['#','1','2','3','4','5','6','7','8']}}" >
<list-item class="listItem" section="#" ></list-item>
</list>
</div>
```
```
/* index.css */
.container{
flex-direction: column;
background-color: #F1F3F5;
}
.listCss{
height: 100%;
flex-direction: column;
columns: 1
}
```
![en-us_image_0000001223127716](figures/en-us_image_0000001223127716.png)
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:
> - This indexer attribute is valid only when flex-direction is set to column and columns is set to 1.
>
> - You must include "\#" when using a customized indexer.
## Collapsing or Expanding a List
To allow a &lt;list&gt; component to collapse and expand, add groupcollapse and groupexpand events.
```
<!-- index.hml -->
<div class="doc-page">
<list style="width: 100%;" id="mylist">
<list-item-group for="listgroup in list" id="{{listgroup.value}}" ongroupcollapse="collapse" ongroupexpand="expand">
<list-item type="item" style="background-color:#FFF0F5;height:95px;">
<div class="item-group-child">
<text>One---{{listgroup.value}}</text>
</div>
</list-item>
<list-item type="item" style="background-color: #87CEFA;height:145px;" primary="true">
<div class="item-group-child">
<text>Primary---{{listgroup.value}}</text>
</div>
</list-item>
</list-item-group>
</list>
</div>
```
```
/* index.css */
.doc-page {
flex-direction: column;
background-color: #F1F3F5;
}
list-item{
margin-top:30px;
}
.top-list-item {
width:100%;
background-color:#D4F2E7;
}
.item-group-child {
justify-content: center;
align-items: center;
width:100%;
}
```
```
// xxx.js
import prompt from '@system.prompt';
export default {
data: {
direction: 'column',
list: []
},
onInit() {
this.list = []
this.listAdd = []
for (var i = 1; i <= 2; i++) {
var dataItem = {
value: 'GROUP' + i,
};
this.list.push(dataItem);
}
},
collapse(e) {
prompt.showToast({
message: 'Close ' + e.groupid
})
},
expand(e) {
prompt.showToast({
message: 'Open ' + e.groupid
})
}
}
```
![en-us_image_0000001267887845](figures/en-us_image_0000001267887845.gif)
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:
> - The groupcollapse and groupexpand events can be used only by the list-item-group component.
## Example Scenario
Search for contacts by using an alphabetical indexer.
```
<!-- index.hml -->
<div class="doc-page">
<text style="font-size: 35px; font-weight: 500; text-align: center; margin-top: 20px; margin-bottom: 20px;">
<span>Contacts</span>
</text>
<list class="list" indexer="true">
<list-item class="item" for="{{namelist}}" type="{{$item.section}}" section="{{$item.section}}">
<div class="container">
<div class="in-container">
<text class="name">{{$item.name}}</text>
<text class="phone">18888888888</text>
</div>
</div>
</list-item>
<list-item type="end" class="item">
<div style="align-items:center;justify-content:center;width:750px;">
<text style="text-align: center;">Total: 10</text>
</div>
</list-item>
</list>
</div>
```
```
/* index.css */
.doc-page {
flex-direction: column;
background-color: #F1F3F5;
}
.list {
width: 100%;
height: 100%;
}
.item {
height: 120px;
padding-left: 10%;
border-top: 1px solid #dcdcdc;
}
.name {
color: #000000;
font-size: 39px;
}
.phone {
color: black;
font-size: 25px;
}
.container {
flex-direction: row;
align-items: center;
}
.in-container {
flex-direction: column;
justify-content: space-around;
}
```
```
// xxx.js
export default {
data: {
namelist:[{
name: 'Zoey',
section:'Z'
},{
name: 'Quin',
section:'Q'
},{
name:'Sam',
section:'S'
},{
name:'Leo',
section:'L'
},{
name:'Zach',
section:'Z'
},{
name:'Wade',
section:'W'
},{
name:'Zoe',
section:'Z'
},{
name:'Warren',
section:'W'
},{
name:'Kyle',
section:'K'
},{
name:'Zaneta',
section:'Z'
}]
},
onInit() {
}
}
```
![en-us_image_0000001267767861](figures/en-us_image_0000001267767861.gif)
# &lt;picker&gt; Development
The &lt;picker&gt; component supports common, date, time, data and time, and multi-column text selectors. For details, see [picker](../reference/arkui-js/js-components-basic-picker.md).
## Creating a &lt;picker&gt; Component
Create a &lt;picker&gt; component in the .hml file under pages/index.
```
<!-- index.hml -->
<div class="container">
<picker> picker </picker>
<div>
```
```
/* index.css */
.container {
flex-direction: column;
justify-content: center;
align-items: center;
background-color: #F1F3F5;
}
```
![en-us_image_0000001223287716](figures/en-us_image_0000001223287716.gif)
## Setting the Picker Type
Set the type attribute of the &lt;picker&gt; component. For example, set it to date.
```
<!-- index.hml -->
<div class="container">
<picker id="picker_text" type="text" value="{{textvalue}}"range="{{rangetext}}" class="pickertext" ></picker>
<picker id="picker_date" type="date" value="{{datevalue}}" lunarswitch="true" start="2002-2-5" end="2030-6-5" class="pickerdate"></picker>
</div>
```
```
/* index.css */
.container {
flex-direction: column;
justify-content: center;
align-items: center;
background-color: #F1F3F5;
}
.pickertext{
margin-bottom: 30px;
}
```
```
// xxx.js
export default {
data: {
rangetext:['15', "20", "25"],
textvalue:'Select text',
datevalue:'Select date',
}
}
```
![en-us_image_0000001267647893](figures/en-us_image_0000001267647893.gif)
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:
> - When setting the value range of a common selector, you must use the data binding mode.
>
> - The lunarswitch attribute of the date selector is only supported on phones and tablets.
## Setting the Time Format
Set the hours attribute to specify the time format used by the time selector. Available values include 12 and 24, indicating the 12-hour format and 24-hour format, respectively.
```
<!-- index.hml -->
<div class="container">
<picker id="picker_time" type="time" value="12-hour format" hours="12" onchange="timeonchange" class="pickertime"></picker>
<picker id="picker_time" type="time" value="24-hour format" hours="24" onchange="timeonchange" class="pickertime"></picker>
</div>
```
```
/* index.css */
.container {
flex-direction: column;
justify-content: center;
align-items: center;
background-color: #F1F3F5;
}
.pickertime {
margin-bottom:50px;
width: 300px;
height: 50px;
}
```
![en-us_image_0000001222807808](figures/en-us_image_0000001222807808.gif)
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:
> - When hours is set to 12, the time is displayed in 12-hour format and distinguished by a.m. and p.m.
>
> - When hours is set to 24, the time is displayed in 24-hour format.
## Adding Response Events
To confirm and cancel selection, add change and cancel events.
```
<!-- index.hml -->
<div class="container">
<picker id="picker_multi" type="multi-text" value="{{multitextvalue}}" columns="3" range="{{multitext}}" selected="
{{multitextselect}}" onchange="multitextonchange" oncancel="multitextoncancel" class="pickermuitl"></picker>
</div>
```
```
/* index.css */
.container {
flex-direction: column;
justify-content: center;
align-items: center;
background-color: #F1F3F5;
}
.pickermuitl {
margin-bottom:20px;
width: 600px;
height: 50px;
font-size: 25px;
letter-spacing:15px;
}
```
```
// xxx.js
import prompt from '@system.prompt';
export default {
data: {
multitext:[["a", "b", "c"], ["e", "f", "g"], ["h", "i"]],
multitextvalue:'Select multi-line text',
multitextselect:[0,0,0],
},
multitextonchange(e) {
this.multitextvalue=e.newValue;
prompt.showToast({ message:"Multi-column text changed to:" + e.newValue })
},
multitextoncancel() {
prompt.showToast({ message:"multitextoncancel" })
},
}
```
![en-us_image_0000001223127748](figures/en-us_image_0000001223127748.gif)
## Example Scenario
Implement a health check-in application by using the &lt;picker&gt; component.
```
<!-- index.hml -->
<div class="doc-page">
<text class="title">Health check-in</text>
<div class="out-container">
<text class="txt">Office:</text>
<picker class="pick" focusable="true" type="text" value="{{pos}}" range="{{posarr}}" onchange="setPos"></picker>
</div>
<divider class="dvd"></divider>
<div class="out-container">
<text class="txt">Office hours:</text>
<picker class="pick" type="date" value="{{datevalue}}" start="2002-2-5" end="2030-6-5" selected="{{dateselect}}"
lunarswitch="true" onchange="dateonchange"></picker>
</div>
<divider class="dvd"></divider>
<div class="out-container">
<text class="txt">Having fever or cold symptoms</text>
<picker class="pick" type="text" value="{{yorn1}}" range="{{yesno}}" selected="1" onchange="isFever"></picker>
</div>
<divider class="dvd"></divider>
<div class="out-container">
<text class="txt">Close contact with someone with COVID-19</text>
<picker class="pick" type="text" value="{{yorn2}}" range="{{yesno}}" selected="1" onchange="isTouch"></picker>
</div>
<div class="out-container">
<button value="Submit" style="margin-top:100px;width:50%;font-color:#0000ff;height:80px" onclick="showtoast"></button>
</div>
</div>
```
```
/* index.css */
.doc-page {
flex-direction: column;
background-color: #F1F3F5;
}
.title {
margin-top: 30px;
margin-bottom: 30px;
margin-left: 50px;
font-weight: bold;
color: #0000ff;
font-size: 38px;
}
.out-container {
flex-direction: column;
align-items: center;
}
.pick {
width: 80%;
height: 76px;
border: 1px solid #0000ff;
border-radius: 20px;
padding-left: 12px;
}
.txt {
width: 80%;
font-size: 18px;
text-align: left;
margin-bottom: 12px;
margin-left: 12px;
}
.dvd {
margin-top: 30px;
margin-bottom: 30px;
margin-left: 80px;
margin-right: 80px;
color: #6495ED;
stroke-width: 6px;
}
```
```
// xxx.js
import pmt from '@system.prompt'
export default {
data: {
yorn1:'No',
yorn2:'No',
pos:'Home',
yesno:['Yes', 'No'],
posarr:['Home', 'Company'],
datevalue:'Select time',
datetimeselect:'2012-5-6-11-25',
dateselect:'2021-9-17',
showbuild:true
},
onInit() {
},
isFever(e) {
this.yorn1 = e.newValue
},
isTouch(e) {
this.yorn2 = e.newValue
},
setPos(e) {
this.pos = e.newValue
if (e.newValue === 'Non-research center') {
this.showbuild = false
} else {
this.showbuild = true
}
},
setbuild(e) {
this.build = e.newValue
},
dateonchange(e) {
e.month=e.month+1;
this.datevalue = e.year + "-" + e.month + "-" + e.day;
pmt.showToast({ message:"date:"+e.year+"-"+e.month+"-"+e.day })
},
showtoast() {
pmt.showToast({
message: 'Submitted.',
duration: 2000,
gravity: 'center'
})
}
}
```
![en-us_image_0000001267887877](figures/en-us_image_0000001267887877.gif)
# &lt;stepper&gt; Development
When multiple steps are required to complete a task, you can use the &lt;stepper&gt; component to navigate your users through the whole process. For details, see [stepper](../reference/arkui-js/js-components-container-stepper.md).
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:
> This component is supported since API version 5.
## Creating a &lt;stepper&gt; Component
Create a &lt;stepper&gt; component in the .hml file under pages/index.
```
<!-- index.hml -->
<div class="container">
<stepper> <stepper-item>
<text>Step 1</text>
</stepper-item>
<stepper-item>
<text>Step 2</text>
</stepper-item>
</stepper>
</div>
```
```
/* xxx.css */
.container {
flex-direction: column;
justify-content: center;
align-items: center;
background-color: #F1F3F5;
}
text{
width: 100%;
height: 100%;
text-align: center;
}
```
![en-us_image_0000001223287656](figures/en-us_image_0000001223287656.gif)
## Setting the Index
Set index to the index value of the step that you want to display by default.
```
<!-- index.hml -->
<div class="container">
<stepper index="2">
<stepper-item>
<text>stepper-item1</text>
</stepper-item>
<stepper-item>
<text>stepper-item2</text>
</stepper-item>
<stepper-item>
<text>stepper-item3</text>
</stepper-item>
</stepper>
</div>
```
```
/* index.css */
.container {
flex-direction: column;
background-color:#F1F3F5;
}
text{
width: 100%;
height: 100%;
text-align: center;
}
```
![en-us_image_0000001267767837](figures/en-us_image_0000001267767837.gif)
Set the label attribute to customize the button text for the &lt;stepper-item&gt;.
```
<!-- index.hml -->
<div class="container" style="background-color:#F1F3F5;">
<stepper index="1">
<stepper-item label="{{label_1}}">
<text>stepper-item1</text>
</stepper-item>
<stepper-item label="{{label_2}}">
<text>stepper-item2</text>
</stepper-item>
<stepper-item label="{{label_3}}">
<text>stepper-item3</text>
</stepper-item>
<stepper-item>
<text>stepper-item4</text>
</stepper-item>
</stepper>
</div>
```
```
/* index.css */
.container {
flex-direction: column;
background-color:#F1F3F5;
}
text{
width: 100%;
height: 100%;
text-align: center;
}
```
```
/* index.js */
export default {
data: {
label_1:{ nextLabel: 'NEXT', status: 'normal' },
label_2:{
prevLabel: 'BACK',
nextLabel: 'NEXT',
status: 'normal'
},
label_3:{
prevLabel: 'BACK',
nextLabel: 'END',
status: 'disabled'
},
},
}
```
![en-us_image_0000001267767841](figures/en-us_image_0000001267767841.gif)
## Setting the Style
By default, the &lt;stepper&gt; component fills entire space of its container. The sample code below shows how to set the border and background color using the border and background-color attributes.
```
<!-- index.hml -->
<div class="container" >
<div class="stepperContent">
<stepper class="stepperClass">
<stepper-item>
<text>stepper-item1</text>
</stepper-item>
</stepper>
</div>
</div>
```
```
/* index.css */
.container {
flex-direction: column;
align-items: center;
justify-content: center;
background-color:#F1F3F5;
}
.stepperContent{
width: 300px;
height: 300px;
}
.stepperClass{
border:1px solid silver ; background-color: white;
}
text{
width: 100%;
height: 100%;
text-align: center;
}
```
![en-us_image_0000001223287668](figures/en-us_image_0000001223287668.png)
## Adding Events
The &lt;stepper&gt; component supports the finish, change, next, back, and skip events.
- When the change and next or back events exist at the same time, the next or back event is executed before the change event.
- Before resetting the index attribute, you must remove the current value. Otherwise, the value change cannot be detected.
```
<!-- index.hml -->
<div class="container" style="background-color:#F1F3F5;">
<div >
<stepper onfinish="stepperFinish" onchange="stepperChange" onnext="stepperNext" onback="stepperBack" onskip="stepperSkip" id="stepperId" index="{{index}}">
<stepper-item>
<text>stepper-item1</text>
<button value="skip" onclick="skipClick"></button>
</stepper-item>
<stepper-item>
<text>stepper-item2</text>
<button value="skip" onclick="skipClick"></button>
</stepper-item>
<stepper-item>
<text>stepper-item3</text>
</stepper-item>
</stepper>
</div>
</div>
```
```
/* xxx.css */
.doc-page {
flex-direction: column;
align-items: center;
justify-content: center;
}
stepper-item{
width: 100%;
flex-direction: column;
align-self: center;
justify-content: center;
}
text{
margin-top: 45%;
justify-content: center;
align-self: center;
margin-bottom: 50px;
}
button{
width: 80%;
height: 60px;
margin-top: 20px;
}
```
```
/* index.js */
import prompt from '@system.prompt';
export default {
data: {
index:0,
},
stepperSkip(){
this.index = null;
this.index=2;
},
skipClick(){
this.$element('stepperId').setNextButtonStatus({status: 'skip', label: 'SKIP'});
},
stepperFinish(){
prompt.showToast({
message: 'All Finished'
})
},
stepperChange(e){
console.log("stepperChange"+e.index)
prompt.showToast({
message: 'Previous step: '+e.prevIndex+"-------Current step:"+e.index
})
},
stepperNext(e){
console.log("stepperNext"+e.index)
prompt.showToast({
message: 'Current step:'+e.index+"-------Next step:"+e.pendingIndex
})
var index = {pendingIndex:e.pendingIndex }
return index;
},
stepperBack(e){
console.log("stepperBack"+e.index)
var index = {pendingIndex: e.pendingIndex }
return index;
}
}
```
![en-us_image_0000001267607869](figures/en-us_image_0000001267607869.gif)
## Example Scenario
Select the options displayed on the page. Your selection will be shown in real time. Click the next button to dynamically change the font color and font size on the page.
Use the &lt;stepper&gt; component to navigate through the steps. Create a [&lt;toggle&gt;](../reference/arkui-js/js-components-basic-toggle.md) component to implement the functions of selection and displaying the selection result. Then use the [&lt;select&gt;](../reference/arkui-js/js-components-basic-select.md) component to dynamically change the font color or size of the selected options.
```
<div class="container">
<stepper id="mystep" index="0" onfinish="back" style="text-color: indigo;">
<stepper-item label="{{label1}}">
<div style="flex-direction: column;padding: 0px 10px;">
<text class="text" style="margin-top: 10%;text-align: center;width: 100%;">Select error types:</text>
<text style="margin-top: 20px;padding: 10px">
<span>{{error}}</span>
</text>
<div style="justify-content: space-around;flex-wrap: wrap;">
<toggle for="{{togglelist1}}" value="{{$item}}" class="tog" onchange="multiTog({{$item}})"></toggle>
</div>
</div>
</stepper-item>
<stepper-item label="{{label2}}">
<div style="flex-direction: column;align-items: center;">
<text class="txt" style="margin-top: 10%;">Toggle</text>
<div style="justify-content: space-around;flex-wrap: wrap;;margin-top:10%">
<toggle class="tog" for="{{togglelist1}}" value="{{$item}}" style="text-color: {{tcolor}};font-size: {{tsize}}; font-style: {{tstyle}};font-weight: {{tweight}};font-family: {{tfamily}};">
</toggle>
</div>
<div style="flex-wrap: wrap;width: 700px;margin-top:10%">
<div style="flex-direction: column;width: 350px;height: 185px;align-items: center;">
<text class="txt">text-color</text>
<select onchange="settcolor">
<option for="{{color_list}}" value="{{$item}}">{{$item}}</option>
</select>
</div>
<div style="flex-direction: column;width: 350px;height: 185px;align-items: center;">
<text class="txt">font-size</text>
<select onchange="settsize">
<option for="{{size_list}}" value="{{$item}}">{{$item}}</option>
</select>
</div>
</div>
</div>
</stepper-item>
</stepper>
</div>
```
```
/* xxx.css */
.container {
flex-direction: column;
align-items: center;
justify-content: center;
background-color:#F1F3F5;
}
.dvd {
stroke-width: 8px;
color: orangered;
margin: 65px;
}
.tog{
margin-right: 20px;
margin-top: 30px;
}
```
```
/* index.js */
import prompt from '@system.prompt';
import router from '@system.router';
let myset = new Set();
export default {
data: {
error: '',
tcolor:'#FF4500',
color_list:['#FF4500','#5F9EA0','#0000FF'],
tsize: '12px',
size_list: ['12px', '30px', '8px', '50px'],
label1: {
prevLabel: 'The text on the left of the starting step is invalid.',
nextLabel: 'Toggle'
},
label2: {
prevLabel: 'toggle',
nextLabel: 'END'
},
togglelist1:['Program error', 'Software', 'System', 'Application'],
},
multiTog(arg, e) {
this.error = ' '
if (e.checked) {
myset.add(arg)
} else {
myset.delete(arg)
}
for (let item of myset) {
this.error += item + ' '
}
},
settcolor(e) {
this.tcolor = e.newValue
},
settsize(e) {
this.tsize = e.newValue
}
}
```
![en-us_image_0000001267887817](figures/en-us_image_0000001267887817.gif)
# &lt;text&gt; Development
The &lt;text&gt; component is used to display a piece of textual information. For details, see [text](../reference/arkui-js/js-components-basic-text.md).
## Creating a &lt;text&gt; Component
Create a &lt;text&gt; component in the .hml file under pages/index.
```
<!-- xxx.hml -->
<div class="container" style="text-align: center;justify-content: center; align-items: center;">
<text>Hello World</text>
</div>
```
```
/* xxx.css */
.container {
width: 100%;
height: 100%;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #F1F3F5;
}
```
![en-us_image_0000001222807780](figures/en-us_image_0000001222807780.png)
## Setting the Text Style and Attributes
- Adding a text style
Set the color, font-size, allow-scale, word-spacing, and text-valign attributes to apply the color, size, zoom, spacing, and vertical alignment styles to the text.
```
<!-- xxx.hml -->
<div class="container" style="background-color:#F1F3F5;flex-direction: column;justify-content: center; align-items: center;">
<text style="color: blueviolet; font-size: 40px; allow-scale:true">
This is a passage
</text>
<text style="color: blueviolet; font-size: 40px; margin-top: 20px; allow-scale:true;word-spacing: 20px;" >
This is a passage
</text>
</div>
```
```
/* xxx.css */
.container {
width: 100%;
height: 100%;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #F1F3F5;
}
```
![en-us_image_0000001222967764](figures/en-us_image_0000001222967764.png)
- Adding a text modifier
Set the text-decoration attribute to add a line to selected text. Set the text-decoration-color attribute to apply specific color to the added line. For values of text-decoration, see [Text Style](../reference/arkui-js/js-components-basic-text.md).
```
<!-- xxx.hml -->
<div class="container" style="background-color:#F1F3F5;">
<text style="text-decoration:underline">
This is a passage
</text>
<text style="text-decoration:line-through;text-decoration-color: red">
This is a passage
</text>
</div>
```
```
/* xxx.css */
.container {
width: 100%;
height: 100%;
flex-direction: column;
align-items: center;
justify-content: center;
}
text{
font-size: 50px;
}
```
![en-us_image_0000001223287688](figures/en-us_image_0000001223287688.png)
- Hiding text content
Set the text-overflow attribute to ellipsis so that overflowed text is displayed as an ellipsis.
```
<!-- xxx.hml -->
<div class="container">
<text class="text">
This is a passage
</text>
</div>
```
```
/* xxx.css */
.container {
width: 100%;
height: 100%;
flex-direction: column;
align-items: center;
background-color: #F1F3F5;
justify-content: center;
}
.text{
width: 200px;
max-lines: 1;
text-overflow:ellipsis;
}
```
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:
> - text-overflow must be used together with max-lines.
>
> - max-lines indicates the maximum number of lines in the text.
![en-us_image_0000001267647865](figures/en-us_image_0000001267647865.png)
- Setting the text line breaking mode
Set the word-break attribute to specify how to break lines of text. For values of word-break, see [Text Styles](../reference/arkui-js/js-components-basic-text.md).
```
<!-- xxx.hml -->
<div class="container">
<div class="content">
<text class="text1">
Welcome to the world
</text>
<text class="text2">
Welcome to the world
</text>
</div>
</div>
```
```
/* xxx.css */
.container {
background-color: #F1F3F5;
flex-direction: column;
align-items: center;
justify-content: center;
}
.content{
width: 50%;
flex-direction: column;
align-items: center;
justify-content: center;
}
.text1{
height: 200px;
border:1px solid #1a1919;
margin-bottom: 50px;
text-align: center;
word-break: break-word;
font-size: 40px;
}
.text2{
height: 200px;
border:1px solid #0931e8;
text-align: center;
word-break: break-all;
font-size: 40px;
}
```
![en-us_image_0000001267767865](figures/en-us_image_0000001267767865.png)
- Setting the [&lt;span&gt;](../reference/arkui-js/js-components-basic-span.md)child component
```
<!-- xxx.hml -->
<div class="container" style="justify-content: center; align-items: center;flex-direction: column;background-color: #F1F3F5;">
<text style="font-size: 45px;">
This is a passage
</text>
<text style="font-size: 45px;">
<span style="color: aqua;">This </span><span style="color: #F1F3F5;"> 1 </span> <span style="color: blue;"> is a </span> <span style="color: #F1F3F5;"> 1 </span> <span style="color: red;"> passage </span>
</text>
</div>
```
![en-us_image_0000001223127720](figures/en-us_image_0000001223127720.png)
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**:
> - When the &lt;span&gt; child component is used to form a text paragraph, incorrect &lt;span&gt; attribute settings (for example, setting of font-weight to 1000) will result in abnormal display of the text paragraph.
>
> - When the &lt;span&gt; child component is being used, do not include any text you want to show in the &lt;text&gt; component, as such text will not be displayed if you do so.
## Example Scenario
Use the &lt;text&gt; component to display text content through data binding. Use the &lt;span&gt; child component to hide or display text content by setting the show attribute.
```
<!-- xxx.hml -->
<div class="container">
<div style="align-items: center;justify-content: center;">
<text class="title">
{{ content }}
</text>
<switch checked="true" onchange="test"></switch>
</div>
<text class="span-container" style="color: #ff00ff;">
<span show="{{isShow}}"> {{ content }} </span>
<span style="color: white;">
1
</span>
<span style="color: #f76160">Hide clip </span>
</text>
</div>
```
```
/* xxx.css */
.container {
align-items: center;
flex-direction: column;
justify-content: center;
background-color: #F1F3F5;
}
.title {
font-size: 26px;
text-align:center;
width: 200px;
height: 200px;
}
```
```
// xxx.js
export default {
data: {
isShow:true,
content: 'Hello World'
},
onInit(){ },
test(e) {
this.isShow = e.checked
}
}
```
![en-us_image_0000001267887849](figures/en-us_image_0000001267887849.gif)
# Custom Components
The ArkUI that uses the JavaScript-based web-like development paradigm supports custom components for you to extend existing components. You can encapsulate custom private attributes and events into new components to reuse them multiple times in your project. This also improves code readability. The following is an example:
- Building a custom component
```
<!-- comp.hml -->
<div class="item">
<text class="title-style">{{title}}</text>
<text class="text-style" onclick="childClicked" focusable="true">Click to view the hidden text.</text>
<text class="text-style" if="{{showObj}}">hello world</text>
</div>
```
```
/* comp.css */
.item {
width: 700px;
flex-direction: column;
height: 300px;
align-items: center;
margin-top: 100px;
}
.text-style {
width: 100%;
text-align: center;
font-weight: 500;
font-family: Courier;
font-size: 36px;
}
.title-style {
font-weight: 500;
font-family: Courier;
font-size: 50px;
color: #483d8b;
}
```
```
// comp.js
export default {
props: {
title: {
default: 'title',
},
showObject: {},
},
data() {
return {
showObj: this.showObject,
};
},
childClicked () {
this.$emit('eventType1', {text: 'Receive the parameters from the child component.'});
this.showObj = !this.showObj;
},
}
```
- Introducing the custom component
```
<!-- xxx.hml -->
<element name='comp' src='../../common/component/comp.hml'></element>
<div class="container">
<text>Parent component: {{text}}</text>
<comp title="Custom component" show-object="{{isShow}}" @event-type1="textClicked"></comp>
</div>
```
```
/* xxx.css */
.container {
background-color: #f8f8ff;
flex: 1;
flex-direction: column;
align-content: center;
}
```
```
// xxx.js
export default {
data: {
text: 'Start'
isShow: false,
},
textClicked (e) {
this.text = e.detail.text;
},
}
```
In this example, the parent component passes the customized attribute through title to the child component, and the child component receives the attribute value in props. The child component passes text to the parent through the bound event, and the passed value is obtained via e.detail. To successfully bind the child component event to the parent component, ensure that the event name meet the requirements for event binding. For details, see [Basic Usage of Custom Components](../reference/arkui-js/js-components-custom-basic-usage.md). The following figures show how the custom component works.
figure1 Running effect
![en-us_image_0000001222807816](figures/en-us_image_0000001222807816.png)
# Overview
The web-like development paradigm uses the classical three-stage programming model, in which OpenHarmony Markup Language (HML) is used for building layouts, CSS for defining styles, and JavaScript for adding processing logic. UI components are associated with data through one-way data-binding. This means that when data changes, the UI automatically updates with the new data. This development paradigm has a low learning curve for frontend web developers, allowing them to quickly transform existing web applications into ArkUI applications. It could be helpful if you are developing small- and medium-sized applications with simple UIs.
## Overall Architecture
ArkUI with the JavaScript-based web-like development paradigm consists of the following layers: application layer, frontend framework layer, engine layer, and porting layer.
![en-us_image_0000001223127696](figures/en-us_image_0000001223127696.png)
- Application
Contains applications with FAs you developed. The FA application in this document refers to the application with FAs developed using JavaScript.
- Framework
Parses UI pages and provides the Model-View-ViewModel (MVVM), page routing, custom components and more for front end development.
- Engine
Accomplishes animation parsing, Document Object Model (DOM) building, layout computing, rendering command building and drawing, and event management.
- Porting Layer
Abstracts the platform layer to provide abstract interfaces to connect to the platform. For example, event interconnection, rendering pipeline interconnection, and lifecycle interconnection.
# Building a Food Category Grid Layout
The diet application allows food on the home page to display in list or grid mode. You can implement switching between food categories through tabs in grid mode.
1. Import the Category enumeration type to the FoodCategoryList page.
```
import { Category, FoodData } from '../model/FoodData'
```
2. Create the FoodCategoryList and FoodCategory components. The FoodCategoryList component is used as the entry component of the new page, and the initializeOnStartup method is invoked in the entry component.
```
@Component
struct FoodList {
private foodItems: FoodData[]
build() {
......
}
}
@Component
struct FoodCategory {
private foodItems: FoodData[]
build() {
......
}
}
@Entry
@Component
struct FoodCategoryList {
private foodItems: FoodData[] = initializeOnStartup()
build() {
......
}
}
```
3. Create the showList member variable in the FoodCategoryList component to control the rendering switchover between the list layout and grid layout. The conditional rendering statement if...else... is required.
```
@Entry
@Component
struct FoodCategoryList {
private foodItems: FoodData[] = initializeOnStartup()
private showList: boolean = false
build() {
Stack() {
if (this.showList) {
FoodList({ foodItems: this.foodItems })
} else {
FoodCategory({ foodItems: this.foodItems })
}
}
}
}
```
4. In the upper right corner of the page, create an icon for switching between the list and grid layouts. Set the stack alignment mode to TopEnd, top-bottom alignment. Create an image component, and set the click event, that is, negation of showList.
```
@Entry
@Component
struct FoodCategoryList {
private foodItems: FoodData[] = initializeOnStartup()
private showList: boolean = false
build() {
Stack({ alignContent: Alignment.TopEnd }) {
if (this.showList) {
FoodList({ foodItems: this.foodItems })
} else {
FoodCategory({ foodItems: this.foodItems })
}
Image($r('app.media.Switch'))
.height(24)
.width(24)
.margin({ top: 15, right: 10 })
.onClick(() => {
this.showList = !this.showList
})
}.height('100%')
}
}
```
5. Add the @State decorator. After you click the switch tab in the upper right corner, the page does not change. This is because the showList does not have state data and its change does not trigger the page refresh. You need to add the @State decorator to make it state data. The change of the @State decorator will cause re-rendering of the component where the decorator is located.
```
@Entry
@Component
struct FoodCategoryList {
private foodItems: FoodData[] = initializeOnStartup()
@State private showList: boolean = false
build() {
Stack({ alignContent: Alignment.TopEnd }) {
if (this.showList) {
FoodList({ foodItems: this.foodItems })
} else {
FoodCategory({ foodItems: this.foodItems })
}
Image($r('app.media.Switch'))
.height(24)
.width(24)
.margin({ top: 15, right: 10 })
.onClick(() => {
this.showList = !this.showList
})
}.height('100%')
}
}
```
When you click the switch icon, the FoodList component is displayed. When you click the switch icon again, the FoodList component is hidden.
![en-us_image_0000001222807800](figures/en-us_image_0000001222807800.gif)
6. Create a tab to display all food categories (All). Create the <Tabs> component and its child component TabContent in the FoodCategory component, and set tabBar to All. Set the width of the TabBars to 280 and the layout mode to Scrollable. This means that the TabBars can be scrolled when the total length exceeds 280. The <Tabs> component is a container component that allows users to switch between content views through tabs. Each tab page corresponds to a TabContent.
```
@Component
struct FoodCategory {
private foodItems: FoodData[]
build() {
Stack() {
Tabs() {
TabContent() {}.tabBar('All')
}
.barWidth(280)
.barMode(BarMode.Scrollable)
}
}
}
```
![en-us_image_0000001267647881](figures/en-us_image_0000001267647881.png)
7. Create the FoodGrid component to function as a child component of the TabContent component.
```
@Component
struct FoodGrid {
private foodItems: FoodData[]
build() {}
}
@Component
struct FoodCategory {
private foodItems: FoodData[]
build() {
Stack() {
Tabs() {
TabContent() {
FoodGrid({ foodItems: this.foodItems })
}.tabBar('All')
}
.barWidth(280)
.barMode(BarMode.Scrollable)
}
}
}
```
8. Implement a 2 x 6 grid layout (12 food data resources in total). Create a Grid component, and set columnsTemplate to ('1fr 1fr'), rowsTemplate to ('1fr 1fr 1fr 1fr 1fr 1fr'), and both rowsGap and columnsGap to 8. Create a Scroll component so that it can be slid.
```
@Component
struct FoodGrid {
private foodItems: FoodData[]
build() {
Scroll() {
Grid() {
ForEach(this.foodItems, (item: FoodData) => {
GridItem() {}
}, (item: FoodData) => item.id.toString())
}
.rowsTemplate('1fr 1fr 1fr 1fr 1fr 1fr')
.columnsTemplate('1fr 1fr')
.columnsGap(8)
.rowsGap(8)
}
.scrollBar(BarState.Off)
.padding({left: 16, right: 16})
}
}
```
9. Create a FoodGridItem component to display the food image, name, and calories and implement the UI layout. The FoodGridItem component is a child component of the GridItem component. The height of each FoodGridItem is 184, and the line spacing is 8. The total height of the Grid component is calculated as follows: (184 + 8) x 6 – 8 = 1144.
```
@Component
struct FoodGridItem {
private foodItem: FoodData
build() {
Column() {
Row() {
Image(this.foodItem.image)
.objectFit(ImageFit.Contain)
.height(152)
.width('100%')
}.backgroundColor('#FFf1f3f5')
Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
Text(this.foodItem.name)
.fontSize(14)
.flexGrow(1)
.padding({ left: 8 })
Text(this.foodItem.calories + 'kcal')
.fontSize(14)
.margin({ right: 6 })
}
.height(32)
.width('100%')
.backgroundColor('#FFe5e5e5')
}
.height(184)
.width('100%')
}
}
@Component
struct FoodGrid {
private foodItems: FoodData[]
build() {
Scroll() {
Grid() {
ForEach(this.foodItems, (item: FoodData) => {
GridItem() {
FoodGridItem({foodItem: item})
}
}, (item: FoodData) => item.id.toString())
}
.rowsTemplate('1fr 1fr 1fr 1fr 1fr 1fr')
.columnsTemplate('1fr 1fr')
.columnsGap(8)
.rowsGap(8)
.height(1144)
}
.scrollBar(BarState.Off)
.padding({ left: 16, right: 16 })
}
}
```
![en-us_image_0000001223287708](figures/en-us_image_0000001223287708.gif)
10. Create the Category.Vegetable, Category.Fruit, Category.Nut, Category.SeaFood, and Category.Dessert tabs.
```
@Component
struct FoodCategory {
private foodItems: FoodData[]
build() {
Stack() {
Tabs() {
TabContent() {
FoodGrid({ foodItems: this.foodItems })
}.tabBar('All')
TabContent() {
FoodGrid({ foodItems: this.foodItems.filter(item => (item.category === Category.Vegetable)) })
}.tabBar('Vegetable')
TabContent() {
FoodGrid({ foodItems: this.foodItems.filter(item => (item.category === Category.Fruit)) })
}.tabBar('Fruit')
TabContent() {
FoodGrid({ foodItems: this.foodItems.filter(item => (item.category === Category.Nut)) })
}.tabBar('Nut')
TabContent() {
FoodGrid({ foodItems: this.foodItems.filter(item => (item.category === Category.Seafood)) })
}.tabBar('Seafood')
TabContent() {
FoodGrid({ foodItems: this.foodItems.filter(item => (item.category === Category.Dessert)) })
}.tabBar('Dessert')
}
.barWidth(280)
.barMode(BarMode.Scrollable)
}
}
}
```
11. Set the number of rows and height of grids for different food categories. Because the number of foods varies according to the category, the ''1fr 1fr 1fr 1fr 1fr 1fr ' constant cannot be used to set the number of rows to 6.
Create member variables gridRowTemplate and HeightValue, and set the number of grid rows and height by using these member variables.
```
@Component
struct FoodGrid {
private foodItems: FoodData[]
private gridRowTemplate : string = ''
private heightValue: number
build() {
Scroll() {
Grid() {
ForEach(this.foodItems, (item: FoodData) => {
GridItem() {
FoodGridItem({foodItem: item})
}
}, (item: FoodData) => item.id.toString())
}
.rowsTemplate(this.gridRowTemplate)
.columnsTemplate('1fr 1fr')
.columnsGap(8)
.rowsGap(8)
.height(this.heightValue)
}
.scrollBar(BarState.Off)
.padding({left: 16, right: 16})
}
}
```
Invoke the aboutToAppear API to calculate the number of rows (gridRowTemplate) and height (heightValue).
```
aboutToAppear() {
var rows = Math.round(this.foodItems.length / 2);
this.gridRowTemplate = '1fr '.repeat(rows);
this.heightValue = rows * 192 - 8;
}
```
The custom component provides two lifecycle callbacks: aboutToAppear and aboutToDisappear. aboutToAppear is executed after the custom component is created and before the build method of the custom component is executed. aboutToDisappear is executed when the custom component is deinitialized.
![en-us_image_0000001267647885](figures/en-us_image_0000001267647885.png)
```
@Component
struct FoodGrid {
private foodItems: FoodData[]
private gridRowTemplate : string = ''
private heightValue: number
aboutToAppear() {
var rows = Math.round(this.foodItems.length / 2);
this.gridRowTemplate = '1fr '.repeat(rows);
this.heightValue = rows * 192 - 8;
}
build() {
Scroll() {
Grid() {
ForEach(this.foodItems, (item: FoodData) => {
GridItem() {
FoodGridItem({foodItem: item})
}
}, (item: FoodData) => item.id.toString())
}
.rowsTemplate(this.gridRowTemplate)
.columnsTemplate('1fr 1fr')
.columnsGap(8)
.rowsGap(8)
.height(this.heightValue)
}
.scrollBar(BarState.Off)
.padding({left: 16, right: 16})
}
}
```
![en-us_image_0000001267887869](figures/en-us_image_0000001267887869.gif)
# Building a Food Category List Layout
Use the List component and ForEach loop to build the food category list layout.
1. Create a page file named FoodCategoryList.ets in the pages directory, rename the index.ets file FoodDetail.ets, and add the renamed file to the "pages" tag in the config.json file. The first page under the tag is the home page.
```
"js": [
{
"pages": [
"pages/FoodCategoryList",
"pages/FoodDetail"
],
]
```
2. Create a List component named FoodList as the page entry point. Then, add a ListItem component named FoodListItem as its child component. The List component is used to display data of the same type. Its child component <ListItem> is used to display specific items in the list.
```
@Component
struct FoodListItem {
build() {}
}
@Entry
@Component
struct FoodList {
build() {
List() {
ListItem() {
FoodListItem()
}
}
}
}
```
3. Import the FoodData class and initializeOnStartup method.
```
import { FoodData } from '../model/FoodData'
import { initializeOnStartup } from '../model/FoodDataModels'
```
4. Configure the FoodList and FoodListItem components to pass values. Create a member variable named foodItems of the FoodData[] type in the FoodList component and invoke the initializeOnStartup method to assign a value to the variable. Create a member variable foodItem of the FoodData type in the FoodListItem component. Pass the foodItems[0] of the first element in the parent foodItems array as a parameter to FoodListItem.
```
import { FoodData } from '../model/FoodData'
import { initializeOnStartup } from '../model/FoodDataModels'
@Component
struct FoodListItem {
private foodItem: FoodData
build() {}
}
@Entry
@Component
struct FoodList {
private foodItems: FoodData[] = initializeOnStartup()
build() {
List() {
ListItem() {
FoodListItem({ foodItem: this.foodItems[0] })
}
}
}
}
```
5. Declare the UI layout of the FoodListItem child component. Create a Flex component, including the food image thumbnail, food name, and calories in the food.
```
import { FoodData } from '../model/FoodData'
import { initializeOnStartup } from '../model/FoodDataModels'
@Component
struct FoodListItem {
private foodItem: FoodData
build() {
Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
Image(this.foodItem.image)
.objectFit(ImageFit.Contain)
.height(40)
.width(40)
.backgroundColor('#FFf1f3f5')
.margin({ right: 16 })
Text(this.foodItem.name)
.fontSize(14)
.flexGrow(1)
Text(this.foodItem.calories + ' kcal')
.fontSize(14)
}
.height(64)
.margin({ right: 24, left:32 })
}
}
@Entry
@Component
struct FoodList {
private foodItems: FoodData[] = initializeOnStartup()
build() {
List() {
ListItem() {
FoodListItem({ foodItem: this.foodItems[0] })
}
}
}
}
```
![en-us_image_0000001267887833](figures/en-us_image_0000001267887833.png)
6. Create two FoodListItem objects. Create two FoodListItem objects in the List component and pass the first element this.foodItems[0] and the second element foodItem: this.foodItems[1] to the FoodListItem.
```
import { FoodData } from '../model/FoodData'
import { initializeOnStartup } from '../model/FoodDataModels'
@Component
struct FoodListItem {
private foodItem: FoodData
build() {
Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
Image(this.foodItem.image)
.objectFit(ImageFit.Contain)
.height(40)
.width(40)
.backgroundColor('#FFf1f3f5')
.margin({ right: 16 })
Text(this.foodItem.name)
.fontSize(14)
.flexGrow(1)
Text(this.foodItem.calories + ' kcal')
.fontSize(14)
}
.height(64)
.margin({ right: 24, left:32 })
}
}
@Entry
@Component
struct FoodList {
private foodItems: FoodData[] = initializeOnStartup()
build() {
List() {
ListItem() {
FoodListItem({ foodItem: this.foodItems[0] })
}
ListItem() {
FoodListItem({ foodItem: this.foodItems[1] })
}
}
}
}
```
![en-us_image_0000001267767849](figures/en-us_image_0000001267767849.png)
7. Import ForEach so that you do not need to create FoodListItem objects one by one.
```
ForEach(
arr: any[], // Array to be iterated
itemGenerator: (item: any) => void, // child component generator
keyGenerator?: (item: any) => string // (optional) Unique key generator, which is recommended.
)
```
The ForEach group has three parameters. The first parameter is the array to be traversed, the second parameter is the lambda function for generating child components, and the third parameter is the key value generator. The third parameter is optional. Yet, for performance reasons, it is strongly recommended that you provide it. keyGenerator enables the development framework to better recognize array changes without having to rebuild all nodes after item changes.
Traverse the foodItems array to cyclically create the ListItem component. Pass each item in foodItems as a parameter to the FoodListItem component.
```
ForEach(this.foodItems, item => {
ListItem() {
FoodListItem({ foodItem: item })
}
}, item => item.id.toString())
```
The code is as follows:
```
import { FoodData } from '../model/FoodData'
import { initializeOnStartup } from '../model/FoodDataModels'
@Component
struct FoodListItem {
private foodItem: FoodData
build() {
Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
Image(this.foodItem.image)
.objectFit(ImageFit.Contain)
.height(40)
.width(40)
.backgroundColor('#FFf1f3f5')
.margin({ right: 16 })
Text(this.foodItem.name)
.fontSize(14)
.flexGrow(1)
Text(this.foodItem.calories + ' kcal')
.fontSize(14)
}
.height(64)
.margin({ right: 24, left:32 })
}
}
@Entry
@Component
struct FoodList {
private foodItems: FoodData[] = initializeOnStartup()
build() {
List() {
ForEach(this.foodItems, item => {
ListItem() {
FoodListItem({ foodItem: item })
}
}, item => item.id.toString())
}
}
}
```
8. Add a title for the FoodList.
```
@Entry
@Component
struct FoodList {
private foodItems: FoodData[] = initializeOnStartup()
build() {
Column() {
Flex({justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center}) {
Text('Food List')
.fontSize(20)
.margin({ left:20 })
}
.height('7%')
.backgroundColor('#FFf1f3f5')
List() {
ForEach(this.foodItems, item => {
ListItem() {
FoodListItem({ foodItem: item })
}
}, item => item.id.toString())
}
.height('93%')
}
}
}
```
![en-us_image_0000001267607877](figures/en-us_image_0000001267607877.png)
# Building a Food Data Model
On the created page, we use various items to describe food, such as food names, calories, proteins, fats, carbohydrates, and vitamin C. This form of coding is impractical in actual development. Therefore, you need to create food data models to store and manage data in a unified manner.
![en-us_image_0000001267767897](figures/en-us_image_0000001267767897.png)
1. Create a folder named model and create a file named FoodData.ets therein.
![en-us_image_0000001223127760](figures/en-us_image_0000001223127760.png)
2. Define a food data storage model, FoodData, and an enum variable, Category. The FoodData class contains the food ID, name, category, image, calories, protein, fat, carbohydrates, and vitamin C attributes.
The eTS programming language is an extension of the TS language and also supports the TS syntax.
```
enum Category {
Fruit,
Vegetable,
Nut,
Seafood,
Dessert
}
let NextId = 0;
class FoodData {
id: string;
name: string;
image: Resource;
category: Category;
calories: number;
protein: number;
fat: number;
carbohydrates: number;
vitaminC: number;
constructor(name: string, image: Resource, category: Category, calories: number, protein: number, fat: number, carbohydrates: number, vitaminC: number) {
this.id = `${ NextId++ }`;
this.name = name;
this.image = image;
this.category = category;
this.calories = calories;
this.protein = protein;
this.fat = fat;
this.carbohydrates = carbohydrates;
this.vitaminC = vitaminC;
}
}
```
3. Store food image resources in the resources > phone > media directory. Use food names as the image names.
![en-us_image_0000001222967800](figures/en-us_image_0000001222967800.png)
4. Create food resource data. Create FoodDataModels.ets in the model folder and declare a food composition array, FoodComposition on the page.
In this example, 12 pieces of food data are used. You can customize more data resources when needed. Use LazyForEach to load data if a large amount of food data is involved. The following nutritional data is sourced from the Internet.
```
const FoodComposition: any[] = [
{ 'name': 'Tomato', 'image': $r('app.media.Tomato'), 'category': Category.Vegetable, 'calories': 17, 'protein': 0.9, 'fat': 0.2, 'carbohydrates': 3.9, 'vitaminC': 17.8 },
{ 'name': 'Walnut', 'image': $r('app.media.Walnut'), 'category': Category.Nut, 'calories': 654 , 'protein': 15, 'fat': 65, 'carbohydrates': 14, 'vitaminC': 1.3 },
{ 'name': 'Cucumber', 'image': $r('app.media.Cucumber'), 'category': Category.Vegetable, 'calories': 30, 'protein': 3, 'fat': 0, 'carbohydrates': 1.9, 'vitaminC': 2.1 },
{ 'name': 'Blueberry', 'image': $r('app.media.Blueberry'), 'category': Category.Fruit, 'calories': 57, 'protein': 0.7, 'fat': 0.3, 'carbohydrates': 14, 'vitaminC': 9.7 },
{ 'name': 'Crab', 'image': $r('app.media.Crab'), 'category': Category.Seafood, 'calories': 97, 'protein': 19, 'fat': 1.5, 'carbohydrates': 0, 'vitaminC': 7.6 },
{ 'name': 'IceCream', 'image': $r('app.media.IceCream'), 'category': Category.Dessert, 'calories': 207, 'protein': 3.5, 'fat': 11, 'carbohydrates': 24, 'vitaminC': 0.6 },
{ 'name': 'Onion', 'image': $r('app.media.Onion'), 'category': Category.Vegetable, 'calories': 39, 'protein': 1.1, 'fat': 0.1, 'carbohydrates': 9, 'vitaminC': 7.4 },
{ 'name': 'Mushroom', 'image': $r('app.media.Mushroom'), 'category': Category.Vegetable, 'calories': 22, 'protein': 3.1, 'fat': 0.3, 'carbohydrates': 3.3, 'vitaminC': 2.1 },
{ 'name': 'Kiwi', 'image': $r('app.media.Kiwi'), 'category': Category.Fruit, 'calories': 60 , 'protein': 1.1, 'fat': 0.5, 'carbohydrates': 15, 'vitaminC': 20.5 },
{ 'name': 'Pitaya', 'image': $r('app.media.Pitaya'), 'category': Category.Fruit, 'calories': 60, 'protein': 1.2, 'fat': 0, 'carbohydrates': 10, 'vitaminC': 60.9 },
{ 'name': 'Avocado', 'image': $r('app.media.Avocado'), 'category': Category.Fruit, 'calories': 160, 'protein': 2, 'fat': 15, 'carbohydrates': 9, 'vitaminC': 10 },
{ 'name': 'Strawberry', 'image': $r('app.media.Strawberry'), 'category': Category.Fruit, 'calories': 32, 'protein': 0.7, 'fat': 0.3, 'carbohydrates': 8, 'vitaminC': 58.8 }
]
```
5. Create the initializeOnStartUp method to initialize the FoodData array. Export the FoodData class from FoodData.ets, and import FoodData and Category in FoodDataModels.ets.
```
// FoodData.ets
export enum Category {
......
}
export class FoodData {
......
}
// FoodDataModels.ets
import { Category, FoodData } from './FoodData'
export function initializeOnStartup(): Array<FoodData> {
let FoodDataArray: Array<FoodData> = []
FoodComposition.forEach(item => {
FoodDataArray.push(new FoodData(item.name, item.image, item.category, item.calories, item.protein, item.fat, item.carbohydrates, item.vitaminC ));
})
return FoodDataArray;
}
```
The data resources for the diet application are now ready. You can continue to create a food category list by loading the data.
# Web
The \<Web> component can be used to display web pages. For details, see [Web API](../reference/arkui-ts/ts-basic-components-web.md).
## Creating a Component
Create a \<Web> component in the .ets file under main/ets/MainAbility/pages. Then, in the created component, use src to specify the web page URI to be referenced, and bind a controller to the component to call the component APIs.
```
// xxx.ets
@Entry
@Component
struct WebComponent {
controller: WebController = new WebController();
build() {
Column() {
Web({ src: 'https://www.example.com', controller: this.controller })
}
}
}
```
## Setting Styles and Attributes
When using the \<Web> component, you need to specify the styles and attributes. The sample code is as follows.
```
// xxx.ets
@Entry
@Component
struct WebComponent {
fileAccess: boolean = true;
controller: WebController = new WebController();
build() {
Column() {
Text('Hello world!')
.fontSize(20)
Web({ src: 'https://www.example.com', controller: this.controller })
// Set the height and padding.
.height(500)
.padding(20)
// Set the file access permission and script execution permission.
.fileAccess(this.fileAccess)
.javaScriptAccess(true)
Text('End')
.fontSize(20)
}
}
}
```
## Adding Events and Methods
Add the onProgressChange event for the \<Web> component, which is triggered when the loading progress changes and returns the progress value in its callback. Assign the progress value to the \<Progress> component to control the status of the component. When the progress reaches 100%, the \<Progress> component is hidden, and the web page loading is complete.
```
// xxx.ets
@Entry
@Component
struct WebComponent {
@State progress: number = 0;
@State hideProgress: boolean = true;
fileAccess: boolean = true;
controller: WebController = new WebController();
build() {
Column() {
Text('Hello world!')
.fontSize(20)
Progress({value: this.progress, total: 100})
.color('#0000ff')
.visibility(this.hideProgress ? Visibility.None : Visibility.Visible)
Web({ src: 'https://www.example.com', controller: this.controller })
.fileAccess(this.fileAccess)
.javaScriptAccess(true)
.height(500)
.padding(20)
// Add an onProgressChange event.
.onProgressChange(e => {
this.progress = e.newProgress;
// When the progress reaches 100%, the progress bar disappears.
if (this.progress === 100) {
this.hideProgress = true;
} else {
this.hideProgress = false;
}
})
Text('End')
.fontSize(20)
}
}
}
```
Add the runJavaScript method to the onPageEnd event. The onPageEnd event is triggered when the web page finishes loading. In this case, the runJavaScript method can be used to execute JavaScript scripts in the HTML file. In the sample code below, when the web page finishes loading, the onPageEnd event is triggered to call the test method in the HTML file and print information on the console.
```
// xxx.ets
@Entry
@Component
struct WebComponent {
@State progress: number = 0;
@State hideProgress: boolean = true;
fileAccess: boolean = true;
// Define the controller for the \<Web> component.
controller: WebController = new WebController();
build() {
Column() {
Text('Hello world!')
.fontSize(20)
Progress({value: this.progress, total: 100})
.color('#0000ff')
.visibility(this.hideProgress ? Visibility.None : Visibility.Visible)
// Initialize the \<Web> component and bind it to the controller.
Web({ src: $rawfile('index.html'), controller: this.controller })
.fileAccess(this.fileAccess)
.javaScriptAccess(true)
.height(500)
.padding(20)
.onProgressChange(e => {
this.progress = e.newProgress;
if (this.progress === 100) {
this.hideProgress = true;
} else {
this.hideProgress = false;
}
})
.onPageEnd(e => {
// test() is defined in index.html.
this.controller.runJavaScript({ script: 'test()' });
console.info('url: ', e.url);
})
Text('End')
.fontSize(20)
}
}
}
```
Create an HTML file in main/resources/rawfile.
```
<!-- index.html -->
<!DOCTYPE html>
<html>
<meta charset="utf-8">
<body>
Hello world!
</body>
<script type="text/javascript">
function test() {
console.info('Ark Web Component');
}
</script>
</html>
```
## Scenario Example
In this example, you'll implement a \<Web> component where videos can be played dynamically. Embed a video resource into an HTML page, and then use the \<Web> component controller to invoke the onActive and onInactive methods to activate and pause page rendering, respectively. When the page is hidden, the \<Web> component stops rendering and the video playback pauses. When the page is displayed, the \<Web> component is activated and the video playback resumes.
```
// xxx.ets
@Entry
@Component
struct WebComponent {
controller: WebController = new WebController();
build() {
Column() {
Web({ src: $rawfile('index.html'), controller: this.controller })
.fileAccess(true)
}
}
onPageHide() {
// Invoked when the page is hidden.
this.controller.onInactive();
}
onPageShow() {
// Invoked when the page is displayed.
this.controller.onActive();
}
}
```
```
<!-- index.html -->
<!DOCTYPE html>
<html>
<meta charset="utf-8">
<body>
<video width="320" height="240" controls="controls" muted="muted" autoplay="autoplay">
<!-- The test.mp4 video file is stored in main/resources/rawfile. -->
<source src="test.mp4" type="video/mp4">
</video>
</body>
</html>
```
# Getting to Know Components
Before customizing a component, get to know what the [component and decorator](#components-and-decorators) are and initialize the component. Then, [modify the component attributes and constructor parameters](#modifying-component-attributes-and-constructor-parameters) to customize a component.
## Components and Decorators
In a declarative UI, all pages are composed of components. The data structure of the component is struct, and the decorator [@Component](ts-component-based-component.md) is the component-based flag. The struct decorated by @Component indicates that the struct has the component capability.
The method for declaring a custom component is as follows:
```
@Component
struct MyComponent {}
```
In an IDE project template, MyComponent is a custom component that can display text in the center. You can describe your UI structures in the build method of the component, by complying with the API constraints of the Builder.
```
interface Builder {
build: () => void
}
```
The component decorated by [@Entry](ts-component-based-entry.md) indicates that the component is the main entry of the page and can also be considered as the root node of the page. **NOTE** that a page must have one and only one @Entry. Only the @Entry decorated component and its child components are displayed on the page.
@Component and @Entry are basic and important decorators. To put it simply, a decorator assigns a capability to an object to be decorated. For example, @Entry assigns the capability of the page entry, and @Component assigns the component capability.
With a basic knowledge of the component and decorator, you are ready to develop a diet application.
## Modifying Component Attributes and Constructor Parameters
When you create a system component, the default style is used. You can change the display of the component by changing its attributes and styles.
1. Modify the fontSize attribute of the <Text> component to change the font size of the component. In this example, the font size is set to 26 and font weight 500. Two configuration methods are available for setting the font weight:
1. The value of the number type ranges from 100 to 900. The default value is 400. A larger value indicates a thicker font.
2. fontWeight is a built-in enumeration type. Its value can be Lighter, Normal, Bold, or Bolder.
The attribute method must follow the component and be connected by the operator ".". You can also configure multiple attributes of the component in method chaining mode.
```
@Entry
@Component
struct MyComponent {
build() {
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
Text('Hello World')
.fontSize(26)
.fontWeight(500)
}
.width('100%')
.height('100%')
}
}
```
![en-us_image_0000001267767845](figures/en-us_image_0000001267767845.png)
2. Change the display content of the <Text> component from Hello World to Tomato by modifying the constructor parameters of the <Text> component.
```
@Entry
@Component
struct MyComponent {
build() {
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
Text('Tomato')
.fontSize(26)
.fontWeight(500)
}
.width('100%')
.height('100%')
}
}
```
![en-us_image_0000001267887829](figures/en-us_image_0000001267887829.png)
# Creating a Declarative UI Project
Before creating a project, you need to install DevEco Studio.
1. Open DevEco Studio and click Create Project. If there is already a project, choose File > New > New project.
![en-us_image_0000001267607861](figures/en-us_image_0000001267607861.png)
2.
On the page for selecting an ability template, select [Standard]Empty Ability.
![en-us_image_0000001223127704](figures/en-us_image_0000001223127704.png)
3.
Install the OpenHarmony SDK.
![en-us_image_0000001223127700](figures/en-us_image_0000001223127700.png)
4. On the project configuration page, set Project Name to HealthyDiet, Project Type to Application, Device Type to Phone, Language to eTS, and Compatible API Version to SDK: API Version 7. By default, DevEco Studio saves the project to drive C. You can change the save path by setting Save Location. When you are done, click Finish.
![en-us_image_0000001267647849](figures/en-us_image_0000001267647849.png)
5. After the project is created, open the app.ets file.
The app.ets file provides the onCreate and onDestroy methods for the application lifecycle. onCreate is called when an application is created, and onDestroy is called when an application is destroyed. Global variables can be declared in the app.ets file, wherein the declared data and methods are shared by the entire application.
```
export default {
onCreate() {
console.info('Application onCreate')
},
onDestroy() {
console.info('Application onDestroy')
},
}
```
6. In the project navigation tree, open index.ets. This page displays the current UI description. The declarative UI framework automatically generates a component-based struct, which complies with the Builder API declaration. The current layout and components are declared in the build method.
```
@Entry
@Component
struct MyComponent {
build() {
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
Text('Hello World')
.fontSize(50)
.fontWeight(FontWeight.Bold)
}
.width('100%')
.height('100%')
}
}
```
7. Click Previewer on the right to open the Previewer window. In the Previewer window of the phone type, Hello World is displayed in the middle and in bold.
If the Previewer button is unavailable, choose Settings > SDK Manager >OpenHarmony SDK > Tools to check whether the Previewer is installed.
![en-us_image_0000001222807756](figures/en-us_image_0000001222807756.png)
8. Install the application on the phone and run the application. Connect the phone to the computer. After the IDE identifies the phone, click Run'entry'.
![en-us_image_0000001267607865](figures/en-us_image_0000001267607865.png)
Before the installation, you must configure an application signature. For details, see [Configuring the OpenHarmony App Signature](../quick-start/configuring-the-openharmony-app-signature.md). After the installation is complete, click the Run icon on the screen to open the application. Hello World is displayed in the center of the screen.
![en-us_image_0000001267647841](figures/en-us_image_0000001267647841.png)
# Creating a Simple Page
In this section, we will develop an infographic food details page, by building custom components through the container components &lt;Stack&gt; and &lt;Flex&gt; as well as basic components &lt;Image&gt; and &lt;Text&gt;.
## Building the Stack Layout
1. Create a food name.
Delete the code of the build method in the project template, create a &lt;Stack&gt; component, and place the &lt;Text&gt; component in the braces of the &lt;Stack&gt; component so that the &lt;Text&gt; component becomes a child component of the &lt;Stack&gt; component. A &lt;Stack&gt; component consists of one or more child components. The latter child component overwrites the former one.
```
@Entry
@Component
struct MyComponent {
build() {
Stack() {
Text('Tomato')
.fontSize(26)
.fontWeight(500)
}
}
}
```
![en-us_image_0000001222967780](figures/en-us_image_0000001222967780.png)
2. Display food pictures.
Create an &lt;Image&gt; component and specify a URL for it. The &lt;Image&gt; and &lt;Text&gt; components are mandatory. To display the &lt;Text&gt; component above the &lt;Image&gt; component, you need to declare the &lt;Image&gt; component first. Image resources are stored in the rawfile folder in resources. When referencing the resources in the rawfile folder, use the $rawfile('_filename_ loaded) format, where filename indicates the relative path of the file in the rawfile folder. Currently, $rawfile only allows the &lt;Image&gt; component to reference image resources.
```
@Entry
@Component
struct MyComponent {
build() {
Stack() {
Image($rawfile('Tomato.png'))
Text('Tomato')
.fontSize(26)
.fontWeight(500)
}
}
}
```
![en-us_image_0000001267887861](figures/en-us_image_0000001267887861.png)
3. Access images through resources.
In addition to specifying the image path, you can also use the media resource symbol $r to reference resources based on the resource qualifier rules in the resources folder. Right-click the resources folder, choose New &gt; Resource Directory, set Resource Type to Media (image resource), and set the resource qualifier to Device-Phone (currently, phones are used).
![en-us_image_0000001267887853](figures/en-us_image_0000001267887853.png)
Click OK. The phone.media folder is generated in the resources folder. Place Tomato.png in the folder.
![en-us_image_0000001222807784](figures/en-us_image_0000001222807784.png)
You can then can reference the application resource in the $r('app.type.name') format, that is, $r('app.media.Tomato').
```
@Entry
@Component
struct MyComponent {
build() {
Stack() {
Image($r('app.media.Tomato'))
.objectFit(ImageFit.Contain)
.height(357)
Text('Tomato')
.fontSize(26)
.fontWeight(500)
}
}
}
```
4. Set the width and height of the image, and set the objectFit attribute of the image to ImageFit.Contain, which means to keep the aspect ratio of the image to ensure that the image is completely displayed within the boundary.
If the image fills the entire screen, the possible causes are as follows:
1. The width and height of the image are not set.
2. The default attribute of objectFit of the image is ImageFit.Cover, that is, the image is zoomed in or zoomed out to fill the entire display boundary with the aspect ratio locked.
```
@Entry
@Component
struct MyComponent {
build() {
Stack() {
Image($r('app.media.Tomato'))
.objectFit(ImageFit.Contain)
.height(357)
Text('Tomato')
.fontSize(26)
.fontWeight(500)
}
}
}
```
![en-us_image_0000001223127732](figures/en-us_image_0000001223127732.png)
5. Set the food image and name layout. Set the alignment mode of the stack to bottom alignment. By default, the stack is center aligned. Set alignContent to Alignment.BottomStart. Similar to FontWeight, Alignment is a built-in enumeration type provided by the framework.
```
@Entry
@Component
struct MyComponent {
build() {
Stack({ alignContent: Alignment.BottomStart }) {
Image($r('app.media.Tomato'))
.objectFit(ImageFit.Contain)
.height(357)
Text('Tomato')
.fontSize(26)
.fontWeight(500)
}
}
}
```
![en-us_image_0000001267647873](figures/en-us_image_0000001267647873.png)
6. You can change the background color of the food image by setting the background color of the stack. You can set the background color in either of the following ways:
1. By using the built-in enumeration value of Color provided by the framework. For example, backgroundColor(Color.Red) indicates that the background color is set to red.
2. By using the parameter of the string type. The supported color formats are rgb, rgba, and HEX. For example, you can set the background color to blue by setting backgroundColor(??\#0000FF??) and set the background color to white by setting backgroundColor(??rgb(255, 255, 255)??).
```
@Entry
@Component
struct MyComponent {
build() {
Stack({ alignContent: Alignment.BottomStart }) {
Image($r('app.media.Tomato'))
.objectFit(ImageFit.Contain)
.height(357)
Text('Tomato')
.fontSize(26)
.fontWeight(500)
}
.backgroundColor('#FFedf2f5')
}
}
```
![en-us_image_0000001222967772](figures/en-us_image_0000001222967772.png)
7. Adjust the left and bottom margin of the &lt;Text&gt; component. Margin is a shorthand attribute. You can specify the margins of the four edges in a unified manner or separately. The configuration method is as follows:
1. When the parameter is set to Length, the outer margins of the four edges are specified. For example, margin(20) indicates that the outer margins of the top, right, bottom, and left edges are all 20.
2. {top?: Length, right?: Length, bottom?: Length, left?:Length} specifies the margins of the four edges. For example, margin({ left: 26, bottom: 17.4 }) indicates that the left margin is 26 and the bottom margin is 17.4.
```
@Entry
@Component
struct MyComponent {
build() {
Stack({ alignContent: Alignment.BottomStart }) {
Image($r('app.media.Tomato'))
.objectFit(ImageFit.Contain)
.height(357)
Text('Tomato')
.fontSize(26)
.fontWeight(500)
.margin({left: 26, bottom: 17.4})
}
.backgroundColor('#FFedf2f5')
}
}
```
![en-us_image_0000001222967776](figures/en-us_image_0000001222967776.png)
8. Adjust the structure between components and semanticize component names. Create the FoodDetail page entry component, create a column in FoodDetail, and set the alignment to alignItems(HorizontalAlign.Center). Change the name of the MyComponent component to FoodImageDisplay, which is a child component of the FoodDetail component.
A column is a container component whose child components are vertically arranged. It is a linear layout in essence. Therefore, only the alignment in the cross axis direction can be set.
```
@Component
struct FoodImageDisplay {
build() {
Stack({ alignContent: Alignment.BottomStart }) {
Image($r('app.media.Tomato'))
.objectFit(ImageFit.Contain)
Text('Tomato')
.fontSize(26)
.fontWeight(500)
.margin({ left: 26, bottom: 17.4 })
}
.height(357)
.backgroundColor('#FFedf2f5')
}
}
@Entry
@Component
struct FoodDetail {
build() {
Column() {
FoodImageDisplay()
}
.alignItems(HorizontalAlign.Center)
}
}
```
## Building the Flex Layout
You can use the Flex layout to build a food composition table. In this way you do not need to worry about the width and height calculation. The size of different cells can be flexibly set based on the proportion.
1. Create a ContentTable component as a child component of the FoodDetail component.
```
@Component
struct FoodImageDisplay {
build() {
Stack({ alignContent: Alignment.BottomStart }) {
Image($r('app.media.Tomato'))
.objectFit(ImageFit.Contain)
.height(357)
Text('Tomato')
.fontSize(26)
.fontWeight(500)
.margin({ left: 26, bottom: 17.4 })
}
.backgroundColor('#FFedf2f5')
}
}
@Component
struct ContentTable {
build() {}
}
@Entry
@Component
struct FoodDetail {
build() {
Column() {
FoodImageDisplay()
ContentTable()
}
.alignItems(HorizontalAlign.Center)
}
}
```
2. Create a Flex component to display two food composition categories in the tomato: Calories and Nutrition.
Calories contains information about calories. Nutrition contains information about protein, fat, carbohydrates, and vitamin C.
Create the Calories class. Create a Flex component and set its height to 280, and the top, right, and left margins to 30. The Flex component contains three Text child components, which represent the category name (Calories), content name (Calories), and contain value (17 kcal), respectively. By default, child components in the Flex component are arranged horizontally.
In the following example, code of FoodImageDisplay is omitted, and only code of ContentTable is provided.
```
@Component
struct ContentTable {
build() {
Flex() {
Text('Calories')
.fontSize(17.4)
.fontWeight(FontWeight.Bold)
Text('Calories')
.fontSize(17.4)
Text('17kcal')
.fontSize(17.4)
}
.height(280)
.padding({ top: 30, right: 30, left: 30 })
}
}
@Entry
@Component
struct FoodDetail {
build() {
Column() {
FoodImageDisplay()
ContentTable()
}
.alignItems(HorizontalAlign.Center)
}
}
```
![en-us_image_0000001267767869](figures/en-us_image_0000001267767869.png)
3. Adjust the layout and set the proportion (layoutWeight) of each part. Set the proportion of the category name to 1, and the total proportion of content name and content value to 2. The content name and content value are in a same Flex, and the content name occupies all remaining space flexGrow(1).
```
@Component
struct FoodImageDisplay {
build() {
Stack({ alignContent: Alignment.BottomStart }) {
Image($m('Tomato.png'))
.objectFit(ImageFit.Contain)
.height(357)
Text('Tomato')
.fontSize(26)
.fontWeight(500)
.margin({ left: 26, bottom: 17.4 })
}
.backgroundColor('#FFedf2f5')
}
}
@Component
struct ContentTable {
build() {
Flex() {
Text('Calories')
.fontSize(17.4)
.fontWeight(FontWeight.Bold)
.layoutWeight(1)
Flex() {
Text('Calories')
.fontSize(17.4)
.flexGrow(1)
Text('17kcal')
.fontSize(17.4)
}
.layoutWeight(2)
}
.height(280)
.padding({ top: 30, right: 30, left: 30 })
}
}
@Entry
@Component
struct FoodDetail {
build() {
Column() {
FoodImageDisplay()
ContentTable()
}
.alignItems(HorizontalAlign.Center)
}
}
```
![en-us_image_0000001267607901](figures/en-us_image_0000001267607901.png)
4. Create the Nutrient class in a similar process. Nutrition consists of four parts: Protein, Fat, Carbohydrates, and VitaminC. The names of the last three parts are omitted in the table and represented by spaces.
Set FlexDirection.Column, FlexAlign.SpaceBetween, and ItemAlign.Start.
```
@Component
struct ContentTable {
build() {
Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Start }) {
Flex() {
Text('Calories')
.fontSize(17.4)
.fontWeight(FontWeight.Bold)
.layoutWeight(1)
Flex() {
Text('Calories')
.fontSize(17.4)
.flexGrow(1)
Text('17kcal')
.fontSize(17.4)
}
.layoutWeight(2)
}
Flex() {
Text('Nutrition')
.fontSize(17.4)
.fontWeight(FontWeight.Bold)
.layoutWeight(1)
Flex() {
Text('Protein')
.fontSize(17.4)
.flexGrow(1)
Text('0.9g')
.fontSize(17.4)
}
.layoutWeight(2)
}
Flex() {
Text(' ')
.fontSize(17.4)
.fontWeight(FontWeight.Bold)
.layoutWeight(1)
Flex() {
Text('Fat')
.fontSize(17.4)
.flexGrow(1)
Text('0.2g')
.fontSize(17.4)
}
.layoutWeight(2)
}
Flex() {
Text(' ')
.fontSize(17.4)
.fontWeight(FontWeight.Bold)
.layoutWeight(1)
Flex() {
Text('Carbohydrates')
.fontSize(17.4)
.flexGrow(1)
Text('3.9g')
.fontSize(17.4)
}
.layoutWeight(2)
}
Flex() {
Text(' ')
.fontSize(17.4)
.fontWeight(FontWeight.Bold)
.layoutWeight(1)
Flex() {
Text('vitaminC')
.fontSize(17.4)
.flexGrow(1)
Text('17.8mg')
.fontSize(17.4)
}
.layoutWeight(2)
}
}
.height(280)
.padding({ top: 30, right: 30, left: 30 })
}
}
@Entry
@Component
struct FoodDetail {
build() {
Column() {
FoodImageDisplay()
ContentTable()
}
.alignItems(HorizontalAlign.Center)
}
}
```
5. Use the custom constructor @Builder to simplify the code. It can be found that the food groups in each food composition table are actually of the same UI structure.
![en-us_image_0000001223287704](figures/en-us_image_0000001223287704.png)
Currently, all food groups are declared, resulting in code duplication and redundancy. You can use @Builder to build a custom method and abstract the same UI structure declaration. The @Builder decorated method and the build method for the @Component decorated component are used to declare some UI rendering structures and comply with the same eTS syntax. You can define one or more methods decorated by @Builder, but a component decorated by @Component can have only one build method.
Declare the IngredientItem method decorated by @Builder in ContentTable to declare the UI descriptions for the category name, content name, and content value.
```
@Component
struct ContentTable {
@Builder IngredientItem(title:string, name: string, value: string) {
Flex() {
Text(title)
.fontSize(17.4)
.fontWeight(FontWeight.Bold)
.layoutWeight(1)
Flex({ alignItems: ItemAlign.Center }) {
Circle({width: 6, height: 6})
.margin({right: 12})
.fill(colorValue)
Text(name)
.fontSize(17.4)
.flexGrow(1)
Text(value)
.fontSize(17.4)
}
.layoutWeight(2)
}
}
}
```
When the IngredientItem API is called in the build method of ContentTable, this needs to be used to invoke the method in the scope of the component to distinguish the global method call.
```
@Component
struct ContentTable {
......
build() {
Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Start }) {
this.IngredientItem('Calories', 'Calories', '17kcal')
this.IngredientItem('Nutrition', 'Protein', '0.9g')
this.IngredientItem('', 'Fat', '0.2g')
this.IngredientItem('', 'Carbohydrates', '3.9g')
this.IngredientItem('', 'VitaminC', '17.8mg')
}
.height(280)
.padding({ top: 30, right: 30, left: 30 })
}
}
```
The overall code of the ContentTable component is as follows:
```
@Component
struct ContentTable {
@Builder IngredientItem(title:string, name: string, value: string) {
Flex() {
Text(title)
.fontSize(17.4)
.fontWeight(FontWeight.Bold)
.layoutWeight(1)
Flex() {
Text(name)
.fontSize(17.4)
.flexGrow(1)
Text(value)
.fontSize(17.4)
}
.layoutWeight(2)
}
}
build() {
Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Start }) {
this.IngredientItem('Calories', 'Calories', '17kcal')
this.IngredientItem('Nutrition', 'Protein', '0.9g')
this.IngredientItem('', 'Fat', '0.2g')
this.IngredientItem('', 'Carbohydrates', '3.9g')
this.IngredientItem('', 'VitaminC', '17.8mg')
}
.height(280)
.padding({ top: 30, right: 30, left: 30 })
}
}
@Entry
@Component
struct FoodDetail {
build() {
Column() {
FoodImageDisplay()
ContentTable()
}
.alignItems(HorizontalAlign.Center)
}
}
```
![en-us_image_0000001222807792](figures/en-us_image_0000001222807792.png)
You've learned how to build a simple food details page. Read on to learn how to define the page layout and connection.
## Samples
The following sample is provided to help you better understand how to use build a simple page:
- [eTSBuildCommonView](https://gitee.com/openharmony/app_samples/tree/master/ETSUI/eTSBuildCommonView)
This sample shows how to build a common view to display the picture of tomatoes and nutrition information, with the stack layout and flex layout.
# Overview
The TypeScript-based declarative development paradigm of ArkUI is a simplified, high-performance UI development framework for cross-device applications.
## Basic Capabilities
In ArkUI that uses the TypeScript-based declarative development paradigm, the programming mode is closer to natural semantics. You can intuitively describe the UI without caring about how the framework implements UI drawing and rendering, leading to simplified and efficient development. The UI capabilities are provided from three dimensions: component, animation, and state management. System capability APIs are also provided to allow for effortless invocation of system capabilities.
- Out-of-the-box components
A wide range of preset system components are provided. You can set the rendering effect of these components in method chaining mode. You can combine system components to form custom components. In this way, page components are divided into independent UI units to implement independent creation, development, and reuse of different units on pages, making pages more engineering-oriented.
- A diverse array of animation APIs
By drawing from the standard SVG drawing capability and various open animation APIs, you can customize animation tracks by encapsulating physical models or calling the provided APIs.
- State and data management
State data management provides clear page update and rendering processes and pipes through decorators with different functions. State management covers UI component states and application states. With these features, you are able to build an application-wide data update and UI rendering process.
- System capability APIs
Development has never been so easy, with a diverse array of encapsulated system capability APIs, from UI design to system capability invoking.
## Overall Architecture
![en-us_image_0000001223287712](figures/en-us_image_0000001223287712.png)
- Declarative UI frontend
Provides basic language specifications of the UI development paradigm, built-in UI components, layouts, and animations, and multiple state management mechanisms, with a wide array of APIs for you to call as required.
- Language runtime
Provides the parsing capability for the UI paradigm syntax and allows for cross-language API calls for a high-performance running environment of the TS language.
- Declarative UI backend engine
Provides UI rendering pipelines that are compatible with different development paradigms, multiple basic components, layout calculation, dynamic effects, and interaction events, with state management and drawing capabilities.
- Render engine
Provides efficient drawing capabilities, which enable rendering instructions collected by the rendering pipeline to be drawn to the screen.
- Porting layer
Provides abstract APIs to connect to different systems, such as system rendering pipelines and lifecycle scheduling.
# Implementing Page Redirection and Data Transmission
This section describes how to implement page redirection and data transmission between pages:
1. Page redirection: Click a food item on the food list page to go to the food details page. Click the back button on the food details page to go back to the food list page.
2. Data transmission between pages: After you click a food item, FoodDetail receives data from the previous page and renders the corresponding food details page.
## Page Redirection
The declarative UI paradigm provides two mechanisms for page redirection:
1. Navigator: encapsulates the page routing capability. After the page target is specified, all child components in the page target have the routing capability.
2. Router APIs: called to implement various operations of page routing. You'll need to import router before calling the router APIs.
The procedure below uses these two mechanisms for redirection between the page list page and food details page.
1. Click FoodListItem. The FoodDetail page is displayed. Create a Navigator component in FoodListItem to enable its child components to have the routing function. The target page is 'pages/FoodDetail'.
```
@Component
struct FoodListItem {
private foodItem: FoodData
build() {
Navigator({ target: 'pages/FoodDetail' }) {
Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
Image(this.foodItem.image)
.objectFit(ImageFit.Contain)
.height(40)
.width(40)
.backgroundColor('#FFf1f3f5')
.margin({ right: 16 })
Text(this.foodItem.name)
.fontSize(14)
.flexGrow(1)
Text(this.foodItem.calories + ' kcal')
.fontSize(14)
}
.height(64)
}
.margin({ right: 24, left:32 })
}
}
```
![en-us_image_0000001223127744](figures/en-us_image_0000001223127744.gif)
2. Click FoodGridItem. The FoodDetail page is displayed. Import the router module, and then call the push API of this module to push the FoodDetail page to the route stack to implement page redirection.
```
import router from '@system.router'
@Component
struct FoodGridItem {
private foodItem: FoodData
build() {
Column() {
......
}
.height(184)
.width('100%')
.onClick(() => {
router.push({ uri: 'pages/FoodDetail' })
})
}
}
```
![en-us_image_0000001267607909](figures/en-us_image_0000001267607909.gif)
3. Add the icon for returning from the FoodDetail page to the food list page. Save the Back.png file to the resources > phone > media directory. Create a custom component PageTitle, which contains the back icon and Food Detail text. Call the router.back() API of the router to display the top page of the route stack, that is, the upper-level page.
```
// FoodDetail.ets
import router from '@system.router'
@Component
struct PageTitle {
build() {
Flex({ alignItems: ItemAlign.Start }) {
Image($r('app.media.Back'))
.width(21.8)
.height(19.6)
Text('Food Detail')
.fontSize(21.8)
.margin({left: 17.4})
}
.height(61)
.backgroundColor('#FFedf2f5')
.padding({ top: 13, bottom: 15, left: 28.3 })
.onClick(() => {
router.back()
})
}
}
```
4. Create the Stack component in the FoodDetail component, including the FoodImageDisplay and PageTitle child components. Set the alignment mode to TopStart.
```
@Entry
@Component
struct FoodDetail {
build() {
Column() {
Stack( { alignContent: Alignment.TopStart }) {
FoodImageDisplay()
PageTitle()
}
ContentTable()
}
.alignItems(HorizontalAlign.Center)
}
}
```
![en-us_image_0000001267767881](figures/en-us_image_0000001267767881.png)
## Data Transmission Between Pages
We have implemented the redirection and going back of the FoodCategoryList and FoodDetail pages. At this point, the tomato details page is displayed no matter which FoodListItem/FoodGridItem is clicked. This is because the data transmission between pages is not yet configured. To configure data transmission between pages, set the routing with parameters as follows:
1. Set the params attribute in the Navigator of the FoodListItem component. The params attribute accepts the key-value object.
```
// FoodList.ets
@Component
struct FoodListItem {
private foodItem: FoodData
build() {
Navigator({ target: 'pages/FoodDetail' }) {
......
}
.params({ foodData: this.foodItem })
}
}
```
The router API called by FoodGridItem also has the capability of redirection with parameters. The method of using the router API is similar to that of using the Navigator.
```
router.push({
uri: 'pages/FoodDetail',
params: { foodData: this.foodItem }
})
```
2. Import the FoodData class to the FoodDetail page and add the foodItem member variable to the FoodDetail component.
```
// FoodDetail.ets
import { FoodData } from '../model/FoodData'
@Entry
@Component
struct FoodDetail {
private foodItem: FoodData
build() {
......
}
}
```
3. Obtain the value of foodData. Call router.getParams().foodData to obtain the data corresponding to foodData carried when the FoodCategoryList page is displayed.
```
@Entry
@Component
struct FoodDetail {
private foodItem: FoodData = router.getParams().foodData
build() {
......
}
}
```
4. Re-build the components on the FoodDetail page. During building, the food information on the FoodDetail page is all directly declared constants. You need to use the passed FoodData data to assign a new value to the constants. The code is as follows:
```
@Component
struct PageTitle {
build() {
Flex({ alignItems: ItemAlign.Start }) {
Image($r('app.media.Back'))
.width(21.8)
.height(19.6)
Text('Food Detail')
.fontSize(21.8)
.margin({left: 17.4})
}
.height(61)
.backgroundColor('#FFedf2f5')
.padding({ top: 13, bottom: 15, left: 28.3 })
.onClick(() => {
router.back()
})
}
}
@Component
struct FoodImageDisplay {
private foodItem: FoodData
build() {
Stack({ alignContent: Alignment.BottomStart }) {
Image(this.foodItem.image)
.objectFit(ImageFit.Contain)
Text(this.foodItem.name)
.fontSize(26)
.fontWeight(500)
.margin({ left: 26, bottom: 17.4 })
}
.height(357)
.backgroundColor('#FFedf2f5')
}
}
@Component
struct ContentTable {
private foodItem: FoodData
@Builder IngredientItem(title:string, name: string, value: string) {
Flex() {
Text(title)
.fontSize(17.4)
.fontWeight(FontWeight.Bold)
.layoutWeight(1)
Flex() {
Text(name)
.fontSize(17.4)
.flexGrow(1)
Text(value)
.fontSize(17.4)
}
.layoutWeight(2)
}
}
build() {
Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Start }) {
this.IngredientItem('Calories', 'Calories', this.foodItem.calories + 'kcal')
this.IngredientItem('Nutrition', 'Protein', this.foodItem.protein + 'g')
this.IngredientItem('', 'Fat', this.foodItem.fat + 'g')
this.IngredientItem('', 'Carbohydrates', this.foodItem.carbohydrates + 'g')
this.IngredientItem('', 'VitaminC', this.foodItem.vitaminC + 'mg')
}
.height(280)
.padding({ top: 30, right: 30, left: 30 })
}
}
@Entry
@Component
struct FoodDetail {
private foodItem: FoodData = router.getParams().foodData
build() {
Column() {
Stack( { alignContent: Alignment.TopStart }) {
FoodImageDisplay({ foodItem: this.foodItem })
PageTitle()
}
ContentTable({ foodItem: this.foodItem })
}
.alignItems(HorizontalAlign.Center)
}
}
```
## Samples
The following sample is provided to help you better understand how to define the page layout and connection:
- [eTSDefiningPageLayoutAndConnection](https://gitee.com/openharmony/app_samples/tree/master/ETSUI/eTSDefiningPageLayoutAndConnection)
This sample exemplifies the basic usage of the list layout, grid layout, and page routing, by building the food list page and food details page.
# Vibrator Development
## When to Use
You can set different vibration effects as needed, for example, customizing vibration effects with different intensities and durations for buttons on the device, and customizing one-shot or periodic vibration effects with different intensities and durations for alarm clocks and incoming calls.
## Available APIs
| Module | API | Description |
| -------- | -------- | -------- |
| ohos.vibrator | vibrate(duration:&nbsp;number):&nbsp;Promise&lt;void&gt; | Triggers&nbsp;vibration&nbsp;with&nbsp;the&nbsp;specified&nbsp;duration.&nbsp;This&nbsp;API&nbsp;uses&nbsp;a&nbsp;promise&nbsp;to&nbsp;return&nbsp;the&nbsp;result. |
| ohos.vibrator | vibrate(duration:&nbsp;number,&nbsp;callback?:&nbsp;AsyncCallback&lt;void&gt;):&nbsp;void | Triggers&nbsp;vibration&nbsp;with&nbsp;the&nbsp;specified&nbsp;duration.&nbsp;This&nbsp;API&nbsp;uses&nbsp;a&nbsp;callback&nbsp;to&nbsp;return&nbsp;the&nbsp;result. |
| ohos.vibrator | vibrate(effectId:&nbsp;EffectId):&nbsp;Promise&lt;void&gt; | Triggers&nbsp;vibration&nbsp;with&nbsp;the&nbsp;specified&nbsp;effect.&nbsp;This&nbsp;API&nbsp;uses&nbsp;a&nbsp;promise&nbsp;to&nbsp;return&nbsp;the&nbsp;result. |
| ohos.vibrator | vibrate(effectId:&nbsp;EffectId,&nbsp;callback?:&nbsp;AsyncCallback&lt;void&gt;):&nbsp;void | Triggers&nbsp;vibration&nbsp;with&nbsp;the&nbsp;specified&nbsp;effect.&nbsp;This&nbsp;API&nbsp;uses&nbsp;a&nbsp;callback&nbsp;to&nbsp;return&nbsp;the&nbsp;result. |
| ohos.vibrator | stop(stopMode:&nbsp;VibratorStopMode):&nbsp;Promise&lt;void&gt; | Stops&nbsp;vibration.&nbsp;This&nbsp;API&nbsp;uses&nbsp;a&nbsp;promise&nbsp;to&nbsp;return&nbsp;the&nbsp;result. |
| ohos.vibrator | stop(stopMode:&nbsp;VibratorStopMode,&nbsp;callback?:&nbsp;AsyncCallback&lt;void&gt;):&nbsp;void | Stops&nbsp;vibration.&nbsp;This&nbsp;API&nbsp;uses&nbsp;a&nbsp;callback&nbsp;to&nbsp;return&nbsp;the&nbsp;result. |
## How to Develop
1. Declare the permissions required for controlling vibrators on the hardware device in the **config.json** file.
```
"reqPermissions":[
{
"name":"ohos.permission.ACCELEROMETER",
"reason"":"",
"usedScene":{
"ability""[
".MainAbility"
],
"when":"inuse"
}
},
{
"name":"ohos.permission.VIBRATE",
"reason"":"",
"usedScene":{
"ability""[
".MainAbility"
],
"when":"inuse"
}
},
{
"name":"ohos.permission.ACTIVITY_MOTION",
"reason"":"",
"usedScene":{
"ability""[
".MainAbility"
],
"when":"inuse"
}
},
]
```
2. Trigger the device to vibrate.
```
import vibrator from "@ohos.vibrator"
vibrator.vibrate(duration: number).then((error)=>{
if(error){// The call fails, and error.code and error.message are printed.
console.log("Promise return failed.error.code"+error.code+"error.message"+error.message);
}else{// The call succeeded. The device starts to vibrate.
console.log("Promise returned to indicate a successful vibration.")
};
})
```
3. Stop the vibration.
```
import vibrator from "@ohos.vibrator"
vibrator.stop(stopMode: VibratorStopMode).then((error)=>{
if(error){// The call fails, and error.code and error.message are printed.
console.log("Promise return failed. error.code"+error.code+"error.message"+error.message);
}else{// The call succeeded. The device stops vibration.
Console.log("Promise returned to indicate a successful stop.");
};
})
```
# Vibrator Overview
The vibrator service opens up the latest capabilities of the vibrator hardware to the maximum extent. By expanding the native vibrator service to implement integrated vibration and interaction design, the service delivers an exquisite integrated vibration experience and differentiated experience, and improves user interaction efficiency and usability.
## Working Principles
The vibrator is a Misc device that consists of four modules: Vibrator API, Vibrator Framework, Vibrator Service, and HD_IDL.
**Figure1** Vibrator in Misc devices
![en-us_image_0000001180249428](figures/en-us_image_0000001180249428.png)
- Vibrator API: provides basic vibrator APIs, including the APIs for querying the vibrator list, querying the vibrator by effect, and triggering and stopping vibration.
- Vibrator Framework: manages the framework layer of the vibrator and communicates with the Misc Device Service.
- Vibrator Service: manages services of vibrators.
- HD_IDL: adapts to different devices.
## Constraints
When using a vibrator, you need to declare and obtain the **ohos.permission.VIBRATE** permission first so that you can control the vibration effect.
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册