介紹
瀑布流式展示圖片文字,在當前產品設計中已非常常見,本篇將介紹關于WaterFlow的圖片瀏覽場景,順便集成Video控件,以提高實踐的趣味性
準備
- 請參照[官方指導],創建一個Demo工程,選擇Stage模型
- 熟讀HarmonyOS 官方指導“[https://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 = 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數據請求
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< 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 = 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
}
}
}
審核編輯 黃宇
聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。
舉報投訴
-
鴻蒙
+關注
關注
57文章
2361瀏覽量
42878 -
HarmonyOS
+關注
關注
79文章
1977瀏覽量
30251 -
鴻蒙OS
+關注
關注
0文章
188瀏覽量
4421
發布評論請先 登錄
相關推薦
HDC2024華為發布鴻蒙原生智能:AI與OS深度融合,開啟全新的AI時代
6月21日,華為開發者大會2024(HDC.2024)召開。 HarmonyOS NEXT將AI與OS深度融合,構筑全新鴻蒙原生智能框架。大會現場,華為常務董事、終端BG董事長、智能汽車解決方案BU
HarmonyOS實戰開發-如何通過BlendMode屬性來實現掛件和圖片的混合
||---BlendModeView.ets // 視圖層-應用主頁面
模塊依賴
本實例依賴common模塊來實現日志的打印、資源 的調用、依賴動態路由模塊來實現頁面的動態加載。
最后
如果大家覺得這篇內容對學習鴻蒙開發有幫
發表于 05-07 14:45
鴻蒙OS崛起,鴻蒙應用開發工程師成市場新寵
應用的形態也在發生著翻天覆地的變化。作為全球領先的移動操作系統和智能終端制造商,華為公司自主研發的鴻蒙OS應運而生,致力于構建一個統一的分布式操作系統,為各行各業的應用開發帶來全新的可能性。
一、
發表于 04-29 17:32
鴻蒙OS開發實例:【HarmonyHttpClient】網絡框架
鴻蒙上使用的Http網絡框架,里面包含純Java實現的HttpNet,類似okhttp使用,支持同步和異步兩種請求方式;還有鴻蒙版retrofit,和Android版Retrofit相似的使用,解放雙手般優雅使用注解、自動解析json
鴻蒙OS開發實例:【組件化模式】
組件化一直是移動端比較流行的開發方式,有著編譯運行快,業務邏輯分明,任務劃分清晰等優點,針對Android端的組件化;與Android端的組件化相比,HarmonyOS的組件化可以說實現起來就頗費
鴻蒙OS開發實例:【應用事件打點】
傳統的日志系統里匯聚了整個設備上所有程序運行的過程流水日志,難以識別其中的關鍵信息。因此,應用開發者需要一種數據打點機制,用來評估如訪問數、日活、用戶操作習慣以及影響用戶使用的關鍵因素等關鍵信息
使用 Taro 開發鴻蒙原生應用 —— 快速上手,鴻蒙應用開發指南
隨著鴻蒙系統的不斷完善,許多應用廠商都希望將自己的應用移植到鴻蒙平臺上。最近,Taro 發布了 v4.0.0-beta.x 版本,支持使用 Taro 快速開發鴻蒙原生應用,也可將現有的
免費學習鴻蒙(HarmonyOS)開發,一些地址分享
國內一流高校。通過鴻蒙班的設立,高校可以為學生提供專業的鴻蒙OS學習環境和豐富的實踐機會,培養出更多的鴻蒙開發人才,為
發表于 01-12 20:48
評論