鸿蒙简易版影视APP案例实战

article/2025/6/8 3:58:23

目录

1. 案例效果

2. 资源初始化和资源文件

2.1. string.json (en_US)

2.2. string.json (zh_CN)

2.3. constants

3. 视频列表

3.1. 顶部导航

3.1.1. TobBar 组件

3.1.2. TopBar 数据源

3.2. 全部分类内容页面

3.2.1. 全部分类组件

3.2.2. 轮播图组件

3.2.3. 图片列表组件

3.2.4. 图片视图

3.2.5. 图片视图模型

3.2.6. 图片模型

3.2.7. 图片类型和数据源

3.3. 电影分类页面

3.3.1. 电影分类组件

3.3.2. 电影分类视图

3.4. 其他分类页面

3.4.1. 电视剧组件

3.4.2. 综艺组件

3.4.3. 直播组件

3.4.4. 游戏组件

4. 轮播实现

4.1. 首页

4.2. 修改 TabBar 组件

4.3. 修改 Banner 组件

4.4. 联调预览

5. 视频滑动播放

5.1. 在 Banner 组件上添加路由

5.2. 在图片视图上添加路由

5.3. 视频播放首页

5.4. 视图模型

5.4.1. 视频模型

5.4.2. 视频数据源

5.4.3. 视频视图模型

5.5. 播放组件

5.5.1. 播放视图

5.5.2. 导航视图

5.5.3. 互动视图

5.5.4. 描述视图


1. 案例效果

2. 资源初始化和资源文件

2.1. string.json (en_US)

{"string": [{"name": "module_desc","value": "module description"},{"name": "EntryAbility_desc","value": "description"},{"name": "EntryAbility_label","value": "Use of Swiper"},{"name": "recently","value": "Recent Plays"},{"name": "photo","value": "camera"},{"name": "more","value": "more >"},{"name": "movie_classic","value": "Selected Films"},{"name": "lately","value": "latest"},{"name": "like","value": "like"},{"name": "comment","value": "comment"},{"name": "share","value": "share"},{"name": "movie","value": "movie"},{"name": "movie_description_1","value": "@HarmonyOS Official website"},{"name": "movie_description_2","value": "#HarmonyOS Huawei Developer Conference"},{"name": "TV","value": "TV"},{"name": "game","value": "Game"},{"name": "live","value": "Live"},{"name": "entertainment","value": "Entertainment"}]
}

2.2. string.json (zh_CN)

{"string": [{"name": "module_desc","value": "module description"},{"name": "EntryAbility_desc","value": "description"},{"name": "EntryAbility_label","value": "Swiper的使用"},{"name": "recently","value": "最近播放"},{"name": "photo","value": "相机"},{"name": "more","value": "更多 >"},{"name": "movie_classic","value": "电影精选"},{"name": "lately","value": "最新"},{"name": "like","value": "点赞"},{"name": "comment","value": "评论"},{"name": "share","value": "转发"},{"name": "movie","value": "视频"},{"name": "movie_description_1","value": "@HarmonyOS 官网"},{"name": "movie_description_2","value": "#HarmonyOS HDC大会"},{"name": "TV","value": "电视剧"},{"name": "game","value": "游戏"},{"name": "live","value": "直播"},{"name": "entertainment","value": "综艺"}]
}

2.3. constants

  • CommonConstants
// ets/common/constants/CommonConstants.etsexport class CommonConstants {static readonly DURATION_PAGE = 50static readonly DURATION_ADS = 200static readonly HEIGHT_HEAD = 40static readonly HEIGHT_CAROUSEL_TITLE = 90static readonly FONT_SIZE_DESCRIPTION = 12static readonly FONT_SIZE_PHOTO_NAME = 14static readonly FONT_SIZE_SORT_TITLE = 16static readonly FONT_SIZE_UNCHECKED = 18static readonly FONT_SIZE_TITLE = 20static readonly FONT_SIZE_CHECKED = 24static readonly FONT_SIZE_PAGE_CONTENT = 28static readonly FONT_WEIGHT_LIGHT = 400static readonly FONT_WEIGHT_NORMAL = 500static readonly FONT_WEIGHT_BOLD = 700static readonly LAYOUT_WEIGHT = 1static readonly BORDER_RADIUS = 12static readonly LINE_HEIGHT_MORE = 19static readonly LINE_HEIGHT_NAVIGATION = 28static readonly SPACE_TOP_BAR = 16static readonly SPACE_NAVIGATION = 8static readonly WIDTH_HEAD_BORDER = 2static readonly WIDTH_HEAD = 40static readonly RADIUS_HEAD = 20static readonly SWIPER_TIME = 1500static readonly MARGIN_PLAY_PAGE = 10static readonly BOTTOM_TEXT = 4static readonly TOP_ADS = 12static readonly LEFT_POSITION = '3%'static readonly ADS_LEFT = 12static readonly TOP_NAME = 8static readonly TOP_DESCRIPTION = 4static readonly TOP_IMAGE_VOTE = 20static readonly TOP_HEAD = 40static readonly FULL_WIDTH = '100%'static readonly FULL_HEIGHT = '100%'static readonly WIDTH_PLAY = '95%'static readonly PAGE_WIDTH = '94.4%'static readonly WIDTH_SORT_NAME = '62.2%'static readonly WIDTH_SORT = '92%'static readonly WIDTH_MOVIE_SORT = '90%'static readonly WIDTH_PICTURE = '72%'static readonly HEIGHT_BANNER = '27%'static readonly WIDTH_VOTE = '8.9%'static readonly WIDTH_BACK_ICON = '6.7%'static readonly MARGIN_TOP_SORT = '3.2%'static readonly MARGIN_BOTTOM_SORT = '1.7%'static readonly MARGIN_BOTTOM_GRID = '4.2%'static readonly WIDTH_VIDEO = '26.2%'static readonly TWO_COLUMNS = '1fr 1fr'static readonly TWO_ROWS = '1fr 1fr'static readonly THREE_COLUMNS = '1fr 1fr 1fr'static readonly THREE_ROWS = '1fr 1fr 1fr'static readonly GAP_COLUMNS = '2.2%'static readonly HEIGHT_GRID = '45%'static readonly HEIGHT_DESCRIPTION = '12.3%'static readonly TOP_BAR_HEIGHT = '7.2%'static readonly HEIGHT_COMMENT = '4.1%'static readonly HEIGHT_BACK_ICON = '3.1%'static readonly OFFSET_COMMENT_X = '-5%'static readonly OFFSET_COMMENT_Y = '10%'static readonly OFFSET_DESCRIPTION_Y = '45%'static readonly START_POSITION = '0%'static readonly PLAY_PAGE = 'pages/PageVideo'static readonly HOME_PAGE = 'pages/SwiperIndex'
}

3. 视频列表

3.1. 顶部导航

3.1.1. TobBar 组件
// ets/view/common/TopBar.etsimport { TopBarItem } from '../../viewmodel/TopBarItem'
import { initializeOnStartup } from '../../viewmodel/TopBarViewModel'
import { CommonConstants } from '../../common/constants/CommonConstants'@Component
export struct TopBar {@Prop index: number = 0private tabArray: Array<TopBarItem> = initializeOnStartup()build() {Row({ space: CommonConstants.SPACE_TOP_BAR }) {ForEach(this.tabArray,(item: TopBarItem) => {Text(item.name).fontSize(this.index === item.id ? CommonConstants.FONT_SIZE_CHECKED : CommonConstants.FONT_SIZE_UNCHECKED).fontColor(Color.Black).textAlign(TextAlign.Center).fontWeight(this.index === item.id ? FontWeight.Bold : FontWeight.Regular)}, (item: TopBarItem) => JSON.stringify(item))}.margin({ left: CommonConstants.ADS_LEFT }).width(CommonConstants.FULL_WIDTH).height(CommonConstants.TOP_BAR_HEIGHT)}
}
3.1.2. TopBar 数据源
// ets/viewmodel/TopBarViewModel.etsimport { TopBarItem } from './TopBarItem'
import { TOP_BAR_DATA } from '../common/constants/TopBarConstants'export function initializeOnStartup(): Array<TopBarItem> {let tabDataArray: Array<TopBarItem> = []TOP_BAR_DATA.forEach((item: TopBarItem) => {tabDataArray.push(new TopBarItem(item.id, item.name))})return tabDataArray
}
// ets/common/constants/TopBarConstants.etsimport { TopBarItem } from '../../viewmodel/TopBarItem'export const TOP_BAR_DATA: TopBarItem[] = [new TopBarItem(0, '全部'),new TopBarItem(1, '电影'),new TopBarItem(2, '电视剧'),new TopBarItem(3, '综艺'),new TopBarItem(4, '直播'),new TopBarItem(5, '游戏')
]
// ets/viewmodel/TopBarItem.etsexport class TopBarItem {id: numbername: stringconstructor(id: number, name: string) {this.id = idthis.name = name}
}

3.2. 全部分类内容页面

3.2.1. 全部分类组件
// ets/view/tabcontent/PageAll.etsimport { Banner } from '../common/Banner'
import { PictureSort } from '../all/PictureSort'
import { CommonConstants } from '../../common/constants/CommonConstants'
import { PictureType } from '../../common/constants/PictureConstants'@Preview
@Component
export struct PageAll {build() {Scroll() {Column() {Banner()PictureSort({ initType: PictureType.RECENTLY })PictureSort({ initType: PictureType.PHOTO })}.width(CommonConstants.FULL_WIDTH)}}
}
3.2.2. 轮播图组件
// ets/view/common/Banner.etsimport { CommonConstants } from "../../common/constants/CommonConstants"@Component
export struct Banner {build() {Column() {Text('swiper')}.width(CommonConstants.PAGE_WIDTH).height(CommonConstants.HEIGHT_BANNER)}
}
3.2.3. 图片列表组件
// ets/view/all/PictureSort.etsimport { PictureItem } from '../../viewmodel/PictureItem'
import { initializePictures } from '../../viewmodel/PictureViewModel'
import { PictureView } from '../common/PictureView'
import { PictureType } from '../../common/constants/PictureConstants'
import { CommonConstants } from '../../common/constants/CommonConstants'@Extend(Text)
function textStyle(fontSize: number, fontWeight: number) {.fontSize(fontSize).fontWeight(fontWeight).fontColor($r('app.color.font_black'))
}@Component
export struct PictureSort {@State photos: Array<PictureItem> = []@State private sortName: Resource = $r('app.string.recently')private initType: string = ''aboutToAppear() {if (PictureType.RECENTLY === this.initType) {this.sortName = $r('app.string.recently')this.photos = initializePictures(PictureType.RECENTLY)} else {this.sortName = $r('app.string.photo');this.photos = initializePictures(PictureType.PHOTO)}}build() {Column() {Row() {Text(this.sortName).width(CommonConstants.WIDTH_SORT_NAME).textStyle(CommonConstants.FONT_SIZE_SORT_TITLE, CommonConstants.FONT_WEIGHT_NORMAL)Text($r('app.string.more')).layoutWeight(CommonConstants.LAYOUT_WEIGHT).textAlign(TextAlign.End).textStyle(CommonConstants.FONT_SIZE_PHOTO_NAME, CommonConstants.FONT_WEIGHT_LIGHT).lineHeight(CommonConstants.LINE_HEIGHT_MORE).opacity($r('app.float.opacity_light'))}.width(CommonConstants.WIDTH_SORT).margin({ top: CommonConstants.MARGIN_TOP_SORT, bottom: CommonConstants.MARGIN_BOTTOM_SORT })Grid() {ForEach(this.photos, (item: PictureItem) => {GridItem() {PictureView({ photos: item })}}, (item: PictureItem) => JSON.stringify(item))}.columnsTemplate(CommonConstants.TWO_COLUMNS).rowsTemplate(CommonConstants.TWO_ROWS).columnsGap(CommonConstants.GAP_COLUMNS).rowsGap(CommonConstants.GAP_COLUMNS).width(CommonConstants.PAGE_WIDTH).height(CommonConstants.HEIGHT_GRID).margin({ bottom: CommonConstants.MARGIN_BOTTOM_GRID })}}
}
3.2.4. 图片视图
// ets/view/common/PictureView.etsimport { PictureItem } from '../../viewmodel/PictureItem'
import { CommonConstants } from '../../common/constants/CommonConstants'@Component
export struct PictureView {private photos: PictureItem = new PictureItem()build() {Column() {Image(this.photos.image).borderRadius(CommonConstants.BORDER_RADIUS).height(CommonConstants.WIDTH_PICTURE)Text(this.photos.name).width(CommonConstants.PAGE_WIDTH).fontSize(CommonConstants.FONT_SIZE_PHOTO_NAME).fontWeight(CommonConstants.FONT_WEIGHT_NORMAL).margin({ top: CommonConstants.TOP_NAME })Text(this.photos.description).width(CommonConstants.PAGE_WIDTH).fontSize(CommonConstants.FONT_SIZE_DESCRIPTION).fontWeight(CommonConstants.FONT_WEIGHT_LIGHT).opacity($r('app.float.opacity_light')).margin({ top: CommonConstants.TOP_DESCRIPTION, bottom: CommonConstants.BOTTOM_TEXT })}.height(CommonConstants.FULL_HEIGHT)}
}
3.2.5. 图片视图模型
// ets/viewmodel/PictureViewModel.etsimport { PictureItem } from './PictureItem'
import { PICTURE_RECENTLY, PICTURE_PHOTO, PICTURE_LATEST, PICTURE_BANNER } from '../common/constants/PictureConstants'
import { PictureType } from '../common/constants/PictureConstants'export function initializePictures(initType: string): Array<PictureItem> {let imageDataArray: Array<PictureItem> = []switch (initType) {case PictureType.BANNER:PICTURE_BANNER.forEach((item: PictureItem) => {imageDataArray.push(item)})breakcase PictureType.RECENTLY:PICTURE_RECENTLY.forEach((item: PictureItem) => {imageDataArray.push(item)})breakcase PictureType.PHOTO:PICTURE_PHOTO.forEach((item: PictureItem) => {imageDataArray.push(item)})breakcase PictureType.LATEST:PICTURE_LATEST.forEach((item: PictureItem) => {imageDataArray.push(item)})breakdefault:break}return imageDataArray
}
3.2.6. 图片模型
export class PictureItem {id: string = ''name: string = ''description: string = ''image: Resource = $r('app.media.image1')
}
3.2.7. 图片类型和数据源
// ets/common/constants/PictureConstants.etsimport { PictureItem } from '../../viewmodel/PictureItem'export const PICTURE_BANNER: PictureItem[] = [{ id: '1', name: '怒海', description: '怒海波涛', image: $r('app.media.image1') },{ id: '2', name: '大山深处', description: '大山深处感人的亲情之歌', image: $r('app.media.image2') },{ id: '3', name: '荒漠', description: '荒漠的亲情之歌', image: $r('app.media.image3') }
]export const PICTURE_RECENTLY: PictureItem[] = [{ id: '1', name: '背影', description: '感人的亲情之歌', image: $r('app.media.recently1') },{ id: '2', name: '废墟之上', description: '勇闯无人之境', image: $r('app.media.recently2') },{ id: '3', name: '无根之人', description: '悬疑国产力作', image: $r('app.media.recently3') },{ id: '4', name: '摩天轮', description: '每个人心中都有一个童话', image: $r('app.media.recently4') }
]export const PICTURE_PHOTO: PictureItem[] = [{ id: '1', name: '蓝·静', description: '用放大镜看世界', image: $r('app.media.photo1') },{ id: '2', name: '花', description: '每个人心中都有一个童话', image: $r('app.media.photo2') },{ id: '3', name: '无根之人', description: '悬疑国产力作', image: $r('app.media.recently3') },{ id: '4', name: '摩天轮', description: '每个人心中都有一个童话', image: $r('app.media.recently4') }
]export const PICTURE_LATEST: PictureItem[] = [{ id: '1', name: '潮·设计大会', description: '国际设计大师分...', image: $r('app.media.movie1') },{ id: '2', name: '食客', description: '味蕾爆炸', image: $r('app.media.movie2') },{ id: '3', name: '绿野仙踪', description: '热带雨林的故事', image: $r('app.media.image3') },{ id: '4', name: '塔', description: '2021最期待的电...', image: $r('app.media.movie4') },{ id: '5', name: '微缩世界', description: '用放大镜看世界', image: $r('app.media.movie5') },{ id: '6', name: '非常规接触', description: '少年的奇妙之旅', image: $r('app.media.movie6') },{ id: '7', name: '绿野仙踪', description: '热带雨林的故事', image: $r('app.media.movie7') },{ id: '8', name: '塔', description: '用放大镜看世界', image: $r('app.media.movie8') },{ id: '9', name: '食客', description: '热带雨林的故事', image: $r('app.media.movie9') }
]export enum PictureType {RECENTLY = 'recently',PHOTO = 'photo',LATEST = 'latest',BANNER = 'banner'
}

3.3. 电影分类页面

3.3.1. 电影分类组件
// ets/view/tabcontent/PageMovie.etsimport { Banner } from '../common/Banner'
import { MovieSort } from '../movie/MovieSort'
import { CommonConstants } from '../../common/constants/CommonConstants'@Preview
@Component
export struct PageMovie {build() {Scroll() {Column() {Banner()MovieSort()}.width(CommonConstants.FULL_WIDTH)}.scrollable(ScrollDirection.Vertical).scrollBar(BarState.Off)}
}
3.3.2. 电影分类视图
// ets/view/movie/MovieSort.etsimport { PictureItem } from '../../viewmodel/PictureItem'
import { initializePictures } from '../../viewmodel/PictureViewModel'
import { PictureType } from '../../common/constants/PictureConstants'
import { PictureView } from '../common/PictureView'
import { CommonConstants } from '../../common/constants/CommonConstants'@Component
export struct MovieSort {@State photos: Array<PictureItem> = initializePictures(PictureType.LATEST)build() {Column() {Text($r('app.string.lately')).width(CommonConstants.WIDTH_SORT).margin({ top: CommonConstants.MARGIN_TOP_SORT, bottom: CommonConstants.MARGIN_BOTTOM_SORT }).fontSize(CommonConstants.FONT_SIZE_SORT_TITLE).fontWeight(CommonConstants.FONT_WEIGHT_NORMAL).fontColor($r('app.color.font_black'))Grid() {ForEach(this.photos, (item: PictureItem) => {GridItem() {PictureView({ photos: item })}}, (item: PictureItem) => JSON.stringify(item))}.columnsTemplate(CommonConstants.THREE_COLUMNS).rowsTemplate(CommonConstants.THREE_ROWS).columnsGap(CommonConstants.GAP_COLUMNS).rowsGap(CommonConstants.GAP_COLUMNS).width(CommonConstants.PAGE_WIDTH).height(CommonConstants.WIDTH_MOVIE_SORT).margin({ bottom: CommonConstants.MARGIN_BOTTOM_GRID })}}
}

3.4. 其他分类页面

3.4.1. 电视剧组件
// ets/view/tabcontent/PageTV.etsimport { CommonConstants } from '../../common/constants/CommonConstants'@Component
export struct PageTV {build() {Column() {Text($r('app.string.TV')).height(CommonConstants.FULL_HEIGHT).fontSize(CommonConstants.FONT_SIZE_PAGE_CONTENT).fontWeight(CommonConstants.FONT_WEIGHT_LIGHT)}.width(CommonConstants.FULL_WIDTH).height(CommonConstants.FULL_HEIGHT)}
}
3.4.2. 综艺组件
// ets/view/tabcontent/PageEntertainment.etsimport { CommonConstants } from '../../common/constants/CommonConstants'@Component
export struct PageEntertainment {build() {Column() {Text($r('app.string.entertainment')).height(CommonConstants.FULL_HEIGHT).fontSize(CommonConstants.FONT_SIZE_PAGE_CONTENT).fontWeight(CommonConstants.FONT_WEIGHT_LIGHT)}.width(CommonConstants.FULL_WIDTH).height(CommonConstants.FULL_HEIGHT)}
}
3.4.3. 直播组件
// ets/view/tabcontent/PageLive.etsimport { CommonConstants } from '../../common/constants/CommonConstants'@Component
export struct PageLive {build() {Column() {Text($r('app.string.live')).height(CommonConstants.FULL_HEIGHT).fontSize(CommonConstants.FONT_SIZE_PAGE_CONTENT).fontWeight(CommonConstants.FONT_WEIGHT_LIGHT)}.width(CommonConstants.FULL_WIDTH).height(CommonConstants.FULL_HEIGHT)}
}
3.4.4. 游戏组件
// ets/view/tabcontent/PageContent.etsimport { CommonConstants } from '../../common/constants/CommonConstants'@Component
export struct PageGame {build() {Column() {Text($r('app.string.game')).height(CommonConstants.FULL_HEIGHT).fontSize(CommonConstants.FONT_SIZE_PAGE_CONTENT).fontWeight(CommonConstants.FONT_WEIGHT_LIGHT)}.width(CommonConstants.FULL_WIDTH).height(CommonConstants.FULL_HEIGHT)}
}

4. 轮播实现

4.1. 首页

// ets/pages/SwiperIndex.etsimport { CommonConstants } from '../common/constants/CommonConstants'
import { TopBar } from '../view/common/TobBar'
import { PageAll } from '../view/tabcontent/PageAll'
import { PageEntertainment } from '../view/tabcontent/PageEntertainment'
import { PageGame } from '../view/tabcontent/PageGame'
import { PageLive } from '../view/tabcontent/PageLive'
import { PageMovie } from '../view/tabcontent/PageMovie'
import { PageTV } from '../view/tabcontent/PageTV'@Entry
@Component
struct SwiperIndex {@State index: number = 0build() {Flex({direction: FlexDirection.Column,alignItems: ItemAlign.Start}) {TopBar({ index: $index })Swiper() {PageAll()PageMovie()PageTV()PageEntertainment()PageLive()PageGame()}.index(this.index).indicator(false).loop(false).duration(CommonConstants.DURATION_PAGE).onChange((index: number) => {this.index = index})}.backgroundColor($r('app.color.start_window_background'))}
}

4.2. 修改 TabBar 组件

import { TopBarItem } from '../../viewmodel/TopBarItem'
import { initializeOnStartup } from '../../viewmodel/TopBarViewModel'
import { CommonConstants } from '../../common/constants/CommonConstants'@Preview
@Component
export struct TopBar {// @Prop index: number = 0// 1. @Prop 改为 @Link@Link index: numberprivate tabArray: Array<TopBarItem> = initializeOnStartup()build() {Row({ space: CommonConstants.SPACE_TOP_BAR }) {ForEach(this.tabArray,(item: TopBarItem) => {Text(item.name).fontSize(this.index === item.id ? CommonConstants.FONT_SIZE_CHECKED : CommonConstants.FONT_SIZE_UNCHECKED).fontColor(Color.Black).textAlign(TextAlign.Center).fontWeight(this.index === item.id ? FontWeight.Bold : FontWeight.Regular)// 2. 绑定事件,修改index,实现swiper切换.onClick(() => {this.index = item.id})}, (item: TopBarItem) => JSON.stringify(item))}.margin({ left: CommonConstants.ADS_LEFT }).width(CommonConstants.FULL_WIDTH).height(CommonConstants.TOP_BAR_HEIGHT)}
}

4.3. 修改 Banner 组件

// ets/view/common/Banner.etsimport { PictureItem } from '../../viewmodel/PictureItem'
import { PictureType } from '../../common/constants/PictureConstants'
import { initializePictures, startPlay, stopPlay } from '../../viewmodel/PictureViewModel'
import { CommonConstants } from '../../common/constants/CommonConstants'@Extend(Text)
function textStyle(fontSize: number, fontWeight: number) {.fontSize(fontSize).fontColor($r('app.color.start_window_background')).fontWeight(fontWeight)
}@Component
export struct Banner {@State index: number = 0private imageArray: Array<PictureItem> = []private swiperController: SwiperController = new SwiperController()private dotIndicator: DotIndicator = new DotIndicator()aboutToAppear() {this.dotIndicator.selectedColor($r('app.color.start_window_background'));this.imageArray = initializePictures(PictureType.BANNER);startPlay(this.swiperController);}aboutToDisappear() {stopPlay()}build() {Swiper(this.swiperController) {ForEach(this.imageArray, (item: PictureItem) => {Stack({ alignContent: Alignment.TopStart }) {Image(item.image).objectFit(ImageFit.Fill).height(CommonConstants.FULL_HEIGHT).width(CommonConstants.FULL_WIDTH).borderRadius(CommonConstants.BORDER_RADIUS).align(Alignment.Center)Column() {Text($r('app.string.movie_classic')).textStyle(CommonConstants.FONT_SIZE_DESCRIPTION, CommonConstants.FONT_WEIGHT_LIGHT).opacity($r('app.float.opacity_deep')).margin({ bottom: CommonConstants.BOTTOM_TEXT })Text(item.name).textStyle(CommonConstants.FONT_SIZE_TITLE, CommonConstants.FONT_WEIGHT_BOLD)}.alignItems(HorizontalAlign.Start).height(CommonConstants.HEIGHT_CAROUSEL_TITLE).margin({ top: CommonConstants.TOP_ADS, left: CommonConstants.ADS_LEFT })}.height(CommonConstants.FULL_HEIGHT).width(CommonConstants.FULL_WIDTH)}, (item: PictureItem) => JSON.stringify(item))}.width(CommonConstants.PAGE_WIDTH).height(CommonConstants.HEIGHT_BANNER).index(this.index).indicator(this.dotIndicator).duration(CommonConstants.DURATION_ADS)}
}
// ets/viewmoel/PictureViewModel.etsimport { PictureItem } from './PictureItem'
import { PICTURE_RECENTLY, PICTURE_PHOTO, PICTURE_LATEST, PICTURE_BANNER } from '../common/constants/PictureConstants'
import { PictureType } from '../common/constants/PictureConstants'
import { CommonConstants } from '../common/constants/CommonConstants'export function initializePictures(initType: string): Array<PictureItem> {let imageDataArray: Array<PictureItem> = []switch (initType) {case PictureType.BANNER:PICTURE_BANNER.forEach((item: PictureItem) => {imageDataArray.push(item)})breakcase PictureType.RECENTLY:PICTURE_RECENTLY.forEach((item: PictureItem) => {imageDataArray.push(item)})breakcase PictureType.PHOTO:PICTURE_PHOTO.forEach((item: PictureItem) => {imageDataArray.push(item)})breakcase PictureType.LATEST:PICTURE_LATEST.forEach((item: PictureItem) => {imageDataArray.push(item)})breakdefault:break}return imageDataArray
}// 添加swiper调度任务
let timerIds: number[] = []export function startPlay(swiperController: SwiperController): void {let timerId = setInterval(() => {swiperController.showNext()}, CommonConstants.SWIPER_TIME)timerIds.push(timerId)
}export function stopPlay(): void {timerIds.forEach((item: number) => {clearTimeout(item)})
}

4.4. 联调预览

去掉 PageAll、PageMovie、TabBar 等组件的 @Preview 装饰器,打开 SwiperIndex 开始预览。

5. 视频滑动播放

5.1. 在 Banner 组件上添加路由

// 1. 导入路由模块
import { router } from '@kit.ArkUI'import { PictureItem } from '../../viewmodel/PictureItem'
import { PictureType } from '../../common/constants/PictureConstants'
import { initializePictures, startPlay, stopPlay } from '../../viewmodel/PictureViewModel'
import { CommonConstants } from '../../common/constants/CommonConstants'@Extend(Text)
function textStyle(fontSize: number, fontWeight: number) {.fontSize(fontSize).fontColor($r('app.color.start_window_background')).fontWeight(fontWeight)
}@Component
export struct Banner {@State index: number = 0private imageArray: Array<PictureItem> = []private swiperController: SwiperController = new SwiperController()private dotIndicator: DotIndicator = new DotIndicator()aboutToAppear() {this.dotIndicator.selectedColor($r('app.color.start_window_background'));this.imageArray = initializePictures(PictureType.BANNER);startPlay(this.swiperController);}aboutToDisappear() {stopPlay()}build() {Swiper(this.swiperController) {ForEach(this.imageArray, (item: PictureItem) => {Stack({ alignContent: Alignment.TopStart }) {Image(item.image).objectFit(ImageFit.Fill).height(CommonConstants.FULL_HEIGHT).width(CommonConstants.FULL_WIDTH).borderRadius(CommonConstants.BORDER_RADIUS).align(Alignment.Center)// 2.添加路由导航.onClick(() => {router.pushUrl({ url: CommonConstants.PLAY_PAGE })})Column() {Text($r('app.string.movie_classic')).textStyle(CommonConstants.FONT_SIZE_DESCRIPTION, CommonConstants.FONT_WEIGHT_LIGHT).opacity($r('app.float.opacity_deep')).margin({ bottom: CommonConstants.BOTTOM_TEXT })Text(item.name).textStyle(CommonConstants.FONT_SIZE_TITLE, CommonConstants.FONT_WEIGHT_BOLD)}.alignItems(HorizontalAlign.Start).height(CommonConstants.HEIGHT_CAROUSEL_TITLE).margin({ top: CommonConstants.TOP_ADS, left: CommonConstants.ADS_LEFT })}.height(CommonConstants.FULL_HEIGHT).width(CommonConstants.FULL_WIDTH)}, (item: PictureItem) => JSON.stringify(item))}.width(CommonConstants.PAGE_WIDTH).height(CommonConstants.HEIGHT_BANNER).index(this.index).indicator(this.dotIndicator).duration(CommonConstants.DURATION_ADS)}
}

5.2. 在图片视图上添加路由

// ets/view/common/PictureView.etsimport { router } from '@kit.ArkUI'
import { PictureItem } from '../../viewmodel/PictureItem'
import { CommonConstants } from '../../common/constants/CommonConstants'@Component
export struct PictureView {private photos: PictureItem = new PictureItem()build() {Column() {Image(this.photos.image).borderRadius(CommonConstants.BORDER_RADIUS).height(CommonConstants.WIDTH_PICTURE).onClick(() => {router.pushUrl({ url: CommonConstants.PLAY_PAGE })})Text(this.photos.name).width(CommonConstants.PAGE_WIDTH).fontSize(CommonConstants.FONT_SIZE_PHOTO_NAME).fontWeight(CommonConstants.FONT_WEIGHT_NORMAL).margin({ top: CommonConstants.TOP_NAME })Text(this.photos.description).width(CommonConstants.PAGE_WIDTH).fontSize(CommonConstants.FONT_SIZE_DESCRIPTION).fontWeight(CommonConstants.FONT_WEIGHT_LIGHT).opacity($r('app.float.opacity_light')).margin({ top: CommonConstants.TOP_DESCRIPTION, bottom: CommonConstants.BOTTOM_TEXT })}.height(CommonConstants.FULL_HEIGHT)}
}

5.3. 视频播放首页

// ets/pages/PageVideo.etsimport { VideoItem } from '../viewmodel/VideoItem'
import { initializeOnStartup } from '../viewmodel/VideoViewModel'
import { PlayView } from '../view/play/PlayView'
import { CommonConstants } from '../common/constants/CommonConstants'@Entry
@Component
struct PageVideo {@State videoArray: Array<VideoItem> = initializeOnStartup()@State index: number = 0@State pageShow: boolean = falsebuild() {Column() {Swiper() {ForEach(this.videoArray, (item: VideoItem, index: number | undefined) => {PlayView({index: $index,pageShow: $pageShow,item: item,barPosition: index})}, (item: VideoItem) => JSON.stringify(item))}.width(CommonConstants.FULL_WIDTH).height(CommonConstants.FULL_HEIGHT).indicator(false).loop(false).vertical(true).onChange((index: number) => {this.index = index})}}onPageShow(): void {this.pageShow = true}onPageHide(): void {this.pageShow = false}
}

5.4. 视图模型

5.4.1. 视频模型
// ets/viewmodel/VideoItem.ets@Observed
export class VideoItem {id: string = ''src: Resource = $rawfile('video1.mp4')likesCount: number = 0isLikes: boolean = falsecommentCount: number = 102shareTimes: number = 666
}
5.4.2. 视频数据源
// ets/common/constants/VideoConstants.etsimport { VideoItem } from '../../viewmodel/VideoItem'export const VIDEO_DATA: VideoItem[] = [{id: '1',src: $rawfile('video1.mp4'),likesCount: 0,isLikes: false,commentCount: 102,shareTimes: 666},{id: '2',src: $rawfile('video2.mp4'),likesCount: 8654,isLikes: true,commentCount: 0,shareTimes: 0}
]export enum PlayState {STOP = 0,START = 1,PAUSE = 2
}
5.4.3. 视频视图模型
// ets/viewmodel/VideoViewModel.etsimport { VideoItem } from './VideoItem'
import { VIDEO_DATA } from '../common/constants/VideoConstants'export function initializeOnStartup(): Array<VideoItem> {let videoDataArray: Array<VideoItem> = []VIDEO_DATA.forEach((item: VideoItem) => {videoDataArray.push(item)})return videoDataArray
}

5.5. 播放组件

5.5.1. 播放视图
// ets/view/play/PlayView.etsimport { VideoItem } from '../../viewmodel/VideoItem'
import { CommonConstants } from '../../common/constants/CommonConstants'
import { PlayState } from '../../common/constants/VideoConstants'
import { NavigationView } from './NavigationView'
import { CommentView } from './CommentView'
import { DescriptionView } from './DescriptionView'@Component
export struct PlayView {private isShow: boolean = false@Link @Watch('needPageShow') index: number@Link @Watch('needPageShow') pageShow: boolean@State item: VideoItem = new VideoItem()private barPosition: number = 0@State private playState: number = PlayState.STOPprivate videoController: VideoController = new VideoController()build() {Stack({ alignContent: Alignment.End }) {Video({src: this.item.src,controller: this.videoController}).controls(false).autoPlay(this.playState === PlayState.START ? true : false).objectFit(ImageFit.Fill).loop(true).height(CommonConstants.WIDTH_VIDEO).width(CommonConstants.FULL_WIDTH).onClick(() => {if (this.playState === PlayState.START) {this.playState = PlayState.PAUSEthis.videoController.pause()} else if (this.playState === PlayState.PAUSE) {this.playState = PlayState.STARTthis.videoController.start()}})NavigationView()CommentView({ item: this.item })DescriptionView()}.backgroundColor(Color.Black).width(CommonConstants.FULL_WIDTH).height(CommonConstants.FULL_HEIGHT)}onPageSwiperShow(): void {if (this.playState != PlayState.START) {this.playState = PlayState.STARTthis.videoController.start()}}onPageSwiperHide(): void {if (this.playState != PlayState.STOP) {this.playState = PlayState.STOPthis.videoController.stop()}}needPageShow(): void {if (this.pageShow === true) {if (this.barPosition === this.index) {this.isShow = truethis.onPageSwiperShow()} else {if (this.isShow === true) {this.isShow = falsethis.onPageSwiperHide()}}} else {this.onPageSwiperHide()}}
}
5.5.2. 导航视图
// ets/view/play/NavigationView.etsimport { CommonConstants } from '../../common/constants/CommonConstants'@Component
export struct NavigationView {build() {Navigator({ target: CommonConstants.HOME_PAGE, type: NavigationType.Back }) {Row({ space: CommonConstants.SPACE_NAVIGATION }) {Image($r('app.media.ic_back')).width(CommonConstants.WIDTH_BACK_ICON).height(CommonConstants.HEIGHT_BACK_ICON).objectFit(ImageFit.Contain)Text($r('app.string.movie')).fontSize(CommonConstants.FONT_SIZE_TITLE).fontWeight(CommonConstants.FONT_WEIGHT_BOLD).fontColor($r('app.color.start_window_background')).textAlign(TextAlign.Center).margin(CommonConstants.MARGIN_PLAY_PAGE).lineHeight(CommonConstants.LINE_HEIGHT_NAVIGATION)}}.position({ x: CommonConstants.LEFT_POSITION, y: CommonConstants.START_POSITION })}
}
5.5.3. 互动视图
// ets/view/play/CommentView.etsimport { VideoItem } from '../../viewmodel/VideoItem';
import { CommonConstants } from '../../common/constants/CommonConstants'@Extend(Text) function textStyle(fontSize: number, fonWeight: number) {.fontSize(fontSize).fontWeight(fonWeight).fontColor($r('app.color.start_window_background')).textAlign(TextAlign.Center)
}@Component
export struct CommentView {@ObjectLink item: VideoItembuild() {Column() {Image($r('app.media.head')).width(CommonConstants.WIDTH_HEAD).height(CommonConstants.HEIGHT_HEAD).margin({ top: CommonConstants.TOP_HEAD }).objectFit(ImageFit.Contain).border({width: CommonConstants.WIDTH_HEAD_BORDER,color: Color.White,radius: CommonConstants.RADIUS_HEAD})Image(this.item.isLikes ? $r('app.media.vote1') : $r('app.media.vote0')).width(CommonConstants.WIDTH_VOTE).height(CommonConstants.HEIGHT_COMMENT).onClick(() => {if (this.item.isLikes) {this.item.likesCount--} else {this.item.likesCount++}this.item.isLikes = !this.item.isLikes;}).margin({ top: CommonConstants.TOP_IMAGE_VOTE })Text(this.item.likesCount === 0 ? $r('app.string.like') : (this.item.likesCount.toString())).textStyle(CommonConstants.FONT_SIZE_DESCRIPTION, CommonConstants.FONT_WEIGHT_LIGHT)Image($r('app.media.comment')).width(CommonConstants.WIDTH_VOTE).height(CommonConstants.HEIGHT_COMMENT).margin({ top: CommonConstants.TOP_IMAGE_VOTE })Text(this.item.commentCount === 0 ? $r('app.string.comment') : (this.item.commentCount.toString())).textStyle(CommonConstants.FONT_SIZE_DESCRIPTION, CommonConstants.FONT_WEIGHT_LIGHT)Image($r('app.media.share')).width(CommonConstants.WIDTH_VOTE).height(CommonConstants.HEIGHT_COMMENT).margin({ top: CommonConstants.TOP_IMAGE_VOTE })Text(this.item.shareTimes === 0 ? $r('app.string.share') : (this.item.shareTimes.toString())).textStyle(CommonConstants.FONT_SIZE_DESCRIPTION, CommonConstants.FONT_WEIGHT_LIGHT)}.offset({ x: CommonConstants.OFFSET_COMMENT_X, y: CommonConstants.OFFSET_COMMENT_Y })}
}
5.5.4. 描述视图
// ets/view/play/DescriptionView.etsimport { CommonConstants } from '../../common/constants/CommonConstants'@Extend(Text) function textStyle(fontSize: number, fonWeight: number) {.fontSize(fontSize).fontWeight(fonWeight).fontColor($r('app.color.start_window_background')).textAlign(TextAlign.Center).margin(CommonConstants.MARGIN_PLAY_PAGE)
}@Component
export struct DescriptionView {build() {Column() {Text($r('app.string.movie_description_1')).textStyle(CommonConstants.FONT_SIZE_SORT_TITLE, CommonConstants.FONT_WEIGHT_NORMAL)Text($r('app.string.movie_description_2')).textStyle(CommonConstants.FONT_SIZE_PHOTO_NAME, CommonConstants.FONT_WEIGHT_LIGHT).opacity($r('app.float.opacity_deep'))}.height(CommonConstants.HEIGHT_DESCRIPTION).width(CommonConstants.WIDTH_PLAY).alignItems(HorizontalAlign.Start).offset({ y: CommonConstants.OFFSET_DESCRIPTION_Y })}
}


http://www.hkcw.cn/article/ZYEHIRiyyM.shtml

相关文章

对于python中“FileNotFoundError: [Errno 2] No such file or directory”的解决办法

写在前面 最近在使用 vscode 写代码 (python) 时发现使用相对路径读取文件以及写入文件时&#xff0c;想要直接在当前目录下读写一直提示没有该文件&#xff0c;需要返回根目录。并且使用 vscode 自带调试"F5"以及 Code Runner 扩展即右上角三角形都是如此。参考了许…

VS2022中配置Anaconda3环境和scikit-learn库

VS2022中配置Anaconda3环境和scikit-learn库 安装Anaconda安装scikit-learn库在VS2022中配置该环境 安装Anaconda 1.双击应用程序开始安装 2.点击Next 3.I Agree 4.Just Me 5.修改安装路径到D盘 6.没有选择自动配置环境变量&#xff0c;点击Install安装 7.安装完成 8.进…

Q:知识库-文档的搜索框逻辑是怎样的?

【回到目录】~~~~【回到问题集】 Q&#xff1a;知识库-文档的搜索框逻辑是怎样的? dify知识库的关键字检索响应速度很快,效果如上图 A&#xff1a;查看源代码&#xff0c;搜索逻辑是通过搜索框查看 document_segments.content字段满足条件的记录 , 程序逻辑参考 datasets_se…

Manus AI与多语言手写识别的创新革命:从技术突破到行业赋能

文章目录 一、Manus AI技术架构&#xff1a;从像素到语义的端到端进化1. 动态多尺度卷积网络&#xff08;Dynamic Multi-Scale CNN&#xff09;2. 跨语言注意力机制&#xff08;Cross-Lingual Attention&#xff09; 二、多语言挑战与突破&#xff1a;从数据到算法的全面创新1.…

【ISAQB大纲解读】LG 1-8:区分显性陈述和隐性假设(R1)

软件架构师&#xff1a; 应明确提出假设或先决条件&#xff0c;从而防止隐性假设 知道隐性假设可能会导致利益相关方之间的潜在误解 1. 应明确提出假设或先决条件&#xff0c;防止隐性假设 为什么重要&#xff1f; 隐性假设是架构风险的温床 例如&#xff1a;假设“所有服务都…

Dify-5:Web 前端架构

本文档提供了 Dify Web 前端架构的技术概述&#xff0c;包括核心组件、结构和关键技术。它解释了前端如何组织、组件如何通信以及国际化功能如何实现。 技术栈 Dify 的 Web 前端基于现代 JavaScript 技术栈构建&#xff1a; 框架&#xff1a;Next.js&#xff08;基于 React …

T/CCSA 663-2025《医疗科研云平台技术要求》标准解读与深度分析

参考地址:https://www.doc88.com/p-30280431175529.html 引言 随着医疗信息化建设的深入推进,医疗行业正经历从"业务驱动"向"数据驱动"的转型。在这一背景下,中国通信标准化协会(CCSA)于2025年发布了T/CCSA 663-2025《医疗科研云平台技术要求》标准,并…

基于PostGIS的GeoTools执行原生SQL查询制图实践-以贵州省行政区划及地级市驻地为例

目录 前言 一、空间相关表简介 1、地市行政区划表 2、地市驻地信息表 3、空间查询检索 二、GeoTools制图实现 1、数据类型绑定 2、WKT转Geometry 3、原生SQL转SimpleFeatureCollection 4、集成调用 5、成果预览 三、总结 前言 在当今这个信息爆炸的时代&#xff0c…

[yolov11改进系列]基于yolov11引入自集成注意力机制SEAM解决遮挡问题的python源码+训练源码

【SEAM注意力机制介绍】 本文给大家带来的改进机制是由YOLO-Face提出能够改善物体遮挡检测的注意力机制SEAM&#xff0c;SEAM&#xff08;Spatially Enhanced Attention Module&#xff09;注意力网络模块旨在补偿被遮挡面部的响应损失&#xff0c;通过增强未遮挡面部的响应来…

第35次CCF计算机软件能力认证-5-木板切割

原题链接&#xff1a; TUOJ 我自己写的35分正确但严重超时的代码 #include <bits/stdc.h> using namespace std; int main() {int n, m, k;cin >> n >> m >> k;vector<unordered_map<int, int>> mp(2);int y;for (int i 1; i < n; …

Ubuntu24.04.2 + kubectl1.33.1 + containerdv1.7.27 + calicov3.30.0

Ubuntu24.04.2 kubectl1.33.1 containerdv1.7.27 calicov3.30.0 安装Ubuntu24.04.2 kubectl1.33.1 containerdv1.7.27 calicov3.30.0 1.安装Ubuntu24.04.2&#xff0c;设置阿里云镜像地址 $ sudo vim /etc/apt/sources.list.d/ubuntu.sources URIs: https://mirrors.aliy…

Agent智能体应用教程系列(四):仅需几步,拥有自己专属的多agent智能体!

一个智能体完成多种角色任务&#xff01;今天开放猫教你用Coze&#xff08;扣子&#xff09;搭建一个可以同时输出知乎文案&#xff0c;小红书文案等多种功能的智能体搭建教程。 保证一看就会&#xff01; 以下是具体步骤&#xff1a; 创建多Agent智能体 1.1 创建智能体 1.2…

原始数据去哪找?分享15个免费官方网站

目录 一、找数据的免费官方网站 &#xff08;一&#xff09;国家级数据宝库&#xff1a;权威且全面 1.中国国家统计局 2.香港政府数据中心 3.OECD数据库 &#xff08;二&#xff09;企业情报中心&#xff1a;洞察商业本质 4.巨潮资讯 5.EDGAR数据库 6.天眼查/企查查&a…

[yolov11改进系列]基于yolov11使用图像去雾网络UnfogNet替换backbone的python源码+训练源码

【UnfogNet介绍】 UnfogNet是一种专为图像去雾设计的深度学习网络&#xff0c;旨在通过先进的算法恢复雾霾天气下图像的清晰度&#xff0c;提升视觉效果与后续计算机视觉任务的性能。其核心架构融合了编码器-解码器结构与注意力机制&#xff0c;通过多尺度特征提取与融合&…

腾讯 ovCompose 开源,Kuikly 鸿蒙和 Compose DSL 开源,腾讯的“双”鸿蒙方案发布

近日&#xff0c;腾讯的 ovCompose 和 Kuikly 都发布了全新开源更新&#xff0c;其中 Kuikly 在之前我们聊过&#xff0c;本次 Kuikly 主要是正式开源鸿蒙支持部分和 Compose DSL 的相关支持&#xff0c;而 ovCompose 是腾讯视频团队基于 Compose Multiplatform 生态推出的跨平…

SP网络结构:现代密码学的核心设计

概述 SP网络&#xff08;Substitution-Permutation Network&#xff09;是一种对称密钥密码结构&#xff0c;由Claude Shannon在1949年提出的混淆(Confusion)与扩散(Diffusion) 原则发展而来。与Feistel网络不同&#xff0c;SP网络在每轮中对整个数据块进行非线性替换和线性置…

HCIP(BGP基础)

一、BGP 基础概念 1. 网络分类与协议定位 IGP&#xff08;内部网关协议&#xff09;&#xff1a;用于自治系统&#xff08;AS&#xff09;内部路由&#xff0c;如 RIP、OSPF、EIGRP&#xff0c;关注选路效率、收敛速度和资源占用。EGP&#xff08;外部网关协议&#xff09;&a…

身份证实名认证API接口-透明网络空间-实名认证api

数字化时代&#xff0c;线上交易、社交互动、信息共享等活动已经成为人们日常生活的一部分。但随之而来的是身份盗用、欺诈等网络安全问题的不断上升。为应对这一挑战&#xff0c;身份证实名认证作为网络平台的一项基础安全功能&#xff0c;逐渐成为确保用户身份真实性、保障交…

数据安全中心是什么?如何做好数据安全管理?

目录 一、数据安全中心是什么 &#xff08;一&#xff09;数据安全中心的定义 &#xff08;二&#xff09;数据安全中心的功能 1. 数据分类分级 2. 访问控制 3. 数据加密 4. 安全审计 5. 威胁检测与响应 二、数据安全管理的重要性 三、如何借助数据安全中心做好数据安…

【Oracle】视图

个人主页&#xff1a;Guiat 归属专栏&#xff1a;Oracle 文章目录 1. 视图基础概述1.1 视图的概念与特点1.2 视图的工作原理1.3 视图的分类 2. 简单视图2.1 创建简单视图2.1.1 基本简单视图2.1.2 带计算列的简单视图 2.2 简单视图的DML操作2.2.1 通过视图进行INSERT操作2.2.2 通…