HarmonyOS Development: Customize a Search Template

AbnerMingAbnerMing
5 min read

Foreword

the code case is based on api13.

In the previous article, we briefly analyzed the flexible layout Flex and used Flex to implement a simple streaming layout. In today's article, we will combine the search box to complete a common search page. The specific effect is shown in the following figure:

such a template can be simply divided into three parts, namely, the Search box at the top, the historical Search in the middle and the popular Search and Search box at the bottom. We can directly use the system's component Search and historical Search. As it is the content of searches with different contents, flexible layout Flex is used here, and the popular Search at the bottom has the same item specifications. Here we directly use Grid Grid components.

Quick Use

at present, it has been uploaded to the central warehouse. You can directly rely on it remotely. Of course, you can also download the source code for use.

There are two options for remote dependency, as follows:

method 1: in the Terminal window, run the following command to install the third-party package. DevEco Studio automatically adds the third-party package dependency to the project oh-package.json5.

Suggestion: Execute the command under the module path used.

ohpm install @abner/search

Method 2: Set the three-party package dependency in the project oh-package.json5. The configuration example is as follows:

"dependencies": { "@abner/search": "^1.0.0"}

code Use

generally speaking, it is a UI component. You can directly call the SearchLayout component on the required page. The relevant code is as follows:

import { HotBean, SearchLayout } from '@abner/search';

@Entry
  @Component
  struct Index {
    @State hotList: HotBean[] = []

    aboutToAppear(): void {
      this.hotList.push(new HotBean("yiming", { bgColor: Color.Red }))
      this.hotList.push(new HotBean("AbnerMing", { bgColor: Color.Orange }))
      this.hotList.push(new HotBean("harmonyos", { bgColor: Color.Pink }))
      this.hotList.push(new HotBean("yige", { bgColor: Color.Gray }))
    }

    build() {
      RelativeContainer() {
        SearchLayout({
          hotList: this.hotList,
          onItemClick: (text: string) => {
            console.log("===item click:" + text)
          },
          onSearchAttribute: (attr) => {
            attr.onSubmit = (text) => {
              console.log("===click search:" + text)
            }
          }
        })
          .alignRules({
            center: { anchor: '__container__', align: VerticalAlign.Center },
            middle: { anchor: '__container__', align: HorizontalAlign.Center }
          })
      }
      .height('100%')
        .width('100%')
    }
  }

source Code Analysis

search Components

The Search component at the top directly uses the Search component of the system. Although the system has met most of the scenarios, considering other special scenarios, the left and right custom views are also exposed here. You can set the components you need on the left and right of the Search component. It should be noted here that regarding some attributes of the Search component, we should expose the exposure for the convenience of the caller.

RelativeContainer() {

        Column() {
          if (this.searchLeftView != undefined) {
            this.searchLeftView()
          }
        }.id("search_left")
        .height("100%")
        .justifyContent(FlexAlign.Center)

        Search({
          placeholder: this.searchAttribute.placeholder,
          value: this.searchText,
          icon: this.searchAttribute.icon,
          controller: this.controller
        })
          .placeholderFont(this.searchAttribute.placeholderFont)
          .placeholderColor(this.searchAttribute.placeholderColor)
          .textFont(this.searchAttribute.textFont)
          .textAlign(this.searchAttribute.textAlign)
          .copyOption(this.searchAttribute.copyOption)
          .searchIcon(this.searchAttribute.searchIcon)
          .cancelButton(this.searchAttribute.cancelButton)
          .fontColor(this.searchAttribute.fontColor)
          .caretStyle(this.searchAttribute.caretStyle)
          .enableKeyboardOnFocus(this.searchAttribute.enableKeyboardOnFocus)
          .selectionMenuHidden(this.searchAttribute.selectionMenuHidden)
          .maxLength(this.searchAttribute.maxLength)
          .enterKeyType(this.searchAttribute.enterKeyType)
          .type(this.searchAttribute.type)
          .alignRules({
            left: { anchor: "search_left", align: HorizontalAlign.End },
            right: { anchor: "search_right", align: HorizontalAlign.Start },
            center: { anchor: "__container__", align: VerticalAlign.Center }
          })
          .onSubmit((text: string) => {

            this.submit(text)
          })
          .onChange((value) => {
            this.searchResult = value
          })

        Column() {
          if (this.searchRightView != undefined) {
            this.searchRightView()
          }
        }.id("search_right")
        .height("100%")
        .alignRules({
          center: { anchor: "__container__", align: VerticalAlign.Center },
          right: { anchor: "__container__", align: HorizontalAlign.End }
        }).justifyContent(FlexAlign.Center)

      }.width("100%")
      .height(this.searchAttribute.height)
      .margin(this.searchAttribute.margin)

due to the content of historical search, the length of each time is different. Here, we use flexible layout Flex to implement, and set the wrap attribute to FlexWrap.Wrap to support multi-line display.

It should be noted that the historical search needs to store the search records persisting, that is, the user can still see the previous search records when exiting the application and entering again. The user preferences.Preferences used here use temporary variables for storage when the user performs the search, store them when the page exits, and take them out for display when the page is initialized.

In actual development, each historical search entry needs to support clicking to facilitate the caller to execute logic. Here, the click event of the entry needs to be exposed.

To better display the UI view, you can dynamically display the history search component based on whether there is a history record.

RelativeContainer() {

        Text(this.historyTagAttribute.title)
          .fontColor(this.historyTagAttribute.fontColor)
          .fontSize(this.historyTagAttribute.fontSize)
          .fontWeight(this.historyTagAttribute.fontWeight)

        Image(this.historyTagAttribute.deleteSrc)
          .width(this.historyTagAttribute.imageWidth)
          .width(this.historyTagAttribute.imageHeight)
          .alignRules({
            center: { anchor: "__container__", align: VerticalAlign.Center },
            right: { anchor: "__container__", align: HorizontalAlign.End }
          }).onClick(() => {
          //delete
          this.historyList = []
          PreferencesUtil.getPreferencesUtil().delete("searchList")
        })

      }.margin(this.historyTagAttribute.margin)
      .height(this.historyTagAttribute.height)
      .visibility(this.historyList.length == 0 ? Visibility.None : Visibility.Visible)

      Flex({
        direction: FlexDirection.Row, wrap: FlexWrap.Wrap,
        space: { main: LengthMetrics.vp(10), cross: LengthMetrics.vp(10) }
      }) {
        ForEach(this.historyList, (item: string) => {
          Text(item)
            .padding(this.historyItemAttribute.padding)
            .borderRadius(this.historyItemAttribute.borderRadius)
            .fontColor(this.historyItemAttribute.fontColor)
            .fontSize(this.historyItemAttribute.fontSize)
            .fontWeight(this.historyItemAttribute.fontWeight)
            .backgroundColor(this.historyItemAttribute.backgroundColor)
            .onClick(() => {
              if (this.onItemClick != undefined) {
                this.onItemClick(item)
              }
            })

        })
      }
      .margin({ top: this.historyViewMarginTop, bottom: this.historyViewMarginBottom })
      .visibility(this.historyList.length == 0 ? Visibility.None : Visibility.Visible)

Top Searches

the popular search is relatively simple, that is, a Grid Grid list. It should be noted that the UI style of the entry, the object data transfer used here, and the label setting of the prefix are convenient. Of course, in actual development, everyone can expose the UI of the sub-entry and pass it on to the caller, thus the display of the UI is more flexible.

 Text(this.hotTagAttribute.title)
        .fontColor(this.hotTagAttribute.fontColor)
        .fontSize(this.hotTagAttribute.fontSize)
        .fontWeight(this.hotTagAttribute.fontWeight)
        .margin(this.hotTagAttribute.margin)

      Grid() {
        ForEach(this.hotList, (item: HotBean) => {
          GridItem() {
            Row() {
              Text(item.label)
                .backgroundColor(item.labelBgColor)
                .width(this.hotItemAttribute.labelSize)
                .height(this.hotItemAttribute.labelSize)
                .textAlign(TextAlign.Center)
                .borderRadius(this.hotItemAttribute.labelSize)
                .margin({ right: this.hotItemAttribute.labelMarginRight })

              Text(item.name)
                .fontSize(this.hotItemAttribute.fontSize)
                .fontColor(this.hotItemAttribute.fontColor)
                .fontWeight(this.hotItemAttribute.fontWeight)
            }
            .width("100%")
            .backgroundColor(this.hotItemAttribute.backgroundColor)
            .justifyContent(FlexAlign.Start)
            .onClick(() => {
              if (this.onItemClick != undefined) {
                this.onItemClick(item.name!)
              }
            })
          }
        })
      }.columnsTemplate(this.hotItemAttribute.columnsTemplate)
      .margin({ top: this.hotViewMarginTop })
      .rowsGap(this.hotItemAttribute.rowsGap)

In Daily component packaging, if all attributes are uniformly exposed to the attributes at the level of custom components, we will find that there are a lot of attribute settings, and even if widgets are independent, they will appear messy. In view of this situation, in fact, we can package individual widget attributes independently and set them one by one in the form of callback functions. For example, our custom search template has many widget attributes.

onSearchAttribute?: (attribute: SearchViewAttribute) => void //搜索属性
private searchAttribute: SearchViewAttribute = new SearchViewAttribute()

When you need to set the search widget attribute, you can directly call onSearchAttribute:

SearchLayout({
        hotList: this.hotList,
        onItemClick: (text: string) => {
          console.log("===ITEM CLICK:" + text)
        },
        onSearchAttribute: (attr) => {

          attr.placeholder = "search"
          attr.onSubmit = (text) => {
            console.log("===click:" + text)
          }
        }
      })

one thing to note is that when setting properties using callback functions, you must remember to initialize the settings, that is, to call back the properties we initialized and receive the settings of the caller.

aboutToAppear(): void {

    if (this.onSearchAttribute != undefined) {
      this.onSearchAttribute(this.searchAttribute)
    }

  }
0
Subscribe to my newsletter

Read articles from AbnerMing directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

AbnerMing
AbnerMing