背景
幾乎每家應(yīng)用中都帶有搜索功能,關(guān)于這個(gè)功能的頁面不是特別復(fù)雜,但如果要追究其背后的一系列邏輯,可能是整個(gè)應(yīng)用中最復(fù)雜的一個(gè)功能。今天主要實(shí)踐目標(biāo),會拋開復(fù)雜的邏輯,嘗試純粹實(shí)現(xiàn)一個(gè)“搜索主頁”,主要包含,輸入框文字輸入,熱門詞展示,熱門帖子展示。全篇主要使用到的控件是TextInput, Flex, Swiper。為了貼近實(shí)戰(zhàn),文字輸入過程中,也增加了聯(lián)想詞功能。整個(gè)示例將在模擬狀態(tài)下完成,不做任何網(wǎng)絡(luò)請求。
功能清單
- 輸入框 - TextInput用法
- 按鈕搜索詞刪除 - 觸摸事件透傳用法
- 搜索按鈕 - 頁面返回用法
- 聯(lián)想詞 - Span用法,if...else 渲染用法
- 歷史搜索詞 - 行數(shù)限制,排序
- 熱門搜索詞 - 換行布局,行為識別(打開鏈接,發(fā)起搜索)
- 熱門帖子 - Swiper用法,Span用法
效果
布局結(jié)構(gòu)
整體頁面分為上下布局兩大部分:
- 搜索欄
- 可滾動內(nèi)容區(qū)域
開始前熟悉鴻蒙文檔
鴻蒙OS開發(fā) | 更多內(nèi)容↓點(diǎn)擊 | HarmonyOS與OpenHarmony技術(shù) |
---|---|---|
鴻蒙技術(shù)文檔 | 《鴻蒙NEXT星河版開發(fā)學(xué)習(xí)文檔》 |
搜索框
HarmonyOS 提供了Search控件, 這種樣式不太滿足今天要做的需求,所以我還是準(zhǔn)備采用TextInput控件重新定制
預(yù)期的搜索框需要包含基礎(chǔ)的三個(gè)功能
- 文字輸入
- 文字刪除
- 提交已輸入的文字(即:準(zhǔn)備發(fā)起搜索)
這個(gè)樣式的實(shí)現(xiàn)方式,我采用了左右布局,左布局采用疊加布局方式,翻譯為代碼,表現(xiàn)形式如下
//一. (左布局)輸入框 + (右布局)搜索按鈕
Row() {
Stack() {
// 輸入框
TextInput()
// 放大鏡圖片 + 刪除按鈕圖片
Row() {
Image(放大鏡圖片)
if (this.currentInputBoxContent.length != 0) {
Image(刪除按鈕圖片)
}
}
//搜索按鈕 / 返回按鈕
Text(this.searchButtonText)
}
這里的Stack布局方式,實(shí)際中會引發(fā)一個(gè)問題:點(diǎn)擊TextInput控件時(shí)非常不靈敏,實(shí)際情況是“放大鏡圖片+刪除按鈕圖解片”Row布局,消耗了輸入框的觸摸事件。 解決這個(gè)問題,可以使用系統(tǒng)提供的hitTestBehavior(HitTestMode.None)
這個(gè)接口,這個(gè)接口的參數(shù)提供了4種響應(yīng)觸摸事件的功能
所以解決此問題只需要添加完這個(gè)接口即可恢復(fù)正常觸摸事件:見代碼中的 NOTE:父組件不消耗觸摸事件
//一. (左布局)輸入框 + (右布局)搜索按鈕
Row() {
Stack() {
// 輸入框
TextInput()
// 放大鏡圖片 + 刪除按鈕圖片
Row() {
Image(放大鏡圖片)
if (this.currentInputBoxContent.length != 0) {
Image(刪除按鈕圖片)
}
.hitTestBehavior(HitTestMode.None) // NOTE:父組件不消耗觸摸事件
}
//搜索按鈕 / 返回按鈕
Text(this.searchButtonText)
}
由于采用的是Stack疊加布局方式,所以要解決的第二個(gè)問題是如何將Row布局兩邊對齊Stack即,處于TextInput控件的兩端,根據(jù)[Row容器內(nèi)子元素在水平方向上的排列]可知,在Row布局上添加justifyContent(FlexAlign.SpaceBetween)
這句代碼即可。
官方指導(dǎo)示意圖:
變更后的代碼
//一. (左布局)輸入框 + (右布局)搜索按鈕
Row() {
Stack() {
// 輸入框
TextInput()
// 放大鏡圖片 + 刪除按鈕圖片
Row() {
Image(放大鏡圖片)
if (this.currentInputBoxContent.length != 0) {
Image(刪除按鈕圖片)
}
.hitTestBehavior(HitTestMode.None) // NOTE:父組件不消耗觸摸事件
.justifyContent(FlexAlign.SpaceBetween) // NOTE: 兩端對齊
}
//搜索按鈕 / 返回按鈕
Text(this.searchButtonText)
}
TextInput的構(gòu)造函數(shù)參數(shù)說明
- placeholder: 俗稱:按提示,提示詞,引導(dǎo)詞
- text: 輸入框已輸入的文字內(nèi)容
TextInput的屬性方法onChange
用來監(jiān)聽最新已輸入的文字
- 這個(gè)方法中,我們可以通過判斷內(nèi)容長度,來設(shè)置控制中的搜索按鈕文字,如果有內(nèi)容,按鈕文案將變?yōu)?搜索",反之,按鈕文案變?yōu)椤叭∠保袋c(diǎn)擊之后將關(guān)閉當(dāng)前頁面; 同時(shí)請求遠(yuǎn)端聯(lián)想詞的功能也是在這里觸發(fā),
注意
:本篇文章中的聯(lián)想詞僅僅是本地模擬的數(shù)據(jù),沒有進(jìn)行網(wǎng)絡(luò)請求,也沒有模擬網(wǎng)絡(luò)延時(shí)加載,在真實(shí)場景中一定要注意用戶的操作行為應(yīng)該中斷本次聯(lián)想詞的網(wǎng)絡(luò)請求(即使網(wǎng)絡(luò)請求已經(jīng)發(fā)出去,回來之后,也要扔掉拿到的聯(lián)想詞數(shù)據(jù))。
TextInput的屬性方法enterKeyType
這個(gè)用來修改軟件盤上的回車鍵文字提示,這個(gè)設(shè)置的值為EnterKeyType.Search
,所以在中文模式下,你會發(fā)現(xiàn)鍵盤上的文字為:搜索
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));
})
至此,一個(gè)完整的輸入框已完美的完成布局。
歷史搜索詞
一個(gè)搜索的新手產(chǎn)品,在講解這部分需求時(shí),會使用簡短的話術(shù):把搜索過的內(nèi)容顯示出來。
實(shí)際情況是比較嚴(yán)謹(jǐn)復(fù)雜的,最多多展示多少行? 每個(gè)歷史詞最多展示多少個(gè)字符? 要不要識別詞性?......`, 針對這些嚴(yán)格的邏輯,研發(fā)人員需要優(yōu)先解決動態(tài)布局的問題,剩下的僅僅是堆積代碼。
在Android系統(tǒng)中,針對這種布局場景,需要代碼動態(tài)實(shí)現(xiàn),即采用Java方式布局,不幸的是HarmonyOS 中沒有這個(gè)說法。
解決方案:
給歷史詞變量添加 @State 修飾,根據(jù)視圖高度動態(tài)計(jì)算行數(shù),然后動態(tài)刪除多余關(guān)鍵詞記錄
注意
:@State 修飾的Array無法對sort方法生效,結(jié)合場景描述即:最新搜索的關(guān)鍵詞,都要排在第一個(gè)位置,所以每發(fā)起一次搜索,都要對Array類型的變量進(jìn)行一次排序,由于@State的限制,我們需要在中間中轉(zhuǎn)一次。
既然已經(jīng)知道問題,那么先看一下布局代碼,然后繼續(xù)完成需求
首先,對動態(tài)布局的需求來講,HarmonyOS中,貌似只能用Flex容器來解決,因?yàn)樗粌H可以包含子組件,也有自動換行功能,所這里我采用的是Flex容器,如果你要更好的方案,歡迎留言交流
通過視圖高度動態(tài)計(jì)算行數(shù),可以依賴onAreaChange
接口,在其回調(diào)中,通過每次的新值結(jié)構(gòu)體(即Area),獲取當(dāng)前布局高度,然后除以第一次獲取到的高度,這樣即可完成行數(shù)的測算
關(guān)于Flex自動換行功能,這個(gè)要依賴于一個(gè)參數(shù)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
//全局聲明一個(gè)歷史詞單行高度變量,初始值設(shè)置為0,一旦產(chǎn)生歷史詞,將行高設(shè)置為此值
//后續(xù)將以此值為標(biāo)準(zhǔn)來計(jì)算歷史詞行數(shù)
if(this.currentHistoryHeight == 0){
this.currentHistoryHeight = newHeight
}
//這里僅僅取整
this.currentLineNumbs = newHeight / this.currentHistoryHeight
//MAX_LINES 代表最大行數(shù)
if (this.currentLineNumbs >= MAX_LINES) {
//刪除一個(gè)歷史詞,由于historyWords添加了@State修飾,所以數(shù)據(jù)發(fā)生變化后,頁面會刷新
//頁面刷新后,又會重新觸發(fā)此方法
this.historyWords = this.historyWords.slice(0, this.historyWords.length-1)
}
})
}
剛剛提到過一個(gè)問題,@State 修飾的Array變量是無法進(jìn)行排序的。應(yīng)對這個(gè)問題,可以在中間中轉(zhuǎn)一下,即聲明一個(gè)局部Array,先將歷史記錄賦值給它,讓這個(gè)局部Array參與sort,然后清空@State修飾的Array變量,最終將局部Array賦值給@State修飾的Array變量,描述有點(diǎn)繁瑣,直接看代碼。
只要發(fā)起搜索行為,都會使用到此方法
, 另外注意閱讀代碼注釋有NOTE
的文字
submitData(wordModel: HistoryWordModel) {
if (wordModel.word.length != 0) {
//標(biāo)識本次搜索的關(guān)鍵詞是否存在
let exist: boolean = false
//如果搜索關(guān)鍵詞存在,記錄其位置,如果發(fā)現(xiàn)其已經(jīng)是第一個(gè)位置,則不進(jìn)行排序刷新動作
let existIndex: number = -1
//判斷搜索關(guān)鍵是否存在于歷史搜索詞列表中
this.historyWords.forEach((item, index) = > {
if(item.word === wordModel.word){
//如果本次搜索關(guān)鍵詞已經(jīng)處于歷史搜索詞的第一個(gè)位置,不做刪除動作
if(index != 0){
//如果存在,先刪除歷史詞列表中的這個(gè)關(guān)鍵詞
this.historyWords.splice(index, 1)
}
exist = true
existIndex = index
}
});
//本次搜索關(guān)鍵詞在歷史搜索詞列表中處于第一個(gè)位置,因此不做任何額外處理
//NOTE:真實(shí)場景中,這里除了重置狀態(tài),應(yīng)該發(fā)起網(wǎng)絡(luò)請求
if(existIndex == 0){
console.log('不需要刷新頁面')
this.currentInputBoxContent = ''
this.searchButtonText = '取消'
this.showThinkWord = false
return
}
if(!exist){
//如果本次搜索關(guān)鍵詞在歷史詞列表中不存在,則將其加入其中
wordModel.index = this.historyWordIndex++
this.historyWords.push(wordModel)
} else {
//如果本次搜索關(guān)鍵詞已存在于歷史詞列表中,將其對應(yīng)的下標(biāo)加1,因?yàn)楹罄m(xù)會用下表排序
//下標(biāo)越大代表離發(fā)生過的搜索行為離當(dāng)前越近
this.historyWordIndex++
this.historyWords.push(new HistoryWordModel(this.historyWordIndex, wordModel.word, wordModel.link))
}
//NOTE:這個(gè)就是中轉(zhuǎn)排序的起始代碼
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:這個(gè)就是中轉(zhuǎn)排序的結(jié)束代碼
this.currentInputBoxContent = ''
this.searchButtonText = '取消'
this.showThinkWord = false
} else {
Prompt.showToast({
message: '請輸入關(guān)鍵詞',
bottom: px2vp(this.toastBottom)
})
}
}
至此,歷史記錄也實(shí)現(xiàn)完成。
聯(lián)想詞實(shí)現(xiàn)
在已有的搜索場景中,我們都知道,當(dāng)發(fā)起聯(lián)想詞時(shí),歷史搜索記錄,熱詞等等,均不會出現(xiàn)在當(dāng)前屏幕中,為了實(shí)現(xiàn)此種效果,我采用了Stack控件疊加覆蓋機(jī)制和if...else渲染機(jī)制,最終實(shí)現(xiàn)完成之后,發(fā)現(xiàn)沒必要使用Stack控件,因?yàn)橛昧薸f...else布局后,像當(dāng)于會動態(tài)掛載和卸載試圖。
聯(lián)想詞實(shí)現(xiàn)還會碰到的一個(gè)問題:高亮關(guān)鍵詞
, 按照HarmonyOS 的布局機(jī)制,一切布局都應(yīng)該提前計(jì)算好,全量布局多種場景樣式,以if...else機(jī)制為基礎(chǔ),完整最終的業(yè)務(wù)場景效果。
那么,如何提前計(jì)算好數(shù)據(jù)呢?高亮詞在數(shù)據(jù)結(jié)構(gòu)上我們可以分為三段:前,中,后。如何理解呢?比如搜索關(guān)鍵詞“1”,那我的聯(lián)想詞無非就幾種情況1
23,21
3,1
,231
,那么,我聲明三個(gè)變量s, m, e, 分別代表前,中,后,此時(shí)你會發(fā)現(xiàn)三個(gè)變量是完全可以覆蓋所有的匹配場景的。這種方式暫且命名為:分割
,“分割”后,在最終展示時(shí),由于需要高亮文字,所以,我們還需要知曉已“分割”的文字中,到底哪一段應(yīng)該高亮,基于此種考慮,需要額外聲明高亮下標(biāo),參考“前,中,后”,將下標(biāo)分別定義為0,1,2
具體實(shí)現(xiàn),看代碼吧
Stack() {
//聯(lián)想詞需要展示時(shí)
if (this.showThinkWord) {
Column() {
//遍歷聯(lián)想詞
ForEach(this.thinkWords, (item: ThinkWordModel, index) = > {
//NOTE: Span控件可以實(shí)現(xiàn)文字分段多種樣式
Text() {
//判斷一條聯(lián)想詞數(shù)據(jù)的“前”
if (item.wordStart && item.wordStart.length != 0) {
Span(item.wordStart)
.fontSize(18)
.fontColor(item.highLightIndex == 0 ? item.highLightColor : item.normalColor)
}
//判斷一條聯(lián)想詞數(shù)據(jù)的“中”
if (item.wordMid && item.wordMid.length != 0) {
Span(item.wordMid)
.fontSize(18)
.fontColor(item.highLightIndex == 1 ? item.highLightColor : item.normalColor)
}
//判斷一條聯(lián)想詞數(shù)據(jù)的“后”
if (item.wordEnd && item.wordEnd.length != 0) {
Span(item.wordEnd)
.fontSize(18)
.fontColor(item.highLightIndex == 2 ? item.highLightColor : item.normalColor)
}
}......
})
}......
} else {
// 沒有聯(lián)想詞時(shí),系統(tǒng)機(jī)制會講聯(lián)想詞視圖卸載掉,即if中的視圖會完全從視圖節(jié)點(diǎn)中拿掉
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)
}
}
}
}
熱門帖子實(shí)現(xiàn)
在整個(gè)搜索主頁中,這個(gè)功能可能算比較簡單的,在Scroll控件中放置Swiper控件,然后按照官方文檔,循環(huán)塞入數(shù)據(jù),整個(gè)效果即可實(shí)現(xiàn)。 這個(gè)里邊用到了Span,由于我們在聯(lián)想詞實(shí)現(xiàn)時(shí)已經(jīng)實(shí)踐過了Span, 這里就不再描述。
NOTE:為了迎合需求,將滑動指示隱藏掉,indicator(false)
false代表隱藏滑動指示
//四. 熱門帖子
Text('熱門帖子')
.fontSize('20fp')
.width('100%')
.fontWeight(FontWeight.Bold)
.margin({ left: '10vp', top: '20vp' })
Swiper(this.swiperController) {
//data僅僅是為了循環(huán),數(shù)據(jù)總個(gè)數(shù)是3
LazyForEach(this.data, (item: string, index: number) = > {
//每一頁Swiper內(nèi)容視圖,通過 @Builder 修飾的方法進(jìn)行一次封裝
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 > = [
'四種醉駕可從寬處理',
'冰面摔倒至腹腔出血',
'董宇輝復(fù)播',
'朱一龍拍戲受傷送醫(yī)',
'音樂節(jié)求婚觀眾退票',
'周杰倫新歌歌名',
'用好“改革開放”這關(guān)鍵一招',
'男子冬釣失聯(lián) 遺體在冰縫中被發(fā)現(xiàn)',
'女孩用科目三跳繩 獲省級比賽第1名',
'美麗鄉(xiāng)村 幸福生活',
]
private hotTopicList2: Array< string > = [
'醉駕輕微可不起訴',
'狄龍被驅(qū)逐',
'勞榮枝希望還清花唄',
'周海媚告別儀式完成',
'董宇輝兼任副總裁',
'小米智能鎖自動開門',
'李家超:基本法第23條明年內(nèi)實(shí)施',
'山東兩幼師出租房內(nèi)遇害',
'南京同曦老總大鬧裁判休息室',
'女子出車禍鯊魚夾插入后腦勺',
'官方辟謠南京過江隧道連環(huán)追尾',
'上海地鐵開啟瘋狂動物城模式',
]
private hotTopicList3: Array< string > = [
'朱丹好友起訴朱丹',
'"中年大叔"自拍刷屏',
'西方臻選回應(yīng)被封號',
'草莓價(jià)格大跳水',
'庫里三分球8中0',
'國足開啟亞洲杯備戰(zhàn)',
]
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 > = []
// 當(dāng)設(shè)備橫屏?xí)r條件成立
listener = mediaquery.matchMediaSync('(orientation: landscape)');
dialogController: CustomDialogController = new CustomDialogController({
builder: CustomDialogExample({
historyWords: $historyWords,
title: '確認(rèn)全部刪除?',
cancel: this.onCancel,
confirm: this.onAccept,
}),
alignment: DialogAlignment.Default, // 可設(shè)置dialog的對齊方式,設(shè)定顯示在底部或中間等,默認(rèn)為底部顯示
})
onCancel() {
}
onAccept() {
console.log('當(dāng)前數(shù)組長度:' + 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[] = [
'聯(lián)想詞測試',
'測試聯(lián)想詞',
'全城尋找測試在哪里',
'找不到人',
'哈爾濱的啤酒好喝',
'HarmonyOS版權(quán)歸屬華為'
]
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('實(shí)名認(rèn)證', '#5d5d5d'))
this.hotWords.push(new HotWordsModel('HMS Core', '#5d5d5d'))
this.hotWords.push(new HotWordsModel('Serverless', '#5d5d5d'))
this.hotWords.push(new HotWordsModel('生態(tài)市場', '#5d5d5d'))
this.hotWords.push(new HotWordsModel('應(yīng)用上架', '#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.');
// 進(jìn)行相關(guān)數(shù)據(jù)操作
})
}
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: '請輸入關(guān)鍵詞',
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('當(dāng)前行數(shù): ' + 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('確認(rèn)')
.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() {
}
}
歷史詞數(shù)據(jù)結(jié)構(gòu) 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
}
}
熱詞數(shù)據(jù)結(jié)構(gòu) 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
}
}
聯(lián)想詞數(shù)據(jù)結(jié)構(gòu) 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
}
}
鴻蒙開發(fā)技術(shù):mau123789記住是v喔
總結(jié)
-
移動開發(fā)
+關(guān)注
關(guān)注
0文章
52瀏覽量
9762 -
鴻蒙
+關(guān)注
關(guān)注
57文章
2365瀏覽量
42894 -
HarmonyOS
+關(guān)注
關(guān)注
79文章
1978瀏覽量
30273 -
OpenHarmony
+關(guān)注
關(guān)注
25文章
3727瀏覽量
16382 -
鴻蒙OS
+關(guān)注
關(guān)注
0文章
189瀏覽量
4452
發(fā)布評論請先 登錄
相關(guān)推薦
評論