HarmonyOS开始玩耍:Grid网格组件结合Swiper组件实现微信朋友圈布局及图片预览效果(十五)

81 阅读10分钟

一、Grid组件

网格容器,由“行”和“列”分割的单元格所组成,通过指定“项目”所在的单元格做出各种各样的布局。

Grid(scroller?: Scroller)


1、参数
参数名参数类型必填参数描述
scrollerScroller可滚动组件的控制器。用于与可滚动组件进行绑定。 说明: 不允许和其他滚动类组件绑定同一个滚动控制对象。
2、属性
名称参数类型描述
columnsTemplate            string          设置当前网格布局列的数量,不设置时默认1列。 例如, ‘1fr 1fr 2fr’ 是将父组件分3列,将父组件允许的宽分为4等份,第一列占1份,第二列占1份,第三列占2份。 说明: 设置为’0fr’时,该列的列宽为0,不显示GridItem。设置为其他非法值时,GridItem显示为固定1列。
rowsTemplatestring设置当前网格布局行的数量,不设置时默认1行。 例如,‘1fr 1fr 2fr’是将父组件分三行,将父组件允许的高分为4等份,第一行占1份,第二行占一份,第三行占2份。 说明: 设置为’0fr’,则这一行的行宽为0,这一行GridItem不显示。设置为其他非法值,按固定1行处理。
columnsGapLength设置列与列的间距。 默认值:0 说明: 设置为小于0的值时,按默认值显示。
rowsGapLength设置行与行的间距。 默认值:0 说明: 设置为小于0的值时,按默认值显示。
3、Grid组件根据rowsTemplate、columnsTemplate属性的设置情况,可分为以下三种布局模式:
3.1、rowsTemplate、columnsTemplate同时设置:
  • Grid只展示固定行列数的元素,其余元素不展示,且Grid不可滚动。
  • 此模式下以下属性不生效:layoutDirection、maxCount、minCount、cellLength。
  • Grid的宽高没有设置时,默认适应父组件尺寸。
  • Grid网格列大小按照Grid自身内容区域大小减去所有行列Gap后按各个行列所占比重分配。
  • GridItem默认填满网格大小。
3.2、rowsTemplate、columnsTemplate仅设置其中的一个:
  • 元素按照设置的方向进行排布,超出Grid显示区域后,Grid可通过滚动的方式展示。
  • 如果设置了columnsTemplate,Grid滚动方向为垂直方向,主轴方向为垂直方向,交叉轴方向为水平方向。
  • 如果设置了rowsTemplate,Grid滚动方向为水平方向,主轴方向为水平方向,交叉轴方向为垂直方向。
  • 此模式下以下属性不生效:layoutDirection、maxCount、minCount、cellLength。
  • 网格交叉轴方向尺寸根据Grid自身内容区域交叉轴尺寸减去交叉轴方向所有Gap后按所占比重分配。
  • 网格主轴方向尺寸取当前网格交叉轴方向所有GridItem高度最大值。
3.3、rowsTemplate、columnsTemplate都不设置:
  • 元素在layoutDirection方向上排布,列数由Grid的宽度、首个元素的宽度、minCount、maxCount、columnsGap共同决定。
  • 行数由Grid高度、首个元素高度、cellLength、rowsGap共同决定。超出行列容纳范围的元素不显示,也不能通过滚动进行展示。
  • 此模式下仅生效以下属性:layoutDirection、maxCount、minCount、cellLength、editMode、columnsGap、rowsGap。
  • 当前layoutDirection设置为Row时,先从左到右排列,排满一行再排一下一列。剩余高度不足时不再布局,整体内容顶部居中。
  • 当前layoutDirection设置为Column时,先从上到下排列,排满一列再排一下一列,剩余宽度度不足时不再。整体内容顶部居中。

二、GridItem组件

网格容器中单项内容容器。

GridItem()


1、属性
名称参数类型描述
rowStartnumber指定当前元素起始行号。
rowEndnumber指定当前元素终点行号。
columnStartnumber指定当前元素起始列号。
columnEndnumber指定当前元素终点列号。

起始行号、终点行号、起始列号、终点列号生效规则如下:

  • rowStart/rowEnd合理取值范围为0总行数-1,columnStart/columnEnd合理取值范围为0总列数-1。
  • 只有在设置columnTemplate和rowTemplate的Grid中,设置合理的rowStart/rowEnd/columnStart/columnEnd四个属性的GridItem才能按照指定的行列号布局。
  • 在设置columnTemplate和rowTemplate的Grid中,单独设置行号rowStart/rowEnd或列号columnStart/columnEnd的GridItem会占用指定的行数(rowEnd-rowStart+1)或列数(columnEnd-columnStart+1)。
  • 在只设置columnTemplate的Grid中设置列号columnStart/columnEnd的GridItem按照指定的列数布局。
  • 在只设置rowTemplate的Grid中设置行号rowStart/rowEnd的GridItem按照指定的行数布局。
  • columnTemplate和rowTemplate都不设置的Grid中GridItem的行列号属性无效。

三、Swiper组件

滑块视图容器,提供子组件滑动轮播显示的能力。

Swiper本身是一个容器组件,当设置了多个子组件后,可以对这些子组件进行轮播显示。通常,在一些应用首页显示推荐的内容时,需要用到轮播显示的能力。

Swiper(controller?: SwiperController)


1、参数
参数名参数类型必填参数描述
controllerSwiperController给组件绑定一个控制器,用来控制组件翻页。
2、SwiperController

Swiper容器组件的控制器,可以将此对象绑定至Swiper组件,然后通过它控制翻页。

showNext
showNext(): void


翻至下一页。翻页带动效切换过程,时长通过duration指定。

showPrevious
showPrevious(): void


翻至上一页。翻页带动效切换过程,时长通过duration指定。

finishAnimation
finishAnimation(callback?: () => void): void


停止播放动画。

3、属性
名称参数类型描述
index                              number设置当前在容器中显示的子组件的索引值。 默认值:0 说明: 设置小于0或大于等于子组件数量时,按照默认值0处理。
autoPlayboolean子组件是否自动播放。 默认值:false 说明: loop为false时,自动轮播到最后一页时停止轮播。手势切换后不是最后一页时继续播放。
intervalnumber使用自动播放时播放的时间间隔,单位为毫秒。 默认值:3000
indicatorboolean是否启用导航点指示器。 默认值:true
loopboolean是否开启循环。 设置为true时表示开启循环,在LazyForEach懒循环加载模式下,加载的组件数量建议大于5个。 默认值:true
durationnumber子组件切换的动画时长,单位为毫秒。 默认值:400
verticalboolean是否为纵向滑动。 默认值:false
itemSpacenumber、string设置子组件与子组件之间间隙。 默认值:0 说明: 不支持设置百分比。
displayModeSwiperDisplayMode主轴方向上元素排列的模式,优先以displayCount设置的个数显示,displayCount未设置时本属性生效。 默认值:SwiperDisplayMode.Stretch
cachedCountnumber设置预加载子组件个数。 默认值:1
disableSwipeboolean禁用组件滑动切换功能。 默认值:false
curveCurve、string设置Swiper的动画曲线,默认为淡入淡出曲线,常用曲线参考Curve枚举说明,也可以通过插值计算模块提供的接口创建自定义的插值曲线对象。 默认值:Curve.Linear
indicatorStyle{ left?: Length, top?: Length, right?: Length, bottom?: Length, size?: Length, mask?: boolean, color?: ResourceColor, selectedColor?: ResourceColor }设置导航点样式: - left: 设置导航点距离Swiper组件左边的距离。 - top: 设置导航点距离Swiper组件顶部的距离。 - right: 设置导航点距离Swiper组件右边的距离。 - bottom: 设置导航点距离Swiper组件底部的距离。 - size: 设置导航点的直径。不支持设置百分比。默认值:6vp。 - mask: 设置是否显示导航点蒙层样式。 - color: 设置导航点的颜色。 - selectedColor: 设置选中的导航点的颜色。
displayCountnumber、string设置一页内元素显示个数。 默认值:1 说明: 字符串类型仅支持设置为’auto’,显示效果同SwiperDisplayMode.AutoLinear。 使用number类型且设置小于等于0时,按默认值1显示。 使用number类型时,子组件按照主轴均分Swiper宽度(减去displayCount-1的itemSpace)的方式进行主轴拉伸(收缩)布局。
effectModeEdgeEffect滑动效果,目前支持的滑动效果参见EdgeEffect的枚举说明。 默认值:EdgeEffect.Spring 说明: 控制器接口调用时不生效回弹。
4、事件
  • index:当前显示元素的索引。
onChange(event: (index: number) => void)

1

当前显示的子组件索引变化时触发该事件,返回值为当前显示的子组件的索引值。
说明:
Swiper组件结合LazyForEach使用时,不能在onChange事件里触发子页面UI的刷新。

onAnimationStart9+(event: (index: number) => void)


切换动画开始时触发该回调。
说明:
参数为动画开始前的index值(不是最终结束动画的index值),多列Swiper时,index为最左侧组件的索引。

onAnimationEnd9+(event: (index: number) => void)


切换动画结束时触发该回调。
说明:
当Swiper切换动效结束时触发,包括动画过程中手势中断,通过SwiperController调用finishAnimation。参数为动画结束后的index值,多列Swiper时,index为最左侧组件的索引。

四、Stack组件

堆叠容器,子组件按照顺序依次入栈,后一个子组件覆盖前一个子组件。

Stack(value?: { alignContent?: Alignment })


1、参数
参数名参数类型必填参数描述
alignContentAlignment设置子组件在容器内的对齐方式。 默认值:Alignment.Center
2、属性
参数名参数类型参数描述
alignContentAlignment设置所有子组件在容器内的对齐方式。 默认值:Alignment.Center 从API version 9开始,该接口支持在ArkTS卡片中使用。 说明: 该属性与通用属性align同时设置时,后设置的属性生效。

五、示例:微信朋友圈布局及图片预览效果

  • 头像昵称与背景图的叠加布局
  • 发表内容图片的网格布局
  • 点击图片预览的Swiper效果
  • 准备数据,定义一个数据模型,然后手写10条朋友圈数据
  • 这里分别展示了0-9张图的效果
  • 根据图片的数量计算列数和行数及宽度和高度
1、朋友圈布局页面
import router from '@ohos.router';

class MomentClass {
  public nickName: string; //昵称
  public content: string; //内容
  public images: ResourceStr[]; //内容图片列表

  constructor(nickName: string, content: string, images: ResourceStr[]) {
    this.nickName = nickName;
    this.content = content;
    this.images = images;
  }
}


@Entry
@Component
struct GridViewPage {
  @State momentList: MomentClass[] = [
    new MomentClass('英语一句话', 'Seeing much, suffering much, and studying much, are the three pillars of learning.', []),
    new MomentClass('每日一句', '我们大多数人骨子里头都有一种刚愎任性的意气,尤其是在少不更事和坠入爱河之时。', [$r("app.media.1")]),
    new MomentClass('今日份毒鸡汤', '你的计划,就像零食,吃到肚子里之后就是个屁。', [$r("app.media.1"), $r("app.media.2")]),
    new MomentClass('每日一句', '妈妈看着我们渐渐长大,奔向前程;我们却得看着妈妈缓缓老去,走近垂暮。同样的岁月却有不同的滋味。孩子在最懵懂的时光里得到最多的呵护,把陪伴视作理所当然,等到了懂得珍惜的年纪,总是懊恼错过了许多时光。', [$r("app.media.1"), $r("app.media.2"), $r("app.media.3")]),
    new MomentClass('每日一句', '幸福与满足很难得到共鸣,失败与伤痛却轻而易举。真假并不重要,人们是依靠疤痕、伤口,以及血的腥味去辨识同类的。', [$r("app.media.1"), $r("app.media.2"), $r("app.media.3"), $r("app.media.4")]),
    new MomentClass('今日份毒鸡汤', '我不会两面三刀,可我经常被两面插三刀。', [$r("app.media.1"), $r("app.media.2"), $r("app.media.3"), $r("app.media.4"), $r("app.media.5")]),
    new MomentClass('毒鸡汤', '因为穷我连关心你都不敢,就怕你说嘘寒问暖,不如打笔巨款。。', [$r("app.media.1"), $r("app.media.2"), $r("app.media.3"), $r("app.media.4"), $r("app.media.5"), $r("app.media.6")]),
    new MomentClass('英语一句话', 'He who cannot dance puts the blame on the floor.', [$r("app.media.1"), $r("app.media.2"), $r("app.media.3"), $r("app.media.4"), $r("app.media.5"), $r("app.media.6"), $r("app.media.ic_widget")]),
    new MomentClass('毒鸡汤', '有钱人可以选择低调,而你,却只能低调。', [$r("app.media.1"), $r("app.media.2"), $r("app.media.3"), $r("app.media.4"), $r("app.media.5"), $r("app.media.6"), $r("app.media.ic_widget"), $r("app.media.icon")]),
    new MomentClass('毒鸡汤', '谁说你没有真爱,烦恼与你同在。', [$r("app.media.1"), $r("app.media.2"), $r("app.media.3"), $r("app.media.4"), $r("app.media.5"), $r("app.media.6"), $r("app.media.ic_widget"), $r("app.media.icon"), $r("app.media.download")])
  ];


  //计算列数
  calcColumnsTemplate(index) {
    let result: string = '1fr'
    let length: number = this.momentList[index].images.length || 0
    if (length == 1) {
      result = '1fr'
    } else if (length == 2 || length == 4) {
      result = '1fr 1fr'
    } else {
      result = '1fr 1fr 1fr'
    }
    return result
  }

  //计算行数
  calcRowsTemplate(index) {
    let result: string = '1fr'
    let length: number = this.momentList[index].images.length || 0
    if (length == 1) {
      result = '1fr'
    } else if (length >= 2 && length <= 6 && length != 3) {
      result = '1fr 1fr'
    } else {
      result = '1fr 1fr 1fr'
    }
    return result
  }

  //计算宽度
  calcGridWidth(index) {
    let result: number = 0
    let length: number = this.momentList[index].images.length || 0
    if (length == 1) {
      result = 70
    } else if (length == 2 || length == 4) {
      result = 145
    } else {
      result = 220
    }
    return result
  }

  //计算高度
  calcGridHeight(index) {
    let result: number = 0
    let length: number = this.momentList[index].images.length || 0
    if (length <= 3) {
      result = 70
    } else if (length > 3 && length <= 6) {
      result = 145
    } else {
      result = 220
    }
    return result
  }

  build() {
    Column() {
      List({ space: 20 }) {
        ListItem() {
          Stack({ alignContent: Alignment.BottomEnd }) {
            Column() {
              Image($r("app.media.4"))
                .width('100%')
                .height(276)
                .objectFit(ImageFit.Cover)
            }
            .width('100%')
            .height(300)

            Row({ space: 10 }) {
              Text('A 赢乐')
                .fontSize(20)
                .fontColor(0xffffff)
                .margin({ bottom: 10 })
              Image($r("app.media.user"))
                .width(80)
                .height(80)
                .borderRadius(8)
                .objectFit(ImageFit.Cover)
            }
            .height(80)
            .justifyContent(FlexAlign.End)
            .padding(14)
          }
          .width('100%')
          .height(300)
        }

        ForEach(this.momentList, (item: MomentClass, index: number) => {
          ListItem() {
            Row({ space: 10 }) {
              Image($r("app.media.icon"))
                .width(50)
                .height(50)
                .borderRadius(6)
                .objectFit(ImageFit.Cover)

              Column({ space: 10 }) {
                Text(`${item.nickName} - ${index}图`)
                  .fontSize(16)
                  .fontColor('#409EFF')
                Text(item.content)
                  .fontSize(16)
                  .lineHeight(22)
                if (item.images && item.images.length > 0) {
                  Grid() {
                    ForEach(item.images, (img: ResourceStr, index: number) => {
                      GridItem() {
                        Image(img)
                          .height(70)
                          .width(70)
                          .objectFit(ImageFit.Cover)
                          .borderRadius(2)
                          .onClick(() => {
                            router.pushUrl({
                              url: "pages/SwiperPage",
                              params: {
                                images: item.images,
                                index: index
                              }
                            })
                          })
                      }
                    })
                  }
                  .columnsTemplate(this.calcColumnsTemplate(index))
                  .rowsTemplate(this.calcRowsTemplate(index))
                  .columnsGap(5)
                  .rowsGap(5)
                  .width(this.calcGridWidth(index))
                  .height(this.calcGridHeight(index))
                }
              }
              .layoutWeight(1)
              .alignItems(HorizontalAlign.Start)
              .justifyContent(FlexAlign.Start)
            }
            .width('100%')
            .alignItems(VerticalAlign.Top)
          }
          .width('100%')
          .padding(10)
        })

      }
      .divider({ strokeWidth: 1 })
      .width('100%')
    }
    .width('100%')
    .height(('100%'))
  }
}


2、图片预览页面
import router from '@ohos.router';

@Entry
@Component
struct SwiperPage {
  @State images: ResourceStr[] = []
  @State index: number = 0

  aboutToAppear() {
    let params: object = router.getParams()
    this.images = params["images"]
    this.index = params["index"]
  }

  build() {
    Column() {
      Swiper() {
        ForEach(this.images, (item: ResourceStr) => {
          Image(item)
            .width("100%")
            .objectFit(ImageFit.Auto)
        })
      }
      .height("100%")
      .index(this.index)
      .indicatorStyle({
        color: '#ffffff'
      })
    }
    .width('100%')
    .height('100%')
    .backgroundColor(Color.Black)
    .justifyContent(FlexAlign.Center)
    .onClick(() => {
      // 点击返回至上一页面
      router.back()
    })
  }
}



六、效果展示

在这里插入图片描述

在这里插入图片描述

我的博客只写前端博文,点击我去看更多喜欢的前端博文,欢迎大家一起讨论学习!【https://blog.csdn.net/qq_29101285?spm=1011.2266.3001.5343】