【鸿蒙心迹】从《凡人修仙传》到免费观影神器
在繁忙的都市生活中,我喜欢找一个安静的角落,拿起手机沉浸在《凡人修仙传》的世界里跟随道友一同修仙。然而,这本该是享受的过程,却常常被各种广告打断,甚至有些视频需要付费才能观看。我意识到如果能开发一款既无广告又免费的观影App,不仅能满足自己,还能帮助更多和我一样的鸿蒙用户作为有趣的学习素材。于是,我决定将这个想法付诸行动。”
鸿蒙HarmonyOS操作系统以其独特的优势吸引了我。它不仅界面简洁流畅、开发效率高,还具有强大的生态系统支持。我开始学习鸿蒙开发,不断从社区里汲取知识,最终决定开发一个名为“鸿蒙观影”的App。从零开始,我充满了期待,但也伴随着不少挑战。
第一次跑通功能的狂喜
初入鸿蒙开发圈,我就像一个初入修仙界的凡人。韩老魔在乱星海修仙,我则是在鸿蒙的世界里修练,充满了好奇和兴奋。我开始学习鸿蒙的基本开发框架,阅读官方文档,参加各类线上课程,逐渐掌握了鸿蒙开发的基础知识。随着知识的积累,我开始动手编写代码,尝试搭建观影App的基本框架。
经过数日的努力,我终于实现了一个完整的观影app功能。当在鸿蒙手机上看到视频流畅播放时,我激动不已。那一刻,所有的辛劳都化为了喜悦,我知道自己已经迈出了第一步,鸿蒙观影的第一步已经成功了。
项目开源地址: HarmonyOS版github :https://github.com/yangyongzhen/hmmovie
深夜改bug的倔强
开发过程并非一帆风顺,遇到bug是常有的事情。尤其是当我们开发的应用涉及到网络请求和视频播放时,bug的出现更是难以避免。为了实现更稳定的应用体验,我经常熬夜排查问题,查找资料,反复测试,直到问题解决为止。
有一次,我在测试视频播放功能时发现,视频播放时会出现卡顿现象。起初我以为是网络问题,但经过多次测试,我发现无论是在高速网络还是4G网络下,卡顿现象依然存在。我迅速意识到,这可能与应用的代码逻辑优化有关。于是,我开始阅读更多关于鸿蒙开发优化的资料,尝试各种方法解决这个问题。经过数日的努力,我终于找到了问题的根源,并成功优化了代码,使视频播放更加流畅。
原始网络请求繁琐,简化网络接口使用
我在项目中引入了@nutpi/axios
库,配置了网络请求的基础URL和拦截器。原始的写法太繁琐,给我造成了不少困难。结果我封装成了三方库的形式,nutpi/axios
库就是我封装并发布到开源鸿蒙中心仓的。通过这个库,写网络接口彻底变成一种享受,一分钟写完一个网络接口。
// 引入@nutpi/axios库
import axios from '@nutpi/axios';
// 配置网络请求的基础URL
axios.defaults.baseURL = 'https://api.example.com';
// 添加请求拦截器
axios.interceptors.request.use(config => {
// 添加请求拦截器
return config;
}, error => {
return Promise.reject(error);
});
实现影视首页功能
在影视首页中,我实现了轮播图、热映电影、即将上映电影和热门电视剧集的功能。通过API获取数据并在前端展示,整个过程充满了学习和实践的乐趣。以下是网络后台接口封装。在HarmonyOS NEXT开发环境中,可以使用@nutpi/axios
库来简化网络请求的操作。本项目使用HarmonyOS NEXT框架和@nutpi/axios
库实现一行代码写接口,大幅简化了网络接口的实现。
// 引入@nutpi/axios库
import {axiosClient, HttpPromise} from '../../utils/axiosClient';
import { HotMovieReq, MovieRespData, SwiperData } from '../bean/ApiTypes';
// 1. 获取轮播图接口
export const getSwiperData = (): HttpPromise<SwiperData> => axiosClient.get({url:'/swiperdata'});
// 2. 获取即将上映影视接口
export const getSoonMovie = (start:number, count:number): HttpPromise<MovieRespData> =>
axiosClient.post({url:'/soonmovie', data: { start:start, count:count }});
// 3. 获取热门影视接口
export const getHotMovie = (req:HotMovieReq): HttpPromise<MovieRespData> =>
axiosClient.post({url:'/hotmovie', data:req});
// 4. 获取最新上演影视接口
export const getNewMovie = (start:number, count:number): HttpPromise<MovieRespData> =>
axiosClient.post({url:'/newmovie', data: { start:start, count:count }});
// 5. 获取最热门剧集接口
export const getHotTv = (start:number, count:number): HttpPromise<MovieRespData> =>
axiosClient.post({url:'/tvhot', data: { start:start, count:count }});
电影详情页的设计
在电影详情页中,将使用Badge
、SymbolSpan
、Button
、Rating
等组件来展示电影的详细信息。整个设计过程让我深刻体会到了鸿蒙组件的强大和灵活。
// 引入相关组件和方法
import { getDetailMv, getMovieSrc } from "../../common/api/movie";
import { Log } from "../../utils/logutil";
import { BusinessError } from "@kit.BasicServicesKit";
import { DetailMvResp, DetailMvRespCast } from "../../common/bean/DetailMvResp";
import { promptAction } from "@kit.ArkUI";
@Builder export function MovieDetailPageBuilder() {
Detail();
}
@Component struct Detail {
pageStack: NavPathStack = new NavPathStack();
private uid = '';
@State detailData: DetailMvResp | null = null;
private srcData: MvSourceResp | null = null;
private description: string = '';
private isToggle = false;
@State toggleText: string = '';
@State toggleBtn: string = '展开';
build() {
NavDestination() {
Column({ space: 0 }) {
Row() {
Image(this.detailData?.images).objectFit(ImageFit.Auto).width(120).borderRadius(5)
Column({ space: 8 }) {
Text(this.detailData?.title).fontSize(18)
Text(this.detailData?.year + " " + this.detailData?.genre).fontSize(14)
Row() {
Badge({
count: this.detailData?.wish_count,
maxCount: 10000,
position: BadgePosition.RightTop,
style: { badgeSize: 22, badgeColor: '#fffab52a' }
}) {
Row() {
Text() {
SymbolSpan($r('sys.symbol.heart'))
.fontWeight(FontWeight.Lighter)
.fontSize(32)
.fontColor(['#fffab52a'])
}
Text('想看')
}.backgroundColor('#f8f4f5').borderRadius(5).padding(5)
}.padding(8)
Blank(10).width(40)
Badge({
count: this.detailData?.reviews_count,
maxCount: 10000,
position: BadgePosition.RightTop,
style: { badgeSize: 22, badgeColor: '#fffab52a' }
}) {
Row() {
Text() {
SymbolSpan($r('sys.symbol.star'))
.fontWeight(FontWeight.Lighter)
.fontSize(32)
.fontColor(['#fffab52a'])
}
Text('看过')
}.backgroundColor('#f8f4f5').borderRadius(5).padding(5)
}.padding(8)
}
Button('播放', { buttonStyle: ButtonStyleMode.NORMAL, role: ButtonRole.NORMAL })
.borderRadius(8)
.borderColor('#fffab52a')
.fontColor('#fffab52a')
.width(100)
.height(35)
.onClick(() => {
console.info('Button onClick');
if (this.srcData != null) {
this.pageStack.pushDestinationByName("VideoPlayerPage", { item: { video: this.srcData.urls[0], tvurls: this.srcData.tvurls, title: this.srcData.title, desc: this.detailData?.summary } })
.catch((e: Error) => {
// 跳转失败,会返回错误码及错误信息
console.log(`catch exception: ${JSON.stringify(e)}`);
}).then(() => {
// 跳转成功
});
} else {
promptAction.showToast({ message: '暂无资源' });
}
});
}.alignItems(HorizontalAlign.Start) // 水平方向靠左对齐
.justifyContent(FlexAlign.Start) // 垂直方向靠上对齐
.padding(10);
}.height(160).width('100%');
Row() {
Text('豆瓣评分').fontSize(16).padding(5);
Rating({ rating: (this.detailData?.rate ?? 0) / 2, indicator: true })
.stars(5)
.stepSize(0.5).height(28);
Text(this.detailData?.rate.toString()).fontColor('#fffab52a').fontWeight(FontWeight.Bold).fontSize(36).padding(5);
}.width('100%').height(80).borderRadius(5).backgroundColor('#f8f4f5').margin(20);
Text('简介').fontSize(18).padding({ bottom: 10 }).fontWeight(FontWeight.Bold).alignSelf(ItemAlign.Start);
Text(this.toggleText).fontSize(14).lineHeight(20).alignSelf(ItemAlign.Start);
Text(this.toggleBtn).fontSize(14).fontColor(Color.Gray).padding(10).alignSelf(ItemAlign.End).onClick(() => {
this.isToggle = !this.isToggle;
if (this.isToggle) {
this.toggleBtn = '收起';
this.toggleText = this.description;
} else {
this.toggleBtn = '展开';
this.toggleText = this.description.substring(0, 100) + '...';
}
});
Text('影人').fontSize(18).padding({ bottom: 10 }).fontWeight(FontWeight.Bold).alignSelf(ItemAlign.Start);
Scroll() {
Row({ space: 5 }) {
ForEach(this.detailData?.cast, (item: DetailMvRespCast) => {
Column({ space: 0 }) {
Image(item.cover).objectFit(ImageFit.Auto).height(120).borderRadius(5)
.onClick(() => {
});
Text(item.name)
.alignSelf(ItemAlign.Center)
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.fontSize(14).padding(10);
}.justifyContent(FlexAlign.Center);
}, (itm: DetailMvRespCast, idx) => itm.id);
}
}.scrollable(ScrollDirection.Horizontal);
}.padding({ left: 10, right: 10 });
}.title('电影详情')
.width('100%')
.height('100%')
.onReady(ctx => {
this.pageStack = ctx.pathStack;
// 从上个页面拿参数
this.pageStack.getParamByName("MovieDetailPage");
interface params {
id: string;
}
let par = ctx.pathInfo.param as params;
Log.debug("par:%s", par.id);
this.uid = par.id;
})
.onShown(() => {
console.info('Detail onShown');
getDetailMv(this.uid).then((res) => {
Log.debug(res.data.message);
Log.debug("request", "res.data.code:%{public}d", res.data.code);
this.detailData = res.data;
this.description = this.detailData.summary;
this.toggleText = this.description.substring(0, 100) + '...';
}).catch((err: BusinessError) => {
Log.debug("request", "err.data.code:%d", err.code);
Log.debug("request", err.message);
});
getMovieSrc(this.uid).then((res) => {
Log.debug(res.data.message);
Log.debug("request", "res.data.code:%{public}d", res.data.code);
if (res.data.code == 0) {
this.srcData = res.data;
}
}).catch((err: BusinessError) => {
Log.debug("request", "err.data.code:%d", err.code);
Log.debug("request", err.message);
});
});
}
}
折叠效果的实现
在电影详情页中,对于电影的简介,使用了折叠效果,即默认只显示部分简介内容,用户点击“展开”按钮后可以查看完整简介。这个效果的实现主要通过控制Text
组件的显示内容来实现。
Text(this.toggleText).fontSize(14).lineHeight(20).alignSelf(ItemAlign.Start);
Text(this.toggleBtn).fontSize(14).fontColor(Color.Gray).padding(10).alignSelf(ItemAlign.End).onClick(() => {
this.isToggle = !this.isToggle;
if (this.isToggle) {
this.toggleBtn = '收起';
this.toggleText = this.description;
} else {
this.toggleBtn = '展开';
this.toggleText = this.description.substring(0, 100) + '...';
}
});
开发心路历程
从一开始对鸿蒙开发的陌生,到如今能够熟练地完成项目,这背后是无数次的尝试、失败和总结。遇到问题时,我会查阅官方文档,甚至会寻求社区的帮助。每当解决一个问题,都会有一种成就感。通过这个项目,我不仅提升了编程技能,也学会了如何进行项目管理和时间规划。最重要的是,我体验到了项目开发的乐趣,每一次的进步都让我更加自信。
选择@nutpi/axios
的原因
为什么选择@nutpi/axios
?nutpi/axios
是坚果派对axios封装过的鸿蒙HTTP客户端库,用于简化axios库的使用和以最简单的形式写代码。使用nutpi/axios
库可以大大简化代码,使网络接口变得简单直观。
结语
“想干什么就去干,干得烂总比不干强!”,360周董的这句话对我来说意义非凡。也许一开始的作品并不完美,但只要迈出了第一步,未来就会越来越熟练,也就会有成绩有起色。做事情不要想太多,尤其是别太去计较什么意义和得失,开心就好。希望我的开发手记能够激励到更多的鸿蒙开发者,让我们一起踏上鸿蒙之旅,让鸿蒙生态因你而更加繁荣!
通过这个项目,还感受到了鸿蒙开发的魅力和乐趣。未来,我将继续探索鸿蒙开发的更多可能性,为鸿蒙社区贡献一份力量。同时,我也希望更多的开发者能够加入到鸿蒙开发的行列中来,成为我的道友,咱们共同打造一个更加美好的鸿蒙生态系统。道友,不知你意下如何?
更多推荐
所有评论(0)