介绍
瀑布流式展示图片文字,在当前产品设计中已非常常见,本篇将介绍关于WaterFlow的图片浏览场景,顺便集成Video控件,以提高实践的趣味性
准备
- 请参照[官方指导],创建一个Demo工程,选择Stage模型
- 熟读HarmonyOS 官方指导“https://gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md”
效果
竖屏
横屏
数据源
鸿蒙OS开发 | 更多内容↓点击 | HarmonyOS与OpenHarmony技术 |
---|---|---|
鸿蒙技术文档 | 开发知识更新库gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md在这。 |
功能介绍
- 瀑布流式图片展示
- 横竖屏图片/视频展示
核心代码
布局
整体结构为:瀑布流 + 加载进度条
每条数据结构: 图片 + 文字 【由于没有设定图片宽高比,因此通过文字长度来自然生成瀑布流效果】
由于有点数据量,按照官方指导,采用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 = truesetTimeout(() => {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 Configurationthis.screenDirection = config.directionthis.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.totalsearchResult.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: numberpublic message: stringpublic result: childResult
}class childResult {public total: numberpublic list: ItemModel[]
};
Item Model
export class ItemModel{public id: numberpublic tilte: stringpublic userName: stringpublic userPic: stringpublic coverUrl: stringpublic playUrl: stringpublic duration: string
}
WaterFlow数据源接口
import List from '@ohos.util.List';
import { ItemModel } from './ItemModel';export class PicData implements IDataSource {private data: List<ItemModel> = new List<ItemModel>()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 = 0total: 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 = truesetTimeout(() => {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.totalsearchResult.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 = truecontroller: 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.ColumnaboutToAppear() {console.log('准备加载数据')if(AppStorage.Get('screenDirection') != 'undefined'){this.screenDirection = AppStorage.Get(CommonConstants.ScreenDirection)}this.configureParamsByScreenDirection()this.extParams = router.getParams() as ItemModelthis.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 Configurationthis.screenDirection = config.directionthis.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}}}
鸿蒙开发岗位需要掌握那些核心要领?
目前还有很多小伙伴不知道要学习哪些鸿蒙技术?不知道重点掌握哪些?为了避免学习时频繁踩坑,最终浪费大量时间的。
自己学习时必须要有一份实用的鸿蒙(Harmony NEXT)资料非常有必要。 这里我推荐,根据鸿蒙开发官网梳理与华为内部人员的分享总结出的开发文档。内容包含了:【ArkTS、ArkUI、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、Harmony南向开发、鸿蒙项目实战】等技术知识点。
废话就不多说了,接下来好好看下这份资料。
如果你是一名Android、Java、前端等等开发人员,想要转入鸿蒙方向发展。可以直接领取这份资料辅助你的学习。鸿蒙OpenHarmony知识←前往。下面是鸿蒙开发的学习路线图。
针对鸿蒙成长路线打造的鸿蒙学习文档。鸿蒙(OpenHarmony )学习手册(共计1236页)与鸿蒙(OpenHarmony )开发入门教学视频,帮助大家在技术的道路上更进一步。
其中内容包含:
《鸿蒙开发基础》鸿蒙OpenHarmony知识←前往
- ArkTS语言
- 安装DevEco Studio
- 运用你的第一个ArkTS应用
- ArkUI声明式UI开发
- .……
《鸿蒙开发进阶》鸿蒙OpenHarmony知识←前往
- Stage模型入门
- 网络管理
- 数据管理
- 电话服务
- 分布式应用开发
- 通知与窗口管理
- 多媒体技术
- 安全技能
- 任务管理
- WebGL
- 国际化开发
- 应用测试
- DFX面向未来设计
- 鸿蒙系统移植和裁剪定制
- ……
《鸿蒙开发实战》鸿蒙OpenHarmony知识←前往
- ArkTS实践
- UIAbility应用
- 网络案例
- ……
最后
鸿蒙是完全具备无与伦比的机遇和潜力的;预计到年底将有 5,000 款的应用完成原生鸿蒙开发,这么多的应用需要开发,也就意味着需要有更多的鸿蒙人才。鸿蒙开发工程师也将会迎来爆发式的增长,学习鸿蒙势在必行!