【Android App】实战项目之仿抖音的短视频分享App(附源码和演示视频 超详细必看)

news/2024/4/20 17:38:14/文章来源:https://blog.csdn.net/jiebaoshayebuhui/article/details/128099591

需要全部代码请点赞关注收藏后评论区留言私信~~~

与传统的影视行业相比,诞生于移动互联网时代的短视频是个全新行业,它制作方便又容易传播,一出现就成为大街小巷的时髦潮流。 各行各业的人们均可通过短视频展示自己,短小精悍的视频片段原来能够容纳如此丰富的内蕴。许多人依靠短视频获得大量关注,乃至成为谋生手段,本人甚至成为网红。 这些都离不开短视频分享平台的推波助澜,尤其是一对欢喜冤家——抖音和快手,它们的使用频率甚至超过了老牌App。其中抖音的国际版TikTok更是风靡全球,它鼓励国外年轻人勇敢表现自我,成为中国企业的出海标杆。

一、需求描述

短视频分享平台需要满足两种角色的使用:

(1)一种是作为内容创作者发布自己的视频,

(2)另一种是作为用户欣赏平台上的已有视频。

还有一些点赞关注弹幕功能这里就不再赘述了 相信大家对短视频平台都不陌生

二、功能分析

短视频分享与传统的图文界面大相径庭,因为单个短视频就已经填满了整块屏幕,势必要求采取专门的方式浏览短视频,从用户界面到后台服务,短视频分享主要集成了以下App技术

(1)二代翻页视图:向上滑动浏览界面能够拉出新视频。

(2)下拉刷新控件:下拉手势会触发刷新动作获取最新发布的视频。

(3)新型播放器:ExoPlayer能够快速响应网络视频。

(4)相机视图:录制短视频需要自定义相机视图。

(5)媒体检索工具:从视频中截取某帧作为视频封面。

(6)循环视图:备选的视频各帧画面从左往右依次排开。

(7)网络通信框架:上传视频信息与获取视频列表均需与后端交互。

(8)移动数据格式JSON:通过JSON结构封装http交互数据。

下面介绍代码模块中各部分之间的关系

(1)ShortViewActivity.java:这是短视频分享的浏览首页。

(2)VideoFragment.java:这是每个短视频的碎片页。

(3)ShortTakeActivity.java:这是视频录制页面。

(4)ShortEditActivity.java:这是视频信息的编辑页面。

(5)ShortCoverActivity.java:这是视频封面的挑选页面。

(6)VideoLoadTask.java:这是视频列表的加载任务。

三、效果展示

在初始界面可以点击录制视频 录制完之后进入编辑页面,可以输入视频的名称 摘要 描述 日期等等信息  发布之后可以自己观看(此处要连接后台服务器IDEA)

 

 

 四、代码

部分代码如下 需要全部代码请点赞关注收藏后评论区留言私信~~~

短视频分享首页代码

package com.example.video;import android.content.Intent;
import android.os.Bundle;
import android.view.WindowManager;import androidx.appcompat.app.AppCompatActivity;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import androidx.viewpager2.widget.ViewPager2;import com.example.video.adapter.VideoPagerAdapter;
import com.example.video.bean.VideoInfo;
import com.example.video.task.VideoLoadTask;import java.util.ArrayList;
import java.util.List;public class ShortViewActivity extends AppCompatActivity {private SwipeRefreshLayout srl_dynamic; // 声明一个下拉刷新布局对象private List<VideoInfo> mVideoList = new ArrayList<>(); // 声明一个地址列表private VideoPagerAdapter mAdapter; // 声明一个视频翻页适配器对象@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_short_view);getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); // 保持屏幕常亮initView(); // 初始化视图startLoad(); // 开始加载短视频}// 初始化视图private void initView() {srl_dynamic = findViewById(R.id.srl_dynamic);ViewPager2 vp2_content = findViewById(R.id.vp2_content);srl_dynamic.setOnRefreshListener(() -> startLoad()); // 设置下拉布局的下拉刷新监听器// 构建一个视频地址的翻页适配器mAdapter = new VideoPagerAdapter(this, mVideoList);vp2_content.setAdapter(mAdapter); // 设置二代翻页视图的适配器findViewById(R.id.iv_add).setOnClickListener(v ->startActivity(new Intent(this, ShortTakeActivity.class)));}// 开始加载最新的短视频private void startLoad() {// 创建一个视频列表加载任务VideoLoadTask task = new VideoLoadTask(this, videoList -> {srl_dynamic.setRefreshing(false); // 结束下拉刷新布局的刷新动作mVideoList.clear();mVideoList.addAll(videoList);mAdapter.notifyDataSetChanged(); // 通知适配器数据发生变化});task.start(); // 启动短视频列表加载任务}}

短视频任务类代码

package com.example.video;import android.content.Intent;
import android.hardware.camera2.CameraCharacteristics;
import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.Transformation;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.VideoView;import androidx.appcompat.app.AppCompatActivity;import com.example.video.util.MediaUtil;
import com.example.video.util.Utils;
import com.example.video.widget.ArcView;
import com.example.video.widget.Camera2View;public class ShortTakeActivity extends AppCompatActivity {private final static String TAG = "ShortTakeActivity";private RelativeLayout rl_preview; // 声明一个相对布局对象private VideoView vv_content; // 声明一个视频视图对象private RelativeLayout rl_record; // 声明一个相对布局对象private Camera2View c2v_preview; // 声明一个二代相机视图对象private TextView tv_cost; // 声明一个文本视图对象private ImageView iv_record; // 声明一个图像视图对象private ArcView av_progress; // 声明一个圆弧视图对象private LinearLayout ll_next; // 声明一个线性布局对象private int mCameraType = CameraCharacteristics.LENS_FACING_BACK; // 默认使用后置摄像头拍照private boolean isRecording = false; // 是否正在录像private int MAX_RECORD_TIME = 15; // 最大录制时长,默认15秒private String mVideoPath; // 待上传的视频文件路径private int CHOOSE_CODE = 3; // 只在视频库挑选视频的请求码@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_short_take);initView(); // 初始化视图initCamera(); // 初始化相机}// 初始化视图private void initView() {findViewById(R.id.iv_back).setOnClickListener(v -> finish());rl_preview = findViewById(R.id.rl_preview);c2v_preview = findViewById(R.id.c2v_preview);vv_content = findViewById(R.id.vv_content);rl_record = findViewById(R.id.rl_record);tv_cost = findViewById(R.id.tv_cost);iv_record = findViewById(R.id.iv_record);av_progress = findViewById(R.id.av_progress);iv_record.setOnClickListener((v) -> dealRecord()); // 处理录像动作findViewById(R.id.btn_album).setOnClickListener(v -> {// 创建一个内容获取动作的意图(准备跳到系统视频库)Intent intent = new Intent(Intent.ACTION_GET_CONTENT);intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, false); // 是否允许多选intent.setType("video/*"); // 类型为视频startActivityForResult(intent, CHOOSE_CODE); // 打开系统视频库});findViewById(R.id.iv_switch).setOnClickListener((v) -> {mCameraType = (mCameraType == CameraCharacteristics.LENS_FACING_BACK) ?CameraCharacteristics.LENS_FACING_FRONT : CameraCharacteristics.LENS_FACING_BACK;c2v_preview.switchCamera(mCameraType); // 切换摄像头});ll_next = findViewById(R.id.ll_next);findViewById(R.id.btn_cancel).setOnClickListener(v -> finish());findViewById(R.id.btn_next).setOnClickListener((v) -> {// 下面跳到视频编辑界面Intent intent = new Intent(this, ShortEditActivity.class);intent.putExtra("video_path", mVideoPath);startActivity(intent);});}// 处理录像动作private void dealRecord() {if (!isRecording) {iv_record.setImageResource(R.drawable.short_stop);c2v_preview.startRecord(MAX_RECORD_TIME, false); // 开始录像startRecordAnim(); // 播放录像动画isRecording = !isRecording;} else {rl_record.setVisibility(View.GONE);c2v_preview.stopRecord(); // 停止录像}}// 初始化相机private void initCamera() {// 初始化二代相机,并指定准备就绪监听器和停止录像监听器c2v_preview.initCamera(mCameraType, (size) -> {int adjustHeight = Utils.getScreenWidth(this) * size.getWidth() / size.getHeight();RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) c2v_preview.getLayoutParams();params.height = adjustHeight; // 根据预览尺寸调整预览窗口的高度c2v_preview.setLayoutParams(params); // 设置预览视图的布局参数}, (result) -> {runOnUiThread(() -> {isRecording = false;Toast.makeText(this, result, Toast.LENGTH_SHORT).show();mVideoPath = c2v_preview.getVideoPath();playVideo(); // 播放视频});});}// 播放视频private void playVideo() {c2v_preview.closeCamera(); // 关闭相机rl_preview.setVisibility(View.GONE);rl_record.setVisibility(View.GONE);vv_content.setVisibility(View.VISIBLE);vv_content.setVideoURI(Uri.parse(mVideoPath)); // 设置视频视图的视频路径// 设置视频视图的就绪监听器,在播放源准备完成后,命令媒体播放器循环播放视频vv_content.setOnPreparedListener(mp -> mp.setLooping(true));vv_content.start(); // 视频视图开始播放ll_next.setVisibility(View.VISIBLE);}// 播放录像动画private void startRecordAnim() {tv_cost.setVisibility(View.VISIBLE);av_progress.setVisibility(View.VISIBLE);// 定义一个圆弧渐进动画Animation animation = new Animation() {private String costDesc=""; // 耗时描述@Overrideprotected void applyTransformation(float interpolatedTime, Transformation t) {String cost = String.format("%.1f秒", MAX_RECORD_TIME*interpolatedTime);if (!costDesc.equals(cost)) { // 秒数发生变化costDesc = cost;tv_cost.setText(costDesc);av_progress.setAngle((int) (360*interpolatedTime)); // 设置圆弧的角度}}};animation.setDuration(MAX_RECORD_TIME*1000); // 设置动画的持续时间tv_cost.startAnimation(animation); // 开始播放动画}@Overrideprotected void onDestroy() {super.onDestroy();c2v_preview.closeCamera(); // 关闭相机}@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent intent) {super.onActivityResult(requestCode, resultCode, intent);if (resultCode==RESULT_OK && requestCode==CHOOSE_CODE) { // 从视频库回来if (intent.getData() != null) { // 选择一个视频Uri uri = intent.getData(); // 获得已选择视频的路径对象// 把指定Uri的视频复制一份到内部存储空间,并返回存储路径mVideoPath = MediaUtil.copyVideoFromUri(this, uri);playVideo(); // 播放视频}}}private int mCurrentPosition = 0; // 当前的播放位置@Overrideprotected void onResume() {super.onResume();// 恢复页面时立即从上次断点开始播放视频if (!TextUtils.isEmpty(mVideoPath) && mCurrentPosition>0 && !vv_content.isPlaying()) {vv_content.seekTo(mCurrentPosition); // 找到指定位置vv_content.start(); // 视频视图开始播放}}@Overrideprotected void onPause() {super.onPause();// 暂停页面时保存当前的播放进度if (!TextUtils.isEmpty(mVideoPath) && vv_content.isPlaying()) { // 视频视图正在播放// 获得视频视图当前的播放位置mCurrentPosition = vv_content.getCurrentPosition();vv_content.pause(); // 视频视图暂停播放}}}

创作不易 觉得有帮助请点赞关注收藏~~~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_38648.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

社区系统项目复盘-6

文章目录什么是Elasticsearch&#xff1f;Spring是怎么整合Elasticsearch的&#xff1f;开发社区搜索功能Elasticsearch实现全文搜索功能什么是Elasticsearch&#xff1f; Elasticsearch简介 一个分布式的、Restful风格的搜索引擎支持对各种类型的数据的检索搜索速度快&#xf…

基于粒子群算法和遗传算法优化的高速列车横向悬挂

目录 前言 1.高速列车模型 2.优化算法优化模糊PID流程 3.普通PID、优化算法模糊PID仿真对比 3.1 模糊控制器设计 3.2 仿真结果 3.2.1粒子群优化PID 3.2.2粒子群优化模糊PID 3.2.3遗传算法优化模糊PID 4.总结 前言 高速列车&#xff0c;是指最高行驶速度在200km/h 及以…

小知识· Zigbee 简介

1. 介绍 ZigBee是一种近距离、低复杂度、低功耗、低速率、低成本的双向无线通讯技术 ZigBee建立在IEEE 802.15.4标准&#xff08;定义了PHY和MAC层&#xff09;之上&#xff0c;ZigBee联盟对其网络层和应用层进行了标准化 ZigBee协议栈可分为五层 - 物理层&#xff08;PHY&a…

多进程并发服务器

TCP三次握手建立连接错误处理模块&#xff1a;wrap.c,函数声明&#xff1a;wrap.h并发服务器模型&#xff08;多进程&#xff0c;多线程&#xff09; 转换大小写程序 服务端 #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #incl…

Mybatis Plus 多租户id使用

本文就不多逼逼&#xff0c;直接进入正题。 什么是多租户 多租户技术&#xff08;Multi-TenancyTechnology&#xff09;又称多重租赁技术&#xff0c;简称SaaS&#xff0c;是一种软件架构技术&#xff0c;是实现如何在多用户环境下 &#xff08;此处的多用户一般是面向企业用…

Java SPI机制的使用和理解

前言&#xff1a; SPI(Service Provider Interface)&#xff0c;是JDK内置的一种服务提供发现机制&#xff0c;Java中 SPI 机制主要思想是将装配的控制权移到程序之外&#xff0c;在模块化设计中这个机制尤其重要&#xff0c;其核心思想就是解耦 1、大家都知道API&#xff0c;却…

【C++】STL —— map和set的模拟实现

目录 一、基础铺垫 二、基本结构分析 1. 节点结构分析 2. 模板参数中仿函数分析 三、正向迭代器 四、封装完成的红黑树 五、map的模拟实现 六、set的模拟实现 一、基础铺垫 在前面的博客中我们了解了map和set的基本使用&#xff0c;以及对二叉搜索树、AVL树和红黑树的…

IPv6进阶:IPv6 过渡技术之 NAT64(IPv6 节点主动访问 IPv4 节点-地址池方式)

实验拓扑 PC1是IPv4网络的一个节点&#xff0c;处于Trust安全域&#xff1b;PC2是IPv6网络的一个节点&#xff0c;处于Untrust安全域。 实验需求 完成防火墙IPv4、IPv6接口的配置&#xff0c;并将接口添加到相应的安全域&#xff1b;在防火墙上配置NAT64的IPv6前缀3001::/64&…

【毕业设计】30-基于单片机矿井瓦斯_气体浓度_烟雾浓度报警设计(原理图+源代码+仿真+答辩论文+答辩PPT)

【毕业设计】30-基于单片机矿井瓦斯/气体浓度/烟雾浓度报警设计&#xff08;原理图源代码仿真答辩论文答辩PPT&#xff09; 文章目录【毕业设计】30-基于单片机矿井瓦斯/气体浓度/烟雾浓度报警设计&#xff08;原理图源代码仿真答辩论文答辩PPT&#xff09;任务书设计说明书摘要…

网络套接字编程(UDP协议)

文章目录预备知识socket&#xff08;网络套接字&#xff09;编程接口简单的UDP网络程序增加多用户可以互相通信预备知识 网络字节序 大端存储&#xff1a;数据的高字节内容保存在内存的低地址处&#xff0c;数据的低字节内容保存在内存的高地址处 小端存储&#xff1a;数据的高…

global关键字、python实现ATM简单功能

目录 一.局部变量、全局变量 二.global关键字 演示 三.编写ATM程序 要求 详细步骤 存在问题 改进 完整代码 一.局部变量、全局变量 1.什么是局部变量 作用范围在函数内部&#xff0c;在函数外部无法使用 2.什么是全局变量 在函数内部和外部均可使用 3.如何将函数内定…

[附源码]SSM计算机毕业设计校园自行车租售管理系统JAVA

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

高等数学(第七版)同济大学 习题10-3 (前9题)个人解答

高等数学&#xff08;第七版&#xff09;同济大学 习题10-3&#xff08;前9题&#xff09; 函数作图软件&#xff1a;Mathematica 1.化三重积分I∭Ωf(x,y,z)dxdydz为三次积分&#xff0c;其中积分区域Ω分别是\begin{aligned}&1. \ 化三重积分I\iiint_{\Omega}f(x, \ y, …

【C++】类型转换

目录 一、C语言风格类型转换 二、C风格类型转换 1.static_case 2.reinterpret_case 3、const_case 4、dynamic_case 三、RTTI 总结 一、C语言风格类型转换 在C语言中&#xff0c;如果赋值运算符左右两侧类型不同&#xff0c;或者形参与实参类型不匹配&#xff0c;或者返…

【正点原子FPGA连载】 第二十章 LCD触摸屏实验摘自【正点原子】DFZU2EG/4EV MPSoC 之FPGA开发指南V1.0

1&#xff09;实验平台&#xff1a;正点原子MPSoC开发板 2&#xff09;平台购买地址&#xff1a;https://detail.tmall.com/item.htm?id692450874670 3&#xff09;全套实验源码手册视频下载地址&#xff1a; http://www.openedv.com/thread-340252-1-1.html 第二十章 LCD触摸…

Vue.js 加入高德地图的实现方法

一、功能需求 1.根据输入内容进行模糊查询&#xff0c;选择地址后在地图上插上标记&#xff0c;并更新经纬度坐标显示 2.在地图点击后&#xff0c;根据回传的左边更新地址信息和坐标显示 二、准备 1.申请高德地图账号&#xff0c;创建应用 2.在应用管理中 获得key 和安全密…

[附源码]Python计算机毕业设计Django常见Web漏洞对应POC应用系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

Python学习:json对象与string相互转换教程

首先要明确&#xff0c;python里有json这个库&#xff0c;但并没有json这个类&#xff0c;所以所谓的json对象本质上就是一个dict&#xff1b;而json这个库&#xff0c;用于实现dict到string、string到dict的互转。 更具体一点&#xff0c;json对象&#xff08;dict&#xff0…

Linux网络编程——IO多路复用

文章目录1&#xff0c;I/O模型2&#xff0c;阻塞I/O 模式2.1&#xff0c;读阻塞&#xff08;以read函数为例&#xff09;2.2&#xff0c;写阻塞3&#xff0c;非阻塞I/O模式3.1&#xff0c;非阻塞I/O模式的实现&#xff08;fcntl()函数、ioctl() 函数&#xff09;3.1.1&#xff…

leetcode 343. 整数拆分(动态规划)

题目链接&#xff1a;343. 整数拆分 动态规划 (1) 确定 dpdpdp 数组下标含义&#xff1a; dp[i]dp[i]dp[i]: 将 iii 拆分为至少两个正整数之后的最大乘积&#xff1b; (2) 确定递推公式&#xff1a; 当 i≥2i \ge 2i≥2 时, 设 jjj 是 iii 拆分出来的第一个正整数&#xff0c…