#HarmonyOS NEXT体验官# 体验HarmonyOS开发流程,开发一个简单图册展示app(二) 原创

RandomBoolean
发布于 2024-7-29 16:42
浏览
0收藏

上一篇文章构建了一个登录页面,包含基本的登录流程。点击登录之后成功登录进入到首页中。

创建tabbar切换页面

在开发首页之前,需要先规划一下app底部的tab栏,本次开发只涉及两个tab,分别是首页和我的页面。
#HarmonyOS NEXT体验官# 体验HarmonyOS开发流程,开发一个简单图册展示app(二)-鸿蒙开发者社区

在pages目录下创建MainPage页面,UI结构代码如下。

 build() {
    Tabs({
      barPosition: BarPosition.End,
      controller: this.tabsController
    }) {
      TabContent() {
        Home()
      }
      .padding({ left: $r('app.float.mainPage_padding'), right: $r('app.float.mainPage_padding') })
      .backgroundColor($r('app.color.mainPage_backgroundColor'))
      .tabBar(this.TabBuilder(CommonConstants.HOME_TITLE, CommonConstants.HOME_TAB_INDEX,
        $r('app.media.home_selected'), $r('app.media.home_normal')))

      TabContent() {
        Setting()
      }
      .padding({ left: $r('app.float.mainPage_padding'), right: $r('app.float.mainPage_padding') })
      .backgroundColor($r('app.color.mainPage_backgroundColor'))
      .tabBar(this.TabBuilder(CommonConstants.MINE_TITLE, CommonConstants.MINE_TAB_INDEX,
        $r('app.media.mine_selected'), $r('app.media.mine_normal')))
    }
    .width(CommonConstants.FULL_PARENT)
    .backgroundColor(Color.White)
    .barHeight($r('app.float.mainPage_barHeight'))
    .barMode(BarMode.Fixed)
    .onChange((index: number) => {
      this.currentIndex = index;
    })
  }

其中 Home() 和 Setting()为引入的组件页面。分别是首页和我的页面。在view中创建Home和Setting文件。 整体目录结构如下图所示。
#HarmonyOS NEXT体验官# 体验HarmonyOS开发流程,开发一个简单图册展示app(二)-鸿蒙开发者社区

需要注意MainPage文件需要@Entry注解,标识为页面

这里贴一下mainpage文件完整的源码, 主要是定义一个tabs组件。

import CommonConstants from '../common/constants/CommonConstants';
import Add from '../view/Add';
import Home from "../view/Home"
import Setting from "../view/Setting"

/**
 * Main page
 */
@Entry
@Component
struct MainPage {
  @State currentIndex: number = CommonConstants.HOME_TAB_INDEX;
  private tabsController: TabsController = new TabsController();

  @Builder TabBuilder(title: string, index: number, selectedImg: Resource, normalImg: Resource) {
    Column() {
      Image(this.currentIndex === index ? selectedImg : normalImg)
        .width($r('app.float.mainPage_baseTab_size'))
        .height($r('app.float.mainPage_baseTab_size'))
      Text(title)
        .margin({ top: $r('app.float.mainPage_baseTab_top') })
        .fontSize($r('app.float.main_tab_fontSize'))
        .fontColor(this.currentIndex === index ? $r('app.color.mainPage_selected') : $r('app.color.mainPage_normal'))
    }
    .justifyContent(FlexAlign.Center)
    .height($r('app.float.mainPage_barHeight'))
    .width(CommonConstants.FULL_PARENT)
    .onClick(() => {
      this.currentIndex = index;
      this.tabsController.changeIndex(this.currentIndex);
    })
  }

  build() {
    Tabs({
      barPosition: BarPosition.End,
      controller: this.tabsController
    }) {
      TabContent() {
        Home()
      }
      .padding({ left: $r('app.float.mainPage_padding'), right: $r('app.float.mainPage_padding') })
      .backgroundColor($r('app.color.mainPage_backgroundColor'))
      .tabBar(this.TabBuilder(CommonConstants.HOME_TITLE, CommonConstants.HOME_TAB_INDEX,
        $r('app.media.home_selected'), $r('app.media.home_normal')))

      TabContent() {
        Setting()
      }
      .padding({ left: $r('app.float.mainPage_padding'), right: $r('app.float.mainPage_padding') })
      .backgroundColor($r('app.color.mainPage_backgroundColor'))
      .tabBar(this.TabBuilder(CommonConstants.MINE_TITLE, CommonConstants.MINE_TAB_INDEX,
        $r('app.media.mine_selected'), $r('app.media.mine_normal')))
    }
    .width(CommonConstants.FULL_PARENT)
    .backgroundColor(Color.White)
    .barHeight($r('app.float.mainPage_barHeight'))
    .barMode(BarMode.Fixed)
    .onChange((index: number) => {
      this.currentIndex = index;
    })
  }
}

创建Setting页面

setting页面比较简单,展示一个登录用户信息和退出登录按钮。
效果图如下
#HarmonyOS NEXT体验官# 体验HarmonyOS开发流程,开发一个简单图册展示app(二)-鸿蒙开发者社区

完整代码就不再贴了,功能比较简单。接下来重点是首页。

创建Home主页

首页规划元素如下。
#HarmonyOS NEXT体验官# 体验HarmonyOS开发流程,开发一个简单图册展示app(二)-鸿蒙开发者社区

顶部是一个文字标题、接下来是轮播图、分类列表、瀑布流卡片列表。

整个首页主要为UI层,完整代码如下。

import CommonConstants from '../common/constants/CommonConstants';
import mainViewModel from '../viewmodel/MainViewModel';
import ItemData from '../viewmodel/ItemData';
import WaterFlowComponent from './WaterFlowComponent';
import { promptAction, router } from '@kit.ArkUI';
import Constants from '../common/constants/Constants';
import { httpRequestGet } from '../common/utils/HttpUtil';
import ResponseResult from '../viewmodel/ResponseResult';

/**
 * Home tab content
 */
@Component
@Preview
export default struct Home {
  private swiperController: SwiperController = new SwiperController();

  build() {
    Column() {
      Scroll() {
        Column({ space: CommonConstants.COMMON_SPACE }) {
          Column() {
            Text($r('app.string.mainPage_tabTitles_home'))
              .fontWeight(FontWeight.Medium)
              .fontSize($r('app.float.page_title_text_size'))
              .margin({ top: $r('app.float.mainPage_tabTitles_margin') })
              .padding({ left: $r('app.float.mainPage_tabTitles_padding') })
          }
          .width(CommonConstants.FULL_PARENT)
          .alignItems(HorizontalAlign.Start)

          Swiper(this.swiperController) {
            ForEach(mainViewModel.getSwiperImages(), (img: Resource) => {
              Image(img).borderRadius($r('app.float.home_swiper_borderRadius'))
            }, (img: Resource) => JSON.stringify(img.id))
          }
          .margin({ top: $r('app.float.home_swiper_margin') })
          .autoPlay(true)
          .borderRadius($r('app.float.home_swiper_borderRadius'))

          Text('分类')
            .fontSize($r('app.float.normal_text_size'))
            .fontWeight(FontWeight.Medium)
            .width(CommonConstants.FULL_PARENT)
            .margin({ top: $r('app.float.home_text_margin') })

          Grid() {
            GridItem() {
              Column() {
                Text('全部')
                  .fontColor(Color.White)
                Text('全部作品展示')
                  .margin({ top: $r('app.float.home_list_margin') })
                  .fontSize($r('app.float.little_text_size'))
                  .fontColor(Color.White)
              }
              .alignItems(HorizontalAlign.Start)
            }.columnStart(1).columnEnd(2)
            .onClick(() => {
              router.pushUrl({ url: 'pages/IndexPage' });
            })
            .padding({ top: $r('app.float.home_list_padding'), left: $r('app.float.home_list_padding') })
            .borderRadius($r('app.float.home_backgroundImage_borderRadius'))
            .align(Alignment.TopStart)
            .backgroundImage($r("app.media.home_fenlei_1"))
            .backgroundImageSize(ImageSize.Cover)
            .width(CommonConstants.FULL_PARENT)
            .height(CommonConstants.FULL_PARENT)
            .clickEffect({
              level: ClickEffectLevel.MIDDLE,
              scale: 0.8
            })

            GridItem() {
              Column() {
                Text('石膏娃娃')
                  .fontSize($r('app.float.normal_text_size'))
                  .fontWeight(FontWeight.Medium)
                  .fontColor(Color.White)

                Text('彩绘石膏娃娃')
                  .margin({ top: $r('app.float.home_list_margin') })
                  .fontSize($r('app.float.little_text_size'))
                  .fontColor($r('app.color.home_grid_fontColor'))
                  .fontColor(Color.White)
              }
              .alignItems(HorizontalAlign.Start)
            }
            .padding({ top: $r('app.float.home_list_padding'), left: $r('app.float.home_list_padding') })
            .borderRadius($r('app.float.home_backgroundImage_borderRadius'))
            .align(Alignment.TopStart)
            .backgroundImage($r("app.media.home_fenlei_3"))
            .backgroundImageSize(ImageSize.Cover)
            .width(CommonConstants.FULL_PARENT)
            .height(CommonConstants.FULL_PARENT)
            .clickEffect({
              level: ClickEffectLevel.MIDDLE,
              scale: 0.8
            }).onClick((secondItem) => {
              httpRequestGet(`${CommonConstants.API_URL}/getArt`).then((data: ResponseResult) => {
                let arr1 = []
                let arr2 = []
                data.data.forEach((item: ESObject, key: string) => {
                  if (item.category === 1) {
                    arr1 = arr1.concat(item.images)
                  } else if (item.category === 2) {
                    arr2 = arr2.concat(item.images)
                  }
                })

                router.pushUrl({
                  url: Constants.URL_LIST_PAGE,
                  params: { photoArr: arr1 }
                });
              })

            })

            GridItem() {
              Column() {
                Text('手工DIY')
                  .fontSize($r('app.float.normal_text_size'))
                  .fontWeight(FontWeight.Medium)
                  .fontColor(Color.White)

                Text('手工DIY')
                  .margin({ top: $r('app.float.home_list_margin') })
                  .fontSize($r('app.float.little_text_size'))
                  .fontColor($r('app.color.home_grid_fontColor'))
                  .fontColor(Color.White)
              }
              .alignItems(HorizontalAlign.Start)
            }
            .padding({ top: $r('app.float.home_list_padding'), left: $r('app.float.home_list_padding') })
            .borderRadius($r('app.float.home_backgroundImage_borderRadius'))
            .align(Alignment.TopStart)
            .backgroundImage($r("app.media.home_fenlei_4"))
            .backgroundImageSize(ImageSize.Cover)
            .width(CommonConstants.FULL_PARENT)
            .height(CommonConstants.FULL_PARENT)
            .clickEffect({
              level: ClickEffectLevel.MIDDLE,
              scale: 0.8
            }).onClick((secondItem) => {
              httpRequestGet(`${CommonConstants.API_URL}/getArt`).then((data: ResponseResult) => {
                let arr1 = []
                let arr2 = []
                data.data.forEach((item: ESObject, key: string) => {
                  if (item.category === 1) {
                    arr1 = arr1.concat(item.images)
                  } else if (item.category === 2) {
                    arr2 = arr2.concat(item.images)
                  }
                })

                router.pushUrl({
                  url: Constants.URL_LIST_PAGE,
                  params: { photoArr: arr2 }
                });
              })
            })
          }
          .width(CommonConstants.FULL_PARENT)
          .height($r('app.float.home_secondGrid_height'))
          .columnsTemplate('1fr 1fr')
          .rowsTemplate('1fr 1fr')
          .columnsGap($r('app.float.home_grid_columnsGap'))
          .rowsGap($r('app.float.home_grid_rowGap'))

          Column({ space: CommonConstants.COMMON_SPACE }) {
            Text('列表')
              .fontSize($r('app.float.normal_text_size'))
              .fontWeight(FontWeight.Medium)
              .width(CommonConstants.FULL_PARENT)
              .margin({ top: $r('app.float.home_text_margin') })

            WaterFlowComponent()
          }

        }
      }
      .scrollBar(BarState.Off)
      .width("100%").height("100%")
      .onReachStart(() => {
        // promptAction.showToast({ message: '到顶' })
      })
      .onReachEnd(() => {
        // promptAction.showToast({ message: '到底' })
      })
    }
  }
}

其中提取了 WaterFlowComponent() 为瀑布流的组件。
瀑布流展示效果如下图所示。
#HarmonyOS NEXT体验官# 体验HarmonyOS开发流程,开发一个简单图册展示app(二)-鸿蒙开发者社区

在这里可能会遇到一个问题,Scroll组件和瀑布流的组件滑动有冲突!解决办法在WaterFlowComponent组件中。
主要是在WaterFlow组件中设置nestedScroll属性。
WaterFlowComponent的源码如下。

import ProductItem from '../viewmodel/ProductItem';
import { WaterFlowDataSource } from '../viewmodel/WaterFlowDataSource';
import Const from '../common/constants/CommonConstants';
import { waterFlowData } from '../viewmodel/HomeViewModel';
import FlowItemComponent from '../view/FlowItemComponent';
import { httpRequestPost } from '../common/utils/HttpUtil';
import CommonConstants from '../common/constants/CommonConstants';
import ResponseResult from '../viewmodel/ResponseResult';
import { router } from '@kit.ArkUI';

/**
 * Water flow component.
 *
 * Usage: Directly reference WaterFlowComponent().
 */
@Component
export default struct WaterFlowComponent {
  @State listPosition: number = 0; // 0代表滚动到List顶部,1代表中间值,2代表滚动到List底部。
  private datasource: WaterFlowDataSource = new WaterFlowDataSource();
  @State ArrData: Array<object | undefined> = [];

  aboutToAppear() {
    httpRequestPost(`${CommonConstants.API_URL}/getArt`).then((data: ResponseResult) => {
      // this.dataArray = data.data || []
      this.ArrData = data.data
    })
  }

  build() {
    WaterFlow({ footer: (): void => this.itemFoot() }) {
      ForEach(this.ArrData, (item: ESObject) => {
        FlowItem() {
          FlowItemComponent({ item: item })
            .clickEffect({
              level: ClickEffectLevel.MIDDLE,
              scale: 0.8
            }).onClick(() => {
            router.pushUrl({
              url: 'pages/ArtDetail',
              params: {
                id: item?._id || ''
              }
            })
          })
        }
      }, (item: ProductItem) => JSON.stringify(item))
    }
    .nestedScroll({ scrollForward: NestedScrollMode.PARENT_FIRST, scrollBackward: NestedScrollMode.SELF_FIRST })
    .layoutWeight(Const.WATER_FLOW_LAYOUT_WEIGHT)
    .layoutDirection(FlexDirection.Column)
    .columnsTemplate(Const.WATER_FLOW_COLUMNS_TEMPLATE)
    .columnsGap($r('app.float.water_flow_columns_gap'))
    .rowsGap($r('app.float.water_flow_row_gap'))
  }

  @Builder
  itemFoot() {
    Column() {
      Text($r('app.string.footer_text'))
        .fontColor(Color.Gray)
        .fontSize($r('app.float.footer_text_size'))
        .width(Const.FULL_WIDTH)
        .height($r('app.float.footer_text_height'))
        .textAlign(TextAlign.Center)
    }
  }
}

本次也可以当成Scroll组件和WaterFlow组件嵌套使用的一个案例。未做触底加载更多的逻辑,但是保留了其方法。方便后续实现。

后续详情页面和相册页面,再做一个小篇幅的介绍。本次更多的是代码上的演示。主要通过代码来介绍的是UI层的构建思路。

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
标签
已于2024-7-29 16:44:50修改
1
收藏
回复
举报
回复
    相关推荐