背景
幾乎每家應用中都帶有搜索功能,關于這個功能的頁面不是特別復雜,但如果要追究其背后的一系列邏輯,可能是整個應用中最復雜的一個功能。今天主要實踐目標,會拋開復雜的邏輯,嘗試純粹實現一個“搜索主頁”,主要包含,輸入框文字輸入,熱門詞展示,熱門帖子展示。全篇主要使用到的控件是TextInput, Flex, Swiper。為了貼近實戰,文字輸入過程中,也增加了聯想詞功能。整個示例將在模擬狀態下完成,不做任何網絡請求。
功能清單
- 輸入框 - TextInput用法
- 按鈕搜索詞刪除 - 觸摸事件透傳用法
- 搜索按鈕 - 頁面返回用法
- 聯想詞 - Span用法,if...else 渲染用法
- 歷史搜索詞 - 行數限制,排序
- 熱門搜索詞 - 換行布局,行為識別(打開鏈接,發起搜索)
- 熱門帖子 - Swiper用法,Span用法
效果
布局結構
整體頁面分為上下布局兩大部分:
- 搜索欄
- 可滾動內容區域
開始前熟悉鴻蒙文檔
鴻蒙OS開發 | 更多內容↓點擊 | HarmonyOS與OpenHarmony技術 |
---|---|---|
鴻蒙技術文檔 | 《鴻蒙NEXT星河版開發學習文檔》 |
搜索框
HarmonyOS 提供了Search控件, 這種樣式不太滿足今天要做的需求,所以我還是準備采用TextInput控件重新定制
預期的搜索框需要包含基礎的三個功能
- 文字輸入
- 文字刪除
- 提交已輸入的文字(即:準備發起搜索)
這個樣式的實現方式,我采用了左右布局,左布局采用疊加布局方式,翻譯為代碼,表現形式如下
//一. (左布局)輸入框 + (右布局)搜索按鈕
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)
這句代碼即可。
官方指導示意圖:
變更后的代碼
//一. (左布局)輸入框 + (右布局)搜索按鈕
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”,那我的聯想詞無非就幾種情況1
23,21
3,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喔
總結
-
移動開發
+關注
關注
0文章
52瀏覽量
9809 -
鴻蒙
+關注
關注
57文章
2388瀏覽量
42964 -
HarmonyOS
+關注
關注
79文章
1980瀏覽量
30401 -
OpenHarmony
+關注
關注
25文章
3744瀏覽量
16473 -
鴻蒙OS
+關注
關注
0文章
190瀏覽量
4488
發布評論請先 登錄
相關推薦
評論