当前位置:   article > 正文

鸿蒙HarmonyOS开发:Grid网格组件结合Swiper组件实现微信朋友圈布局及图片预览效果_鸿蒙仿微信朋友圈动态展示是怎么布局的呢

鸿蒙仿微信朋友圈动态展示是怎么布局的呢

一、Grid组件

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

Grid(scroller?: Scroller)
  • 1
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
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
1、参数
参数名参数类型必填参数描述
controllerSwiperController给组件绑定一个控制器,用来控制组件翻页。
2、SwiperController

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

showNext
showNext(): void
  • 1

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

showPrevious
showPrevious(): void
  • 1

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

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

停止播放动画。

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)
  • 1

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

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

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

四、Stack组件

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

Stack(value?: { alignContent?: Alignment })
  • 1
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%'))
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
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()
    })
  }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

六、效果展示

在这里插入图片描述

在这里插入图片描述

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

闽ICP备14008679号