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

前言

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()
}

上一篇:什么是React属性钻取(Prop Drilling)-四、避免属性钻取


下一篇:v-model 粗略解析-v-model实现代码