基于HarmonyOS ArkUI 3.0 框架,瀑布式显示HDC2021图片 原创 精华

发布于 2021-10-29 11:29
浏览
9收藏

一, 前言

    在介绍之前,虽然上篇 基于HarmonyOS ArkUI 3.0 框架,我成功开发了流式布局网络语 吐槽过了, 再吐槽一下为什么文档Ets组件里没有文本输入框,这么基础的组件都不先提供的,好的当时在HDC大会体验Ets实例时,见到有用输入框,名叫TextInput,在开发工具是没有提示的,然后我也尝试用它来为我的Demo提供输入,然而发现个问题,当我把TextInput组件里的text属于绑定@State 定义变量时,每次在文本框输入内容,点击按钮,清空变量值,这时文本输入框也清空了,但当我再次输入内容时,输入文本框会追加上次的内容,估计是因为TextInput有这个Bug,所以还没有在文档上显示出来。这里显示的瀑布式布局效果,我也是在HDC大会上,印象里见到过这样的效果,但现在已经记不清楚在哪里看到过了,然后以为文档里有实例或组件说明,找了一遍, 没有找到,然后就在网上科普了一下瀑布式布局原理,道理都懂了,由于前端太菜了,没有在eTS上按照原理实现, 然后就想到了用Flex布局的FlexDirection.Column, 再加上高度设备,效果也出来了,但觉得不是真正的瀑布式布局,我想下来官网文档开放布局了,一行代码就可以有瀑布式布局了,目前先用着自己写的效果了.

    简单介绍一下本文的实现效果,在文本输入框输入图片名称, 模糊搜索出符合条件的图片,点击搜索按钮,把符合条件图片添加到下面瀑布式布局的组件里,图片以x、y轴缩放从0.5变化到1,透明度从0到1 显示出来,点击随机删除按钮时,从下面图片随机删除一个,并且以沿y轴旋转360度消失。

二, 实现效果
开发环境效果: https://www.bilibili.com/video/BV1JQ4y1Q7z2/
远程模拟器效果: https://www.bilibili.com/video/BV1uq4y1R7vB/
基于HarmonyOS ArkUI 3.0 框架,瀑布式显示HDC2021图片-开源基础软件社区

三, 创建工程

    在这当作你已经安装好最新版本DevEco-Studio开发工具, 点击File -> New -> New Project… 弹出Create HarmonyOS Project窗口, 这里我选择空白eTS模板创建, 下来就跟着一起玩转HarmonyOS ArkUI 3.0 框架声明式开发吧.
基于HarmonyOS ArkUI 3.0 框架,瀑布式显示HDC2021图片-开源基础软件社区基于HarmonyOS ArkUI 3.0 框架,瀑布式显示HDC2021图片-开源基础软件社区

四, 界面开发

    界面有三个组件组合而成,文本输入框和搜索按钮组合成一个自定义组件, 历史记录和随机删除按钮组合成一个自定义组件,滚动组件和多个图片组件组合成一个自定义组件,同时还有Model结构, 初始化数据模型, 下面我们分别从上到下来介绍自定义组件:

import { PictureData } from '../model/PictureData.ets'
import { initOnStartup } from '../model/PictureDataModels.ets'

@Entry
@Component
struct PictureList {
  @State pictureItems: PictureData[] = initOnStartup()

  build() {
    Column() {
      // 文本输入框和搜索按钮组合自定义组件
      Search_Input({ pictureArr: $pictureItems })
      // 历史记录和随机删除按钮组合成自定义组件
      Operation_Picture({ pictureArr: $pictureItems })
      // 滚动组件和多个图片组件组合成自定义组件
      Flowlayout_Container({ pictureArr: $pictureItems})
    }
    .alignItems(HorizontalAlign.Center)
  }
}

    实现组件内转场动效,通过点击搜索按钮或随机删除按钮来控制图片组件的查找和移除,呈现容器组件子组件过滤和移除时的动效。

    这里用到组件转场动画,简单说一下组件转场主要通过transition属性方法配置转场参数,在组件搜索和移除时会执行过渡动效,需要配合animteTo才能生效。动效时长、曲线、延时跟随animateTo中的配置。

  1. 文本输入框和搜索按钮组合,在新增按钮的onClick事件中添加animateTo方法,来使下面图片子组件动效生效

@Component
struct Search_Input {
  @State searchInput: string = ''
  @Link pictureArr: PictureData[]

  build() {
    Flex({ alignItems: ItemAlign.Center }){
      TextInput({ placeholder: '请输入...', text: this.searchInput })
        .type(InputType.Normal)
        .placeholderColor(Color.Gray)
        .placeholderFont({ size: 50, weight: 2})
        .enterKeyType(EnterKeyType.Search)
        .caretColor(Color.Green)
        .layoutWeight(8)
        .height(40)
        .borderRadius('20px')
        .backgroundColor(Color.White)
        .onChange((value: string) => {
          this.searchInput = value
        })

      Button({type: ButtonType.Capsule, stateEffect:false}) {
        Text('查找').fontSize(17).fontColor(Color.Blue)
      }
      .layoutWeight(2)
      .backgroundColor('#00000000')
      .onClick((event: ClickEvent) => {
        if (this.searchInput != null && this.searchInput.length > 0) {
          let that = this;
          animateTo({ duration: 600 }, () => {
            this.pictureArr = this.pictureArr.filter((item, idx, arr) => item.name.indexOf(that.searchInput) > -1)
          })
          this.searchInput = ''
        }
      })
    }
    .height(60)
    .padding({left: 10})
    .backgroundColor('#FFedf2f5')
  }
}
  1. 历史记录和随机删除按钮组合
@Component
struct Operation_Picture {
  @Link pictureArr: PictureData[]

  build() {
    Flex({ alignItems: ItemAlign.Center }) {
      if (this.pictureArr.length > 0) {
        Text('历史记录')
          .fontSize(14)
          .fontColor(Color.Grey)
          .layoutWeight(5)

        Text('随机删除')
          .textAlign(TextAlign.End)
          .margin({right: 30})
          .fontSize(14)
          .fontColor(Color.Red)
          .layoutWeight(5)
          .onClick((event: ClickEvent) => {
            animateTo({ duration: 600 }, () => {
              var idx = Math.floor(Math.random()*this.pictureArr.length);
              this.pictureArr.splice(idx, 1)
            })
          })
      }
    }
    .height(40)
    .padding({ left: 20, top: 10 })
  }
}
  1. 滚动组件和多个图片组件组合成, 给图片组件添加两个transition属性,分别用于定义组件的添加动效和移除动效, 同时为实现瀑布式布局,设置好每张图片高度.
@Component
struct Flowlayout_Container {
// 链接主入口图片数组
  @Link pictureArr: PictureData[]
  private picturesHeight: number

  aboutToAppear() {
    let tmpHeight: number;

    ForEach(this.pictureArr,
      (item:PictureData) => {
        if (item.id % 2 == 0) {
          tmpHeight = Number(tmpHeight) + 300;
        } else {
          tmpHeight = Number(tmpHeight) + 800;
        }
      },
      (item:PictureData) => item.id.toString()
    )

    this.picturesHeight = tmpHeight;
  }

  build() {
    // 滚动组件
    Scroll() {
      // Flex布局, wrap为FlexWrap.Wrap为流式布局
      Flex({justifyContent: FlexAlign.Start, direction: FlexDirection.Column, alignContent: FlexAlign.Start, alignItems: ItemAlign.Start, wrap: FlexWrap.Wrap}) {
        if (this.pictureArr.length > 0) {
          // 循环显示图片到Image组件
          ForEach(this.pictureArr,
            (item:PictureData) => {
              if (item.id % 2 == 0) {
                Image(item.image)
                  .objectFit(ImageFit.Auto).width(px2vp(530))
                  .height(px2vp(300)).margin(2)
                // 搜索时的动画
                  .transition({ type: TransitionType.Insert, scale: { x: 0.5, y: 0.5 }, opacity: 0 })
                // 删除时的动画
                  .transition({ type: TransitionType.Delete, rotate: { x: 0, y: 1, z: 0, angle: 360 }, scale: { x: 0, y: 0 } })
              } else {
                Image(item.image)
                  .objectFit(ImageFit.Auto).width(px2vp(530))
                  .height(px2vp(800)).margin(2)
                // 搜索时的动画
                  .transition({ type: TransitionType.Insert, scale: { x: 0.5, y: 0.5 }, opacity: 0 })
                // 删除时的动画
                  .transition({ type: TransitionType.Delete, rotate: { x: 0, y: 1, z: 0, angle: 360 }, scale: { x: 0, y: 0 } })
              }

            },
            (item:PictureData) => item.id.toString()
          )
        }
      }
      .margin({left: 10, top: 10, right: 10, bottom: 100})
      .padding({bottom: 10}).align(Alignment.TopStart).width(px2vp(1024))
      .height(px2vp(this.picturesHeight))
    }
  }
}
  1. 数据Model
export class PictureData {
  id: number;
  name: string;
  image: Resource;

  constructor(id: number, name: string, image: Resource) {
    this.id = id;
    this.name = name;
    this.image = image;
  }
}
  1. 初始化数据方法
import { PictureData } from './PictureData.ets'

const PictureArr: any[] = [
  {id: 1, name: 'aa1', image: $r("app.media.1")},
  {id: 2, name: 'aa2', image: $r("app.media.2")},
  {id: 3, name: 'bb3', image: $r("app.media.3")},
  {id: 4, name: 'bb4', image: $r("app.media.4")},
  {id: 5, name: 'aa1', image: $r("app.media.5")},
  {id: 6, name: 'aa2', image: $r("app.media.6")},
  {id: 7, name: 'aa3', image: $r("app.media.7")},
  {id: 8, name: 'aa4', image: $r("app.media.8")},
  {id: 9, name: 'cc1', image: $r("app.media.9")},
  {id: 10, name: 'cc2', image: $r("app.media.10")},
  {id: 11, name: 'bb3', image: $r("app.media.11")},
  {id: 12, name: 'bb4', image: $r("app.media.12")},
  {id: 13, name: 'aa1', image: $r("app.media.13")},
  {id: 14, name: 'aa2', image: $r("app.media.14")},
  {id: 15, name: 'bb3', image: $r("app.media.15")},
  {id: 16, name: 'bb4', image: $r("app.media.16")},
  {id: 17, name: 'aa1', image: $r("app.media.17")},
  {id: 18, name: 'aa2', image: $r("app.media.18")},
  {id: 19, name: 'aa3', image: $r("app.media.19")}
];

export function initOnStartup(): Array<PictureData> {
  let PictureDataArray: Array<PictureData> = []

  PictureArr.forEach(item => {
    PictureDataArray.push(new PictureData(item.id, item.name, item.image));
  })

  return PictureDataArray;
};

介绍就到此了,声明式开发,是不是简洁了很多,大家一起撸起来吧。

源码在这: https://gitee.com/army16_harmony/qin-hong-jun-ark-ui

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
13
收藏 9
回复
举报
回复
添加资源
添加资源将有机会获得更多曝光,你也可以直接关联已上传资源 去关联
    相关推荐