前言
DevEco Studio版本:4.0.0.600
WanAndroid的API链接:玩Android 开放API-玩Android - wanandroid.com
其他篇文章参考:
1、WanAndroid(鸿蒙版)开发的第一篇
2、WanAndroid(鸿蒙版)开发的第二篇
3、WanAndroid(鸿蒙版)开发的第三篇
4、WanAndroid(鸿蒙版)开发的第四篇
5、WanAndroid(鸿蒙版)开发的第五篇
6、WanAndroid(鸿蒙版)开发的第六篇
效果
搜索页面实现
从效果图上可以知道整体是竖直方向(Column),包括:搜索框、热搜、搜索历史三个模块
1、搜索框
代码实现:
RelativeContainer() {
Image($r('app.media.ic_back'))
.width(32)
.height(32)
.id('imageBack')
.margin({ left: 10, right: 10 })
.alignRules({
center: { anchor: '__container__', align: VerticalAlign.Center },
left: { anchor: '__container__', align: HorizontalAlign.Start }
})
.onClick(() => {
router.back()
})
Button('搜索')
.height(35)
.fontColor(Color.White)
.id('buttonSearch')
.margin({ left: 10, right: 10 })
.alignRules({
center: { anchor: '__container__', align: VerticalAlign.Center },
right: { anchor: '__container__', align: HorizontalAlign.End }
})
.linearGradient({
angle: 0,
colors: [['#E4572F', 0], ['#D64025', 1]]
})
.onClick(() => {
if (this.searchContent.trim().length > 0) {
this.insertData(new SearchContentBean(this.searchContent.trim()))
this.jumpToSearchDetails(this.searchContent)
} else {
promptAction.showToast({ message: '搜索内容为空' })
}
})
Row() {
Image($r('app.media.ic_search_8a8a8a'))
.width(20)
.height(20)
TextInput({ placeholder: '发现更多干货', text: '鸿洋' })
.fontSize(16)
.backgroundColor('#00000000')
.enterKeyType(EnterKeyType.Search)
.width('100%')
.height(45)
.flexShrink(1)
.onChange((value: string) => {
this.searchContent = value
})
}
.height(45)
.padding(5)
.borderWidth(1)
.borderColor('#ED7C12')
.borderRadius(10)
.id('rowSearch')
.alignRules({
center: { anchor: '__container__', align: VerticalAlign.Center },
left: { anchor: 'imageBack', align: HorizontalAlign.End },
right: { anchor: 'buttonSearch', align: HorizontalAlign.Start }
})
}
.width('100%')
.height(70)
2、热搜
从UI效果上可以看出热搜内容是个流式布局,要实现流式布局可以通过
Flex({ justifyContent: FlexAlign.Start, wrap: FlexWrap.Wrap }) 来实现
参考:OpenHarmony Flex
代码实现:
@Component
export struct FlowlayoutView {
@Link flowlayoutArr: string[]
private onItemClick: (item: string, index: number) => void = () => {
}
build() {
// Flex布局, wrap为FlexWrap.Wrap为流式布局
Flex({ justifyContent: FlexAlign.Start, wrap: FlexWrap.Wrap }) {
if (this.flowlayoutArr.length > 0) {
ForEach(this.flowlayoutArr,
(item: string, index: number) => {
Text(`${item}`)
.fontSize(18)
.fontColor(Color.White)
.borderStyle(BorderStyle.Solid)
.padding({ left: 10, right: 10, top: 6, bottom: 6 })
.backgroundColor(Color.Pink)
.borderRadius(5)
.margin({ top: 10, right: 10 })
.textOverflow({ overflow: TextOverflow.Ellipsis })
.maxLines(2)
.onClick(() => {
this.onItemClick(item, index)
})
},
(item: string) => item.toString()
)
}
}
}
}
3、搜索历史
每次点击搜索或点击热搜中的关键词时,将点击的内容保存到数据库中,在搜索页面显示时(onPageShow)去查询数据库。UI上通过List去加载查询的数据
数据库实现:
参考BaseLibrary 中database里面的代码
代码实现:
List() {
ForEach(this.searchHistoryList, (item, index) => {
ListItem() {
Row() {
Image($r('app.media.searchHistory'))
.width(24)
.height(24)
.margin({ left: 20 })
Text(item)
.fontColor(this.getTextColor(index))
.fontSize(20)
.margin({ right: 20 })
}.width('100%')
.padding({ top: 15, bottom: 15 })
.justifyContent(FlexAlign.SpaceBetween)
}.swipeAction({ end: this.itemEnd(index) })
.onClick(() => {
this.jumpToSearchDetails(item)
})
})
}
.flexShrink(1)
.width('100%')
.height('100%')
4、详细代码
import router from '@ohos.router'
import promptAction from '@ohos.promptAction'
import { FlowlayoutView, HttpManager, RequestMethod, SearchContentBean, SQLManager } from '@app/BaseLibrary'
import LogUtils from '@app/BaseLibrary/src/main/ets/utils/LogUtils'
import { SearchHotKey } from '../../bean/search/SearchHotKeyBean'
const TAG = 'SearchPage--- ';
@Entry
@Component
struct SearchPage {
private sqlManager = new SQLManager();
@State searchContent: string = ''
@State searchHotKeyArr: string[] = []
@State searchHistoryList: string[] = []
@State searchContentBeanList: SearchContentBean[] = []
aboutToAppear() {
this.getSearchHotKeyData()
}
onPageShow() {
this.queryAllData()
}
private getSearchHotKeyData() {
HttpManager.getInstance()
.request<SearchHotKey>({
method: RequestMethod.GET,
url: `https://www.wanandroid.com/hotkey/json`, //wanAndroid的API:搜索热词
})
.then((result: SearchHotKey) => {
LogUtils.info(TAG, "result: " + JSON.stringify(result))
if (result.errorCode == 0) {
for (let i = 0; i < result.data.length; i++) {
this.searchHotKeyArr = this.searchHotKeyArr.concat(result.data[i].name)
}
}
LogUtils.info(TAG, "添加后的searchHotKeyArr: " + JSON.stringify(this.searchHotKeyArr))
})
.catch((error) => {
LogUtils.info(TAG, "error: " + JSON.stringify(error))
})
}
build() {
Column() {
RelativeContainer() {
Image($r('app.media.ic_back'))
.width(32)
.height(32)
.id('imageBack')
.margin({ left: 10, right: 10 })
.alignRules({
center: { anchor: '__container__', align: VerticalAlign.Center },
left: { anchor: '__container__', align: HorizontalAlign.Start }
})
.onClick(() => {
router.back()
})
Button('搜索')
.height(35)
.fontColor(Color.White)
.id('buttonSearch')
.margin({ left: 10, right: 10 })
.alignRules({
center: { anchor: '__container__', align: VerticalAlign.Center },
right: { anchor: '__container__', align: HorizontalAlign.End }
})
.linearGradient({
angle: 0,
colors: [['#E4572F', 0], ['#D64025', 1]]
})
.onClick(() => {
if (this.searchContent.trim().length > 0) {
this.insertData(new SearchContentBean(this.searchContent.trim()))
this.jumpToSearchDetails(this.searchContent)
} else {
promptAction.showToast({ message: '搜索内容为空' })
}
})
Row() {
Image($r('app.media.ic_search_8a8a8a'))
.width(20)
.height(20)
TextInput({ placeholder: '发现更多干货', text: '鸿洋' })
.fontSize(16)
.backgroundColor('#00000000')
.enterKeyType(EnterKeyType.Search)
.width('100%')
.height(45)
.flexShrink(1)
.onChange((value: string) => {
this.searchContent = value
})
}
.height(45)
.padding(5)
.borderWidth(1)
.borderColor('#ED7C12')
.borderRadius(10)
.id('rowSearch')
.alignRules({
center: { anchor: '__container__', align: VerticalAlign.Center },
left: { anchor: 'imageBack', align: HorizontalAlign.End },
right: { anchor: 'buttonSearch', align: HorizontalAlign.Start }
})
}
.width('100%')
.height(70)
Divider().strokeWidth(1).color('#F1F3F5')
Text('热搜')
.fontSize(20)
.fontColor('#D64025')
.margin({ left: 15, right: 15, top: 10 })
.alignSelf(ItemAlign.Start)
//自定义流式布局
FlowlayoutView({
flowlayoutArr: this.searchHotKeyArr,
onItemClick: (item, index) => {
LogUtils.info(TAG, "Index------ 点击了:index: " + index + " item: " + item)
this.insertData(new SearchContentBean(item))
this.jumpToSearchDetails(item)
}
}).margin({ left: 20, right: 20 })
Row() {
Text('搜索历史')
.fontSize(20)
.fontColor('#1296db')
.margin({ left: 15, right: 15, top: 15, bottom: 15 })
.alignSelf(ItemAlign.Start)
Row() {
Image($r('app.media.deleteAll'))
.width(22)
.height(22)
Text('清空')
.fontColor(Color.Black)
.margin({ left: 5 })
.fontSize(20)
}.margin({ left: 15, right: 15, top: 15, bottom: 15 })
.onClick(() => {
this.deleteAllData()
})
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
List() {
ForEach(this.searchHistoryList, (item, index) => {
ListItem() {
Row() {
Image($r('app.media.searchHistory'))
.width(24)
.height(24)
.margin({ left: 20 })
Text(item)
.fontColor(this.getTextColor(index))
.fontSize(20)
.margin({ right: 20 })
}.width('100%')
.padding({ top: 15, bottom: 15 })
.justifyContent(FlexAlign.SpaceBetween)
}.swipeAction({ end: this.itemEnd(index) })
.onClick(() => {
this.jumpToSearchDetails(item)
})
})
}
.flexShrink(1)
.width('100%')
.height('100%')
}
.width('100%')
.height('100%')
.backgroundColor(Color.White)
}
@Builder
itemEnd(index: number) { // 侧滑后尾端出现的组件
Image($r('app.media.deleteAll'))
.width(30)
.height(30)
.margin(10)
.onClick(() => {
this.deleteData(this.searchContentBeanList[index])
this.searchHistoryList.splice(index, 1);
this.searchContentBeanList.splice(index, 1);
})
}
/**
* 跳转到搜索详情页
*/
private jumpToSearchDetails(content: string) {
router.pushUrl({
url: 'pages/search/SearchDetailsPage',
params: {
searchContent: content
}
}, router.RouterMode.Single)
}
private deleteData(searchContentBean: SearchContentBean) {
LogUtils.info("Rdb----- deleteData result: " + searchContentBean.id + " searchContent: " + searchContentBean.searchContent)
this.sqlManager.deleteData(searchContentBean, (result) => {
LogUtils.info("Rdb----- 删除 result: " + result)
})
}
/**
* 删除所有数据
*/
private deleteAllData() {
if (this.searchHistoryList.length <= 0) {
promptAction.showToast({ message: '没有可清除的数据' })
return
}
this.sqlManager.deleteDataAll((result) => {
LogUtils.info(TAG, "Rdb----- 删除所有 result: " + result)
if (result) {
promptAction.showToast({ message: '清除完成' })
this.searchHistoryList = []
}
})
}
/**
* 查询所有数据
*/
private queryAllData() {
this.sqlManager.getRdbStore(() => {
this.sqlManager.query((result: Array<SearchContentBean>) => {
LogUtils.info(TAG, "Rdb----- 查询 result: " + JSON.stringify(result))
this.searchContentBeanList = result
this.searchHistoryList = []
for (let i = 0; i < result.length; i++) {
this.searchHistoryList.push(result[i].searchContent)
}
})
})
}
/**
* 插入数据
*/
private insertData(searchContentBean: SearchContentBean) {
this.sqlManager.insertData(searchContentBean, (id: number) => {
LogUtils.info(TAG, "Rdb----- result 插入 id: " + id)
searchContentBean.id = id
if (id >= 0) { //id < 0 表示插入数据失败
}
})
}
private getTextColor(index: number): ResourceColor {
if (index % 3 == 0) {
return Color.Orange
} else if (index % 3 == 1) {
return Color.Blue
} else if (index % 3 == 2) {
return Color.Pink
}
return Color.Black
}
}
搜索详情页面实现
1、代码实现:
import router from '@ohos.router';
import {
Constants,
HtmlUtils,
HttpManager,
LoadingDialog,
RefreshController,
RefreshListView,
RequestMethod
} from '@app/BaseLibrary';
import LogUtils from '@app/BaseLibrary/src/main/ets/utils/LogUtils';
import { SearchDetailsItemBean } from '../../bean/search/SearchDetailsItemBean';
import { SearchDetailsBean } from '../../bean/search/SearchDetailsBean';
import promptAction from '@ohos.promptAction';
import { AppTitleBar } from '../../widget/AppTitleBar';
const TAG = 'SearchDetailsPage--- ';
@Entry
@Component
struct SearchDetailsPage {
@State searchContent: string = router.getParams()?.['searchContent'];
@State controller: RefreshController = new RefreshController()
@State searchDetailsListData: Array<SearchDetailsItemBean> = [];
@State pageNum: number = 0
@State isRefresh: boolean = true
@State userName: string = ''
@State token_pass: string = ''
aboutToAppear() {
LogUtils.info(TAG, " aboutToAppear: " + this.searchContent)
if (AppStorage.Has(Constants.APPSTORAGE_USERNAME)) {
this.userName = AppStorage.Get(Constants.APPSTORAGE_USERNAME) as string
}
if (AppStorage.Has(Constants.APPSTORAGE_TOKEN_PASS)) {
this.token_pass = AppStorage.Get(Constants.APPSTORAGE_TOKEN_PASS) as string
}
this.dialogController.open()
this.getSearchDetailsData()
}
private getSearchDetailsData() {
HttpManager.getInstance()
.request<SearchDetailsBean>({
method: RequestMethod.POST,
header: {
"Content-Type": "application/json",
"Cookie": `loginUserName=${this.userName}; token_pass=${this.token_pass}`
},
url: `https://www.wanandroid.com/article/query/${this.pageNum}/json/?k=${encodeURIComponent(this.searchContent)}`, //wanAndroid的API:搜索 ?k=${this.searchContent}
})
.then((result: SearchDetailsBean) => {
LogUtils.info(TAG, "result: " + JSON.stringify(result))
if (this.isRefresh) {
this.controller.finishRefresh()
} else {
this.controller.finishLoadMore()
}
if (result.errorCode == 0) {
if (this.isRefresh) {
this.searchDetailsListData = result.data.datas
} else {
if (result.data.datas.length > 0) {
this.searchDetailsListData = this.searchDetailsListData.concat(result.data.datas)
} else {
promptAction.showToast({ message: '没有更多数据啦!' })
}
}
}
this.dialogController.close()
})
.catch((error) => {
LogUtils.info(TAG, "error: " + JSON.stringify(error))
if (this.isRefresh) {
this.controller.finishRefresh()
} else {
this.controller.finishLoadMore()
}
this.dialogController.close()
})
}
build() {
Column() {
AppTitleBar({ title: this.searchContent })
RefreshListView({
list: this.searchDetailsListData,
controller: this.controller,
isEnableLog: true,
paddingRefresh: { left: 10, right: 10, top: 5, bottom: 5 },
refreshLayout: (item: SearchDetailsItemBean, index: number): void => this.itemLayout(item, index),
onItemClick: (item: SearchDetailsItemBean, index: number) => {
LogUtils.info(TAG, "点击了:index: " + index + " item: " + item)
router.pushUrl({
url: 'pages/WebPage',
params: {
title: item.title,
uriLink: item.link
}
}, router.RouterMode.Single)
},
onRefresh: () => {
//下拉刷新
this.isRefresh = true
this.pageNum = 0
this.getSearchDetailsData()
},
onLoadMore: () => {
//上拉加载
this.isRefresh = false
this.pageNum++
this.getSearchDetailsData()
}
}).flexShrink(1)
}
.width('100%')
.height('100%')
.backgroundColor('#F1F3F5')
}
@Builder
itemLayout(item: SearchDetailsItemBean, index: number) {
RelativeContainer() {
//作者或分享人
Text(item.author.length > 0 ? "作者:" + item.author : "分享人:" + item.shareUser)
.fontColor('#666666')
.fontSize(14)
.id("textAuthor")
.alignRules({
top: { anchor: '__container__', align: VerticalAlign.Top },
left: { anchor: '__container__', align: HorizontalAlign.Start }
})
Text(item.superChapterName + '/' + item.chapterName)
.fontColor('#1296db')
.fontSize(14)
.id("textChapterName")
.alignRules({
top: { anchor: '__container__', align: VerticalAlign.Top },
right: { anchor: '__container__', align: HorizontalAlign.End }
})
//标题
Text(HtmlUtils.formatStr(item.title))
.fontColor('#333333')
.fontWeight(FontWeight.Bold)
.maxLines(2)
.textOverflow({
overflow: TextOverflow.Ellipsis
})
.fontSize(20)
.margin({ top: 10 })
.id("textTitle")
.alignRules({
top: { anchor: 'textAuthor', align: VerticalAlign.Bottom },
left: { anchor: '__container__', align: HorizontalAlign.Start }
})
//更新时间
Text("时间:" + item.niceDate)
.fontColor('#666666')
.fontSize(14)
.id("textNiceDate")
.alignRules({
bottom: { anchor: '__container__', align: VerticalAlign.Bottom },
left: { anchor: '__container__', align: HorizontalAlign.Start }
})
}
.width('100%')
.height(120)
.padding(10)
.borderRadius(10)
.backgroundColor(Color.White)
}
private dialogController = new CustomDialogController({
builder: LoadingDialog(),
customStyle: true,
alignment: DialogAlignment.Center, // 可设置dialog的对齐方式,设定显示在底部或中间等,默认为底部显示
})
}
2、根据传入的搜索词获取数据
aboutToAppear() {
this.getSearchDetailsData()
}