随易App开发实战 :Swiper页面设计开发

项目地址:app_EasyRandom: 随易app (gitee.com)

本案例,将通过Swiper组件ForEach循环渲染配合断点功能,设计实现一个完整的响应式的Swiper页面案例。此轮播图组件会根据设备宽度自动更改轮播图的显示数量,使用ArkTS原生框架开发。

ce0dc6d22d7373ddea162c6f70f454b.jpg

相关技术要素

  • ForEach:循环渲染
  • Swiper:轮播图组件
  • 断点监听:实现响应式布局

本案例依照本人开发的原生应用随易,本案例完整代码将写在文章最后,项目的完整代码和详情请访问:随易Gitee项目地址(本案例文件位于项目product/default/src/main/ets/pages/RandomPage文件中)

项目整体结构

├──common/src/main/ets                       //公共库                      
│  ├──components                             //公共组件
│  └──utils                                  //公共函数库
├──product/default/src/main/ets              // 代码区                       
│  ├──entryability                           //页面Ability
│  ├──components                             //子组件
│  ├──constants                              //公共常量
│  ├──datas                                  //页面数据
│  ├──pages                                  //主要页面
│  ├──defaultformability                     //卡片Ability
│  └──widgets                                //卡片组件
└──product/default/src/main/resources        // 资源文件目录

具体实现

1.分析数据结构

首先要进行对于组件的数据类型进行分析,通过统一的数据类型方便后续的循环渲染,轮播图组件的核心是图片和标题,在功能性上则需要根据需求设计(例如有跳转功能可以加入跳转的链接与参数)。为了美观性我们还可以定义一个颜色,因此设计如下的数据结构:

class AppItem {
  name: string  //标题
  img: Resource //图片
  url: string | null    //链接
  params: string | null //参数
  colors: Array<[ResourceColor, number]> | null  //颜色

  constructor(name: string, img: Resource, url: string, params: string | null,
    colors: Array<[ResourceColor, number]> | null) {
    this.name = name;
    this.img = img;
    this.url = url
    this.params = params
    this.colors = colors
  }
}

2.页面基础搭建

使用Swiper、子组件swiperItem配合列表渲染完成组件基础搭建

2.1轮播图子组件封装

通过子组件的封装和统一的数据结构,方便后续的列表渲染。在组件添加onClick事件函数,如此处为组件添加router功能,在数据定义时便可统一给组件的跳转位置和附带参数进行赋值,方便后续的修改添加等。

 @Builder
  swiperItem(app: AppItem) {
    Image(app.img)
      .width('100%')
      .aspectRatio(1)
      .objectFit(ImageFit.Fill)
      .borderRadius(16)
      .backgroundColor(0x00beff)
      .onClick(() => {
        router.pushUrl({ url: app.url, params: app.params })
      })
  }

先前规范定义的参数在此便能够充分发挥作用,通过合理的赋值关系方便后续统一的进行列表渲染

2.2 forEach循环渲染

通过forEach循环渲染,我们可以方便的通过数据驱动来修改页面内容,方便后续的更改与维护。

let Swipers: Array<AppItem> = [
  new AppItem('真心话大冒险', $r("app.media.icon_HonestOrChallenge"),
    'components/HonestOrChallenge', '', [[0xFF6347, 0], [0xFFD700, 0.5], [0xFFA500, 1]]),
  new AppItem('幸运转盘', $r("app.media.icon_Rolls"),
    'pages/Index', '', [[0x00BFFF, 0], [0x1E90FF, 0.5], [0x00008B, 1]]),
  new AppItem('人生必去的100个地方', $r("app.media.icon_RandomPlaces"),
    'components/RandomPlaces', '', [[0x00beff, 0], [0xffeb00, 0.5], [0xffff00, 1]]),
]//测试数据


Swiper(
        ForEach(Swipers, (app: AppItem) => {
            this.swiperItem(app)        
        })
)
2.3 调整组件样式

外观样式比较主观,仅作简单参考(完整示例代码在文章末尾)。Swiper组件的具体开发细节可查看官方文档,在此列举几个重要的属性和用法:

  • autoPlay(false):是否自动轮播
  • indicator(true):是否显示下标
  • ​​​​itemSpace(10):子组件间距
  • displayCount():显示数量,此属性为后续的响应式布局提供基础,内部传入number类型可决定子组件的显示数量

3.实现响应式(通过监听断点)

首先要了解ArkUI有很多种实现响应式和监听的方式:可在参考此官方文档响应式布局-布局能力进行选择,本案例这里选择使用断点BreakPoint来实现,断点的基础使用方式如下:

import { BreakpointState, BreakpointSystem } from '@ohos/common'
//以上文件为从官网案例中下载

  @State currentBreakpoint: BreakpointState<string> = BreakpointState.of({ sm: "sm", md: "md", lg: "lg" })
  aboutToAppear() {
  BreakpointSystem.getInstance().attach(this.currentBreakpoint)
  BreakpointSystem.getInstance().start()
  }
  aboutToDisappear() { 
  BreakpointSystem.getInstance().attach(this.currentBreakpoint)
   BreakpointSystem.getInstance().stop()
  }

此响应式的关键点便在于将断点值与上文提到的重要属性displayCount()有机配合使用,使用三元运算符this.currentBreakpoint.value === 'sm' ? 1 : (this.currentBreakpoint.value === 'md' ? 2 : 3)便可实现轮播图组件在小设备显示1个,中设备显示两个,大设备显示三个的效果。可根据实际情况进行调整,使用其它监听方式也同样可以使用此方法完成响应式操作,Swiper组件完整写法如下:

@Component
struct Banner {
  @State currentBreakpoint: BreakpointState<string> = BreakpointState.of({ sm: "sm", md: "md", lg: "lg" })

  aboutToAppear() {
    BreakpointSystem.getInstance().attach(this.currentBreakpoint)
    BreakpointSystem.getInstance().start()
  }

  aboutToDisappear() {
    BreakpointSystem.getInstance().attach(this.currentBreakpoint)
    BreakpointSystem.getInstance().stop()
  }


  build() {
    Swiper() {
        ForEach(Swipers, (app: AppItem) => {
            this.swiperItem(app)        
        })
    }
    .autoPlay(false)
    .indicator(true)
    .itemSpace(10)
    .displayCount(this.currentBreakpoint.value === 'sm' ? 1 : (this.currentBreakpoint.value === 'md' ? 2 : 3))
    .width('100%')
    .padding({
      bottom: 36,
    })
  }
}

 

项目完整代码可前往app_EasyRandom - 码云 - 开源中国 (gitee.com)

页面完整代码:

页面依赖的BreakPointState文件为官方教学案例文件,可从官方文档和代码示例codelabs中下载,也可直接在本项目common模块中下载使用。

import { BreakpointState, BreakpointSystem } from '@ohos/common'
import { router } from '@kit.ArkUI'

@Entry
@Component
struct RandomPage {
  @State currentBreakpoint: BreakpointState<string> = BreakpointState.of({ sm: "sm", md: "md", lg: "lg" })

  aboutToAppear() {
    BreakpointSystem.getInstance().attach(this.currentBreakpoint)
    BreakpointSystem.getInstance().start()
  }

  aboutToDisappear() {
    BreakpointSystem.getInstance().attach(this.currentBreakpoint)
    BreakpointSystem.getInstance().stop()
  }
  build() {
    List() {
      ListItem().margin({top:12})
      ListItem() {
        Banner()
      }

      ListItem() {
        AppList()
      }
    }
    .width("100%")
    .padding({ left:this.currentBreakpoint.value=='sm'?12:24,right:this.currentBreakpoint.value=='sm'?12:24})
  }
}

class AppItem {
  name: string
  img: Resource
  url: string | null
  params: string | null
  colors: Array<[ResourceColor, number]> | null

  constructor(name: string, img: Resource, url: string, params: string | null,
    colors: Array<[ResourceColor, number]> | null) {
    this.name = name;
    this.img = img;
    this.url = url
    this.params = params
    this.colors = colors
  }
}

let Swipers: Array<AppItem> = [
  new AppItem('真心话大冒险', $r("app.media.icon_HonestOrChallenge"),
    'components/HonestOrChallenge', '', [[0xFF6347, 0], [0xFFD700, 0.5], [0xFFA500, 1]]),

  new AppItem('幸运转盘', $r("app.media.icon_Rolls"),
    'pages/Index', '', [[0x00BFFF, 0], [0x1E90FF, 0.5], [0x00008B, 1]]),

  new AppItem('人生必去的100个地方', $r("app.media.icon_RandomPlaces"),
    'components/RandomPlaces', '', [[0x00beff, 0], [0xffeb00, 0.5], [0xffff00, 1]]),
]

@Component
struct Banner {
  @State currentBreakpoint: BreakpointState<string> = BreakpointState.of({ sm: "sm", md: "md", lg: "lg" })

  aboutToAppear() {
    BreakpointSystem.getInstance().attach(this.currentBreakpoint)
    BreakpointSystem.getInstance().start()
  }

  aboutToDisappear() {
    BreakpointSystem.getInstance().attach(this.currentBreakpoint)
    BreakpointSystem.getInstance().stop()
  }

  @Builder
  swiperItem(app: AppItem) {
    Image(app.img)
      .width('100%')
      .aspectRatio(1)
      .objectFit(ImageFit.Fill)
      .borderRadius(16)
      .backgroundColor(0x00beff)
      .onClick(() => {
        router.pushUrl({ url: app.url, params: app.params })
      })
  }

  build() {
    Swiper() {
      this.swiperItem(Swipers[0])
      this.swiperItem(Swipers[1])
      this.swiperItem(Swipers[2])
    }
    .autoPlay(false)
    .indicator(true)
    .itemSpace(10)
    .displayCount(this.currentBreakpoint.value === 'sm' ? 1 : (this.currentBreakpoint.value === 'md' ? 2 : 3))
    .width('100%')
    .padding({
     // left: 12,
      //right: 12,
      bottom: 36,
      //top: 16
    })
  }
}


let Apps: Array<AppItem> = [
  new AppItem('颜色搭配', $r("app.media.icon_RandomColors"),
    'components/RandomColors', '', [[0xDDA0DD, 0], [0xEE82EE, 0.5], [0x9400D3, 1]]),

  new AppItem('扔硬币', $r("app.media.icon_FlipCoin"),
    'components/FlipCoin', '', [[0xFF6347, 0], [0xFFD700, 0.5], [0xFFA500, 1]]),

  new AppItem('抽签', $r("app.media.icon_RandomNames"),
    'components/RandomNames', '', [[0x98FB98, 0], [0x32CD32, 0.5], [0x006400, 1]]),

  new AppItem('掷骰子', $r("app.media.icon_RollDices"),
    'components/RollDices', '', [[0x6A5ACD, 0], [0x483D8B, 0.5], [0x0000FF, 1]]),

  new AppItem('吃什么', $r("app.media.icon_RandomFoods"),
    'components/RandomFoods', '', [[0xADFF2F, 0], [0x7FFF00, 0.5], [0x008000, 1]]),

  new AppItem('随机数', $r("app.media.icon_RollNumbers"),
    'components/RandomNumbers', '', [[0xFFDAB9, 0], [0xFFE4B5, 0.5], [0xB8860B, 1]]),

]

@Component
struct AppList {
  @State currentBreakpoint: BreakpointState<string> = BreakpointState.of({ sm: "sm", md: "md", lg: "lg" })
  private apps: Array<AppItem> = Apps;

  aboutToAppear() {
    BreakpointSystem.getInstance().attach(this.currentBreakpoint)
    BreakpointSystem.getInstance().start()
  }

  aboutToDisappear() {
    BreakpointSystem.getInstance().attach(this.currentBreakpoint)
    BreakpointSystem.getInstance().stop()
  }

  @Builder
  appListItem(app: AppItem) {
    Column() {

      Image(app.img)
        .width(this.currentBreakpoint.value === 'lg' ? 120 : 112)//80 56
        .height(this.currentBreakpoint.value === 'lg' ? 120 : 112)//80 56
        .margin({ bottom: 8 })//.backgroundColor(app.bgc)
        .linearGradient({ angle: 180, colors: app.colors })
        .clip(true)
        .aspectRatio(1)
        .borderRadius(16)
      Text(app.name)
        .width(this.currentBreakpoint.value === 'lg' ? 120 : 112)//80 56
        .height(32)
        .fontSize(16)
        .fontWeight(FontWeight.Bold)
        .textAlign(TextAlign.Center)
        .fontColor('#18181A')
        .margin({ bottom: 8 })
    }
    .onClick(() => {
      router.pushUrl({ url: app.url, params: app.params })
    })
  }

  build() {
    Column() {
      Row() {
        Text('热门功能').fontSize(20).fontWeight(FontWeight.Bold)
      }.justifyContent(FlexAlign.Start).width('100%').height(40).alignItems(VerticalAlign.Center)

      List({ space: this.currentBreakpoint.value === 'lg' ? 22 : 20 }) {
        ForEach(this.apps, (app: AppItem) => {
          ListItem() {
            this.appListItem(app)
          }
        })
      }
      .width('100%')
      .height(this.currentBreakpoint.value === 'lg' ? 320 : 280)
      .listDirection(Axis.Horizontal)
    }
    .width('100%')
    .height(this.currentBreakpoint.value === 'lg' ? 338 : 218)
    .padding({ bottom: 8,
      //left: 12,
    //  right: 12
      })
  }
}

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐