鸿蒙OS开发实例:【瀑布流式图片浏览】

介绍

瀑布流式展示图片文字,在当前产品设计中已非常常见,本篇将介绍关于WaterFlow的图片浏览场景,顺便集成Video控件,以提高实践的趣味性

准备

  1. 请参照[官方指导],创建一个Demo工程,选择Stage模型

效果

竖屏

image

横屏

数据源 |

功能介绍

  1. 瀑布流式图片展示
  2. 横竖屏图片/视频展示

核心代码

布局

整体结构为:瀑布流 + 加载进度条

每条数据结构: 图片 + 文字 【由于没有设定图片宽高比,因此通过文字长度来自然生成瀑布流效果】

由于有点数据量,按照官方指导,采用LazyForEach懒加载方式

Stack() {
WaterFlow({ scroller: this.scroller }) {
LazyForEach(dataSource, item => {
FlowItem() {
Column({ space: 10 }) {
Image(item.coverUrl).objectFit(ImageFit.Cover)
.width('100%')
.height(this.imageHeight)
Text(item.title)
.fontSize(px2fp(50))
.fontColor(Color.Black)
.width('100%')
}.onClick(() => {
router.pushUrl({ url: 'custompages/waterflow/Detail', params: item })
})
}
}, item => item)
}
.columnsTemplate(this.columnsTemplate)
.columnsGap(5)
.rowsGap(5)
.onReachStart(() => {
console.info("onReachStart")
})
.onReachEnd(() => {
console.info("onReachEnd")

if (!this.running) {
if ((this.pageNo + 1) * 15 < this.total) {
this.pageNo++
this.running = true

setTimeout(() => {
this.requestData()
}, 2000)
}
}

})
.width('100%')
.height('100%')
.layoutDirection(FlexDirection.Column)

if (this.running) {
this.loadDataFooter()
}

}
复制

横竖屏感知

横竖屏感知整体有两个场景:1. 当前页面发生变化 2.初次进入页面
这里介绍几种监听方式:

当前页面监听

import mediaquery from '@ohos.mediaquery';

//这里你也可以使用"orientation: portrait" 参数
listener = mediaquery.matchMediaSync('(orientation: landscape)');
this.listener.on('change', 回调方法)
复制

外部传参

通过UIAbility, 一直传到Page文件

事件传递

采用EeventHub机制,在UIAbility把横竖屏切换事件发出来,Page文件注册监听事件

this.context.eventHub.on('onConfigurationUpdate', (data) => {
console.log(JSON.stringify(data))
let config = data as Configuration
this.screenDirection = config.direction
this.configureParamsByScreenDirection()
});
复制

API数据请求

这里需要设置Android 或者 iOS 特征UA

requestData() {
let url = https://api.apiopen.top/api/getHaoKanVideo?page=${this.pageNo}&size=15
let httpRequest = http.createHttp()
httpRequest.request(
url,
{
header: {
"User-Agent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G955U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Mobile Safari/537.36"
}
}).then((value: http.HttpResponse) => {

if (value.responseCode == 200) {

let searchResult: SearchResult = JSON.parse(value.result as string)

if (searchResult) {
this.total = searchResult.result.total

searchResult.result.list.forEach(ItemModel => {
dataSource.addData(ItemModel)
})
}
} else {
console.error(JSON.stringify(value))
}

}).catch(e => {

Logger.d(JSON.stringify(e))

promptAction.showToast({
message: '网络异常: ' + JSON.stringify(e),
duration: 2000
})

}).finally(() => {
this.running = false
})

}
复制

横竖屏布局调整

因为要适应横竖屏,所以需要在原有布局的基础上做一点改造, 让瀑布流的列参数改造为@State 变量 , 让图片高度的参数改造为@State 变量

WaterFlow({ scroller: this.scroller }) {
LazyForEach(dataSource, item => {
FlowItem() {
Column({ space: 10 }) {
Image(item.coverUrl).objectFit(ImageFit.Cover)
.width('100%')
.height(this.imageHeight)
Text(item.title)
.fontSize(px2fp(50))
.fontColor(Color.Black)
.width('100%')
}.onClick(() => {
router.pushUrl({ url: 'custompages/waterflow/Detail', params: item })
})
}
}, item => item)
}
.columnsTemplate(this.columnsTemplate)
复制

瀑布流完整代码

API返回的数据结构

import { ItemModel } from './ItemModel'

export default class SearchResult{
public code: number
public message: string
public result: childResult
}

class childResult {
public total: number
public list: ItemModel[]
};
复制

Item Model

export class ItemModel{
public id: number
public tilte: string
public userName: string
public userPic: string
public coverUrl: string
public playUrl: string
public duration: string
}复制

WaterFlow数据源接口

import List from '@ohos.util.List';
import { ItemModel } from './ItemModel';

export class PicData implements IDataSource {

private data: List = new List()

addData(item: ItemModel){
this.data.add(item)
}

unregisterDataChangeListener(listener: DataChangeListener): void {

}

registerDataChangeListener(listener: DataChangeListener): void {

}

getData(index: number): ItemModel {
return this.data.get(index)
}

totalCount(): number {
return this.data.length
}

}复制

布局

import http from '@ohos.net.http';
import { CommonConstants } from '../../common/CommonConstants';
import Logger from '../../common/Logger';
import { PicData } from './PicData';
import SearchResult from './Result';
import promptAction from '@ohos.promptAction'
import router from '@ohos.router';
import common from '@ohos.app.ability.common';
import { Configuration } from '@ohos.app.ability.Configuration';
import mediaquery from '@ohos.mediaquery';

let dataSource = new PicData()

/**
* 问题: 横竖屏切换,间距会发生偶发性变化
* 解决方案:延迟300毫秒改变参数
*
*/
@Entry
@Component
struct GridLayoutIndex {
private context = getContext(this) as common.UIAbilityContext;
@State pageNo: number = 0
total: number = 0
@State running: boolean = true
@State screenDirection: number = this.context.config.direction
@State columnsTemplate: string = '1fr 1fr'
@State imageHeight: string = '20%'
scroller: Scroller = new Scroller()

// 当设备横屏时条件成立
listener = mediaquery.matchMediaSync('(orientation: landscape)');

onPortrait(mediaQueryResult) {
if (mediaQueryResult.matches) {
//横屏
this.screenDirection = 1
} else {
//竖屏
this.screenDirection = 0
}

setTimeout(()=>{
this.configureParamsByScreenDirection()
}, 300)
}

onBackPress(){
this.context.eventHub.off('onConfigurationUpdate')
}

aboutToAppear() {
console.log('已进入瀑布流页面')

console.log('当前屏幕方向:' + this.context.config.direction)

if (AppStorage.Get('screenDirection') != 'undefined') {
this.screenDirection = AppStorage.Get(CommonConstants.ScreenDirection)
}

this.configureParamsByScreenDirection()

this.eventHubFunc()

let portraitFunc = this.onPortrait.bind(this)
this.listener.on('change', portraitFunc)

this.requestData()
}

@Builder loadDataFooter() {
LoadingProgress()
.width(px2vp(150))
.height(px2vp(150))
.color(Color.Orange)
}

build() {
Stack() {
WaterFlow({ scroller: this.scroller }) {
LazyForEach(dataSource, item => {
FlowItem() {
Column({ space: 10 }) {
Image(item.coverUrl).objectFit(ImageFit.Cover)
.width('100%')
.height(this.imageHeight)
Text(item.title)
.fontSize(px2fp(50))
.fontColor(Color.Black)
.width('100%')
}.onClick(() => {
router.pushUrl({ url: 'custompages/waterflow/Detail', params: item })
})
}
}, item => item)
}
.columnsTemplate(this.columnsTemplate)
.columnsGap(5)
.rowsGap(5)
.onReachStart(() => {
console.info("onReachStart")
})
.onReachEnd(() => {
console.info("onReachEnd")

if (!this.running) {
if ((this.pageNo + 1) * 15 < this.total) {
this.pageNo++
this.running = true

setTimeout(() => {
this.requestData()
}, 2000)
}
}

})
.width('100%')
.height('100%')
.layoutDirection(FlexDirection.Column)

if (this.running) {
this.loadDataFooter()
}

}

}

requestData() {
let url = https://api.apiopen.top/api/getHaoKanVideo?page=${this.pageNo}&size=15
let httpRequest = http.createHttp()
httpRequest.request(
url,
{
header: {
"User-Agent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G955U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Mobile Safari/537.36"
}
}).then((value: http.HttpResponse) => {

if (value.responseCode == 200) {

let searchResult: SearchResult = JSON.parse(value.result as string)

if (searchResult) {
this.total = searchResult.result.total

searchResult.result.list.forEach(ItemModel => {
dataSource.addData(ItemModel)
})
}
} else {
console.error(JSON.stringify(value))
}

}).catch(e => {

Logger.d(JSON.stringify(e))

promptAction.showToast({
message: '网络异常: ' + JSON.stringify(e),
duration: 2000
})

}).finally(() => {
this.running = false
})

}

eventHubFunc() {
this.context.eventHub.on('onConfigurationUpdate', (data) => {
console.log(JSON.stringify(data))
// let config = data as Configuration
// this.screenDirection = config.direction
// this.configureParamsByScreenDirection()
});
}

configureParamsByScreenDirection(){
if (this.screenDirection == 0) {
this.columnsTemplate = '1fr 1fr'
this.imageHeight = '20%'
} else {
this.columnsTemplate = '1fr 1fr 1fr 1fr'
this.imageHeight = '50%'
}
}

}

复制

图片详情页

import { CommonConstants } from '../../common/CommonConstants';
import router from '@ohos.router';
import { ItemModel } from './ItemModel';
import common from '@ohos.app.ability.common';
import { Configuration } from '@ohos.app.ability.Configuration';

@Entry
@Component
struct DetailIndex{
private context = getContext(this) as common.UIAbilityContext;

extParams: ItemModel
@State previewUri: Resource = $r('app.media.splash')
@State curRate: PlaybackSpeed = PlaybackSpeed.Speed_Forward_1_00_X
@State isAutoPlay: boolean = false
@State showControls: boolean = true
controller: VideoController = new VideoController()

@State screenDirection: number = 0
@State videoWidth: string = '100%'
@State videoHeight: string = '70%'

@State tipWidth: string = '100%'
@State tipHeight: string = '30%'

@State componentDirection: number = FlexDirection.Column
@State tipDirection: number = FlexDirection.Column

aboutToAppear() {
console.log('准备加载数据')
if(AppStorage.Get('screenDirection') != 'undefined'){
this.screenDirection = AppStorage.Get(CommonConstants.ScreenDirection)
}

this.configureParamsByScreenDirection()

this.extParams = router.getParams() as ItemModel
this.eventHubFunc()
}

onBackPress(){
this.context.eventHub.off('onConfigurationUpdate')
}

build() {
Flex({direction: this.componentDirection}){
Video({
src: this.extParams.playUrl,
previewUri: this.extParams.coverUrl,
currentProgressRate: this.curRate,
controller: this.controller,
}).width(this.videoWidth).height(this.videoHeight)
.autoPlay(this.isAutoPlay)
.objectFit(ImageFit.Contain)
.controls(this.showControls)
.onStart(() => {
console.info('onStart')
})
.onPause(() => {
console.info('onPause')
})
.onFinish(() => {
console.info('onFinish')
})
.onError(() => {
console.info('onError')
})
.onPrepared((e) => {
console.info('onPrepared is ' + e.duration)
})
.onSeeking((e) => {
console.info('onSeeking is ' + e.time)
})
.onSeeked((e) => {
console.info('onSeeked is ' + e.time)
})
.onUpdate((e) => {
console.info('onUpdate is ' + e.time)
})

Flex({direction: this.tipDirection, justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center, alignContent: FlexAlign.Center}){
Row() {
Button('src').onClick(() => {
// this.videoSrc = $rawfile('video2.mp4') // 切换视频源
}).margin(5)
Button('previewUri').onClick(() => {
// this.previewUri = $r('app.media.poster2') // 切换视频预览海报
}).margin(5)
Button('controls').onClick(() => {
this.showControls = !this.showControls // 切换是否显示视频控制栏
}).margin(5)
}

Row() {
Button('start').onClick(() => {
this.controller.start() // 开始播放
}).margin(5)
Button('pause').onClick(() => {
this.controller.pause() // 暂停播放
}).margin(5)
Button('stop').onClick(() => {
this.controller.stop() // 结束播放
}).margin(5)
Button('setTime').onClick(() => {
this.controller.setCurrentTime(10, SeekMode.Accurate) // 精准跳转到视频的10s位置
}).margin(5)
}
Row() {
Button('rate 0.75').onClick(() => {
this.curRate = PlaybackSpeed.Speed_Forward_0_75_X // 0.75倍速播放
}).margin(5)
Button('rate 1').onClick(() => {
this.curRate = PlaybackSpeed.Speed_Forward_1_00_X // 原倍速播放
}).margin(5)
Button('rate 2').onClick(() => {
this.curRate = PlaybackSpeed.Speed_Forward_2_00_X // 2倍速播放
}).margin(5)
}
}
.width(this.tipWidth).height(this.tipHeight)
}

}

eventHubFunc() {
this.context.eventHub.on('onConfigurationUpdate', (data) => {
console.log(JSON.stringify(data))
let config = data as Configuration
this.screenDirection = config.direction

this.configureParamsByScreenDirection()

});
}

configureParamsByScreenDirection(){
if(this.screenDirection == 0){
this.videoWidth = '100%'
this.videoHeight = '70%'
this.tipWidth = '100%'
this.tipHeight = '30%'
this.componentDirection = FlexDirection.Column

} else {
this.videoWidth = '60%'
this.videoHeight = '100%'
this.tipWidth = '40%'
this.tipHeight = '100%'
this.componentDirection = FlexDirection.Row

}

}

}

posted @ 2024-05-23 14:37  烧脑猴  阅读(191)  评论(1)    收藏  举报
鸿蒙NEXT文档资料 mau123789是助理v直接可拿取