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

news2024/11/28 4:23:43

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

与传统的影视行业相比,诞生于移动互联网时代的短视频是个全新行业,它制作方便又容易传播,一出现就成为大街小巷的时髦潮流。 各行各业的人们均可通过短视频展示自己,短小精悍的视频片段原来能够容纳如此丰富的内蕴。许多人依靠短视频获得大量关注,乃至成为谋生手段,本人甚至成为网红。 这些都离不开短视频分享平台的推波助澜,尤其是一对欢喜冤家——抖音和快手,它们的使用频率甚至超过了老牌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; // 声明一个视频翻页适配器对象

    @Override
    protected 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; // 只在视频库挑选视频的请求码

    @Override
    protected 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=""; // 耗时描述
            @Override
            protected 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); // 开始播放动画
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        c2v_preview.closeCamera(); // 关闭相机
    }

    @Override
    protected 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; // 当前的播放位置
    @Override
    protected void onResume() {
        super.onResume();
        // 恢复页面时立即从上次断点开始播放视频
        if (!TextUtils.isEmpty(mVideoPath) && mCurrentPosition>0 && !vv_content.isPlaying()) {
            vv_content.seekTo(mCurrentPosition); // 找到指定位置
            vv_content.start(); // 视频视图开始播放
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        // 暂停页面时保存当前的播放进度
        if (!TextUtils.isEmpty(mVideoPath) && vv_content.isPlaying()) { // 视频视图正在播放
            // 获得视频视图当前的播放位置
            mCurrentPosition = vv_content.getCurrentPosition();
            vv_content.pause(); // 视频视图暂停播放
        }
    }

}

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

网络面试-0x12 UDP和TCP的区别以及应用场景

一、 UDP &#xff08;user datagram protocol&#xff09;用户数据报协议 ①&#xff1a; 一种简单的面向数据报的通讯协议&#xff0c;即&#xff1a;应用层传下来的报文&#xff0c;不合并&#xff0c;不拆分&#xff0c;只是在其上面加上首部后就交给了下面的网络层。无论应…

Github优秀项目-使用Python基于CPM文本自动生成

基于CPM模型的中文文本生成项目,可用于作文、小说、新闻、古诗等中文生成任务。 虽然说开源的,但是实际部署应用的过程中很多小伙伴还遇见了不少的问题,这里我用自己的方式部署应用,如果还没有实现该功能的小伙伴可以跟我一起来操作。 如果有不了解的小伙伴先来看一下实验…

【多目标进化优化】多目标进化算法的收敛性

0 前言 \quad\quad对 MOEAMOEAMOEA 收敛性的研究是 MOEAMOEAMOEA 研究的重要内容&#xff0c;但目前这方面的研究结果比较少。 一个 MOEAMOEAMOEA 的收敛性可以从两个方面考虑&#xff1b;一是有限时间内的收敛&#xff1b;二是当时间趋向于无穷大时的收敛。第一类收敛是最理想…

Kotlin高仿微信-第9篇-单聊-文本

Kotlin高仿微信-项目实践58篇详细讲解了各个功能点&#xff0c;包括&#xff1a;注册、登录、主页、单聊(文本、表情、语音、图片、小视频、视频通话、语音通话、红包、转账)、群聊、个人信息、朋友圈、支付服务、扫一扫、搜索好友、添加好友、开通VIP等众多功能。 Kotlin高仿…

麦芽糖-阿奇霉素 maltose-Azithromycin 阿奇霉素-PEG-麦芽糖

麦芽糖-阿奇霉素 maltose-Azithromycin 阿奇霉素-PEG-麦芽糖 中文名称&#xff1a;麦芽糖-阿奇霉素 英文名称&#xff1a;maltose-Azithromycin 别称&#xff1a;阿奇霉素修饰麦芽糖&#xff0c;阿奇霉素-麦芽糖 纯度&#xff1a;95% 存储条件&#xff1a;-20C&#xff0c…

webscoket学习

webscoket基本使用 WebSocket - Web API 接口参考 | MDN 使用node编写webscoket服务 nodejs-webscoket 在github的地址↓ GitHub - sitegui/nodejs-websocket: A node.js module for websocket server and client ws和socket.io 是wbscket的两个库 仓库地址&#xff1a;l…

Scala系列-5、scala中的泛型、actor、akka

版权声明&#xff1a;本文为博主原创文章&#xff0c;遵循 CC 4.0 BY-SA 版权协议&#xff0c;转载请附上原文出处链接和本声明。 传送门&#xff1a;大数据系列文章目录 目录scala的 泛型给方法定义泛型给类定义泛型泛型的上下界泛型中 非变 协变 和 逆变scala中actor相关内…

echarts中tooltip设为渐变色与模糊背景滤镜

关于echarts各项内容&#xff08;包括图表面积区域&#xff09;设为渐变色已在上篇文章里全部阐述&#xff1a; echarts折线图与柱状图等绘成渐变色的方法 单独将tooltip拉出来再写一篇&#xff0c;是因为用formatter配合超文本的形式在echarts的配置项中&#xff0c;于自定义样…

Python使用magic判断文件MIME类型

文章目录官网安装使用判断文件的MIME类型支持中文的代码问题官网 GitHub - ahupp/python-magic: A python wrapper for libmagic 安装 pip install python-magic pip install python-magic-bin使用 判断文件的MIME类型 代码 # encodingutf-8 import magic #pip install pyt…

希尔贝壳受邀参加IEEE自动语音识别与理解研讨会-ASRU 2021

ASRU 2021 IEEE Automatic Speech Recognition and Understanding Workshop&#xff08;2021年IEEE自动语音识别与理解研讨会&#xff0c;以下简称ASRU&#xff09;&#xff0c;将于2021年12月13日至17日在哥伦比亚卡塔赫纳举行。 ASRU 研讨会是IEEE语音和语言处理技术委员会(…

领悟《信号与系统》之 周期信号的傅里叶变换计算

周期信号的傅里叶变换计算一、周期信号的傅里叶变换存在的条件二、周期信号的傅里叶变换例题&#xff1a;一、周期信号的傅里叶变换存在的条件 典型非周期信号&#xff08;如指数信号&#xff0c;矩形信号等&#xff09;都是满足绝对可积&#xff08;或绝对可和&#xff09;条…

IDEA的日常快捷键大全

更多内容在&#xff1a;https://javaxiaobear.gitee.io/ ​​​​​​第1组&#xff1a;通用型 说明 快捷键 复制代码-copy ctrl c 粘贴-paste ctrl v 剪切-cut ctrl x 撤销-undo ctrl z 反撤销-redo ctrl shift z 保存-save all ctrl s 全选-select all …

(2)点云库处理学习——剔除点云值

1、主要参考 1.1参考地址 (1) 点云离群点剔除 — open3d python_Coding的叶子的博客-CSDN博客_离群点去除 (2) open3d之点云异常值去除&#xff08;笔记5&#xff09;_Satellite_H的博客-CSDN博客 (3)斯坦福经典兔子的点云数据下载地址 下载地址 Model : Bunny 1.2兔子…

3D视觉识别案例:3D无序棒料抓取,阀体圆环上下料,电机定子上料

3D无序棒料抓取 某知名汽车行业 项目背景 长春某知名汽车行业&#xff0c;需求3D视觉实现圆形棒材的上料自动化。 作业流程 钢棒依次经过剪切/锯切下料&#xff0c;从深筐中抓取&#xff0c;先放置在V型二次定位平台上&#xff0c;再从平台抓到输送线上&#xff0c;目标工…

[附源码]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…

超算,先进计算未来的研究方向有哪些?

超算是一个非常有趣的方向&#xff0c;当人们仰天长望思索宇宙大爆炸和起源时&#xff0c;超算便成为了非常重要的一个研究途径&#xff0c;利用超算平台对中国FAST天眼射电望远镜捕捉到的海量信号进行分析和处理&#xff0c;帮助中科院探索发现了诸如脉冲星、最大原子气体、快…

如何将报告从 JasperReports 导入到 FastReport .NET?

FastReport.NET官方版下载 我们在 JasperReports 添加了一个新的导入功能&#xff0c;如果要使用它&#xff0c;需要到 FastReport .NET 设计器中的“文件”菜单并选择“打开...”。在出现的窗口中&#xff0c;选择过滤器“JasperReports 文件 (*.jrxml)”。所选文件将自动转换…

基于Apache-DButils以及Druid(德鲁伊)与数据库交互实现的一个项目:满汉楼

基于Apache-DButils以及Druid(德鲁伊)与数据库交互实现的一个项目&#xff1a;满汉楼 每博一文案 张小贤曾说过: 你不过是做自己喜欢做的事&#xff0c;过自己喜欢过的生活。 若有人因为你喜欢做的事而觉得恶心和取笑你&#xff0c;那是他们的事。是呀&#xff0c;生活是苦&am…

蓝海创意云亮相2022南京融交会,打造沉浸式元宇宙互动体验

11月24日&#xff0c;2022中国&#xff08;南京&#xff09;文化和科技融合成果展览交易会在南京国际展览中心正式开幕&#xff0c;展会聚焦文化数字化战略&#xff0c;集中展示文化数据专网、数字化公共文化服务、“元宇宙”等数字文化新基建、新服务、新场景、新体验。 苏州…

第十三章《集合》第3节:Set集合

Set也是Collection的子接口,它定义了另一种形式的集合,专业上称之为Set集合。Set集合的特点如图13-9所示。 图13-9 Set类型集合 从图13-9可以看出:Set类型的集合就像是一个装苹果的筐子,程序员只要把元素存入这个筐子即可。集合中的元素像是胡乱堆积在一起,因此元素没有索…