当前位置:   article > 正文

WanAndroid(鸿蒙版)开发的第三篇

WanAndroid(鸿蒙版)开发的第三篇

前言

DevEco Studio版本:4.0.0.600

WanAndroid的API链接:玩Android 开放API-玩Android - wanandroid.com

其他篇文章参考:

1、WanAndroid(鸿蒙版)开发的第一篇-CSDN博客

2、WanAndroid(鸿蒙版)开发的第二篇-CSDN博客

效果

   

搜索页面实现

从效果图上可以知道整体是竖直方向(Column),包括:搜索框、热搜、搜索历史三个模块

1、搜索框

代码实现:

  1. RelativeContainer() {
  2. Image($r('app.media.ic_back'))
  3. .width(32)
  4. .height(32)
  5. .id('imageBack')
  6. .margin({ left: 10, right: 10 })
  7. .alignRules({
  8. center: { anchor: '__container__', align: VerticalAlign.Center },
  9. left: { anchor: '__container__', align: HorizontalAlign.Start }
  10. })
  11. .onClick(() => {
  12. router.back()
  13. })
  14. Button('搜索')
  15. .height(35)
  16. .fontColor(Color.White)
  17. .id('buttonSearch')
  18. .margin({ left: 10, right: 10 })
  19. .alignRules({
  20. center: { anchor: '__container__', align: VerticalAlign.Center },
  21. right: { anchor: '__container__', align: HorizontalAlign.End }
  22. })
  23. .linearGradient({
  24. angle: 0,
  25. colors: [['#E4572F', 0], ['#D64025', 1]]
  26. })
  27. .onClick(() => {
  28. if (this.searchContent.trim().length > 0) {
  29. this.insertData(new SearchContentBean(this.searchContent.trim()))
  30. this.jumpToSearchDetails(this.searchContent)
  31. } else {
  32. promptAction.showToast({ message: '搜索内容为空' })
  33. }
  34. })
  35. Row() {
  36. Image($r('app.media.ic_search_8a8a8a'))
  37. .width(20)
  38. .height(20)
  39. TextInput({ placeholder: '发现更多干货', text: '鸿洋' })
  40. .fontSize(16)
  41. .backgroundColor('#00000000')
  42. .enterKeyType(EnterKeyType.Search)
  43. .width('100%')
  44. .height(45)
  45. .flexShrink(1)
  46. .onChange((value: string) => {
  47. this.searchContent = value
  48. })
  49. }
  50. .height(45)
  51. .padding(5)
  52. .borderWidth(1)
  53. .borderColor('#ED7C12')
  54. .borderRadius(10)
  55. .id('rowSearch')
  56. .alignRules({
  57. center: { anchor: '__container__', align: VerticalAlign.Center },
  58. left: { anchor: 'imageBack', align: HorizontalAlign.End },
  59. right: { anchor: 'buttonSearch', align: HorizontalAlign.Start }
  60. })
  61. }
  62. .width('100%')
  63. .height(70)

2、热搜

从UI效果上可以看出热搜内容是个流式布局,要实现流式布局可以通过

Flex({ justifyContent: FlexAlign.Start, wrap: FlexWrap.Wrap }) 来实现

参考:OpenHarmony Flex

代码实现:

  1. @Component
  2. export struct FlowlayoutView {
  3. @Link flowlayoutArr: string[]
  4. private onItemClick: (item: string, index: number) => void = () => {
  5. }
  6. build() {
  7. // Flex布局, wrap为FlexWrap.Wrap为流式布局
  8. Flex({ justifyContent: FlexAlign.Start, wrap: FlexWrap.Wrap }) {
  9. if (this.flowlayoutArr.length > 0) {
  10. ForEach(this.flowlayoutArr,
  11. (item: string, index: number) => {
  12. Text(`${item}`)
  13. .fontSize(18)
  14. .fontColor(Color.White)
  15. .borderStyle(BorderStyle.Solid)
  16. .padding({ left: 10, right: 10, top: 6, bottom: 6 })
  17. .backgroundColor(Color.Pink)
  18. .borderRadius(5)
  19. .margin({ top: 10, right: 10 })
  20. .textOverflow({ overflow: TextOverflow.Ellipsis })
  21. .maxLines(2)
  22. .onClick(() => {
  23. this.onItemClick(item, index)
  24. })
  25. },
  26. (item: string) => item.toString()
  27. )
  28. }
  29. }
  30. }
  31. }

3、搜索历史

每次点击搜索或点击热搜中的关键词时,将点击的内容保存到数据库中,在搜索页面显示时(onPageShow)去查询数据库。UI上通过List去加载查询的数据

数据库实现:

参考BaseLibrary 中database里面的代码

代码实现:

  1. List() {
  2. ForEach(this.searchHistoryList, (item, index) => {
  3. ListItem() {
  4. Row() {
  5. Image($r('app.media.searchHistory'))
  6. .width(24)
  7. .height(24)
  8. .margin({ left: 20 })
  9. Text(item)
  10. .fontColor(this.getTextColor(index))
  11. .fontSize(20)
  12. .margin({ right: 20 })
  13. }.width('100%')
  14. .padding({ top: 15, bottom: 15 })
  15. .justifyContent(FlexAlign.SpaceBetween)
  16. }.swipeAction({ end: this.itemEnd(index) })
  17. .onClick(() => {
  18. this.jumpToSearchDetails(item)
  19. })
  20. })
  21. }
  22. .flexShrink(1)
  23. .width('100%')
  24. .height('100%')

4、详细代码

  1. import router from '@ohos.router'
  2. import promptAction from '@ohos.promptAction'
  3. import { FlowlayoutView, HttpManager, RequestMethod, SearchContentBean, SQLManager } from '@app/BaseLibrary'
  4. import LogUtils from '@app/BaseLibrary/src/main/ets/utils/LogUtils'
  5. import { SearchHotKey } from '../../bean/search/SearchHotKeyBean'
  6. const TAG = 'SearchPage--- ';
  7. @Entry
  8. @Component
  9. struct SearchPage {
  10. private sqlManager = new SQLManager();
  11. @State searchContent: string = ''
  12. @State searchHotKeyArr: string[] = []
  13. @State searchHistoryList: string[] = []
  14. @State searchContentBeanList: SearchContentBean[] = []
  15. aboutToAppear() {
  16. this.getSearchHotKeyData()
  17. }
  18. onPageShow() {
  19. this.queryAllData()
  20. }
  21. private getSearchHotKeyData() {
  22. HttpManager.getInstance()
  23. .request<SearchHotKey>({
  24. method: RequestMethod.GET,
  25. url: `https://www.wanandroid.com/hotkey/json`, //wanAndroid的API:搜索热词
  26. })
  27. .then((result: SearchHotKey) => {
  28. LogUtils.info(TAG, "result: " + JSON.stringify(result))
  29. if (result.errorCode == 0) {
  30. for (let i = 0; i < result.data.length; i++) {
  31. this.searchHotKeyArr = this.searchHotKeyArr.concat(result.data[i].name)
  32. }
  33. }
  34. LogUtils.info(TAG, "添加后的searchHotKeyArr: " + JSON.stringify(this.searchHotKeyArr))
  35. })
  36. .catch((error) => {
  37. LogUtils.info(TAG, "error: " + JSON.stringify(error))
  38. })
  39. }
  40. build() {
  41. Column() {
  42. RelativeContainer() {
  43. Image($r('app.media.ic_back'))
  44. .width(32)
  45. .height(32)
  46. .id('imageBack')
  47. .margin({ left: 10, right: 10 })
  48. .alignRules({
  49. center: { anchor: '__container__', align: VerticalAlign.Center },
  50. left: { anchor: '__container__', align: HorizontalAlign.Start }
  51. })
  52. .onClick(() => {
  53. router.back()
  54. })
  55. Button('搜索')
  56. .height(35)
  57. .fontColor(Color.White)
  58. .id('buttonSearch')
  59. .margin({ left: 10, right: 10 })
  60. .alignRules({
  61. center: { anchor: '__container__', align: VerticalAlign.Center },
  62. right: { anchor: '__container__', align: HorizontalAlign.End }
  63. })
  64. .linearGradient({
  65. angle: 0,
  66. colors: [['#E4572F', 0], ['#D64025', 1]]
  67. })
  68. .onClick(() => {
  69. if (this.searchContent.trim().length > 0) {
  70. this.insertData(new SearchContentBean(this.searchContent.trim()))
  71. this.jumpToSearchDetails(this.searchContent)
  72. } else {
  73. promptAction.showToast({ message: '搜索内容为空' })
  74. }
  75. })
  76. Row() {
  77. Image($r('app.media.ic_search_8a8a8a'))
  78. .width(20)
  79. .height(20)
  80. TextInput({ placeholder: '发现更多干货', text: '鸿洋' })
  81. .fontSize(16)
  82. .backgroundColor('#00000000')
  83. .enterKeyType(EnterKeyType.Search)
  84. .width('100%')
  85. .height(45)
  86. .flexShrink(1)
  87. .onChange((value: string) => {
  88. this.searchContent = value
  89. })
  90. }
  91. .height(45)
  92. .padding(5)
  93. .borderWidth(1)
  94. .borderColor('#ED7C12')
  95. .borderRadius(10)
  96. .id('rowSearch')
  97. .alignRules({
  98. center: { anchor: '__container__', align: VerticalAlign.Center },
  99. left: { anchor: 'imageBack', align: HorizontalAlign.End },
  100. right: { anchor: 'buttonSearch', align: HorizontalAlign.Start }
  101. })
  102. }
  103. .width('100%')
  104. .height(70)
  105. Divider().strokeWidth(1).color('#F1F3F5')
  106. Text('热搜')
  107. .fontSize(20)
  108. .fontColor('#D64025')
  109. .margin({ left: 15, right: 15, top: 10 })
  110. .alignSelf(ItemAlign.Start)
  111. //自定义流式布局
  112. FlowlayoutView({
  113. flowlayoutArr: this.searchHotKeyArr,
  114. onItemClick: (item, index) => {
  115. LogUtils.info(TAG, "Index------ 点击了:index: " + index + " item: " + item)
  116. this.insertData(new SearchContentBean(item))
  117. this.jumpToSearchDetails(item)
  118. }
  119. }).margin({ left: 20, right: 20 })
  120. Row() {
  121. Text('搜索历史')
  122. .fontSize(20)
  123. .fontColor('#1296db')
  124. .margin({ left: 15, right: 15, top: 15, bottom: 15 })
  125. .alignSelf(ItemAlign.Start)
  126. Row() {
  127. Image($r('app.media.deleteAll'))
  128. .width(22)
  129. .height(22)
  130. Text('清空')
  131. .fontColor(Color.Black)
  132. .margin({ left: 5 })
  133. .fontSize(20)
  134. }.margin({ left: 15, right: 15, top: 15, bottom: 15 })
  135. .onClick(() => {
  136. this.deleteAllData()
  137. })
  138. }
  139. .width('100%')
  140. .justifyContent(FlexAlign.SpaceBetween)
  141. List() {
  142. ForEach(this.searchHistoryList, (item, index) => {
  143. ListItem() {
  144. Row() {
  145. Image($r('app.media.searchHistory'))
  146. .width(24)
  147. .height(24)
  148. .margin({ left: 20 })
  149. Text(item)
  150. .fontColor(this.getTextColor(index))
  151. .fontSize(20)
  152. .margin({ right: 20 })
  153. }.width('100%')
  154. .padding({ top: 15, bottom: 15 })
  155. .justifyContent(FlexAlign.SpaceBetween)
  156. }.swipeAction({ end: this.itemEnd(index) })
  157. .onClick(() => {
  158. this.jumpToSearchDetails(item)
  159. })
  160. })
  161. }
  162. .flexShrink(1)
  163. .width('100%')
  164. .height('100%')
  165. }
  166. .width('100%')
  167. .height('100%')
  168. .backgroundColor(Color.White)
  169. }
  170. @Builder
  171. itemEnd(index: number) { // 侧滑后尾端出现的组件
  172. Image($r('app.media.deleteAll'))
  173. .width(30)
  174. .height(30)
  175. .margin(10)
  176. .onClick(() => {
  177. this.deleteData(this.searchContentBeanList[index])
  178. this.searchHistoryList.splice(index, 1);
  179. this.searchContentBeanList.splice(index, 1);
  180. })
  181. }
  182. /**
  183. * 跳转到搜索详情页
  184. */
  185. private jumpToSearchDetails(content: string) {
  186. router.pushUrl({
  187. url: 'pages/search/SearchDetailsPage',
  188. params: {
  189. searchContent: content
  190. }
  191. }, router.RouterMode.Single)
  192. }
  193. private deleteData(searchContentBean: SearchContentBean) {
  194. LogUtils.info("Rdb----- deleteData result: " + searchContentBean.id + " searchContent: " + searchContentBean.searchContent)
  195. this.sqlManager.deleteData(searchContentBean, (result) => {
  196. LogUtils.info("Rdb----- 删除 result: " + result)
  197. })
  198. }
  199. /**
  200. * 删除所有数据
  201. */
  202. private deleteAllData() {
  203. if (this.searchHistoryList.length <= 0) {
  204. promptAction.showToast({ message: '没有可清除的数据' })
  205. return
  206. }
  207. this.sqlManager.deleteDataAll((result) => {
  208. LogUtils.info(TAG, "Rdb----- 删除所有 result: " + result)
  209. if (result) {
  210. promptAction.showToast({ message: '清除完成' })
  211. this.searchHistoryList = []
  212. }
  213. })
  214. }
  215. /**
  216. * 查询所有数据
  217. */
  218. private queryAllData() {
  219. this.sqlManager.getRdbStore(() => {
  220. this.sqlManager.query((result: Array<SearchContentBean>) => {
  221. LogUtils.info(TAG, "Rdb----- 查询 result: " + JSON.stringify(result))
  222. this.searchContentBeanList = result
  223. this.searchHistoryList = []
  224. for (let i = 0; i < result.length; i++) {
  225. this.searchHistoryList.push(result[i].searchContent)
  226. }
  227. })
  228. })
  229. }
  230. /**
  231. * 插入数据
  232. */
  233. private insertData(searchContentBean: SearchContentBean) {
  234. this.sqlManager.insertData(searchContentBean, (id: number) => {
  235. LogUtils.info(TAG, "Rdb----- result 插入 id: " + id)
  236. searchContentBean.id = id
  237. if (id >= 0) { //id < 0 表示插入数据失败
  238. }
  239. })
  240. }
  241. private getTextColor(index: number): ResourceColor {
  242. if (index % 3 == 0) {
  243. return Color.Orange
  244. } else if (index % 3 == 1) {
  245. return Color.Blue
  246. } else if (index % 3 == 2) {
  247. return Color.Pink
  248. }
  249. return Color.Black
  250. }
  251. }

搜索详情页面实现

代码实现:

  1. import router from '@ohos.router';
  2. import {
  3. Constants,
  4. HtmlUtils,
  5. HttpManager,
  6. LoadingDialog,
  7. RefreshController,
  8. RefreshListView,
  9. RequestMethod
  10. } from '@app/BaseLibrary';
  11. import LogUtils from '@app/BaseLibrary/src/main/ets/utils/LogUtils';
  12. import { SearchDetailsItemBean } from '../../bean/search/SearchDetailsItemBean';
  13. import { SearchDetailsBean } from '../../bean/search/SearchDetailsBean';
  14. import promptAction from '@ohos.promptAction';
  15. import { AppTitleBar } from '../../widget/AppTitleBar';
  16. const TAG = 'SearchDetailsPage--- ';
  17. @Entry
  18. @Component
  19. struct SearchDetailsPage {
  20. @State searchContent: string = router.getParams()?.['searchContent'];
  21. @State controller: RefreshController = new RefreshController()
  22. @State searchDetailsListData: Array<SearchDetailsItemBean> = [];
  23. @State pageNum: number = 0
  24. @State isRefresh: boolean = true
  25. @State userName: string = ''
  26. @State token_pass: string = ''
  27. aboutToAppear() {
  28. LogUtils.info(TAG, " aboutToAppear: " + this.searchContent)
  29. if (AppStorage.Has(Constants.APPSTORAGE_USERNAME)) {
  30. this.userName = AppStorage.Get(Constants.APPSTORAGE_USERNAME) as string
  31. }
  32. if (AppStorage.Has(Constants.APPSTORAGE_TOKEN_PASS)) {
  33. this.token_pass = AppStorage.Get(Constants.APPSTORAGE_TOKEN_PASS) as string
  34. }
  35. this.dialogController.open()
  36. this.getSearchDetailsData()
  37. }
  38. private getSearchDetailsData() {
  39. HttpManager.getInstance()
  40. .request<SearchDetailsBean>({
  41. method: RequestMethod.POST,
  42. header: {
  43. "Content-Type": "application/json",
  44. "Cookie": `loginUserName=${this.userName}; token_pass=${this.token_pass}`
  45. },
  46. url: `https://www.wanandroid.com/article/query/${this.pageNum}/json/?k=${encodeURIComponent(this.searchContent)}`, //wanAndroid的API:搜索 ?k=${this.searchContent}
  47. })
  48. .then((result: SearchDetailsBean) => {
  49. LogUtils.info(TAG, "result: " + JSON.stringify(result))
  50. if (this.isRefresh) {
  51. this.controller.finishRefresh()
  52. } else {
  53. this.controller.finishLoadMore()
  54. }
  55. if (result.errorCode == 0) {
  56. if (this.isRefresh) {
  57. this.searchDetailsListData = result.data.datas
  58. } else {
  59. if (result.data.datas.length > 0) {
  60. this.searchDetailsListData = this.searchDetailsListData.concat(result.data.datas)
  61. } else {
  62. promptAction.showToast({ message: '没有更多数据啦!' })
  63. }
  64. }
  65. }
  66. this.dialogController.close()
  67. })
  68. .catch((error) => {
  69. LogUtils.info(TAG, "error: " + JSON.stringify(error))
  70. if (this.isRefresh) {
  71. this.controller.finishRefresh()
  72. } else {
  73. this.controller.finishLoadMore()
  74. }
  75. this.dialogController.close()
  76. })
  77. }
  78. build() {
  79. Column() {
  80. AppTitleBar({ title: this.searchContent })
  81. RefreshListView({
  82. list: this.searchDetailsListData,
  83. controller: this.controller,
  84. isEnableLog: true,
  85. paddingRefresh: { left: 10, right: 10, top: 5, bottom: 5 },
  86. refreshLayout: (item: SearchDetailsItemBean, index: number): void => this.itemLayout(item, index),
  87. onItemClick: (item: SearchDetailsItemBean, index: number) => {
  88. LogUtils.info(TAG, "点击了:index: " + index + " item: " + item)
  89. router.pushUrl({
  90. url: 'pages/WebPage',
  91. params: {
  92. title: item.title,
  93. uriLink: item.link
  94. }
  95. }, router.RouterMode.Single)
  96. },
  97. onRefresh: () => {
  98. //下拉刷新
  99. this.isRefresh = true
  100. this.pageNum = 0
  101. this.getSearchDetailsData()
  102. },
  103. onLoadMore: () => {
  104. //上拉加载
  105. this.isRefresh = false
  106. this.pageNum++
  107. this.getSearchDetailsData()
  108. }
  109. }).flexShrink(1)
  110. }
  111. .width('100%')
  112. .height('100%')
  113. .backgroundColor('#F1F3F5')
  114. }
  115. @Builder
  116. itemLayout(item: SearchDetailsItemBean, index: number) {
  117. RelativeContainer() {
  118. //作者或分享人
  119. Text(item.author.length > 0 ? "作者:" + item.author : "分享人:" + item.shareUser)
  120. .fontColor('#666666')
  121. .fontSize(14)
  122. .id("textAuthor")
  123. .alignRules({
  124. top: { anchor: '__container__', align: VerticalAlign.Top },
  125. left: { anchor: '__container__', align: HorizontalAlign.Start }
  126. })
  127. Text(item.superChapterName + '/' + item.chapterName)
  128. .fontColor('#1296db')
  129. .fontSize(14)
  130. .id("textChapterName")
  131. .alignRules({
  132. top: { anchor: '__container__', align: VerticalAlign.Top },
  133. right: { anchor: '__container__', align: HorizontalAlign.End }
  134. })
  135. //标题
  136. Text(HtmlUtils.formatStr(item.title))
  137. .fontColor('#333333')
  138. .fontWeight(FontWeight.Bold)
  139. .maxLines(2)
  140. .textOverflow({
  141. overflow: TextOverflow.Ellipsis
  142. })
  143. .fontSize(20)
  144. .margin({ top: 10 })
  145. .id("textTitle")
  146. .alignRules({
  147. top: { anchor: 'textAuthor', align: VerticalAlign.Bottom },
  148. left: { anchor: '__container__', align: HorizontalAlign.Start }
  149. })
  150. //更新时间
  151. Text("时间:" + item.niceDate)
  152. .fontColor('#666666')
  153. .fontSize(14)
  154. .id("textNiceDate")
  155. .alignRules({
  156. bottom: { anchor: '__container__', align: VerticalAlign.Bottom },
  157. left: { anchor: '__container__', align: HorizontalAlign.Start }
  158. })
  159. }
  160. .width('100%')
  161. .height(120)
  162. .padding(10)
  163. .borderRadius(10)
  164. .backgroundColor(Color.White)
  165. }
  166. private dialogController = new CustomDialogController({
  167. builder: LoadingDialog(),
  168. customStyle: true,
  169. alignment: DialogAlignment.Center, // 可设置dialog的对齐方式,设定显示在底部或中间等,默认为底部显示
  170. })
  171. }

源代码地址:WanAndroid_Harmony: WanAndroid的鸿蒙版本

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/234555
推荐阅读
相关标签
  

闽ICP备14008679号