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

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

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

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

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

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

四、代码

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

短视频分享首页代码

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<>(); // &#x58F0;&#x660E;&#x4E00;&#x4E2A;&#x5730;&#x5740;&#x5217;&#x8868;
    private VideoPagerAdapter mAdapter; // &#x58F0;&#x660E;&#x4E00;&#x4E2A;&#x89C6;&#x9891;&#x7FFB;&#x9875;&#x9002;&#x914D;&#x5668;&#x5BF9;&#x8C61;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_short_view);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); // &#x4FDD;&#x6301;&#x5C4F;&#x5E55;&#x5E38;&#x4EAE;
        initView(); // &#x521D;&#x59CB;&#x5316;&#x89C6;&#x56FE;
        startLoad(); // &#x5F00;&#x59CB;&#x52A0;&#x8F7D;&#x77ED;&#x89C6;&#x9891;
    }

    // &#x521D;&#x59CB;&#x5316;&#x89C6;&#x56FE;
    private void initView() {
        srl_dynamic = findViewById(R.id.srl_dynamic);
        ViewPager2 vp2_content = findViewById(R.id.vp2_content);
        srl_dynamic.setOnRefreshListener(() -> startLoad()); // &#x8BBE;&#x7F6E;&#x4E0B;&#x62C9;&#x5E03;&#x5C40;&#x7684;&#x4E0B;&#x62C9;&#x5237;&#x65B0;&#x76D1;&#x542C;&#x5668;
        // &#x6784;&#x5EFA;&#x4E00;&#x4E2A;&#x89C6;&#x9891;&#x5730;&#x5740;&#x7684;&#x7FFB;&#x9875;&#x9002;&#x914D;&#x5668;
        mAdapter = new VideoPagerAdapter(this, mVideoList);
        vp2_content.setAdapter(mAdapter); // &#x8BBE;&#x7F6E;&#x4E8C;&#x4EE3;&#x7FFB;&#x9875;&#x89C6;&#x56FE;&#x7684;&#x9002;&#x914D;&#x5668;
        findViewById(R.id.iv_add).setOnClickListener(v ->
                startActivity(new Intent(this, ShortTakeActivity.class)));
    }

    // &#x5F00;&#x59CB;&#x52A0;&#x8F7D;&#x6700;&#x65B0;&#x7684;&#x77ED;&#x89C6;&#x9891;
    private void startLoad() {
        // &#x521B;&#x5EFA;&#x4E00;&#x4E2A;&#x89C6;&#x9891;&#x5217;&#x8868;&#x52A0;&#x8F7D;&#x4EFB;&#x52A1;
        VideoLoadTask task = new VideoLoadTask(this, videoList -> {
            srl_dynamic.setRefreshing(false); // &#x7ED3;&#x675F;&#x4E0B;&#x62C9;&#x5237;&#x65B0;&#x5E03;&#x5C40;&#x7684;&#x5237;&#x65B0;&#x52A8;&#x4F5C;
            mVideoList.clear();
            mVideoList.addAll(videoList);
            mAdapter.notifyDataSetChanged(); // &#x901A;&#x77E5;&#x9002;&#x914D;&#x5668;&#x6570;&#x636E;&#x53D1;&#x751F;&#x53D8;&#x5316;
        });
        task.start(); // &#x542F;&#x52A8;&#x77ED;&#x89C6;&#x9891;&#x5217;&#x8868;&#x52A0;&#x8F7D;&#x4EFB;&#x52A1;
    }

}</videoinfo>

短视频任务类代码

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; // &#x58F0;&#x660E;&#x4E00;&#x4E2A;&#x76F8;&#x5BF9;&#x5E03;&#x5C40;&#x5BF9;&#x8C61;
    private VideoView vv_content; // &#x58F0;&#x660E;&#x4E00;&#x4E2A;&#x89C6;&#x9891;&#x89C6;&#x56FE;&#x5BF9;&#x8C61;
    private RelativeLayout rl_record; // &#x58F0;&#x660E;&#x4E00;&#x4E2A;&#x76F8;&#x5BF9;&#x5E03;&#x5C40;&#x5BF9;&#x8C61;
    private Camera2View c2v_preview; // &#x58F0;&#x660E;&#x4E00;&#x4E2A;&#x4E8C;&#x4EE3;&#x76F8;&#x673A;&#x89C6;&#x56FE;&#x5BF9;&#x8C61;
    private TextView tv_cost; // &#x58F0;&#x660E;&#x4E00;&#x4E2A;&#x6587;&#x672C;&#x89C6;&#x56FE;&#x5BF9;&#x8C61;
    private ImageView iv_record; // &#x58F0;&#x660E;&#x4E00;&#x4E2A;&#x56FE;&#x50CF;&#x89C6;&#x56FE;&#x5BF9;&#x8C61;
    private ArcView av_progress; // &#x58F0;&#x660E;&#x4E00;&#x4E2A;&#x5706;&#x5F27;&#x89C6;&#x56FE;&#x5BF9;&#x8C61;
    private LinearLayout ll_next; // &#x58F0;&#x660E;&#x4E00;&#x4E2A;&#x7EBF;&#x6027;&#x5E03;&#x5C40;&#x5BF9;&#x8C61;
    private int mCameraType = CameraCharacteristics.LENS_FACING_BACK; // &#x9ED8;&#x8BA4;&#x4F7F;&#x7528;&#x540E;&#x7F6E;&#x6444;&#x50CF;&#x5934;&#x62CD;&#x7167;
    private boolean isRecording = false; // &#x662F;&#x5426;&#x6B63;&#x5728;&#x5F55;&#x50CF;
    private int MAX_RECORD_TIME = 15; // &#x6700;&#x5927;&#x5F55;&#x5236;&#x65F6;&#x957F;&#xFF0C;&#x9ED8;&#x8BA4;15&#x79D2;
    private String mVideoPath; // &#x5F85;&#x4E0A;&#x4F20;&#x7684;&#x89C6;&#x9891;&#x6587;&#x4EF6;&#x8DEF;&#x5F84;
    private int CHOOSE_CODE = 3; // &#x53EA;&#x5728;&#x89C6;&#x9891;&#x5E93;&#x6311;&#x9009;&#x89C6;&#x9891;&#x7684;&#x8BF7;&#x6C42;&#x7801;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_short_take);
        initView(); // &#x521D;&#x59CB;&#x5316;&#x89C6;&#x56FE;
        initCamera(); // &#x521D;&#x59CB;&#x5316;&#x76F8;&#x673A;
    }

    // &#x521D;&#x59CB;&#x5316;&#x89C6;&#x56FE;
    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()); // &#x5904;&#x7406;&#x5F55;&#x50CF;&#x52A8;&#x4F5C;
        findViewById(R.id.btn_album).setOnClickListener(v -> {
            // &#x521B;&#x5EFA;&#x4E00;&#x4E2A;&#x5185;&#x5BB9;&#x83B7;&#x53D6;&#x52A8;&#x4F5C;&#x7684;&#x610F;&#x56FE;&#xFF08;&#x51C6;&#x5907;&#x8DF3;&#x5230;&#x7CFB;&#x7EDF;&#x89C6;&#x9891;&#x5E93;&#xFF09;
            Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
            intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, false); // &#x662F;&#x5426;&#x5141;&#x8BB8;&#x591A;&#x9009;
            intent.setType("video/*"); // &#x7C7B;&#x578B;&#x4E3A;&#x89C6;&#x9891;
            startActivityForResult(intent, CHOOSE_CODE); // &#x6253;&#x5F00;&#x7CFB;&#x7EDF;&#x89C6;&#x9891;&#x5E93;
        });
        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); // &#x5207;&#x6362;&#x6444;&#x50CF;&#x5934;
        });
        ll_next = findViewById(R.id.ll_next);
        findViewById(R.id.btn_cancel).setOnClickListener(v -> finish());
        findViewById(R.id.btn_next).setOnClickListener((v) -> {
            // &#x4E0B;&#x9762;&#x8DF3;&#x5230;&#x89C6;&#x9891;&#x7F16;&#x8F91;&#x754C;&#x9762;
            Intent intent = new Intent(this, ShortEditActivity.class);
            intent.putExtra("video_path", mVideoPath);
            startActivity(intent);
        });
    }

    // &#x5904;&#x7406;&#x5F55;&#x50CF;&#x52A8;&#x4F5C;
    private void dealRecord() {
        if (!isRecording) {
            iv_record.setImageResource(R.drawable.short_stop);
            c2v_preview.startRecord(MAX_RECORD_TIME, false); // &#x5F00;&#x59CB;&#x5F55;&#x50CF;
            startRecordAnim(); // &#x64AD;&#x653E;&#x5F55;&#x50CF;&#x52A8;&#x753B;
            isRecording = !isRecording;
        } else {
            rl_record.setVisibility(View.GONE);
            c2v_preview.stopRecord(); // &#x505C;&#x6B62;&#x5F55;&#x50CF;
        }
    }

    // &#x521D;&#x59CB;&#x5316;&#x76F8;&#x673A;
    private void initCamera() {
        // &#x521D;&#x59CB;&#x5316;&#x4E8C;&#x4EE3;&#x76F8;&#x673A;&#xFF0C;&#x5E76;&#x6307;&#x5B9A;&#x51C6;&#x5907;&#x5C31;&#x7EEA;&#x76D1;&#x542C;&#x5668;&#x548C;&#x505C;&#x6B62;&#x5F55;&#x50CF;&#x76D1;&#x542C;&#x5668;
        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; // &#x6839;&#x636E;&#x9884;&#x89C8;&#x5C3A;&#x5BF8;&#x8C03;&#x6574;&#x9884;&#x89C8;&#x7A97;&#x53E3;&#x7684;&#x9AD8;&#x5EA6;
            c2v_preview.setLayoutParams(params); // &#x8BBE;&#x7F6E;&#x9884;&#x89C8;&#x89C6;&#x56FE;&#x7684;&#x5E03;&#x5C40;&#x53C2;&#x6570;
        }, (result) -> {
            runOnUiThread(() -> {
                isRecording = false;
                Toast.makeText(this, result, Toast.LENGTH_SHORT).show();
                mVideoPath = c2v_preview.getVideoPath();
                playVideo(); // &#x64AD;&#x653E;&#x89C6;&#x9891;
            });
        });
    }

    // &#x64AD;&#x653E;&#x89C6;&#x9891;
    private void playVideo() {
        c2v_preview.closeCamera(); // &#x5173;&#x95ED;&#x76F8;&#x673A;
        rl_preview.setVisibility(View.GONE);
        rl_record.setVisibility(View.GONE);
        vv_content.setVisibility(View.VISIBLE);
        vv_content.setVideoURI(Uri.parse(mVideoPath)); // &#x8BBE;&#x7F6E;&#x89C6;&#x9891;&#x89C6;&#x56FE;&#x7684;&#x89C6;&#x9891;&#x8DEF;&#x5F84;
        // &#x8BBE;&#x7F6E;&#x89C6;&#x9891;&#x89C6;&#x56FE;&#x7684;&#x5C31;&#x7EEA;&#x76D1;&#x542C;&#x5668;&#xFF0C;&#x5728;&#x64AD;&#x653E;&#x6E90;&#x51C6;&#x5907;&#x5B8C;&#x6210;&#x540E;&#xFF0C;&#x547D;&#x4EE4;&#x5A92;&#x4F53;&#x64AD;&#x653E;&#x5668;&#x5FAA;&#x73AF;&#x64AD;&#x653E;&#x89C6;&#x9891;
        vv_content.setOnPreparedListener(mp -> mp.setLooping(true));
        vv_content.start(); // &#x89C6;&#x9891;&#x89C6;&#x56FE;&#x5F00;&#x59CB;&#x64AD;&#x653E;
        ll_next.setVisibility(View.VISIBLE);
    }

    // &#x64AD;&#x653E;&#x5F55;&#x50CF;&#x52A8;&#x753B;
    private void startRecordAnim() {
        tv_cost.setVisibility(View.VISIBLE);
        av_progress.setVisibility(View.VISIBLE);
        // &#x5B9A;&#x4E49;&#x4E00;&#x4E2A;&#x5706;&#x5F27;&#x6E10;&#x8FDB;&#x52A8;&#x753B;
        Animation animation = new Animation() {
            private String costDesc=""; // &#x8017;&#x65F6;&#x63CF;&#x8FF0;
            @Override
            protected void applyTransformation(float interpolatedTime, Transformation t) {
                String cost = String.format("%.1f&#x79D2;", MAX_RECORD_TIME*interpolatedTime);
                if (!costDesc.equals(cost)) { // &#x79D2;&#x6570;&#x53D1;&#x751F;&#x53D8;&#x5316;
                    costDesc = cost;
                    tv_cost.setText(costDesc);
                    av_progress.setAngle((int) (360*interpolatedTime)); // &#x8BBE;&#x7F6E;&#x5706;&#x5F27;&#x7684;&#x89D2;&#x5EA6;
                }
            }
        };
        animation.setDuration(MAX_RECORD_TIME*1000); // &#x8BBE;&#x7F6E;&#x52A8;&#x753B;&#x7684;&#x6301;&#x7EED;&#x65F6;&#x95F4;
        tv_cost.startAnimation(animation); // &#x5F00;&#x59CB;&#x64AD;&#x653E;&#x52A8;&#x753B;
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        c2v_preview.closeCamera(); // &#x5173;&#x95ED;&#x76F8;&#x673A;
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
        super.onActivityResult(requestCode, resultCode, intent);
        if (resultCode==RESULT_OK && requestCode==CHOOSE_CODE) { // &#x4ECE;&#x89C6;&#x9891;&#x5E93;&#x56DE;&#x6765;
            if (intent.getData() != null) { // &#x9009;&#x62E9;&#x4E00;&#x4E2A;&#x89C6;&#x9891;
                Uri uri = intent.getData(); // &#x83B7;&#x5F97;&#x5DF2;&#x9009;&#x62E9;&#x89C6;&#x9891;&#x7684;&#x8DEF;&#x5F84;&#x5BF9;&#x8C61;
                // &#x628A;&#x6307;&#x5B9A;Uri&#x7684;&#x89C6;&#x9891;&#x590D;&#x5236;&#x4E00;&#x4EFD;&#x5230;&#x5185;&#x90E8;&#x5B58;&#x50A8;&#x7A7A;&#x95F4;&#xFF0C;&#x5E76;&#x8FD4;&#x56DE;&#x5B58;&#x50A8;&#x8DEF;&#x5F84;
                mVideoPath = MediaUtil.copyVideoFromUri(this, uri);
                playVideo(); // &#x64AD;&#x653E;&#x89C6;&#x9891;
            }
        }
    }

    private int mCurrentPosition = 0; // &#x5F53;&#x524D;&#x7684;&#x64AD;&#x653E;&#x4F4D;&#x7F6E;
    @Override
    protected void onResume() {
        super.onResume();
        // &#x6062;&#x590D;&#x9875;&#x9762;&#x65F6;&#x7ACB;&#x5373;&#x4ECE;&#x4E0A;&#x6B21;&#x65AD;&#x70B9;&#x5F00;&#x59CB;&#x64AD;&#x653E;&#x89C6;&#x9891;
        if (!TextUtils.isEmpty(mVideoPath) && mCurrentPosition>0 && !vv_content.isPlaying()) {
            vv_content.seekTo(mCurrentPosition); // &#x627E;&#x5230;&#x6307;&#x5B9A;&#x4F4D;&#x7F6E;
            vv_content.start(); // &#x89C6;&#x9891;&#x89C6;&#x56FE;&#x5F00;&#x59CB;&#x64AD;&#x653E;
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        // &#x6682;&#x505C;&#x9875;&#x9762;&#x65F6;&#x4FDD;&#x5B58;&#x5F53;&#x524D;&#x7684;&#x64AD;&#x653E;&#x8FDB;&#x5EA6;
        if (!TextUtils.isEmpty(mVideoPath) && vv_content.isPlaying()) { // &#x89C6;&#x9891;&#x89C6;&#x56FE;&#x6B63;&#x5728;&#x64AD;&#x653E;
            // &#x83B7;&#x5F97;&#x89C6;&#x9891;&#x89C6;&#x56FE;&#x5F53;&#x524D;&#x7684;&#x64AD;&#x653E;&#x4F4D;&#x7F6E;
            mCurrentPosition = vv_content.getCurrentPosition();
            vv_content.pause(); // &#x89C6;&#x9891;&#x89C6;&#x56FE;&#x6682;&#x505C;&#x64AD;&#x653E;
        }
    }

}

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

Original: https://blog.csdn.net/jiebaoshayebuhui/article/details/128099591
Author: showswoller
Title: 【Android App】实战项目之仿抖音的短视频分享App(附源码和演示视频 超详细必看)

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/794802/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球