色哟哟视频在线观看-色哟哟视频在线-色哟哟欧美15最新在线-色哟哟免费在线观看-国产l精品国产亚洲区在线观看-国产l精品国产亚洲区久久

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

鴻蒙OS開發實戰:【打造自己的搜索入口】

jf_46214456 ? 2024-03-29 20:17 ? 次閱讀

背景

幾乎每家應用中都帶有搜索功能,關于這個功能的頁面不是特別復雜,但如果要追究其背后的一系列邏輯,可能是整個應用中最復雜的一個功能。今天主要實踐目標,會拋開復雜的邏輯,嘗試純粹實現一個“搜索主頁”,主要包含,輸入框文字輸入,熱門詞展示,熱門帖子展示。全篇主要使用到的控件是TextInput, Flex, Swiper。為了貼近實戰,文字輸入過程中,也增加了聯想詞功能。整個示例將在模擬狀態下完成,不做任何網絡請求。

功能清單

  1. 輸入框 - TextInput用法
  2. 按鈕搜索詞刪除 - 觸摸事件透傳用法
  3. 搜索按鈕 - 頁面返回用法
  4. 聯想詞 - Span用法,if...else 渲染用法
  5. 歷史搜索詞 - 行數限制,排序
  6. 熱門搜索詞 - 換行布局,行為識別(打開鏈接,發起搜索)
  7. 熱門帖子 - Swiper用法,Span用法

效果

Screenshot_20231219153940743.pngScreenshot_20231219152615992.png

布局結構

整體頁面分為上下布局兩大部分:

  1. 搜索欄
  2. 可滾動內容區域

開始前熟悉鴻蒙文檔

鴻蒙OS開發更多內容↓點擊HarmonyOSOpenHarmony技術
鴻蒙技術文檔《鴻蒙NEXT星河版開發學習文檔》

搜狗高速瀏覽器截圖20240326151344.png

搜索框

HarmonyOS 提供了Search控件, 這種樣式不太滿足今天要做的需求,所以我還是準備采用TextInput控件重新定制

0000000000011111111.20231116092649.47082174539092627589057824940312.gif

預期的搜索框需要包含基礎的三個功能

  1. 文字輸入
  2. 文字刪除
  3. 提交已輸入的文字(即:準備發起搜索)

這個樣式的實現方式,我采用了左右布局,左布局采用疊加布局方式,翻譯為代碼,表現形式如下

//一. (左布局)輸入框 + (右布局)搜索按鈕
Row() {
  Stack() {
    // 輸入框
    TextInput()
   // 放大鏡圖片 + 刪除按鈕圖片 
    Row() {
      Image(放大鏡圖片)
      if (this.currentInputBoxContent.length != 0) {
         Image(刪除按鈕圖片)
     }
  }
  //搜索按鈕 / 返回按鈕
  Text(this.searchButtonText)
}

這里的Stack布局方式,實際中會引發一個問題:點擊TextInput控件時非常不靈敏,實際情況是“放大鏡圖片+刪除按鈕圖解片”Row布局,消耗了輸入框的觸摸事件。 解決這個問題,可以使用系統提供的hitTestBehavior(HitTestMode.None)這個接口,這個接口的參數提供了4種響應觸摸事件的功能

所以解決此問題只需要添加完這個接口即可恢復正常觸摸事件:見代碼中的 NOTE:父組件不消耗觸摸事件

//一. (左布局)輸入框 + (右布局)搜索按鈕
Row() {
  Stack() {
    // 輸入框
    TextInput()
   // 放大鏡圖片 + 刪除按鈕圖片 
    Row() {
      Image(放大鏡圖片)
      if (this.currentInputBoxContent.length != 0) {
         Image(刪除按鈕圖片)
     }
     .hitTestBehavior(HitTestMode.None) // NOTE:父組件不消耗觸摸事件

  }
  //搜索按鈕 / 返回按鈕
  Text(this.searchButtonText)
}

由于采用的是Stack疊加布局方式,所以要解決的第二個問題是如何將Row布局兩邊對齊Stack即,處于TextInput控件的兩端,根據[Row容器內子元素在水平方向上的排列]可知,在Row布局上添加justifyContent(FlexAlign.SpaceBetween)這句代碼即可。

官方指導示意圖:

0000000000011111111.20231211142810.46416226973241546619287224558714.png

變更后的代碼

//一. (左布局)輸入框 + (右布局)搜索按鈕
Row() {
  Stack() {
    // 輸入框
    TextInput()
   // 放大鏡圖片 + 刪除按鈕圖片 
    Row() {
      Image(放大鏡圖片)
      if (this.currentInputBoxContent.length != 0) {
         Image(刪除按鈕圖片)
     }
     .hitTestBehavior(HitTestMode.None) // NOTE:父組件不消耗觸摸事件
     .justifyContent(FlexAlign.SpaceBetween) // NOTE: 兩端對齊

  }
  //搜索按鈕 / 返回按鈕
  Text(this.searchButtonText)
}

TextInput的構造函數參數說明

  • placeholder: 俗稱:按提示,提示詞,引導詞
  • text: 輸入框已輸入的文字內容

TextInput的屬性方法onChange

用來監聽最新已輸入的文字

  • 這個方法中,我們可以通過判斷內容長度,來設置控制中的搜索按鈕文字,如果有內容,按鈕文案將變為"搜索",反之,按鈕文案變為“取消”,即點擊之后將關閉當前頁面; 同時請求遠端聯想詞的功能也是在這里觸發,注意:本篇文章中的聯想詞僅僅是本地模擬的數據,沒有進行網絡請求,也沒有模擬網絡延時加載,在真實場景中一定要注意用戶的操作行為應該中斷本次聯想詞的網絡請求(即使網絡請求已經發出去,回來之后,也要扔掉拿到的聯想詞數據)。

TextInput的屬性方法enterKeyType

這個用來修改軟件盤上的回車鍵文字提示,這個設置的值為EnterKeyType.Search,所以在中文模式下,你會發現鍵盤上的文字為:搜索

TextInput({ placeholder: '熱詞搜索', text: this.currentInputBoxContent })
  .height('40vp')
  .fontSize('20fp')
  .enterKeyType(EnterKeyType.Search)
  .placeholderColor(Color.Grey)
  .placeholderFont({ size: '14vp', weight: 400 })
  .width('100%')
  .padding({ left: '35vp', right: '35vp' })
  .borderStyle(BorderStyle.Solid)
  .borderWidth('1vp')
  .borderColor(Color.Red)
  .onChange((currentContent) = > {
    this.currentInputBoxContent = currentContent
    if (this.currentInputBoxContent.length != 0) {
      this.searchButtonText = '搜索'
      this.showThinkWord = true
      this.simulatorThinkWord()
    } else {
      this.searchButtonText = '取消'
      this.showThinkWord = false
    }
  })
  .onSubmit((enterKey: EnterKeyType) = > {
    this.submitData(new HistoryWordModel(0, this.currentInputBoxContent));
  })

至此,一個完整的輸入框已完美的完成布局。

歷史搜索詞

一個搜索的新手產品,在講解這部分需求時,會使用簡短的話術:把搜索過的內容顯示出來。

實際情況是比較嚴謹復雜的,最多多展示多少行? 每個歷史詞最多展示多少個字符? 要不要識別詞性?......`, 針對這些嚴格的邏輯,研發人員需要優先解決動態布局的問題,剩下的僅僅是堆積代碼。

Android系統中,針對這種布局場景,需要代碼動態實現,即采用Java方式布局,不幸的是HarmonyOS 中沒有這個說法。

解決方案:

給歷史詞變量添加 @State 修飾,根據視圖高度動態計算行數,然后動態刪除多余關鍵詞記錄

注意:@State 修飾的Array無法對sort方法生效,結合場景描述即:最新搜索的關鍵詞,都要排在第一個位置,所以每發起一次搜索,都要對Array類型的變量進行一次排序,由于@State的限制,我們需要在中間中轉一次。

既然已經知道問題,那么先看一下布局代碼,然后繼續完成需求

首先,對動態布局的需求來講,HarmonyOS中,貌似只能用Flex容器來解決,因為它不僅可以包含子組件,也有自動換行功能,所這里我采用的是Flex容器,如果你要更好的方案,歡迎留言交流

通過視圖高度動態計算行數,可以依賴onAreaChange接口,在其回調中,通過每次的新值結構體(即Area),獲取當前布局高度,然后除以第一次獲取到的高度,這樣即可完成行數的測算

關于Flex自動換行功能,這個要依賴于一個參數wrap: FlexWrap.Wrap

if (this.historyWords.length != 0) {

  Row() {

    Text('歷史搜索')
      .fontSize('20fp')
      .fontWeight(FontWeight.Bold)

    Image($r('app.media.ic_public_delete')).width('20vp').height('20vp')
      .onClick(() = > {
        this.dialogController.open()
      })

  }.width('100%')
  .margin({ top: '20vp' })
  .padding({ left: '10vp', right: '10vp' })
  .justifyContent(FlexAlign.SpaceBetween)

  Flex({ direction: FlexDirection.Row, wrap: FlexWrap.Wrap }) {
    ForEach(this.historyWords, (item: HistoryWordModel, index) = > {

      Text(item.word)
        .fontSize(15)
        .margin(5)
        .fontColor('#5d5d5d')
        .maxLines(1)
        .backgroundColor('#f6f6f6')
        .padding({ left: 20, right: 20, top: 5, bottom: 5 })
        .borderRadius('30vp')
        .textOverflow({ overflow: TextOverflow.Ellipsis })
        .onClick(()= >{
          this.submitData(item);
        })
    })
  }.width('100%')
  .margin({ top: '12vp' })
  .onAreaChange((oldValue: Area, newValue: Area) = > {

    let newHeight = newValue.height as number

    //全局聲明一個歷史詞單行高度變量,初始值設置為0,一旦產生歷史詞,將行高設置為此值
    //后續將以此值為標準來計算歷史詞行數
    if(this.currentHistoryHeight == 0){
       this.currentHistoryHeight = newHeight
    }

    //這里僅僅取整
    this.currentLineNumbs = newHeight / this.currentHistoryHeight

    //MAX_LINES 代表最大行數
    if (this.currentLineNumbs >= MAX_LINES) {
    
      //刪除一個歷史詞,由于historyWords添加了@State修飾,所以數據發生變化后,頁面會刷新
      //頁面刷新后,又會重新觸發此方法
      this.historyWords = this.historyWords.slice(0, this.historyWords.length-1)
    }

  })

}

剛剛提到過一個問題,@State 修飾的Array變量是無法進行排序的。應對這個問題,可以在中間中轉一下,即聲明一個局部Array,先將歷史記錄賦值給它,讓這個局部Array參與sort,然后清空@State修飾的Array變量,最終將局部Array賦值給@State修飾的Array變量,描述有點繁瑣,直接看代碼。

只要發起搜索行為,都會使用到此方法, 另外注意閱讀代碼注釋有NOTE的文字

submitData(wordModel: HistoryWordModel) {
  if (wordModel.word.length != 0) {

    //標識本次搜索的關鍵詞是否存在
    let exist: boolean = false
    
    //如果搜索關鍵詞存在,記錄其位置,如果發現其已經是第一個位置,則不進行排序刷新動作
    let existIndex: number = -1

     //判斷搜索關鍵是否存在于歷史搜索詞列表中
    this.historyWords.forEach((item, index) = > {
         if(item.word === wordModel.word){
           //如果本次搜索關鍵詞已經處于歷史搜索詞的第一個位置,不做刪除動作
           if(index != 0){
             //如果存在,先刪除歷史詞列表中的這個關鍵詞
             this.historyWords.splice(index, 1)
           }
           exist = true
           existIndex = index
         }
    });

    //本次搜索關鍵詞在歷史搜索詞列表中處于第一個位置,因此不做任何額外處理
    //NOTE:真實場景中,這里除了重置狀態,應該發起網絡請求
    if(existIndex == 0){
      console.log('不需要刷新頁面')
      this.currentInputBoxContent = ''
      this.searchButtonText = '取消'
      this.showThinkWord = false
      return
    }

    if(!exist){
      //如果本次搜索關鍵詞在歷史詞列表中不存在,則將其加入其中
      wordModel.index = this.historyWordIndex++
      this.historyWords.push(wordModel)
    } else {
      //如果本次搜索關鍵詞已存在于歷史詞列表中,將其對應的下標加1,因為后續會用下表排序
      //下標越大代表離發生過的搜索行為離當前越近
      this.historyWordIndex++
      this.historyWords.push(new HistoryWordModel(this.historyWordIndex, wordModel.word, wordModel.link))
    }

    //NOTE:這個就是中轉排序的起始代碼
    let Test: Array< HistoryWordModel > = []

    this.historyWords.forEach((item, index) = > {
      Test.push(item)
    })

    Test.sort((a:HistoryWordModel, b:HistoryWordModel) = > {
       return b.index - a.index
    })

    this.historyWords.length = 0

    Test.forEach((item, index) = > {
      this.historyWords.push(item)
    })
    //NOTE:這個就是中轉排序的結束代碼

    this.currentInputBoxContent = ''
    this.searchButtonText = '取消'
    this.showThinkWord = false
  } else {
    Prompt.showToast({
      message: '請輸入關鍵詞',
      bottom: px2vp(this.toastBottom)
    })
  }
}

至此,歷史記錄也實現完成。

聯想詞實現

在已有的搜索場景中,我們都知道,當發起聯想詞時,歷史搜索記錄,熱詞等等,均不會出現在當前屏幕中,為了實現此種效果,我采用了Stack控件疊加覆蓋機制和if...else渲染機制,最終實現完成之后,發現沒必要使用Stack控件,因為用了if...else布局后,像當于會動態掛載和卸載試圖。

聯想詞實現還會碰到的一個問題:高亮關鍵詞, 按照HarmonyOS 的布局機制,一切布局都應該提前計算好,全量布局多種場景樣式,以if...else機制為基礎,完整最終的業務場景效果。

那么,如何提前計算好數據呢?高亮詞在數據結構上我們可以分為三段:前,中,后。如何理解呢?比如搜索關鍵詞“1”,那我的聯想詞無非就幾種情況123,213,1,231,那么,我聲明三個變量s, m, e, 分別代表前,中,后,此時你會發現三個變量是完全可以覆蓋所有的匹配場景的。這種方式暫且命名為:分割,“分割”后,在最終展示時,由于需要高亮文字,所以,我們還需要知曉已“分割”的文字中,到底哪一段應該高亮,基于此種考慮,需要額外聲明高亮下標,參考“前,中,后”,將下標分別定義為0,1,2

具體實現,看代碼吧

Stack() {
  //聯想詞需要展示時
  if (this.showThinkWord) {
    Column() {
      //遍歷聯想詞
      ForEach(this.thinkWords, (item: ThinkWordModel, index) = > {

        //NOTE: Span控件可以實現文字分段多種樣式
        Text() {
          //判斷一條聯想詞數據的“前”
          if (item.wordStart && item.wordStart.length != 0) {
            Span(item.wordStart)
              .fontSize(18)
              .fontColor(item.highLightIndex == 0 ? item.highLightColor : item.normalColor)
          }
          //判斷一條聯想詞數據的“中”
          if (item.wordMid && item.wordMid.length != 0) {
            Span(item.wordMid)
              .fontSize(18)
              .fontColor(item.highLightIndex == 1 ? item.highLightColor : item.normalColor)
          }
          //判斷一條聯想詞數據的“后”
          if (item.wordEnd && item.wordEnd.length != 0) {
            Span(item.wordEnd)
              .fontSize(18)
              .fontColor(item.highLightIndex == 2 ? item.highLightColor : item.normalColor)
          }
        }......

      })

    }......

  } else {
  
// 沒有聯想詞時,系統機制會講聯想詞視圖卸載掉,即if中的視圖會完全從視圖節點中拿掉
    Column() {
      //二. 搜索歷史
      if (this.historyWords.length != 0) {
        ......
      }

      //三. 熱門搜索
      Text('熱門搜索')......

      Flex({ direction: FlexDirection.Row, wrap: FlexWrap.Wrap }) {
        ForEach(this.hotWords, (item: HotWordsModel, index) = > {
          Text(item.word)......
        })
      }
   

      //四. 熱門帖子
      Text('熱門帖子')
      Swiper(this.swiperController) {

        LazyForEach(this.data, (item: string, index: number) = > {
           ......
        }, item = > item)

      }
     
    }

  }

}

熱門帖子實現

在整個搜索主頁中,這個功能可能算比較簡單的,在Scroll控件中放置Swiper控件,然后按照官方文檔,循環塞入數據,整個效果即可實現。 這個里邊用到了Span,由于我們在聯想詞實現時已經實踐過了Span, 這里就不再描述。

NOTE:為了迎合需求,將滑動指示隱藏掉,indicator(false)false代表隱藏滑動指示

//四. 熱門帖子
Text('熱門帖子')
  .fontSize('20fp')
  .width('100%')
  .fontWeight(FontWeight.Bold)
  .margin({ left: '10vp', top: '20vp' })

Swiper(this.swiperController) {

  //data僅僅是為了循環,數據總個數是3  
  LazyForEach(this.data, (item: string, index: number) = > {

    //每一頁Swiper內容視圖,通過 @Builder 修飾的方法進行一次封裝
    if (index == 0) {
      this.swiperList(this.hotTopicList1)
    } else if (index == 1) {
      this.swiperList(this.hotTopicList2)
    } else if (index == 2) {
      this.swiperList(this.hotTopicList3)
    }

  }, item = > item)

}
.padding({ bottom: '50vp' })
.displayMode(SwiperDisplayMode.AutoLinear)
.margin({ top: '12vp' })
.cachedCount(2)
.index(1)
.indicator(false)
.loop(true)
.itemSpace(0)
.curve(Curve.Linear)

完整代碼

主頁面代碼 SearchUI.ets

import common from '@ohos.app.ability.common';
import Prompt from '@system.prompt';
import router from '@ohos.router';
import dataPreferences from '@ohos.data.preferences';

import { CommonConstants } from '../../common/CommonConstants';
import HotWordsModel from '../../viewmodel/HotWordsModel';
import mediaquery from '@ohos.mediaquery';
import ThinkWordModel from '../../viewmodel/ThinkWordModel';
import HistoryWordModel from '../../viewmodel/HistoryWordModel';

const MAX_LINES: number = 3;

@Entry
@Component
struct SearchUIIndex {
  private hotTopicList1: Array< string > = [
    '四種醉駕可從寬處理',
    '冰面摔倒至腹腔出血',
    '董宇輝復播',
    '朱一龍拍戲受傷送醫',
    '音樂節求婚觀眾退票',
    '周杰倫新歌歌名',
    '用好“改革開放”這關鍵一招',
    '男子冬釣失聯 遺體在冰縫中被發現',
    '女孩用科目三跳繩 獲省級比賽第1名',
    '美麗鄉村 幸福生活',
  ]
  private hotTopicList2: Array< string > = [
    '醉駕輕微可不起訴',
    '狄龍被驅逐',
    '勞榮枝希望還清花唄',
    '周海媚告別儀式完成',
    '董宇輝兼任副總裁',
    '小米智能鎖自動開門',
    '李家超:基本法第23條明年內實施',
    '山東兩幼師出租房內遇害',
    '南京同曦老總大鬧裁判休息室',
    '女子出車禍鯊魚夾插入后腦勺',
    '官方辟謠南京過江隧道連環追尾',
    '上海地鐵開啟瘋狂動物城模式',
  ]
  private hotTopicList3: Array< string > = [
    '朱丹好友起訴朱丹',
    '"中年大叔"自拍刷屏',
    '西方臻選回應被封號',
    '草莓價格大跳水',
    '庫里三分球8中0',
    '國足開啟亞洲杯備戰',
  ]

  private currentHistoryHeight: number = 0

  @State toastBottom: number = 0;
  @State currentInputBoxContent: string = ''
  private controller = new TextInputController()
  private hotWords: Array< HotWordsModel > = []
  @State historyWords: Array< HistoryWordModel > = []
  @State inputBoxFocus: boolean = false;
  @State hotwordLines: number = 0
  @State searchButtonText: string = '取消'
  private swiperController: SwiperController = new SwiperController()
  private data: MyDataSource = new MyDataSource([])
  private currentLineNumbs: number = 0
  private context = getContext(this) as common.UIAbilityContext;
  @State screenDirection: number = this.context.config.direction
  @State showThinkWord: boolean = false
  @State thinkWords: Array< ThinkWordModel > = []

  // 當設備橫屏時條件成立
  listener = mediaquery.matchMediaSync('(orientation: landscape)');
  dialogController: CustomDialogController = new CustomDialogController({
    builder: CustomDialogExample({
      historyWords: $historyWords,
      title: '確認全部刪除?',
      cancel: this.onCancel,
      confirm: this.onAccept,
    }),
    alignment: DialogAlignment.Default, // 可設置dialog的對齊方式,設定顯示在底部或中間等,默認為底部顯示
  })

  onCancel() {

  }

  onAccept() {
    console.log('當前數組長度:' + this.historyWords.length)
  }

  configureParamsByScreenDirection() {
    if (this.screenDirection == 0) {
      this.toastBottom = (AppStorage.Get(CommonConstants.ScreenHeight) as number) / 2
    } else {
      this.toastBottom = (AppStorage.Get(CommonConstants.ScreenWidth) as number) / 2
    }
  }

  DATASOURCE: string[] = [
    '聯想詞測試',
    '測試聯想詞',
    '全城尋找測試在哪里',
    '找不到人',
    '哈爾濱的啤酒好喝',
    'HarmonyOS版權歸屬華為'
  ]

  simulatorThinkWord() {

    this.thinkWords = []

    this.DATASOURCE.forEach((value: string, index: number) = > {
      let s: string = ''
      let m: string = ''
      let e: string = ''
      let hIndex: number = -1

      let position = value.indexOf(this.currentInputBoxContent)
      if (position != -1) {

        if (position == 0) {
          s = value.substr(0, this.currentInputBoxContent.length)
        } else {
          s = value.substr(0, position)
        }

        if (s.length < value.length) {

          position = value.substr(s.length).indexOf(this.currentInputBoxContent)

          if (position == -1) {
            m = value.substr(s.length)
          } else {
            m = value.substr(s.length, this.currentInputBoxContent.length)
          }

          if (s.length + m.length < value.length) {
            e = value.substr(s.length + m.length)
          }

        }

        if (s === this.currentInputBoxContent) {
          hIndex = 0
        } else if (m === this.currentInputBoxContent) {
          hIndex = 1
        } else if (e === this.currentInputBoxContent) {
          hIndex = 2
        }

        this.thinkWords.push(new ThinkWordModel('#000000', '#ff0000', hIndex, s, m, e))
      }

    })

  }

  onPortrait(mediaQueryResult) {
    if (mediaQueryResult.matches) {
      //橫屏
      this.screenDirection = 1
    } else {
      //豎屏
      this.screenDirection = 0
    }

    setTimeout(() = > {
      this.configureParamsByScreenDirection()
    }, 300)
  }

  aboutToAppear() {
    this.searchButtonText = '取消'

    let list = []
    for (var i = 1; i <= 3; i++) {
      list.push(i.toString());
    }
    this.data = new MyDataSource(list)

    this.hotWords.push(new HotWordsModel('HarmonyOS', '#E84026', 'https://developer.harmonyos.com/'))
    this.hotWords.push(new HotWordsModel('實名認證', '#5d5d5d'))
    this.hotWords.push(new HotWordsModel('HMS Core', '#5d5d5d'))
    this.hotWords.push(new HotWordsModel('Serverless', '#5d5d5d'))
    this.hotWords.push(new HotWordsModel('生態市場', '#5d5d5d'))
    this.hotWords.push(new HotWordsModel('應用上架', '#5d5d5d'))
    this.hotWords.push(new HotWordsModel('倉頡', '#5d5d5d'))
    this.hotWords.push(new HotWordsModel('HUAWEI HiAI', '#5d5d5d'))
    this.hotWords.push(new HotWordsModel('表盤', '#5d5d5d'))
    this.hotWords.push(new HotWordsModel('推送', '#5d5d5d'))
    this.hotWords.push(new HotWordsModel('主題', '#5d5d5d'))
    this.hotWords.push(new HotWordsModel('公測', '#5d5d5d'))

    let portraitFunc = this.onPortrait.bind(this)
    this.listener.on('change', portraitFunc)

    this.toastBottom = (AppStorage.Get(CommonConstants.ScreenHeight) as number) / 2

    dataPreferences.getPreferences(getContext(this), 'HistoryWord', (err, preferences) = > {
      if (err) {
        console.error(`Failed to get preferences. Code:${err.code},message:${err.message}`);
        return;
      }
      console.info('Succeeded in getting preferences.');
      // 進行相關數據操作
    })

  }

  historyWordIndex: number = 1

  submitData(wordModel: HistoryWordModel) {
    if (wordModel.word.length != 0) {

      let exist: boolean = false
      let existIndex: number = -1

      this.historyWords.forEach((item, index) = > {
           if(item.word === wordModel.word){
             if(index != 0){
               this.historyWords.splice(index, 1)
             }
             exist = true
             existIndex = index
           }
      });

      if(existIndex == 0){
        console.log('不需要刷新頁面')
        this.currentInputBoxContent = ''
        this.searchButtonText = '取消'
        this.showThinkWord = false
        return
      }

      if(!exist){
        wordModel.index = this.historyWordIndex++
        this.historyWords.push(wordModel)
      } else {
        this.historyWordIndex++
        this.historyWords.push(new HistoryWordModel(this.historyWordIndex, wordModel.word, wordModel.link))
      }

      let Test: Array< HistoryWordModel > = []

      this.historyWords.forEach((item, index) = > {
        Test.push(item)
      })

      Test.sort((a:HistoryWordModel, b:HistoryWordModel) = > {
         return b.index - a.index
      })

      this.historyWords.length = 0

      Test.forEach((item, index) = > {
        this.historyWords.push(item)
      })

      this.currentInputBoxContent = ''
      this.searchButtonText = '取消'
      this.showThinkWord = false
    } else {
      Prompt.showToast({
        message: '請輸入關鍵詞',
        bottom: px2vp(this.toastBottom)
      })
    }
  }

  build() {

    Column() {

      //一. 輸入框 + 搜索按鈕
      Row() {
        Stack() {
          TextInput({ placeholder: '熱詞搜索', controller: this.controller, text: this.currentInputBoxContent })
            .height('40vp')
            .fontSize('20fp')
            .enterKeyType(EnterKeyType.Search)
            .placeholderColor(Color.Grey)
            .placeholderFont({ size: '14vp', weight: 400 })
            .width('100%')
            .padding({ left: '35vp', right: '35vp' })
            .borderStyle(BorderStyle.Solid)
            .borderWidth('1vp')
            .borderColor(Color.Red)
            .onChange((currentContent) = > {
              this.currentInputBoxContent = currentContent
              if (this.currentInputBoxContent.length != 0) {
                this.searchButtonText = '搜索'
                this.showThinkWord = true
                this.simulatorThinkWord()
              } else {
                this.searchButtonText = '取消'
                this.showThinkWord = false
              }
            })
            .onSubmit((enterKey: EnterKeyType) = > {
              this.submitData(new HistoryWordModel(0, this.currentInputBoxContent));
            })

          Row() {
            Image($r('app.media.ic_public_input_search')).width('20vp').height('20vp')
            if (this.currentInputBoxContent.length != 0) {
              Image($r('app.media.ic_public_cancel_filled')).width('20vp').height('20vp')
                .onClick(() = > {
                  this.currentInputBoxContent = ''
                })
            }
          }.width('100%')
          .hitTestBehavior(HitTestMode.None)
          .justifyContent(FlexAlign.SpaceBetween)
          .padding({ left: '10vp', right: '10vp' })

        }.alignContent(Alignment.Start)
        .width('83%')

        Text(this.searchButtonText)
          .fontSize('15fp')
          .borderRadius('10vp')
          .padding('5vp')
          .backgroundColor(Color.Red)
          .fontColor(Color.White)
          .width('15%')
          .textAlign(TextAlign.Center)
          .onClick(() = > {
            if ('搜索' === this.searchButtonText) {
              this.submitData(new HistoryWordModel(0, this.currentInputBoxContent));
            } else {
              if ("1" === router.getLength()) {
                this.context.terminateSelf()
              } else {
                router.back()
              }
            }
          })
          .stateStyles({
            focused: {
              .backgroundColor(Color.Orange)
            },
            pressed: {
              .backgroundColor(Color.Orange)
            },
            normal: {
              .backgroundColor(Color.Red)
            }
          })

      }.justifyContent(FlexAlign.SpaceBetween)
      .padding({ left: '10vp', right: '10vp' })
      .width('100%')

      Scroll() {
        Stack() {

          if (this.showThinkWord) {
            Column() {
              ForEach(this.thinkWords, (item: ThinkWordModel, index) = > {

                Text() {
                  if (item.wordStart && item.wordStart.length != 0) {
                    Span(item.wordStart)
                      .fontSize(18)
                      .fontColor(item.highLightIndex == 0 ? item.highLightColor : item.normalColor)
                  }
                  if (item.wordMid && item.wordMid.length != 0) {
                    Span(item.wordMid)
                      .fontSize(18)
                      .fontColor(item.highLightIndex == 1 ? item.highLightColor : item.normalColor)
                  }
                  if (item.wordEnd && item.wordEnd.length != 0) {
                    Span(item.wordEnd)
                      .fontSize(18)
                      .fontColor(item.highLightIndex == 2 ? item.highLightColor : item.normalColor)
                  }
                }
                .width('100%')
                .height(50)
                .textAlign(TextAlign.Center)
                .fontSize(18)
                .textAlign(TextAlign.Start)
                .maxLines(1)
                .textOverflow({ overflow: TextOverflow.Ellipsis })

                Divider().width('100%').height(1).color(Color.Grey)
              })

            }
            .width('100%').height('100%')
            .padding({ left: '12vp', right: '12vp' })
            .backgroundColor(Color.White)

          } else {

            Column() {
              //二. 搜索歷史
              if (this.historyWords.length != 0) {

                Row() {

                  Text('歷史搜索')
                    .fontSize('20fp')
                    .fontWeight(FontWeight.Bold)

                  Image($r('app.media.ic_public_delete')).width('20vp').height('20vp')
                    .onClick(() = > {
                      this.dialogController.open()
                    })

                }.width('100%')
                .margin({ top: '20vp' })
                .padding({ left: '10vp', right: '10vp' })
                .justifyContent(FlexAlign.SpaceBetween)

                Flex({ direction: FlexDirection.Row, wrap: FlexWrap.Wrap }) {
                  ForEach(this.historyWords, (item: HistoryWordModel, index) = > {

                    Text(item.word)
                      .fontSize(15)
                      .margin(5)
                      .fontColor('#5d5d5d')
                      .maxLines(1)
                      .backgroundColor('#f6f6f6')
                      .padding({ left: 20, right: 20, top: 5, bottom: 5 })
                      .borderRadius('30vp')
                      .textOverflow({ overflow: TextOverflow.Ellipsis })
                      .onClick(()= >{
                        this.submitData(item);
                      })
                  })
                }.width('100%')
                .margin({ top: '12vp' })
                .onAreaChange((oldValue: Area, newValue: Area) = > {

                  let newHeight = newValue.height as number

                  if(this.currentHistoryHeight == 0){
                     this.currentHistoryHeight = newHeight
                  }

                  this.currentLineNumbs = newHeight / this.currentHistoryHeight

                  console.log('當前行數: ' + this.currentLineNumbs)

                  if (this.currentLineNumbs >= MAX_LINES) {
                    this.historyWords = this.historyWords.slice(0, this.historyWords.length-1)
                  }

                })

              }

              //三. 熱門搜索
              Text('熱門搜索')
                .fontSize('20fp')
                .width('100%')
                .fontWeight(FontWeight.Bold)
                .margin({ left: '10vp', top: '20vp' })

              Flex({ direction: FlexDirection.Row, wrap: FlexWrap.Wrap }) {
                ForEach(this.hotWords, (item: HotWordsModel, index) = > {

                  Text(item.word)
                    .fontSize(15)
                    .margin(5)
                    .fontColor(item.wordColor)
                    .backgroundColor('#f6f6f6')
                    .padding({ left: 20, right: 20, top: 5, bottom: 5 })
                    .borderRadius('30vp')
                    .onClick(() = > {
                      if (this.hotWords[index].wordLink && this.hotWords[index].wordLink.length != 0) {

                        router.pushUrl({ url: 'custompages/WebView', params: {
                          "targetUrl": this.hotWords[index].wordLink,
                        } })
                          .then(() = > {
                            console.info('Succeeded in jumping to the second page.')
                          }).catch((error) = > {
                          console.log(error)
                        })
                      } else if(this.hotWords[index].word){
                        this.submitData(new HistoryWordModel(0, this.hotWords[index].word));
                      }
                    })
                })
              }
              .width('100%')
              .margin({ top: '12vp' })
              .onAreaChange((oldValue: Area, newValue: Area) = > {
                console.log('熱詞高度:' + newValue.height + '')
              })

              //四. 熱門帖子
              Text('熱門帖子')
                .fontSize('20fp')
                .width('100%')
                .fontWeight(FontWeight.Bold)
                .margin({ left: '10vp', top: '20vp' })

              Swiper(this.swiperController) {

                LazyForEach(this.data, (item: string, index: number) = > {

                  if (index == 0) {
                    this.swiperList(this.hotTopicList1)
                  } else if (index == 1) {
                    this.swiperList(this.hotTopicList2)
                  } else if (index == 2) {
                    this.swiperList(this.hotTopicList3)
                  }

                }, item = > item)

              }
              .padding({ bottom: '50vp' })
              .displayMode(SwiperDisplayMode.AutoLinear)
              .margin({ top: '12vp' })
              .cachedCount(2)
              .index(1)
              .indicator(false)
              .loop(true)
              .itemSpace(0)
              .curve(Curve.Linear)
            }

          }

        }
      }.scrollBar(BarState.Off)

    }.padding({ top: px2vp(AppStorage.Get(CommonConstants.StatusBarHeight)) })

  }

  @Builder swiperList(data: string[]){
    Column() {
      ForEach(data, (da, i) = > {

        if(i == 0){
          Text(){
            Span((i+1)+'. ').fontColor('#E84026').fontSize(20)
            Span(da).fontColor('#5d5d5d').fontSize(18)
          }.width('100%').height(50)
        } else if(i == 1){
          Text(){
            Span((i+1)+'. ').fontColor('#ED6F21').fontSize(20)
            Span(da).fontColor('#5d5d5d').fontSize(18)
          }.width('100%').height(50)
        } else if(i == 2){
          Text(){
            Span((i+1)+'. ').fontColor('#F9A01E').fontSize(20)
            Span(da).fontColor('#5d5d5d').fontSize(18)
          }.width('100%').height(50)
        } else {
          Text((i + 1) + '. '+ da)
            .fontColor('#5d5d5d')
            .width('100%')
            .height(50)
            .textAlign(TextAlign.Center)
            .fontSize(18)
            .textAlign(TextAlign.Start)
        }

        if (i != this.hotTopicList1.length - 1) {
          Divider().width('100%').vertical(false)
        }

      })
    }.borderRadius('10vp')
    .margin({ left: '10vp', right: '10vp', bottom: '25vp' })
    .backgroundColor('#f6f6f6')
    .padding('10vp')

  }
}

@CustomDialog
struct CustomDialogExample {
  controller: CustomDialogController
  title: string = ''
  @Link historyWords: Array< string >
  cancel: () = > void
  confirm: () = > void

  build() {
    Column() {
      Text(this.title).fontSize(20).margin({ top: 10, bottom: 10 })
      Flex({ justifyContent: FlexAlign.SpaceAround }) {
        Button('取消')
          .onClick(() = > {
            this.controller.close()
            this.cancel()
          }).backgroundColor(0xffffff).fontColor(Color.Black)
        Button('確認')
          .onClick(() = > {
            this.controller.close()
            this.confirm()
            this.historyWords = []
          }).backgroundColor(0xffffff).fontColor(Color.Red)
      }.margin({ bottom: 10 })
    }
  }
}

class MyDataSource implements IDataSource {
  private list: number[] = []
  private listener: DataChangeListener

  constructor(list: number[]) {
    this.list = list
  }

  totalCount(): number {
    return this.list.length
  }

  getData(index: number): any {
    return this.list[index]
  }

  registerDataChangeListener(listener: DataChangeListener): void {
    this.listener = listener
  }

  unregisterDataChangeListener() {
  }
}

歷史詞數據結構 HistoryWordModel.ets

export default class HistoryWordModel {
  public index: number
  public link: string
  public word: string

  constructor(index, word, link?) {
    this.index = index
    this.link = link
    this.word = word
  }
}

熱詞數據結構 HotWordModel.ets

export default class HotWordModel {
  public word: string //詞語
  public wordColor: string //文字顏色
  public wordLink?: string //文字超鏈接

  constructor(word, wordColor, wordLink?) {
    this.word = word
    this.wordColor = wordColor
    this.wordLink = wordLink
  }
}

聯想詞數據結構 ThinkWordModel.ets

export default class ThinkWordModel {
   public normalColor: string
   public highLightColor: string
   public wordStart: string //詞語
   public wordMid: string //文字顏色
   public wordEnd: string //文字超鏈接
   public highLightIndex: number

   constructor(normalColor: string, highLightColor: string, highLightIndex: number,wordStart?: string, wordMid?: string,
               wordEnd?: string) {
      this.normalColor = normalColor
      this.highLightColor = highLightColor
      this.highLightIndex = highLightIndex
      this.wordStart = wordStart
      this.wordMid = wordMid
      this.wordEnd = wordEnd
   }

}
鴻蒙開發技術:mau123789記住是v喔

總結

  1. 對于Android&iOS開發者來講,在HarmonyOS中實現動態布局,還是非常容易陷入之前的開發思路中
  2. 新的平臺,熟悉API很重要
聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 移動開發
    +關注

    關注

    0

    文章

    52

    瀏覽量

    9809
  • 鴻蒙
    +關注

    關注

    57

    文章

    2388

    瀏覽量

    42964
  • HarmonyOS
    +關注

    關注

    79

    文章

    1980

    瀏覽量

    30401
  • OpenHarmony
    +關注

    關注

    25

    文章

    3744

    瀏覽量

    16473
  • 鴻蒙OS
    +關注

    關注

    0

    文章

    190

    瀏覽量

    4488
收藏 人收藏

    評論

    相關推薦

    鴻蒙實戰項目開發:【短信服務】

    兩位前阿里高級研發工程師聯合打造的 《鴻蒙NEXT星河版OpenHarmony開發文檔》 里面內容包含了(ArkTS、ArkUI開發組件、Stage模型、多端部署、分布式應用
    發表于 03-03 21:29

    鴻蒙OS崛起,鴻蒙應用開發工程師成市場新寵

    應用的形態也在發生著翻天覆地的變化。作為全球領先的移動操作系統和智能終端制造商,華為公司自主研發的鴻蒙OS應運而生,致力于構建一個統一的分布式操作系統,為各行各業的應用開發帶來全新的可能性。 一、
    發表于 04-29 17:32

    谷歌全新推出的Fuchsia OS,對鴻蒙有什么影響?

    正式公測,搭載的設備也沒有亮相。回想安卓系統對于塞班、Windows Phone的超越,鴻蒙OS也可以說是占得了先機,能夠吸引更多的開發者和優質軟件,才能夠取得更大的優勢。在未來,能夠在不同形態的硬件設備中,都搭建起
    發表于 09-08 16:12

    初識鴻蒙OS

    的元氣叫做鴻蒙。華為HarmonyOS,中文名字取做鴻蒙,寓意將帶領國人在操作系統領域開天辟地,擺脫對IOS和Android兩大生態、兩座大山的依賴,走出國人自己的軟件生態之路。鴻蒙
    發表于 09-10 15:28

    鴻蒙OS適用的全場景到底什么意思?

    鴻蒙系統(HarmonyOS),第一款基于微內核的全場景分布式OS,是華為自主研發的操作系統。華為在開發者大會HDC.2019上正式發布了鴻蒙系統,該系統將率先部署在智慧屏、車載終端、
    發表于 09-25 09:25

    #HarmonyOS征文#—鴻蒙OS開發流程及DevEco Studio安裝

    鴻蒙OS的完整開發流程1. 注冊并實名認證華為開發者賬號鴻蒙官網:www.harmonyos.com注冊登錄華為賬號后,進行實名認證登錄之后
    發表于 07-22 11:43

    鴻蒙 OS 應用開發初體驗

    的操作系統平臺和開發框架。HarmonyOS 的目標是實現跨設備的無縫協同和高性能。 DevEco Studio 對標 Android Studio,開發鴻蒙 OS 應用的 IDE。
    發表于 11-02 19:38

    如何尋找鴻蒙源碼入口

    因為鴻蒙源碼剛開源,所以網上是不會搜到源碼講解的,搜到的基本都是鴻蒙OS應用開發教程,這個和鴻蒙源碼是兩回事哈。
    的頭像 發表于 10-14 14:22 ?4125次閱讀
    如何尋找<b class='flag-5'>鴻蒙</b>源碼<b class='flag-5'>入口</b>

    華為鴻蒙os2.0系統官網報名入口

    之前華為鴻蒙os2.0系統的公測推進迎來更多的內容,在適配機型上也有所增加。另外,在今晚8點華為鴻蒙os2.0系統將會正式發布,但是有不少用戶仍然不清楚在哪里報名申請,下面我們一起來看
    的頭像 發表于 06-02 15:43 ?3.2w次閱讀

    華為鴻蒙系統申請入口 如何升級鴻蒙系統

    。 一.華為鴻蒙系統申請入口 1.打開“我的華為”APP 2.找到首頁的“升級嘗鮮” 3.點擊“立即嘗鮮” 4.選擇自己的機型,并且點擊“報名公測” 5.下載描述文件后就可以接到系統推送了。 二.華為
    的頭像 發表于 06-08 11:32 ?2.3w次閱讀

    鴻蒙os怎么升級

    6月2日,華為正式發布了鴻蒙armonyOS 2系統,那么鴻蒙os如何升級?現將鴻蒙os升級方式告知如下。
    的頭像 發表于 06-08 16:26 ?2772次閱讀

    華為鴻蒙開發者官網申請入口

    華為鴻蒙開發者官網申請入口介紹,6 月 2 日,華為正式發布了鴻蒙系統,肯定許多小伙伴想嘗鮮體驗,那么華為鴻蒙
    的頭像 發表于 06-16 09:29 ?3.6w次閱讀

    華為開發者大會2021鴻蒙os在哪場

    華為開發者大會2021將在10月22日-24日舉辦,地點為東莞松山湖,鴻蒙os 3.0或將與我們見面,那么華為開發者大會2021鴻蒙
    的頭像 發表于 10-22 15:24 ?1933次閱讀

    RISC-V MCU開發實戰 (三):移植鴻蒙OS項目

    移植鴻蒙OS項目
    的頭像 發表于 11-01 11:08 ?2971次閱讀
    RISC-V MCU<b class='flag-5'>開發</b><b class='flag-5'>實戰</b> (三):移植<b class='flag-5'>鴻蒙</b><b class='flag-5'>OS</b>項目

    鴻蒙OS開發之 融合搜索概述

    HarmonyOS 融合搜索開發者提供搜索引擎級的全文搜索能力,可支持應用內搜索和系統全局搜索
    的頭像 發表于 01-29 16:24 ?641次閱讀
    <b class='flag-5'>鴻蒙</b><b class='flag-5'>OS</b><b class='flag-5'>開發</b>之  融合<b class='flag-5'>搜索</b>概述
    主站蜘蛛池模板: 在线成 人av影院| 久在线观看福利视频| 色姐妹久久综合在线av| 动漫美女搞鸡| 亚洲国产成人久久一区www妖精| 精品国产mmd在线观看| 中文国产乱码在线人妻一区二区 | 亚洲国产精品自在自线观看 | 99午夜高清在线视频在观看| 日韩精品在线看| 狠狠爱亚洲五月婷婷av| 97免费观看视频| 亚洲AV成人片色在线观看网站 | 香蕉人人超人人超碰超国产| 久青草国产观看在线视频| 俄罗斯人与动ZOZ0| 在线国产视频观看| 帅哥操美女| 伦理片飘花免费影院| 国产精品亚洲国产三区| 999久久久无码国产精蜜柚| 性虎成人网| 漂亮的保姆3集电影免费观看中文| 国产一区二区波多野结衣| 被窝伦理电影午夜| 在线亚洲视频无码天堂| 无限资源在线看影院免费观看| 木凡的天空在线收听| 精品国产乱码久久久久久软件| 百性阁综合社区| 1级午夜影院费免区| 亚洲AV成人无码999WWW| 拍戏被CAO翻了H| 快乐激情站| 国模啪啪久久久久久久| 俄罗斯9一14 young处| 99re1久久热在线播放| 亚洲性夜色噜噜噜网站2258KK| 午夜国产高清精品一区免费| 全部老头和老太XXXXX| 美女屁股软件|