#DAYU200体验官# 起司播客App 原创 精华
@toc
主题
本帖使用Dayu200为开发板,展示一个在线播音App - 起司播客。
预览效果图
Dayu200的预览配置
为了大幅提高UI的开发效率,降低Dayu200的使用门槛,在开发过程中,强烈建议使用DevEco Studio 3.0 Beta3(OpenHarmony)的MatePadPro作为预览配置,并调整到竖屏模式,最终与Dayu200上的效果近似一致。
资源导入
本案例为了简单起见,文字与颜色直接写在代码中,仅图片资源需要导入,将全部所需图片拖到pages的新建img子目录中:
首页结构
使用默认的index.ets入口页作为启动页,分析页面的结构,可以一个Column,从上至下依次是导航栏、分类标题、分类卡片列表、筛选栏、播客作品列表。
导航栏
导航栏的左侧是一个按列布局的两行文字,右侧是一个头像:
Row {
Column {
Text('起司播客')
.fontSize(20)
.fontWeight(FontWeight.Bold)
Text('爱情,生活,舒缓')
.fontSize(15)
.fontColor('#A3A1AF')
}
.alignItems(HorizontalAlign.Start)
Blank()
Image($r("app.media.profile"))
.objectFit(ImageFit.Contain)
.width(49)
.height(49)
}
.width('100%')
.padding({left:20,top:20,bottom:10,right:20})
分类标题
标题是一行文字,左对齐:
Row {
Text('分类')
.fontSize(20)
.fontColor('#1E3354')
}
.width('100%')
.padding({left:20,top:10,bottom:10,right:20})
分类卡片列表
定义分类列表的数据:
var cate = [
{
title: '音乐 & 娱乐',
img: '/pages/img/cate1.png',
total: '84',
album: ['', '', '', '', '', '', ''],
},
{
title: '生活 & 舒缓',
img: '/pages/img/cate2.png',
total: '96',
album: ['', '', '', '', '', '', ''],
},
{
title: '教育 & 学习',
img: '/pages/img/cate3.png',
total: '72',
album: ['', '', '', '', '', '', ''],
},
]
专辑数据:
var albums = [
{
title: 'Ngobam',
cate: '音乐 & 娱乐',
tag: 'pop',
img: '/pages/img/item1.png',
eps: '84',
artist: 'Gofar Hilman'
},
{
title: 'Semprod',
cate: '生活 & 舒缓',
tag: 'pop',
img: '/pages/img/item2.png',
eps: '44',
artist: 'Kugo娱乐'
},
{
title: 'Sruput Nendang',
cate: '教育 & 学习',
tag: 'pop',
img: '/pages/img/item3.png',
eps: '46',
artist: 'Macro & Marlo'
},
]
卡片本身由背景层和卡片文字组成。
背景层:
Column {
Image(item.img)
.objectFit(ImageFit.Cover)
.borderRadius(20)
}
.width('100%')
.height('100%')
卡片文字又上下排列的Column组成:
Column {
Blank()
Column {
Text(item.title)
.fontSize(16)
.opacity(0.9)
.fontWeight(FontWeight.Bold)
Text(item.total + "个播客 ")
.fontSize(15)
.opacity(0.4)
}
.borderRadius(20)
.alignItems(HorizontalAlign.Start)
.backgroundColor(Color.White)
.opacity(0.6)
.backdropBlur(8)
.padding(20)
.width('100%')
}
.alignItems(HorizontalAlign.Start)
.width('100%')
.height('100%')
将背景层和卡片文字组合起来,加上用户点击的互动:
Stack {
Column {
Image(item.img)
.objectFit(ImageFit.Cover)
.borderRadius(20)
}
.width('100%')
.height('100%')
Column {
Blank()
Column {
Text(item.title)
.fontSize(16)
.opacity(0.9)
.fontWeight(FontWeight.Bold)
Text(item.total + "个播客 ")
.fontSize(15)
.opacity(0.4)
}
.borderRadius(20)
.alignItems(HorizontalAlign.Start)
.backgroundColor(Color.White)
.opacity(0.6)
.backdropBlur(8)
.padding(20)
.width('100%')
}
.alignItems(HorizontalAlign.Start)
.width('100%')
.height('100%')
}
.padding( left: 20 )
.width(224)
.height(296)
.onClick( {() =>
router.push(
uri: 'pages/channel',
params: {
place: item
}
)
})
使用横向滑动的List组件和ForEach对卡片数据进行循环:
List {
ForEach(this.cate, item => {
ListItem {
Stack {
Column {
Image(item.img)
.objectFit(ImageFit.Cover)
.borderRadius(20)
}
.width('100%')
.height('100%')
Column {
Blank()
Column {
Text(item.title)
.fontSize(16)
.opacity(0.9)
.fontWeight(FontWeight.Bold)
Text(item.total + "个播客 ")
.fontSize(15)
.opacity(0.4)
}
.borderRadius(20)
.alignItems(HorizontalAlign.Start)
.backgroundColor(Color.White)
.opacity(0.6)
.backdropBlur(8)
.padding(20)
.width('100%')
}
.alignItems(HorizontalAlign.Start)
.width('100%')
.height('100%')
}.padding( left: 20 )
.width(224)
.height(296)
.onClick( () =>{
router.push(
uri: 'pages/channel',
params: {
place: item
}
)
})
}
})
}
.listDirection(Axis.Horizontal)
.width('100%')
.height(296)
筛选栏
定义筛选栏的数据数组和对应的索引数组:
var filters: string[] = [
"流行", "最近", "音乐", "舒缓", "R&B"
]
var filterIndices: number[] = this.filters.map{(_, index) => index}
定义用户选中的筛选栏状态变量:
@State var selected: number = 0
筛选栏是一系列的自定义按钮。单个按钮由图标加文字组成,第一个流行按钮有带火的图标。筛选栏中如果任何一个按钮被用户点击,则显示按钮的背景以及文字变粗体:
Button {
Row {
if (index == 0) {
Image($r("app.media.fire"))
.objectFit(ImageFit.Contain)
.width(16).height(16)
.margin({ right: 10 })
}
Text(this.filters[index])
.fontSize(17)
.fontWeight(this.selected == index ? FontWeight.Bold : FontWeight.Lighter)
.fontColor(this.selected == index ? '#413E50' : '#A3A1AF')
}.padding(15)
}
.type(ButtonType.Normal)
.backgroundColor(this.selected == index ? '#EDF0FC' : Color.White)
.borderRadius(10)
.height(50)
.onClick( () =>{
this.selected = index
})
将筛选按钮数据使用横向滑动的List和ForEach进行循环渲染,即可得到按钮组:
List( {space: 10} ) {
ForEach(this.filterIndices, index =>{
ListItem {
Button {
Row {
if (index == 0) {
Image($r("app.media.fire"))
.objectFit(ImageFit.Contain)
.width(16).height(16)
.margin({ right: 10 })
}
Text(this.filters[index])
.fontSize(17)
.fontWeight(this.selected == index ? FontWeight.Bold : FontWeight.Lighter)
.fontColor(this.selected == index ? '#413E50' : '#A3A1AF')
}.padding(15)
}
.type(ButtonType.Normal)
.backgroundColor(this.selected == index ? '#EDF0FC' : Color.White)
.borderRadius(10)
.height(50)
.onClick( () =>{
this.selected = index
})
}
})
}
.listDirection(Axis.Horizontal)
.width('100%')
.height(90)
.padding(20)
音乐列表
单个的音乐条目由专辑图标、音乐名和作者、所属分类和专辑数目组合在一行之内。
圆角的专辑图标:
Image(item.img)
.objectFit(ImageFit.Cover)
.width(56)
.height(56)
.borderRadius(20)
音乐名和作者:
Row {
Text(item.title)
.fontSize(16)
.opacity(0.9)
Text("|")
.fontSize(15)
.opacity(0.05)
.padding(left:10,right:10)
Text(item.artist)
.fontSize(15)
.opacity(0.4)
}
.width('90%')
所属分类和专辑数目:
Row {
Text(item.cate)
.fontSize(16)
.opacity(0.4)
Text("·")
.fontSize(15)
.opacity(0.2)
.padding(left:5,right:5)
Text(item.eps + "个Ep")
.fontSize(15)
.opacity(0.4)
}
.width('90%')
将三个部分依次组合起来:
Row {
Image(item.img)
.objectFit(ImageFit.Cover)
.width(56)
.height(56)
.borderRadius(20)
Column {
Blank()
Row {
Text(item.title)
.fontSize(16)
.opacity(0.9)
Text("|")
.fontSize(15)
.opacity(0.05)
.padding(left:10,right:10)
Text(item.artist)
.fontSize(15)
.opacity(0.4)
}
.width('90%')
Row {
Text(item.cate)
.fontSize(16)
.opacity(0.4)
Text("·")
.fontSize(15)
.opacity(0.2)
.padding(left:5,right:5)
Text(item.eps + "个Ep")
.fontSize(15)
.opacity(0.4)
}
.width('90%')
Blank()
}
.width('70%')
.height('100%')
}
.borderRadius(18)
.backgroundColor('#EDF0FC')
.margin({left:20,top:10,bottom: 10, right:20})
.padding(10)
.width('90%')
.height(72)
有了单个音乐条目的实现,就可以使用一个纵向滑动的List和ForEach循环渲染,再加上用户点击跳转到音乐播放页的互动:
List {
ForEach(this.albums, item =>{
ListItem {
Row {
Image(item.img)
.objectFit(ImageFit.Cover)
.width(56)
.height(56)
.borderRadius(20)
Column {
Blank()
Row {
Text(item.title)
.fontSize(16)
.opacity(0.9)
Text("|")
.fontSize(15)
.opacity(0.05)
.padding(left:10,right:10)
Text(item.artist)
.fontSize(15)
.opacity(0.4)
}
.width('90%')
Row {
Text(item.cate)
.fontSize(16)
.opacity(0.4)
Text("·")
.fontSize(15)
.opacity(0.2)
.padding(left:5,right:5)
Text(item.eps + "个Ep")
.fontSize(15)
.opacity(0.4)
}
.width('90%')
Blank()
}
.width('70%')
.height('100%')
}
.borderRadius(18)
.backgroundColor('#EDF0FC')
.margin({left:20,top:10,bottom: 10, right:20})
.padding(10)
.width('90%')
.height(72)
.onClick( () =>{
router.push(
uri: 'pages/detail',
params: {
place: item
}
)
})
}
})
}
.width('100%')
.height('35%')
完整代码和效果
将上述代码从上到下依次放入Column中,组成首页index.ets的完整代码:
import router from '@system.router';
@Entry
@Component
struct Index {
@State selected: number = 0
filters: string[] = [
"流行", "最近", "音乐", "舒缓", "R&B"
]
filterIndices: number[] = this.filters.map((_, index) => index)
cate = [
{
title: '音乐 & 娱乐',
img: '/pages/img/cate1.png',
total: '84',
album: ['', '', '', '', '', '', ''],
},
{
title: '生活 & 舒缓',
img: '/pages/img/cate2.png',
total: '96',
album: ['', '', '', '', '', '', ''],
},
{
title: '教育 & 学习',
img: '/pages/img/cate3.png',
total: '72',
album: ['', '', '', '', '', '', ''],
},
]
albums = [
{
title: 'Ngobam',
cate: '音乐 & 娱乐',
tag: 'pop',
img: '/pages/img/item1.png',
eps: '84',
artist: 'Gofar Hilman'
},
{
title: 'Semprod',
cate: '生活 & 舒缓',
tag: 'pop',
img: '/pages/img/item2.png',
eps: '44',
artist: 'Kugo娱乐'
},
{
title: 'Sruput Nendang',
cate: '教育 & 学习',
tag: 'pop',
img: '/pages/img/item3.png',
eps: '46',
artist: 'Macro & Marlo'
},
]
build() {
Column() {
Row() {
Column() {
Text('起司播客')
.fontSize(20)
.fontWeight(FontWeight.Bold)
Text('爱情,生活,舒缓')
.fontSize(15)
.fontColor('#A3A1AF')
}.alignItems(HorizontalAlign.Start)
Blank()
Image($r("app.media.profile"))
.objectFit(ImageFit.Contain)
.width(49)
.height(49)
}
.width('100%')
.padding({left:20,top:20,bottom:10,right:20})
Row() {
Text('分类')
.fontSize(20)
.fontColor('#1E3354')
}
.width('100%')
.padding({left:20,top:10,bottom:10,right:20})
List() {
ForEach(this.cate, item => {
ListItem() {
Stack() {
Column() {
Image(item.img)
.objectFit(ImageFit.Cover)
.borderRadius(20)
}
.width('100%')
.height('100%')
Column() {
Blank()
Column() {
Text(item.title)
.fontSize(16)
.opacity(0.9)
.fontWeight(FontWeight.Bold)
Text(item.total + "个播客 ").fontSize(15).opacity(0.4)
}
.borderRadius(20)
.alignItems(HorizontalAlign.Start)
.backgroundColor(Color.White)
.opacity(0.6)
.backdropBlur(8)
.padding(20)
.width('100%')
}
.alignItems(HorizontalAlign.Start)
.width('100%')
.height('100%')
}.padding({ left: 20 })
.width(224)
.height(296)
.onClick(() => {
router.push({
uri: 'pages/channel',
params: {
place: item
}
})
})
}
})
}
.listDirection(Axis.Horizontal)
.width('100%')
.height(296)
List({ space: 10 }) {
ForEach(this.filterIndices, index => {
ListItem() {
Button() {
Row() {
if (index == 0) {
Image($r("app.media.fire"))
.objectFit(ImageFit.Contain)
.width(16).height(16)
.margin({ right: 10 })
}
Text(this.filters[index])
.fontSize(17)
.fontWeight(this.selected == index ? FontWeight.Bold : FontWeight.Lighter)
.fontColor(this.selected == index ? '#413E50' : '#A3A1AF')
}.padding(15)
}
.type(ButtonType.Normal)
.backgroundColor(this.selected == index ? '#EDF0FC' : Color.White)
.borderRadius(10)
.height(50)
.onClick(() => {
this.selected = index
})
}
})
}
.listDirection(Axis.Horizontal)
.width('100%')
.height(90)
.padding(20)
List() {
ForEach(this.albums, item => {
ListItem() {
Row() {
Image(item.img)
.objectFit(ImageFit.Cover)
.width(56).height(56)
.borderRadius(20)
Column() {
Blank()
Row() {
Text(item.title)
.fontSize(16)
.opacity(0.9)
Text("|")
.fontSize(15)
.opacity(0.05)
.padding({left:10,right:10})
Text(item.artist).fontSize(15).opacity(0.4)
}
.width('90%')
Row() {
Text(item.cate)
.fontSize(16)
.opacity(0.4)
Text("·").fontSize(15).opacity(0.2).padding({left:5,right:5})
Text(item.eps + "个Ep").fontSize(15).opacity(0.4)
}
.width('90%')
Blank()
}
.width('70%')
.height('100%')
}
.borderRadius(18)
.backgroundColor('#EDF0FC')
.margin({left:20,top:10,bottom: 10, right:20})
.padding(10)
.width('90%')
.height(72)
.onClick(() => {
router.push({
uri: 'pages/detail',
params: {
place: item
}
})
})
}
})
}
.width('100%')
.height('35%')
}
.width('100%')
}
}
频道页
频道页是一个播客作者的详细介绍,结构的上半部分是播客信息区域(导航栏、头像、昵称、播客描述、作品数和昵称),下半部分是播客作品页。在pages下新建一个channel.ets源文件。
播客作品数据
播客个人信息:
var author = {
desc: '听我用音乐娓娓道来',
albums: 256,
name: '奥珍妮博士',
avatar: '/pages/img/uper_avatar1.png'
}
播客作品信息:
var albums = [
{
title: '工作和生活之间',
img: '/pages/img/ep1.png',
duration: '56:38',
eps: '56',
},
{
title: '前进的力量',
img: '/pages/img/ep2.png',
duration: '28:01',
eps: '35',
}, {
title: '让我惊喜的小猴',
img: '/pages/img/ep3.png',
duration: '1:40:20',
eps: '42',
}, {
title: '我的爱情被疫情阻隔',
img: '/pages/img/ep4.png',
duration: '1:05:13',
eps: '51',
}, {
title: '你为什么要振作起来?',
img: '/pages/img/ep5.png',
duration: '45:28',
eps: '77',
},
]
导航栏
导航栏左侧有一个返回按钮,中间是标题,右侧留空:
Row {
Image($r("app.media.back"))
.objectFit(ImageFit.Contain)
.width(20)
.height(20)
.onClick(()=>{
router.back()
})
Blank()
Text('播客')
.fontSize(20)
Blank()
}
.width('100%')
.padding( {left: 20, top: 20, bottom: 10, right: 20} )
播客个人信息区域
头像、昵称、播客描述、作品数和昵称都组合在一个Column中:
Column({space:15}) {
Image(this.author.avatar)
.objectFit(ImageFit.Contain)
.width(84)
.height(84)
.borderRadius(21)
Text(this.author.desc)
.fontSize(18)
.fontWeight(FontWeight.Bold)
Row {
Text(this.author.albums + "个Ep")
.fontSize(15)
.opacity(0.4)
Text("|")
.fontSize(15)
.opacity(0.2)
.padding( left: 5, right: 5 )
Text(this.author.name)
.fontSize(15)
.fontColor('#A3A1AF')
}
Button {
Row {
Image($r("app.media.follow"))
.objectFit(ImageFit.Contain)
.width(24)
.height(24)
.margin({right:10})
Text('关注')
.fontSize(18)
.fontColor(Color.White)
}
}
.type(ButtonType.Normal)
.backgroundColor('#2882F1')
.width(200)
.height(48)
.borderRadius(8)
}
.height('30%')
播客作品列表
列表有一个标题区:
Row {
Text('全部EP')
.fontSize(20)
.fontColor('#1E3354')
}
.width('100%')
.padding({left:20})
单个播客作品项目,由作品封面、作品名、时长和作品数,组合在一个Column中:
Row {
Image(item.img)
.objectFit(ImageFit.Cover)
.width(56).height(56)
.borderRadius(20)
Column {
Blank()
Row {
Text(item.title)
.fontSize(16)
.opacity(0.9)
Text("|")
.fontSize(15)
.opacity(0.05)
.padding( {left: 10, right: 10} )
Text(item.artist)
.fontSize(15)
.opacity(0.4)
}
.width('90%')
Row {
Text(item.duration)
.fontSize(16)
.opacity(0.4)
Text("·")
.fontSize(15)
.opacity(0.2)
.padding( {left: 5, right: 5 })
Text(item.eps + "个Ep")
.fontSize(15)
.opacity(0.4)
}
.width('90%')
Blank()
}
.width('70%')
.height('100%')
}
.borderRadius(18)
.backgroundColor('#EDF0FC')
.margin( {left: 20, top: 10, bottom: 10, right: 20} )
.padding(10)
.width('90%')
.height(72)
对于播客作品数据,使用List和ForEach循环渲染,加上用户互动跳转到播放页:
List {
ForEach(this.albums, item =>{
ListItem {
Row {
Image(item.img)
.objectFit(ImageFit.Cover)
.width(56).height(56)
.borderRadius(20)
Column {
Blank()
Row {
Text(item.title)
.fontSize(16)
.opacity(0.9)
Text("|")
.fontSize(15)
.opacity(0.05)
.padding( {left: 10, right: 10} )
Text(item.artist)
.fontSize(15)
.opacity(0.4)
}
.width('90%')
Row {
Text(item.duration)
.fontSize(16)
.opacity(0.4)
Text("·")
.fontSize(15)
.opacity(0.2)
.padding( left: 5, right: 5 )
Text(item.eps + "个Ep")
.fontSize(15)
.opacity(0.4)
}
.width('90%')
Blank()
}
.width('70%')
.height('100%')
}
.borderRadius(18)
.backgroundColor('#EDF0FC')
.margin( {left: 20, top: 10, bottom: 10, right: 20} )
.padding(10)
.width('90%')
.height(72)
.onClick(() =>{
router.push(
uri: 'pages/detail',
params: {
place: item
}
)
})
}
})
}
.width('100%')
.height('55%')
完整页面代码和预览效果
将以上各个子组件和数据组合起来,channel.ets源文件整个页面的代码:
import router from '@system.router';
@Entry
@Component
struct Channel {
author = {
desc: '听我用音乐娓娓道来',
albums: 256,
name: '奥珍妮博士',
avatar: '/pages/img/uper_avatar1.png'
}
albums = [
{
title: '工作和生活之间',
img: '/pages/img/ep1.png',
duration: '56:38',
eps: '56',
},
{
title: '前进的力量',
img: '/pages/img/ep2.png',
duration: '28:01',
eps: '35',
}, {
title: '让我惊喜的小猴',
img: '/pages/img/ep3.png',
duration: '1:40:20',
eps: '42',
}, {
title: '我的爱情被疫情阻隔',
img: '/pages/img/ep4.png',
duration: '1:05:13',
eps: '51',
}, {
title: '你为什么要振作起来?',
img: '/pages/img/ep5.png',
duration: '45:28',
eps: '77',
},
]
build() {
Column() {
Row() {
Image($r("app.media.back"))
.objectFit(ImageFit.Contain)
.width(20).height(20)
.onClick(()=>{
router.back()
})
Blank()
Text('播客')
.fontSize(20)
Blank()
}.width('100%')
.padding({ left: 20, top: 20, bottom: 10, right: 20 })
Column({space:15}) {
Image(this.author.avatar)
.objectFit(ImageFit.Contain)
.width(84).height(84).borderRadius(21)
Text(this.author.desc)
.fontSize(18)
.fontWeight(FontWeight.Bold)
Row() {
Text(this.author.albums + "个Ep").fontSize(15).opacity(0.4)
Text("|").fontSize(15).opacity(0.2).padding({ left: 5, right: 5 })
Text(this.author.name)
.fontSize(15)
.fontColor('#A3A1AF')
}
Button(){
Row(){
Image($r("app.media.follow"))
.objectFit(ImageFit.Contain)
.width(24).height(24).margin({right:10})
Text('关注').fontSize(18).fontColor(Color.White)
}
}.type(ButtonType.Normal)
.backgroundColor('#2882F1')
.width(200).height(48).borderRadius(8)
}.height('30%')
Row() {
Text('全部EP')
.fontSize(20)
.fontColor('#1E3354')
}
.width('100%')
.padding({left:20})
List() {
ForEach(this.albums, item => {
ListItem() {
Row() {
Image(item.img)
.objectFit(ImageFit.Cover)
.width(56).height(56)
.borderRadius(20)
Column() {
Blank()
Row() {
Text(item.title)
.fontSize(16)
.opacity(0.9)
Text("|")
.fontSize(15)
.opacity(0.05)
.padding({ left: 10, right: 10 })
Text(item.artist).fontSize(15).opacity(0.4)
}
.width('90%')
Row() {
Text(item.duration)
.fontSize(16)
.opacity(0.4)
Text("·")
.fontSize(15)
.opacity(0.2)
.padding({ left: 5, right: 5 })
Text(item.eps + "个Ep").fontSize(15).opacity(0.4)
}
.width('90%')
Blank()
}
.width('70%')
.height('100%')
}
.borderRadius(18)
.backgroundColor('#EDF0FC')
.margin({ left: 20, top: 10, bottom: 10, right: 20 })
.padding(10)
.width('90%')
.height(72)
.onClick(() => {
router.push({
uri: 'pages/detail',
params: {
place: item
}
})
})
}
})
}
.width('100%')
.height('55%')
}
.width('100%')
}
}
播放页
播放页用于播放上一页的频道页的播客作品,包含导航栏、作品大图、作品名和作者、播放控制按钮。在pages目录下新建源文件detail.ets。
状态变量
播放的作品来自上一页,这里使用固定的数据。播放状态是用户可控的,使用@State变量,定义如下:
@State playing: boolean = false
var music = {
img: 'pages/img/ep1.png',
author: '奥珍妮博士',
title: '工作和生活之间',
duration: '56:38',
}
导航栏
导航栏左侧用于返回上一页,右侧可以将作品添加到播放列表:
Row {
Image($r("app.media.back"))
.objectFit(ImageFit.Contain)
.width(20).height(20)
.onClick({()=>
router.back()
})
Blank()
Text(' ')
.fontSize(20)
Blank()
Image($r("app.media.playlist"))
.objectFit(ImageFit.Contain)
.width(20)
.height(20)
}
.width('100%')
.padding(30)
作品大图
作品大图带有圆角和阴影,将Image放入Column中再修饰属性即可:
Column {
Image(this.music.img)
.objectFit(ImageFit.Cover)
.width(279)
.height(326)
.borderRadius(16)
}
.borderRadius(16)
.shadow({radius: 50, color: '#cfcfcf',
offsetX:5, offsetY: 15})
.margin(30)
作品名和作者
作品名和作者按列布局,其中作者的文字略带不透明效果:
Column {
Text(this.music.title)
.fontSize(20)
Text(this.music.author)
.fontSize(15)
.opacity(0.4)
}
.padding(30)
播放控制按钮
播放按钮可以根据作品的播放状态来切换图标,用户点击按钮可以在播放或暂停两种状态进行切换:
Row {
Image(this.playing ? $r("app.media.pause") : $r("app.media.play"))
.objectFit(ImageFit.Cover)
.width(64)
.height(64)
.onClick(()=>{
this.playing = !this.playing
})
}
.padding(30)
完整页面源码和预览效果
将以上子组件和数据组合起来,detail.ets源文件整个页面的代码:
import router from '@system.router';
@Entry
@Component
struct Detail {
@State playing: boolean = false
music = {
img: 'pages/img/ep1.png',
author: '奥珍妮博士',
title: '工作和生活之间',
duration: '56:38',
}
build() {
Column() {
Row() {
Image($r("app.media.back"))
.objectFit(ImageFit.Contain)
.width(20).height(20)
.onClick(()=>{
router.back()
})
Blank()
Text(' ')
.fontSize(20)
Blank()
Image($r("app.media.playlist"))
.objectFit(ImageFit.Contain)
.width(20).height(20)
}.width('100%')
.padding(30)
Column() {
Image(this.music.img)
.objectFit(ImageFit.Cover)
.width(279).height(326)
.borderRadius(16)
}
.borderRadius(16)
.shadow({radius: 50, color: '#cfcfcf',
offsetX:5, offsetY: 15})
.margin(30)
Column() {
Text(this.music.title)
.fontSize(20)
Text(this.music.author)
.fontSize(15).opacity(0.4)
}.padding(30)
Row() {
Image(this.playing ? $r("app.media.pause") : $r("app.media.play"))
.objectFit(ImageFit.Cover)
.width(64).height(64)
.onClick(()=>{
this.playing = !this.playing
})
}.padding(30)
}
.width('100%')
.height('100%')
}
}
总结
Dayu200不仅适合设备开发,更适合App开发,配合最新的DevEco Studio 3.0,即使您手头没有设备,也可以进行相对完善的UI开发大部分工作。
老师的创作热情最近真是高,佩服佩服
嘿嘿 必须的,赶一下
👍👍👍👍👍
得感叹一下,小波老师的审美真的很在线👍