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

RandomBoolean
发布于 2024-8-21 16:34
浏览
0收藏

接上一篇文章,首页完成后,接着是文章的详情页和分类的相册页面。文章详情页相对简单,先介绍一下文章详情。

文章详情

首页瀑布流的布局代码如下

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'))

监听FlowItemComponent组件的onCLick事件,进行调整并传参文章详情id

文章详情的效果如图所示

#HarmonyOS NEXT体验官# 体验HarmonyOS开发流程,开发一个简单图册展示app(三)-鸿蒙开发者社区

顶部是一个Swiper组件,底部是文章详情描述文字。在aboutToAppear生命周期中调用接口,获取数据并展示。

整体页面源码如下

import Constants from '../common/constants/Constants';
import { router } from '@kit.ArkUI';
import ResponseResult from '../viewmodel/ResponseResult';
import { httpRequestGet, httpRequestPost } from '../common/utils/HttpUtil';
import CommonConstants from '../common/constants/CommonConstants';
import { Art } from '../viewmodel/ApiTypeModel';

@Entry
@Component
struct ArtDetail {
  private artId: ESObject = router.getParams();
  @State artObj: Art | undefined  = undefined


  aboutToAppear() {
    this.artId = router.getParams()
    // console.warn(JSON.stringify(this.artId))
    httpRequestGet(`${CommonConstants.API_URL}/getArt?id=${this.artId.id}`).then((data: ResponseResult) => {
      this.artObj = data.data
    })
  }

  build() {
    Navigation() {
      Column() {
        Swiper() {
          ForEach(this.artObj?.images, (item: Resource) => {
            Image(item)
              .width(Constants.FULL_PERCENT)
              .height(Constants.FULL_PERCENT)
              .objectFit(ImageFit.Auto)
          }, (item: Resource, index?: number) => JSON.stringify(item) + index)
        }
        .autoPlay(true)
        .loop(true)
        .clip(true)
        .duration(Constants.BANNER_ANIMATE_DURATION)
        .indicator(false)
        .height('60%')

        Text(this.artObj?.title || '')
          .fontSize('18vp')
          .fontWeight(FontWeight.Medium)
          .width(CommonConstants.FULL_PARENT)
          .margin({ top: '12vp', left: '12vp' })

        Text(this.artObj?.content || '')
          .fontSize('14vp')
          .width(CommonConstants.FULL_PARENT)
          .margin({ top: '12vp', left: '12vp' })

      }
      .width('100%')
    }
    .hideBackButton(false)
    .titleMode(NavigationTitleMode.Mini)
  }
}

全部分类

点击首页全部分类展示的是一个堆叠相册集,效果如图所示。

#HarmonyOS NEXT体验官# 体验HarmonyOS开发流程,开发一个简单图册展示app(三)-鸿蒙开发者社区

本文只展示了两个分类。页面的源码如下

import { router } from '@kit.ArkUI';
import CommonConstants from '../common/constants/CommonConstants';
import Constants from '../common/constants/Constants';
import { httpRequestGet } from '../common/utils/HttpUtil';
import PhotoItem from '../view/PhotoItem';
import ResponseResult from '../viewmodel/ResponseResult';

@Entry
@Component
struct IndexPage {
  swiperController: SwiperController = new SwiperController();
  scroller: Scroller = new Scroller();
  @State currentIndex: number = 0;
  @State angle: number = 0;
  @State ArrData: Resource[][] = [];

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

      this.ArrData = [
        arr1,
        arr2
      ]

    })
  }

  build() {
    Navigation() {
      Column() {
        Grid() {
          ForEach(this.ArrData, (photoArr: Array<Resource>) => {
            GridItem() {
              PhotoItem({ photoArr })
            }
            .width(Constants.FULL_PERCENT)
            .aspectRatio(Constants.STACK_IMG_RATIO)
            .onClick(() => {
              router.pushUrl({
                url: Constants.URL_LIST_PAGE,
                params: { photoArr: photoArr }
              });
            })
          }, (item: Resource, index?: number) => JSON.stringify(item) + index)
        }
        .scrollBar(BarState.Off)
        .columnsTemplate(Constants.INDEX_COLUMNS_TEMPLATE)
        .columnsGap('12vp')
        .rowsGap('12vp')
        .padding({ left: '12vp', right: '12vp' })
        .width(Constants.FULL_PERCENT)
        .layoutWeight(1)
      }
      .width(Constants.FULL_PERCENT)
      .height(Constants.FULL_PERCENT)
    }
    .hideBackButton(false)
    .titleMode(NavigationTitleMode.Mini)
  }
}

PhotoItem组件是实现堆叠的关键,使用Stack进行的实现

import Constants from '../common/constants/Constants';

@Component
export default struct PhotoItem {
  photoArr: Array<Resource> = [];
  @State currentIndex: number = 0;
  private showCount: number = Constants.SHOW_COUNT / Constants.DOUBLE_NUMBER;

  @Builder albumPicBuilder(img: Resource, index: number) {
    Column() {
      Image(img)
        .width(Constants.FULL_PERCENT)
        .height(Constants.FULL_PERCENT)
        .borderRadius('12vp')
        .opacity(1 - (this.showCount - index - 1) * Constants.ITEM_OPACITY_OFFSET)
    }
    .padding((this.showCount - index - 1) * Constants.PHOTO_ITEM_PADDING)
    .offset({ y: (this.showCount - index - 1) * Constants.PHOTO_ITEM_OFFSET })
    .height(Constants.PHOTO_ITEM_PERCENT)
    .width(Constants.FULL_PERCENT)
  }

  build() {
    Stack({ alignContent: Alignment.Top }) {
      ForEach(Constants.CACHE_IMG_LIST, (image: string, index?: number) => {
        if (index) {
          this.albumPicBuilder(this.photoArr[this.showCount - index - 1], index)
        }
      }, (item: string, index?: number) => JSON.stringify(item) + index)
    }
    .width(Constants.FULL_PERCENT)
    .height(Constants.FULL_PERCENT)
  }
}

点击之后平铺展示全部相册,效果如图所示

#HarmonyOS NEXT体验官# 体验HarmonyOS开发流程,开发一个简单图册展示app(三)-鸿蒙开发者社区

Grid布局,源码如下

import { router } from '@kit.ArkUI';
import Constants from '../common/constants/Constants';

@Entry
@Component
struct ListPage {
  photoArr: Array<Resource> = (router.getParams() as Record<string, Array<Resource>>)[`${Constants.PARAM_PHOTO_ARR_KEY}`];
  @StorageLink('selectedIndex') selectedIndex: number = 0;

  build() {
    Navigation() {
      Grid() {
        ForEach(this.photoArr, (img: Resource, index?: number) => {
          GridItem() {
            Image(img)
              .height(Constants.FULL_PERCENT)
              .width(Constants.FULL_PERCENT)
              .objectFit(ImageFit.Cover)
              .onClick(() => {
                if (!index) {
                  index = 0;
                }
                this.selectedIndex = index;
                router.pushUrl({
                  url: Constants.URL_DETAIL_LIST_PAGE,
                  params: {
                    photoArr: this.photoArr,
                  }
                });
              })
          }
          .width(Constants.FULL_PERCENT)
          .aspectRatio(1)
        }, (item: Resource) => JSON.stringify(item))
      }
      .scrollBar(BarState.Off)
      .columnsTemplate(Constants.GRID_COLUMNS_TEMPLATE)
      .rowsGap(Constants.LIST_ITEM_SPACE)
      .columnsGap(Constants.LIST_ITEM_SPACE)
      .layoutWeight(1)
    }
    .title(Constants.PAGE_TITLE)
    .hideBackButton(false)
    .titleMode(NavigationTitleMode.Mini)
  }
}

再次点击某张图片,进入大图游览模式。效果如下图所示。

#HarmonyOS NEXT体验官# 体验HarmonyOS开发流程,开发一个简单图册展示app(三)-鸿蒙开发者社区

分别监听大图滑动和小图滑动的事件。源码如下:

import { router,display } from '@kit.ArkUI';
import Constants from '../common/constants/Constants';

enum scrollTypeEnum {
  STOP = 'onScrollStop',
  SCROLL = 'onScroll'
};

@Entry
@Component
struct DetailListPage {
  private smallScroller: Scroller = new Scroller();
  private bigScroller: Scroller = new Scroller();
  @State deviceWidth: number = Constants.DEFAULT_WIDTH;
  @State smallImgWidth: number = (this.deviceWidth - Constants.LIST_ITEM_SPACE * (Constants.SHOW_COUNT - 1)) /
  Constants.SHOW_COUNT;
  @State imageWidth: number = this.deviceWidth + this.smallImgWidth;
  private photoArr: Array<Resource | string> = (router.getParams() as Record<string, Array<Resource | string>>)[`${Constants.PARAM_PHOTO_ARR_KEY}`];
  private smallPhotoArr: Array<Resource | string> = new Array<Resource | string>().concat(Constants.CACHE_IMG_LIST,
    (router.getParams() as Record<string, Array<Resource | string>>)[`${Constants.PARAM_PHOTO_ARR_KEY}`],
    Constants.CACHE_IMG_LIST)
  @StorageLink('selectedIndex') selectedIndex: number = 0;

  @Builder SmallImgItemBuilder(img: Resource, index?: number) {
    if (index && index > (Constants.CACHE_IMG_SIZE - 1) && index < (this.smallPhotoArr.length - Constants.CACHE_IMG_SIZE)) {
      Image(img)
        .onClick(() => this.smallImgClickAction(index))
    }
  }

  aboutToAppear() {
    let displayClass: display.Display = display.getDefaultDisplaySync();
    let width = displayClass?.width / displayClass.densityPixels ?? Constants.DEFAULT_WIDTH;
    this.deviceWidth = width;
    this.smallImgWidth = (width - Constants.LIST_ITEM_SPACE * (Constants.SHOW_COUNT - 1)) / Constants.SHOW_COUNT;
    this.imageWidth = this.deviceWidth + this.smallImgWidth;
  }

  onPageShow() {
    this.smallScroller.scrollToIndex(this.selectedIndex);
    this.bigScroller.scrollToIndex(this.selectedIndex);
  }

  goDetailPage(): void {
    router.pushUrl({
      url: Constants.URL_DETAIL_PAGE,
      params: { photoArr: this.photoArr }
    });
  }

  smallImgClickAction(index: number): void {
    this.selectedIndex = index - Constants.CACHE_IMG_SIZE;
    this.smallScroller.scrollToIndex(this.selectedIndex);
    this.bigScroller.scrollToIndex(this.selectedIndex);
  }

  smallScrollAction(type: scrollTypeEnum): void {
    this.selectedIndex = Math.round(((this.smallScroller.currentOffset().xOffset as number) +
    this.smallImgWidth / Constants.DOUBLE_NUMBER) / (this.smallImgWidth + Constants.LIST_ITEM_SPACE));
    if (type === scrollTypeEnum.SCROLL) {
      this.bigScroller.scrollTo({ xOffset: this.selectedIndex * this.imageWidth, yOffset: 0 });
    } else {
      this.smallScroller.scrollTo({ xOffset: this.selectedIndex * this.smallImgWidth, yOffset: 0 });
    }
  }

  bigScrollAction(type: scrollTypeEnum): void {
    let smallWidth = this.smallImgWidth + Constants.LIST_ITEM_SPACE;
    this.selectedIndex = Math.round(((this.bigScroller.currentOffset().xOffset as number) +
      smallWidth / Constants.DOUBLE_NUMBER) / this.imageWidth);
    if (type === scrollTypeEnum.SCROLL) {
      this.smallScroller.scrollTo({ xOffset: this.selectedIndex * smallWidth, yOffset: 0 });
    } else {
      this.bigScroller.scrollTo({ xOffset: this.selectedIndex * this.imageWidth, yOffset: 0 });
    }
  }

  build() {
    Navigation() {
      Stack({ alignContent: Alignment.Bottom }) {
        List({ scroller: this.bigScroller, initialIndex: this.selectedIndex }) {
          ForEach(this.photoArr, (img: Resource) => {
            ListItem() {
              Image(img)
                .height(Constants.FULL_PERCENT)
                .width(Constants.FULL_PERCENT)
                .objectFit(ImageFit.Contain)
                .gesture(PinchGesture({ fingers: Constants.DOUBLE_NUMBER })
                  .onActionStart(() => this.goDetailPage()))
                .onClick(() => this.goDetailPage())
            }
            .padding({
              left: this.smallImgWidth / Constants.DOUBLE_NUMBER,
              right: this.smallImgWidth / Constants.DOUBLE_NUMBER
            })
            .width(this.imageWidth)
          }, (item: Resource) => JSON.stringify(item))
        }
        .onScroll((scrollOffset, scrollState) => {
          if (scrollState === ScrollState.Fling) {
            this.bigScrollAction(scrollTypeEnum.SCROLL);
          }
        })
        .scrollBar(BarState.Off)
        .onScrollStop(() => this.bigScrollAction(scrollTypeEnum.STOP))
        .width(Constants.FULL_PERCENT)
        .height(Constants.FULL_PERCENT)
        .padding({ bottom: this.smallImgWidth * Constants.DOUBLE_NUMBER })
        .listDirection(Axis.Horizontal)

        List({
          scroller: this.smallScroller,
          space: Constants.LIST_ITEM_SPACE,
          initialIndex: this.selectedIndex
        }) {
          ForEach(this.smallPhotoArr, (img: Resource, index?: number) => {
            ListItem() {
              this.SmallImgItemBuilder(img, index)
            }
            .width(this.smallImgWidth)
            .aspectRatio(1)
          }, (item: Resource) => JSON.stringify(item))
        }
        .listDirection(Axis.Horizontal)
        .onScroll((scrollOffset, scrollState) => {
          if (scrollState === ScrollState.Fling) {
            this.smallScrollAction(scrollTypeEnum.SCROLL);
          }
        })
        .scrollBar(BarState.Off)
        .onScrollStop(() => this.smallScrollAction(scrollTypeEnum.STOP))
        .margin({ top: '20vp', bottom: '20vp' })
        .height(this.smallImgWidth)
        .width(Constants.FULL_PERCENT)
      }
      .width(this.imageWidth)
      .height(Constants.FULL_PERCENT)
    }
    .title(Constants.PAGE_TITLE)
    .hideBackButton(false)
    .titleMode(NavigationTitleMode.Mini)
  }
}

到此文章整体便介绍完成,以此简单的案例展示学习一下鸿蒙的开发流程。

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
标签
收藏
回复
举报
回复
    相关推荐