(目錄)
主題
本帖使用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開發大部分工作。
起司播客源碼(https://ost.51cto.com/resource/2158)