今时今日,不管是大人,还是小孩,都喜欢刷视频,生活中刷视频的 APP 也多得是,如:抖音,快手,视频号,今日头条,火山…数也不数不清了。


做一个鸿蒙版仿抖音APP!_ico

然而华为论坛鸿蒙版块搞活动,做一个属于自己的视频应用,说真的,看到这个活动我很开心,又可以用所学的鸿蒙知识来做一个小应用了!


看了小提示,都是 JS 组件来实现的,当我看到分布式也可以用 JS 来写时,当时觉得 JS 也太强大了,因为之前写的 Demo 都是用 Java 来写分布式的。


本人工作也是从事 Java 后台开发,对于 JS 前端知识,也就是入门级水平,然后就在想是用 Java 来写这个视频应用,还是用 JS 来写呢。


通过看了 JS 参考 API 实例后,决定使用 JS 来写,简单易懂,同时也希望现在还在观望鸿蒙应用开发的前端开发人员,不要怕自己不会 Java 开发,而一直在观望,没有踏出第一步来写 Demo。


我写的这个视频应用取名为“爱视频 ,99% 是用 JS 前端知识完成的,只有 1% 的 Java 代码是复制过来的,也就是动态授权代码,所以希望还在观望的前端开发者,就从这个爱视频 APP 开始你们的第一个鸿蒙应用吧!


实现效果


实现效果如下图:

做一个鸿蒙版仿抖音APP!_ide_02开发环境下视频:手机下视频:

创建工程


在这当作你已经安装好最新版本 DevEco-Studio 开发工具,点击 File→New→New Project…弹出 Create HarmonyOS Project 窗口。


这里我选择空白 JS 模板创建,写界面还是 JS 比较方便些,对于有一定前端知识的小伙伴来说。

做一个鸿蒙版仿抖音APP!_java_03

做一个鸿蒙版仿抖音APP!_ico_04


主界面开发


在展示源代码之前,先介绍一下使用到了 JS 哪些组件:


  • 滑动容器(swiper)
  • 视频播放(video)
  • 可滑动面板(panel)
  • 列表组件(list)
  • 图片组件(image)
  • 文本组件(text)
  • 交互式组件(input)
  • 按钮组件(button)


通过查看 JS API 参考文档,就可以做出你喜欢的视频应用了。


先介绍简单的 1% Java 代码,如果之前做过分布式 Demo,直接复制过来就可以使用:


Java 代码:

public class MainAbility extends AceAbility {
@Override
public void onStart(Intent intent) {
super.onStart(intent);
// 动态判断权限
if (verifySelfPermission("ohos.permission.DISTRIBUTED_DATASYNC") != IBundleManager.PERMISSION_GRANTED) {
// 应用未被授予权限
if (canRequestPermission("ohos.permission.DISTRIBUTED_DATASYNC")) {
// 是否可以申请弹框授权(首次申请或者用户未选择禁止且不再提示)
requestPermissionsFromUser(new String[]{"ohos.permission.DISTRIBUTED_DATASYNC"}, 0);
}
}
}

@Override
public void onStop() {
super.onStop();
}
}


HML 代码(重要界面布局文件):

<div class="container">
<swiper class="swiper" id="swiper" index="{{ continueAbilityData.currentIndex }}" indicator="false" loop="true" digital="false" vertical="true" onchange="changeSwiper">
<div class = "swiperContent" >
<video id='videoOne' src='{{ continueAbilityData.videoList[0] }}' muted='false' autoplay='true'ontimeupdate='timeupdateCallback' style="object-fit:fill; width:100%; height: 100%;" controls="false" onclick="change_start_pause_one" loop='true' starttime = '{{ ontinueAbilityData.timeupdatetime }}'></video>
</div>
<div class = "swiperContent">
<video id='videoTwo' src='{{ continueAbilityData.videoList[1] }}' muted='false' autoplay='false' ontimeupdate='timeupdateCallback' style="object-fit:fill; width:100%; height: 100%;" controls="false" onclick="change_start_pause_two" loop='true' starttime = '{{ ontinueAbilityData.timeupdatetime }}'></video>
</div>
<div class = "swiperContent">
<video id='videoThree' src='{{ continueAbilityData.videoList[2] }}' muted='false' autoplay='false' ontimeupdate='timeupdateCallback' style="object-fit:fill; width:100%; height: 100%;" controls="false" onclick="change_start_pause_three" loop='true' starttime = '{{ continueAbilityData.timeupdatetime }}'></video>
</div>
</swiper>
<div class="btn-footer">
<image class="comment-icon icon" src="/common/army_icon.jpg"></image>
<text class="footer-label">#HarmonyOS挑战赛第二期#</text>
<image class="comment-icon" src="/common/share.png" onclick="tryContinueAbility"></image>
<image class="comment-icon" src="/common/bxs-message.png" onclick="showPanel"></image>
</div>
<panel id="simplepanel" type="foldable" mode="half" miniheight="400px">
<div class="panel-div">
<list class="todo-wrapper">
<list-item for="{{continueAbilityData.todolist}}" class="todo-item">
<image class="todo-icon" src="/common/avatar04.png"></image>
<text class="todo-title">{{$item.title}}</text>
</list-item>
</list>
<div class="inner-btn">
<input id="input" class="input" type="text" value="{{continueAbilityData.comment}}" maxlength="20" enterkeytype="send" placeholder="请输入评论内容" onchange="changeValue" onenterkeyclick="enterkeyClick" style="margin-right: 10px;"></input>
<button type="capsule" value="关闭" onclick="closePanel"></button>
</div>
</div>
</panel>
</div>



JS 代码(重要逻辑代码,各组件事件):

// @ts-nocheck
import app from '@system.app';
export default {
data: {
img: "resources/media/pic_tv.png",
continueAbilityData: {
currentIndex: 0,
videoList: [
"/common/000001.mp4",
"/common/000002.mp4",
"/common/000003.mp4"
],
timeupdatetime: 2,
isStart: true,
todolist: [
{title: 'HDC2021活动门票进行中'},
{title: '我期待HarmonyOS生态越来越完善'},
{title: 'HarmonyOS你值得拥有'}],
comment: ''
}
},
onInit() {
},
changeSwiper(e) {
console.info("onRestoreData:changeSwiper");
this.switchPlay(e.index);
},
switchPlay(index) {
console.info("onRestoreData:onShow <> " + index);
this.continueAbilityData.currentIndex = index;
if(index == 0) {
this.$element('videoOne').start();
this.$element('videoTwo').pause();
this.$element('videoThree').pause();
console.info("onRestoreData:videoOne <> start " + this.$element('videoOne').starttime);
}else if(index == 1) {
this.$element('videoOne').pause();
this.$element('videoTwo').start();
this.$element('videoThree').pause();
console.info("onRestoreData:videoTwo <> start " + this.$element('videoTwo').starttime);
}else if(index == 2) {
this.$element('videoOne').pause();
this.$element('videoTwo').pause();
this.$element('videoThree').start();
console.info("onRestoreData:videoThree <> start " + this.$element('videoThree').starttime);
}
},

//流转事件
tryContinueAbility: async function() {
// 应用进行迁移
let result = await FeatureAbility.continueAbility();
console.info("result:" + JSON.stringify(result));
},
onStartContinuation() {
// 判断当前的状态是不是适合迁移
console.info("onStartContinuation");
return true;
},
onCompleteContinuation(code) {
// 迁移操作完成,code返回结果
console.info("CompleteContinuation: code = " + code);
app.terminate();

},
onSaveData(saveData) {
// 数据保存到savedData中进行迁移。
var data = this.continueAbilityData;
console.info("onSaveData:" + JSON.stringify(data));
Object.assign(saveData, data)
},
onRestoreData(restoreData) {
console.info("onRestoreData:" + JSON.stringify(restoreData));
// 收到迁移数据,恢复。
this.continueAbilityData = restoreData;

var currentIndex = this.continueAbilityData.currentIndex;
var currentTime = this.continueAbilityData.timeupdatetime;

this.$element('videoOne').pause();
this.$element('videoTwo').pause();
this.$element('videoThree').pause();

this.$element('videoOne').starttime = currentTime;
this.$element('videoTwo').starttime = currentTime;
this.$element('videoThree').starttime = currentTime;

this.switchPlay(currentIndex);
},

//评论事件
showPanel() {
this.$element('simplepanel').show()
},
closePanel() {
this.$element('simplepanel').close()
},
changeValue(e) {
this.continueAbilityData.comment = e.value;
},
enterkeyClick(e) {
this.continueAbilityData.todolist.push({title: this.continueAbilityData.comment});
this.continueAbilityData.comment = "";
},
timeupdateCallback:function(e){ this.continueAbilityData.timeupdatetime = e.currenttime;},
change_start_pause_one: function() {
if(this.continueAbilityData.isStart) {
this.$element('videoOne').pause();
this.continueAbilityData.isStart = false;
} else {
this.$element('videoOne').start();
this.continueAbilityData.isStart = true;
}
},
change_start_pause_two: function() {
if(this.continueAbilityData.isStart) {
this.$element('videoTwo').pause();
this.continueAbilityData.isStart = false;
} else {
this.$element('videoTwo').start();
this.continueAbilityData.isStart = true;
}
},
change_start_pause_three: function() {
if(this.continueAbilityData.isStart) {
this.$element('videoThree').pause();
this.continueAbilityData.isStart = false;
} else {
this.$element('videoThree').start();
this.continueAbilityData.isStart = true;
}
}
}



CSS 代码(重要化妆技术):

.container {
width: 100%;
height: 100%;
flex-direction: column;
}

.img {
object-fit: cover;
background-color: #808080;
}

.swiper {
flex-direction: column;
align-content: center;
align-items: center;
width: 100%;
height: 100%;
background-color: black;
}
.swiperContent {
height: 100%;
justify-content: center;
background-color: black;
}

.btn-footer {
height: 60px;
line-height: 60px;
width: 100%;
background-color: black;
flex-direction: row;
}
.footer-label {
font-size: 16px;
color: white;
padding-top: 0px;
flex-weight: 1;
line-height: 20px;
}
.comment-icon {
width: 32px;
height: 32px;
margin: 8px;
}
.icon {
border-radius: 16px;
}

.panel-div {
flex-direction: column;
align-content: center;
align-items: center;
width: 100%;
height: 100%;
}
.inner-btn {
height: 70px;
padding: 10px;
}
.todo-wrapper {
width: 100%;
height: 100%;
}
.todo-item {
width: 100%;
height: 30px;
padding-left: 10px;
padding-right: 10px;
}
.todo-icon {
width: 16px;
height: 16px;
margin-top: 10px;
margin-right: 10px;
}
.todo-title {
width: 100%;
height: 100%;
text-align: left;
font-size: 14fp;
}


代码就写到此了,不要忘记了 config.json 文件的权限配置哦,在 module 下添加:

"reqPermissions": [
{
"name": "ohos.permission.DISTRIBUTED_DATASYNC"
}
]


总结


说实存的,当看到这个活动时间才几天时,感觉时间不太够用,要滑动视频,要评论功能,要分布式的,加上都是用空闲时间来做的,然而当深入去理解相关组件用法后,发现应该一天时间就可以了。


有兴趣的小伙伴可以下载源码查看,项目代码写得还不算靓仔,很多为了实现效果,都是 Hardcode 的。


有空可以把重复代码抽出来,视频源也可以放到服务器上,然后就可以更愉快的刷更多视频了,源码同步到 gitee 码云,项目素材没有上传,自行添加。

源码在这:

https://gitee.com/army16/qin-hong-jun-video