Show results
I. Overview
Follow the official website to continue learning HarmonyOS
This blog post implements a development demo of a food details page
Through this development process, learn how to use container components Stack and Flex and basic components Image and Text , build user-defined components , and complete food introductions with pictures and texts.
2. Build Stack layout
1.Food name
Create Stack component and Text subcomponent
Stack component is a stacked component that can contain one or more sub-components, and its characteristic is that the latter sub-component covers the previous sub-component.
@Entry
@Component
struct MyComponent {
build() {
Stack() {
Text('Tomato')
.fontSize(26)
.fontWeight(500)
.fontColor(Color.White)
}
}
}
Previewer effect:
2. Food pictures
Create an Image component and specify the URL of the Image component
The Text component needs to be displayed above the Image component, so declare the Image component first .
Image resources are placed in the rawfile folder under resources . When referencing resources under rawfile , use the form $rawfile('filename') , where filename is the relative path of the file in the rawfile directory.
Currently $rawfile only supports the Image control to reference image resources.
@Entry
@Component
struct MyComponent {
build() {
Stack() {
Image($rawfile('Tomato.png'))
Text('Tomato')
.fontSize(26)
.fontWeight(500)
}
}
}
Previewer effect:
3. Access images through resources
In addition to specifying the image path, you can also use the media resource reference symbol $r to reference resources. You need to follow the rules of the resource qualifier of the resources folder.
Right-click the resources folder, click New>Resource Directory , and select Resource Type as Media (picture resources)
Note: The newly created Resource Directory directory can only be under the base directory, but the base directory has media files by default.
Directly put Tomato.png into the media folder, and you can reference the application resources in the form of $r('app.type.name')
Tomato.png $ r('app.media.Tomato') .
Code:
@Entry
@Component
struct MyComponent {
build() {
Stack() {
Image($r('app.media.Tomato'))
.objectFit(ImageFit.Contain)
.height(357)
//Image($rawfile('Tomato.png'))
Text('Tomato')
.fontSize(26)
.fontWeight(500)
}
}
}
Previewer:
4.Image component objectFit attribute
In the example, the objectFit attribute of the image is set to ImageFit.Contain , which means that the image is completely displayed within the boundary while maintaining the aspect ratio of the image.
The default property of Image 's objectFit is ImageFit.Cover ,
which means it can be enlarged or reduced while maintaining the aspect ratio so that it fills the entire display boundary.
If you want the Image to fill the entire screen, the reasons are as follows:
1. The width and height of the Image are not set.
2. The objectFit attribute uses the default value ImageFit.Cover
5. Set Stack layout properties
Stack is aligned in the center by default . In this example, it is modified to be aligned at the bottom and starting end .
Set the Stack construction parameter alignContent to Alignment.BottomStart.
Alignment , like FontWeight , is a built-in enumeration type provided by the framework.
Code:
@Entry
@Component
struct MyComponent {
build() {
Stack({ alignContent: Alignment.BottomStart }) {
Image($r('app.media.Tomato'))
.objectFit(ImageFit.Contain)
.height(357)
//Image($rawfile('Tomato.png'))
Text('Tomato')
.fontSize(26)
.fontWeight(500)
}
}
}
Previewer:
6. Adjust the margin of the Text component
The margin property adjusts the component's outer margins
(1).margin(Length) , that is, the outer margins of the top, right, bottom, and left sides are all Length .
(2).margin { top?: Length,
right?: Length,
bottom?: Length,
left?:Length }, that is, specify the margins of the four sides respectively
Code:
@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})
}
}
}
Previewer:
6. Adjust the structure between components and semanticize component names
Create the page entry component as FoodDetail , create Column in FoodDetail , and set alignItems (HorizontalAlign.Center) to be centered in the horizontal direction.
The name of the MyComponent component was changed to FoodImageDisplay , which is a subcomponent of FoodDetail.
Column is a container component with sub-components arranged vertically. It is essentially a linear layout, so the alignment can only be set in the cross-axis direction.
Code:
@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)
}
}
@Entry
@Component
struct FoodDetail {
build() {
Column() {
FoodImageDisplay()
}
.alignItems(HorizontalAlign.Center)
}
}
Previewer:
3. Build Flex layout
Flex: flexible layout
Use Flex elastic layout to build the food ingredient list of food,
The advantage of flexible layout in this scenario is that it can eliminate unnecessary width and height calculations and set the size of different cells through proportion, which is more flexible.
1. Create a new ContentTable component
Create a new ContentTable component and make it a subcomponent of the page entry component FoodDetail .
Code:
@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 })
}
}
}
@Component
struct ContentTable {
build() {}
}
@Entry
@Component
struct FoodDetail {
build() {
Column() {
FoodImageDisplay()
ContentTable()
}
.alignItems(HorizontalAlign.Center)
}
}
Previewer:
The ContentTable subcomponent is empty and has not been filled with content. The current Previewer effect is the same as the previous section.
2. Create a Flex component to display two types of components in Tomato
One category is Calories : Calories;
One category is nutrition , including: protein (Protein),
fat (Fat),
carbohydrates (Carbohydrates)
vitamin C (VitaminC).
First create the heat category
. Create a new Flex component with a height of 280 and a top, right and left inner margin of 30.
It contains three Text sub-components representing: category name (Calories)
content name (Calories)
content value (17kcal)
Flex Components are arranged horizontally by default.
ContentTable code:
@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 })
}
}
Previewer:
3. Adjust the layout and set the proportion of each part
The category name proportion (layoutWeight) is 1,
The total proportion of ingredient name and ingredient content (layoutWeight) is 2.
The ingredient name and ingredient content are located in the same Flex, and the ingredient name occupies all remaining space flexGrow (1).
ContentTable code:
@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 })
}
}
Previewer:
4. Create a nutritional classification based on the calorie classification
The nutrition part (Nutrition) includes:
protein (Protein),
fat (Fat),
carbohydrates (Carbohydrates)
vitamin C (VitaminC)
Set the outer Flex to be arranged vertically . FlexDirection.Column
is arranged equidistantly in the main axis direction (vertical direction). FlexAlign.SpaceBetween
is aligned in the cross axis direction (horizontal axis direction). ItemAlign.Start
ContentTable code:
@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 })
}
}
Previewer:
5. Optimize code
It can be found that the ingredient units in each ingredient list actually have the same UI structure.
Code can be streamlined through custom @Builder functions
Use custom @Builder to abstract the same UI structure. The methods decorated by
@Builder and the build method of Component are both used to declare some UI rendering structures and follow the same ArkTS syntax.
You can define one or more methods decorated with @Builder, but Component must have only one build method .
Declare the IngredientItem method modified by @Builder in the ContentTable , which is used to declare the category name, ingredient name and ingredient content UI description.
@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 }) {
Text(name)
.fontSize(17.4)
.flexGrow(1)
Text(value)
.fontSize(17.4)
}
.layoutWeight(2)
}
}
}
When calling the IngredientItem interface in the build method of ContentTable , you need to use this to call methods in the Component scope to distinguish global method calls.
@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 full 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 })
}
}
Previewer:
This section only optimizes the code, and the implementation effect is the same as the previous section.
4. Conclusion
Stack layout and Flex layout have completed the graphic display and nutritional information table of food, and built the first normal view food details page.
The next blog post will continue to follow the official website, develop the food classification list page, and complete the jump and data transfer of the food classification list page and the food details page.