目录
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.ets
export class CommonConstants {
static readonly DURATION_PAGE = 50
static readonly DURATION_ADS = 200
static readonly HEIGHT_HEAD = 40
static readonly HEIGHT_CAROUSEL_TITLE = 90
static readonly FONT_SIZE_DESCRIPTION = 12
static readonly FONT_SIZE_PHOTO_NAME = 14
static readonly FONT_SIZE_SORT_TITLE = 16
static readonly FONT_SIZE_UNCHECKED = 18
static readonly FONT_SIZE_TITLE = 20
static readonly FONT_SIZE_CHECKED = 24
static readonly FONT_SIZE_PAGE_CONTENT = 28
static readonly FONT_WEIGHT_LIGHT = 400
static readonly FONT_WEIGHT_NORMAL = 500
static readonly FONT_WEIGHT_BOLD = 700
static readonly LAYOUT_WEIGHT = 1
static readonly BORDER_RADIUS = 12
static readonly LINE_HEIGHT_MORE = 19
static readonly LINE_HEIGHT_NAVIGATION = 28
static readonly SPACE_TOP_BAR = 16
static readonly SPACE_NAVIGATION = 8
static readonly WIDTH_HEAD_BORDER = 2
static readonly WIDTH_HEAD = 40
static readonly RADIUS_HEAD = 20
static readonly SWIPER_TIME = 1500
static readonly MARGIN_PLAY_PAGE = 10
static readonly BOTTOM_TEXT = 4
static readonly TOP_ADS = 12
static readonly LEFT_POSITION = '3%'
static readonly ADS_LEFT = 12
static readonly TOP_NAME = 8
static readonly TOP_DESCRIPTION = 4
static readonly TOP_IMAGE_VOTE = 20
static readonly TOP_HEAD = 40
static 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.ets
import { TopBarItem } from '../../viewmodel/TopBarItem'
import { initializeOnStartup } from '../../viewmodel/TopBarViewModel'
import { CommonConstants } from '../../common/constants/CommonConstants'
@Component
export struct TopBar {
@Prop index: number = 0
private 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.ets
import { 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.ets
import { 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.ets
export class TopBarItem {
id: number
name: string
constructor(id: number, name: string) {
this.id = id
this.name = name
}
}
3.2. 全部分类内容页面
3.2.1. 全部分类组件
// ets/view/tabcontent/PageAll.ets
import { 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.ets
import { 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.ets
import { 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.ets
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)
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.ets
import { 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)
})
break
case PictureType.RECENTLY:
PICTURE_RECENTLY.forEach((item: PictureItem) => {
imageDataArray.push(item)
})
break
case PictureType.PHOTO:
PICTURE_PHOTO.forEach((item: PictureItem) => {
imageDataArray.push(item)
})
break
case PictureType.LATEST:
PICTURE_LATEST.forEach((item: PictureItem) => {
imageDataArray.push(item)
})
break
default:
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.ets
import { 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.ets
import { 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.ets
import { 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.ets
import { 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.ets
import { 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.ets
import { 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.ets
import { 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.ets
import { 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 = 0
build() {
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: number
private 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.ets
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 = 0
private 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.ets
import { 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)
})
break
case PictureType.RECENTLY:
PICTURE_RECENTLY.forEach((item: PictureItem) => {
imageDataArray.push(item)
})
break
case PictureType.PHOTO:
PICTURE_PHOTO.forEach((item: PictureItem) => {
imageDataArray.push(item)
})
break
case PictureType.LATEST:
PICTURE_LATEST.forEach((item: PictureItem) => {
imageDataArray.push(item)
})
break
default:
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 = 0
private 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.ets
import { 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.ets
import { 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 = false
build() {
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 = 0
isLikes: boolean = false
commentCount: number = 102
shareTimes: number = 666
}
5.4.2. 视频数据源
// ets/common/constants/VideoConstants.ets
import { 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.ets
import { 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.ets
import { 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.STOP
private 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.PAUSE
this.videoController.pause()
} else if (this.playState === PlayState.PAUSE) {
this.playState = PlayState.START
this.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.START
this.videoController.start()
}
}
onPageSwiperHide(): void {
if (this.playState != PlayState.STOP) {
this.playState = PlayState.STOP
this.videoController.stop()
}
}
needPageShow(): void {
if (this.pageShow === true) {
if (this.barPosition === this.index) {
this.isShow = true
this.onPageSwiperShow()
} else {
if (this.isShow === true) {
this.isShow = false
this.onPageSwiperHide()
}
}
} else {
this.onPageSwiperHide()
}
}
}
5.5.2. 导航视图
// ets/view/play/NavigationView.ets
import { 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.ets
import { 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: VideoItem
build() {
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.ets
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)
.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 })
}
}