基于HarmonyOS ArkUI 3.0框架,瀑布式顯示HDC2021圖片
一, 前言
在介紹之前,雖然上篇 基于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/

三.創建工程
在這當作你已經安裝好最新版本DevEco-Studio開發工具, 點擊File -> New -> New Project… 彈出Create HarmonyOS Project窗口, 這里我選擇空白eTS模板創建, 下來就跟著一起玩轉HarmonyOS ArkUI 3.0 框架聲明式開發吧.


四. 界面開發
界面有三個組件組合而成,文本輸入框和搜索按鈕組合成一個自定義組件, 歷史記錄和隨機刪除按鈕組合成一個自定義組件,滾動組件和多個圖片組件組合成一個自定義組件,同時還有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中的配置。
文本輸入框和搜索按鈕組合,在新增按鈕的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')
- }
- }
歷史記錄和隨機刪除按鈕組合
- @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 })
- }
- }
滾動組件和多個圖片組件組合成, 給圖片組件添加兩個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))
- }
- }
- }
數據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;
- }
- }
初始化數據方法
- 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;
- };
介紹就到此了,聲明式開發,是不是簡潔了很多,大家一起擼起來吧。