ViewPager2 +Fragment 模仿抖音短视频
项目demo: https://download.csdn.net/download/BirdEatBug/19033060 无需积分
一、工程所依赖的库
1、播放器:IjkPlayer(哔哩哔哩开源库)、2、点赞库 :heartLirary、3、EventBus(用于视频资源回收调用)、4、弹窗:-》'com.kongzue.dialog_v3x:dialog:3.2.4' (视频加载等待)、5、下拉上拉库SmallRefreshLayout 6、so库 使用GsyVideoPlayer so 库 api 'com.github.CarGuo.GSYVideoPlayer:gsyVideoPlayer-ex_so:v8.1.3-jitpack'
目录结构
heartlibraary (子依赖)-->vpplayer、
kgplayer (子依赖)-->vpplayer、
二、主界面
1、activity_list_video.xml activity主界面
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:id="@+id/clContainer"android:layout_width="match_parent"android:layout_height="match_parent"android:background="#616161"tools:ignore="MissingDefaultResource"><Viewandroid:id="@+id/viewTopBg"android:layout_width="match_parent"android:layout_height="165dp"android:background="@drawable/gradient_gray_down"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent" /><com.scwang.smart.refresh.layout.SmartRefreshLayoutandroid:id="@+id/refreshView"android:layout_width="0dp"android:layout_height="0dp"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent"><androidx.constraintlayout.widget.ConstraintLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"><androidx.viewpager2.widget.ViewPager2android:id="@+id/viewPager"android:layout_width="match_parent"android:layout_height="0dp"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" /></androidx.constraintlayout.widget.ConstraintLayout></com.scwang.smart.refresh.layout.SmartRefreshLayout></androidx.constraintlayout.widget.ConstraintLayout>
2、主Activity
package com.qiqilego.vpplayerimport android.os.Handler
import android.view.View
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2
import com.qiqilego.vpplayer.adapter.VideoListPagerAdapter
import com.qiqilego.vpplayer.event.VideoIndexEvent
import com.qiqilego.vpplayer.fragment.VideoPlayForActivityFragment
import com.scwang.smart.refresh.footer.ClassicsFooter
import com.scwang.smart.refresh.header.MaterialHeader
import kotlinx.android.synthetic.main.activity_list_video.*class VideoListPlayerActivity : BaseActivity() {companion object {const val TEST_URL = "http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"}override fun getLayoutId() = R.layout.activity_list_videooverride fun setTitle() = ""private val handler = Handler()private lateinit var adapter: VideoListPagerAdapteroverride fun initView() {//初始化ViewPageradapter = VideoListPagerAdapter(this)viewPager.adapter = adapterviewPager.orientation = ViewPager2.ORIENTATION_VERTICAL//去除左右阴影if (viewPager.getChildAt(0) is RecyclerView) {viewPager.getChildAt(0).overScrollMode = View.OVER_SCROLL_NEVER}viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {override fun onPageSelected(position: Int) {super.onPageSelected(position)//发送EventBushandler.removeCallbacksAndMessages(null)handler.postDelayed({eventBus.post(VideoIndexEvent(position))}, 500)}})refreshView.setRefreshHeader(MaterialHeader(this).apply {//设置强调颜色setProgressBackgroundColorSchemeColor(resources.getColor(R.color.white))})refreshView.setRefreshFooter(ClassicsFooter(this).apply {//设置强调颜色setAccentColorId(R.color.white)//设置主题颜色setPrimaryColorId(R.color._000000)//设置刷新完成显示的停留时间setFinishDuration(0)})refreshView.setOnRefreshListener {val list = mutableListOf<VideoPlayForActivityFragment>()for (index in 0..20) {list.add(VideoPlayForActivityFragment(TEST_URL, index))}adapter.addFirstData(list as MutableList<Fragment>)refreshView.finishRefresh(500)}refreshView.setOnLoadMoreListener {val list = mutableListOf<VideoPlayForActivityFragment>()for (index in 0..10) {list.add(VideoPlayForActivityFragment(TEST_URL, index))}adapter.addData(list as MutableList<Fragment>)refreshView.finishLoadMore(500)}}override fun initDate() {refreshView.autoRefresh()}}
3、ViewPager2适配器
package com.qiqilego.vpplayer.adapterimport androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.viewpager2.adapter.FragmentStateAdapter
import com.qiqilego.vpplayer.VideoListPlayerActivity
import com.qiqilego.vpplayer.fragment.VideoPlayForActivityFragment
import kotlinx.android.synthetic.main.activity_list_video.*class VideoListPagerAdapter(private val activity: AppCompatActivity) : FragmentStateAdapter(activity) {private val dataList = mutableListOf<Fragment>()fun addData(newData: MutableList<Fragment>) {if (newData.isNotEmpty()) {val oldSize = dataList.size//安全验证 》=50条 删除之前缓存数据if (oldSize + newData.size >= 50) {newData.add(0,VideoPlayForActivityFragment((dataList.last() as VideoPlayForActivityFragment).videoUrl,-1))dataList.clear()dataList.addAll(newData)//重新赋值下标dataList.forEachIndexed { index, it ->if (it is VideoPlayForActivityFragment) {it.position = index}}notifyDataSetChanged()if (activity is VideoListPlayerActivity) {activity.viewPager.setCurrentItem(0, false)}} else {dataList.addAll(newData)dataList.forEachIndexed { index, it ->if (it is VideoPlayForActivityFragment) {it.position = index}}notifyItemRangeInserted(oldSize, dataList.size)}}}fun addFirstData(newData: MutableList<Fragment>) {if (newData.isNotEmpty()) {dataList.clear()dataList.addAll(newData)dataList.forEachIndexed { index, it ->if (it is VideoPlayForActivityFragment) {it.position = index}}notifyDataSetChanged()}}fun getData() = dataListoverride fun getItemCount() = dataList.sizeoverride fun createFragment(position: Int): Fragment {return dataList[position]}override fun getItemId(position: Int): Long {return dataList[position].hashCode().toLong()}
}
三、视频播放详情界面
1、视频详情xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:background="#000000"><com.kuaige.player.player.VideoPlayerandroid:id="@+id/video"android:layout_width="0dp"android:layout_height="0dp"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent" /><com.lzj.douyin.redheart.library.RedHeartLayoutandroid:id="@+id/ivHeart"android:layout_width="0dp"android:layout_height="0dp"app:heart_height="100"app:heart_image_resId="@drawable/ic_heart"app:heart_width="100"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent" /><ImageViewandroid:id="@+id/ivVideoThumb"android:layout_width="0dp"android:layout_height="0dp"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent" /><ImageViewandroid:id="@+id/ivPlayer"android:layout_width="85dp"android:layout_height="85dp"android:layout_marginBottom="90dp"android:src="@drawable/ic_find_video_play"android:visibility="gone"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent"tools:visibility="visible" /><ImageViewandroid:id="@+id/ivLike"android:layout_width="55dp"android:layout_height="55dp"android:src="@drawable/ic_gray_heart"android:layout_marginTop="200dp"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintTop_toTopOf="parent"tools:ignore="MissingConstraints" /><includeandroid:id="@+id/videoPlayFail"layout="@layout/video_play_fail"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginBottom="90dp"android:visibility="gone"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent"tools:visibility="visible" /></androidx.constraintlayout.widget.ConstraintLayout>
2、播放失败xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="wrap_content"xmlns:tools="http://schemas.android.com/tools"android:orientation="vertical"><TextViewandroid:singleLine="true"android:textColor="@color/white"android:textSize="18sp"android:layout_gravity="center"tools:text="@string/net_fail_retry"android:layout_width="wrap_content"android:layout_height="wrap_content"/><TextViewandroid:id="@+id/tvPlayRetry"android:gravity="center"android:textSize="18sp"android:textColor="@color/white"android:background="@drawable/white_corner_25"android:text="@string/click_retry"android:layout_marginTop="35dp"android:layout_gravity="center"android:layout_width="160dp"android:layout_height="46dp"/>
</LinearLayout>
3、视频播放Fragment
package com.qiqilego.vpplayer.fragmentimport android.annotation.SuppressLint
import android.os.Handler
import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import androidx.appcompat.app.AppCompatActivity
import com.kongzue.dialog.v3.WaitDialog
import com.kuaige.player.listener.VideoListener
import com.lzj.douyin.redheart.library.RedHeartLayout
import com.qiqilego.vpplayer.R
import com.qiqilego.vpplayer.event.VideoIndexEvent
import kotlinx.android.synthetic.main.fragment_video_recommend.*
import kotlinx.android.synthetic.main.video_play_fail.*
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import tv.danmaku.ijk.media.player.IMediaPlayer
import kotlin.math.abs/*** 适用于Activity管理*/
class VideoPlayForActivityFragment(var videoUrl: String?,var position: Int
) : BaseFragment(),VideoListener, View.OnClickListener,RedHeartLayout.HeartTouchListener {private var isPrepared = false//是否释放过视频资源private var isRelease = false//是否执行过stopprivate var selfStop = falseoverride fun getLayoutId() = R.layout.fragment_video_recommendoverride fun setTitle() = ""private val handler = Handler()@SuppressLint("SetTextI18n")override fun viewCreated(view: View) {video?.run {setVideoListener(this@VideoPlayForActivityFragment)//硬件加速Gpu渲染setEnableMediaCodec(true)}}@SuppressLint("ClickableViewAccessibility")override fun initDate() {tvPlayRetry.setOnClickListener(this)ivPlayer.setOnClickListener(this)//点赞按钮ivLike.setOnClickListener(this)ivLike.tag = falseivLike.setImageResource(R.drawable.ic_gray_heart)ivHeart.setHeartTouchListener(this)}override fun onResume() {super.onResume()if (!video.isPlaying && isPrepared) {if (selfStop || isRelease) {selfStop = falsevideo.reset()video.setPath(videoUrl)video.load()} else {video.start()ivVideoThumb.visibility = GONE}ivPlayer.visibility = GONE}//首次拉取视频uri进行播放else if (!video.isPlaying && !isPrepared) {video.run {setPath(videoUrl)load()}ivPlayer.visibility = GONE}}/*** Eventbus机制 用来释放video资源引用 当前播放视频的前后5页不被释放、其余都要释放视频缓存* @param event.position 为当前播放页视频下标*/@Subscribe(threadMode = ThreadMode.MAIN)override fun onMessageEvent(event: Any?) {super.onMessageEvent(event)if (event is VideoIndexEvent) {val needRelease = abs(position - event.position) > 4if (needRelease && !isRelease) {video?.post {video?.stop()video?.release()videoUrl = nullisRelease = true}}}}override fun onPause() {super.onPause()ivPlayer.visibility = VISIBLEvideo.pause()}override fun onStop() {super.onStop()video?.stop()selfStop = trueivPlayer.visibility = VISIBLE}override fun onDestroy() {super.onDestroy()video?.stop()video?.release()}override fun onSeekComplete(player: IMediaPlayer?) {}override fun onInfo(player: IMediaPlayer?, p1: Int, p2: Int): Boolean {return false}override fun onVideoSizeChanged(player: IMediaPlayer?, p1: Int, p2: Int, p3: Int, p4: Int) {}override fun onBufferingUpdate(player: IMediaPlayer?, p1: Int) {}override fun onPrepared(player: IMediaPlayer?) {WaitDialog.dismiss()isPrepared = trueisRelease = falseif (player != null && !player.isPlaying && isResumed) {player.start()ivVideoThumb.visibility = GONE}videoPlayFail?.visibility = GONE}override fun onCompletion(player: IMediaPlayer?) {//循环播放if (isResumed) {player?.start()}}override fun onError(player: IMediaPlayer?, p1: Int, p2: Int): Boolean {WaitDialog.dismiss()videoPlayFail?.visibility = VISIBLEreturn false}override fun onClick(v: View?) {when (v) {tvPlayRetry -> {WaitDialog.show(activity as AppCompatActivity, R.string.video_loading)videoPlayFail.visibility = GONEvideo.reset()video.setPath(videoUrl)video.load()}ivPlayer -> {if (selfStop) {selfStop = falsevideo.reset()video.setPath(videoUrl)video.load()} else {video.start()ivVideoThumb.visibility = GONE}ivPlayer.visibility = GONE}ivLike -> {if (ivLike.tag == false) {ivLike.tag = trueivLike.setImageResource(R.drawable.ic_red_heart)} else {ivLike.tag = falseivLike.setImageResource(R.drawable.ic_gray_heart)}}}}override fun onDoubleBefore() {handler.postDelayed({if (video.isPlaying) {video.pause()ivPlayer.visibility = VISIBLE}}, 200)}override fun onDoubleListener() {handler.removeCallbacksAndMessages(null)if (ivLike.tag == false) {ivLike.tag = trueivLike.setImageResource(R.drawable.ic_red_heart)} else {ivLike.tag = falseivLike.setImageResource(R.drawable.ic_gray_heart)}}override fun onDoubleAfter() {}
}
3、EventBus 实体类
data class VideoIndexEvent(val position: Int)
四、相关Base类
1、BaseActivity
package com.qiqilego.vpplayerimport android.graphics.Typeface
import android.os.Bundle
import android.view.View
import android.view.WindowManager
import androidx.appcompat.app.AppCompatActivity
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadModeabstract class BaseActivity : AppCompatActivity() {lateinit var eventBus: EventBusoverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)supportActionBar?.hide()eventBus = EventBus.getDefault()if (needInputSoftMode())window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN)setViewBefore()if (getLayoutId() != null) {setContentView(getLayoutId()!!)} else {setContentView(getLayoutView())}initView()initDate()}/*** 布局id*/abstract fun getLayoutId(): Int?open fun getLayoutView(): View? = null/*** 状态栏颜色*/open fun statusBarColor() = -1/*** 设置标题*/abstract fun setTitle(): String//设置标题背景 资源idopen fun setTitleBackGround(): Int? = null//设置ttf格式字体open fun setTitleFontStyle(): Typeface? = null/*** 初始化*/abstract fun initView()/*** 数据初始化*/abstract fun initDate()/*** 左侧返回按钮*/open fun needBackIcon() = false/*** 是否需要返回监听事件*/open fun needBackEvent() = true/*** 右侧返回按钮*/open fun needRightIcon() = false/*** 右侧文字按钮*/open fun needRightTv() = false/*** 输入法高度自适应EdiTText*/open fun needInputSoftMode() = false/*** 隐私游乐园*/open fun needTeamTimeTitle() = false/*** setContentView 之前执行的逻辑*/open fun setViewBefore() {}override fun onStart() {super.onStart()if (!EventBus.getDefault().isRegistered(this))EventBus.getDefault().register(this)}override fun onDestroy() {super.onDestroy()EventBus.getDefault().unregister(this)}@Subscribe(threadMode = ThreadMode.MAIN)open fun onMessageEvent(event: Any?) {}}
2、BaseFragment
package com.qiqilego.vpplayer.fragmentimport android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadModeabstract class BaseFragment : Fragment() {lateinit var eventBus: EventBusoverride fun onCreateView(inflater: LayoutInflater,container: ViewGroup?,savedInstanceState: Bundle?): View? {return inflater.inflate(getLayoutId(), container, false)}override fun onViewCreated(view: View, savedInstanceState: Bundle?) {super.onViewCreated(view, savedInstanceState)viewCreated(view)eventBus=EventBus.getDefault()initDate()}/*** 布局id*/abstract fun getLayoutId(): Int/*** 设置标题*/abstract fun setTitle(): String/*** view初始化*/abstract fun viewCreated(view: View)/*** view监听绑定*/abstract fun initDate()/*** 左侧返回按钮*/open fun needBackIcon() = false/*** 右侧返回按钮*/open fun needRightIcon() = falseoverride fun onAttach(context: Context) {super.onAttach(context)if (!EventBus.getDefault().isRegistered(this))EventBus.getDefault().register(this)}override fun onDetach() {super.onDetach()EventBus.getDefault().unregister(this)}@Subscribe(threadMode = ThreadMode.MAIN)open fun onMessageEvent(event: Any?) {}
}
五、相关图片字符串资源
1、图片资源
drawable 资源
gradient_gray_down.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"><gradientandroid:centerX="0"android:centerY="0"android:endColor="#00000000"android:gradientRadius="0dp"android:angle="270"android:startColor="#59000000"android:type="linear" />
</shape>
white_corner_25.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"><stroke android:width="1dp" android:color="#F4F4F4" /><corners android:radius="25dp" />
</shape>
drawable-xhdpi
ic_find_video_play-> ic_gray_heart-> ic_red_heart->
color 及string 文件
要用到的color <color name="_000000">#000000</color>
相关string.xml
<string name="app_name">仿抖音</string><string name="net_fail_retry">网络不给力,请稍后重试!</string>
<string name="click_retry">点击重试</string>
<string name="video_loading">视频重新加载中…</string>
vpplayer----------->build.gradle
plugins {id 'com.android.application'id 'kotlin-android'id 'kotlin-android-extensions'id 'kotlin-kapt'
}android {compileSdkVersion 30buildToolsVersion "30.0.3"defaultConfig {applicationId "com.qiqilego.vpplayer"minSdkVersion 21targetSdkVersion 30versionCode 1versionName "1.0"testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"}buildTypes {release {minifyEnabled falseproguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'}}compileOptions {sourceCompatibility JavaVersion.VERSION_1_8targetCompatibility JavaVersion.VERSION_1_8}kotlinOptions {jvmTarget = '1.8'}
}dependencies {implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"implementation 'androidx.core:core-ktx:1.3.2'implementation 'androidx.appcompat:appcompat:1.2.0'implementation 'com.google.android.material:material:1.2.1'implementation 'androidx.constraintlayout:constraintlayout:2.0.4'implementation project(path: ':kgplayer')implementation project(path: ':heartlibrary')testImplementation 'junit:junit:4.+'androidTestImplementation 'androidx.test.ext:junit:1.1.2'androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'// SmartRefreshLayout 刷新框架implementation 'com.scwang.smart:refresh-layout-kernel:2.0.3'//经典刷新头implementation 'com.scwang.smart:refresh-header-classics:2.0.3'implementation 'com.scwang.smart:refresh-header-material:2.0.3'//Event buskapt "org.greenrobot:eventbus-annotation-processor:3.2.0"implementation 'com.kongzue.dialog_v3x:dialog:3.2.4'
}kapt {arguments {arg('eventBusIndex', 'com.example.myapp.MyEventBusIndex')}
}
如图
kgplayer ---------------->build.gradle
apply plugin: 'com.android.library'android {compileSdkVersion rootProject.ext.compileSdkVersionbuildToolsVersion rootProject.ext.buildToolsVersiondefaultConfig {minSdkVersion rootProject.ext.minSdkVersiontargetSdkVersion rootProject.ext.targetSdkVersion}/* sourceSets {main {jniLibs.srcDirs = ['jniLibs']}}*/
}dependencies {implementation fileTree(dir: 'libs', include: ['*.jar'])implementation "com.android.support:appcompat-v7:$rootProject.supportLibraryVersion"api "tv.danmaku.ijk.media:ijkplayer-java:$rootProject.ijkPlayerVersion"api 'com.github.CarGuo.GSYVideoPlayer:gsyVideoPlayer-ex_so:v8.1.3-jitpack'api 'org.greenrobot:eventbus:3.2.0'
}
heartlibrary------------->build.gradle
apply plugin: 'com.android.library'android {compileSdkVersion rootProject.ext.compileSdkVersionbuildToolsVersion rootProject.ext.buildToolsVersiondefaultConfig {minSdkVersion rootProject.ext.minSdkVersiontargetSdkVersion rootProject.ext.targetSdkVersion}buildTypes {release {minifyEnabled falseproguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'}}compileOptions {sourceCompatibility = 1.8targetCompatibility = 1.8}
}dependencies {implementation fileTree(dir: 'libs', include: ['*.jar'])implementation 'androidx.appcompat:appcompat:1.1.0'testImplementation 'junit:junit:4.12'androidTestImplementation 'androidx.test.ext:junit:1.1.1'androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}
根目录build.gradle
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {ext.kotlin_version = "1.4.32"repositories {google()jcenter()}dependencies {classpath "com.android.tools.build:gradle:4.1.1"classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"// NOTE: Do not place your application dependencies here; they belong// in the individual module build.gradle files}
}ext {compileSdkVersion = 30buildToolsVersion = "30.0.3"minSdkVersion = 21targetSdkVersion = 30appTargetSdkVersion = 30supportLibraryVersion = '30.0.3'ijkPlayerVersion = '0.8.8'
}allprojects {repositories {google()jcenter()maven { url 'https://jitpack.io' }}
}task clean(type: Delete) {delete rootProject.buildDir
}
版本信息:gradle-6.5 android studio 4.1.1 Kotlin版本 "1.4.32"
sdk版本参考 vpplayer.build.gradle
至此完成!!!
ViewPager2 +Fragment 模仿抖音短视频相关推荐
- 整合vite2.0+electron12+vant3.x跨端仿抖音短视频+聊天+直播exe应用
vite2-electron-douyin 基于vite2.x+electron模仿抖音短视频应用实例. 整合了vite2+electron跨端开发技术仿制抖音界面桌面版exe应用软件.基于Vite2 ...
- 抖音初期运营,如何让自己的抖音短视频账号快速涨粉:国仁楠哥
在没有做抖音短视频之前是不是感觉很简单,只是简单那的发发视频就可以上热门,就可以有很多人看?当自己在看别人视频的时候,内心就在想,我其实可以做的比他更好的内容,也能轻松上热门. 有多少人曾经对自己发布 ...
- 抖音短视频企业号如何运营
我们今天说说企业号的运营,我们知道个人号的内容,就像我们自己在生活中遇到的一些有趣的事,有意思的人.和个人号运营不同的是,企业号的运营,其实有更多的技巧和出路,当然也会有更多的资源和优势,在我们现在这 ...
- 抖音短视频零基础能做到百万粉丝吗?国仁楠哥
抖音短视频,一个旨在帮助大众用户表达自我,记录美好生活的短视频分享平台. 相信很多人遇到这样糟心的经历,抖音发作品浏览量不是很高,慢慢的做着做着就放弃了,都在抱怨没有流量,看到别人在抖音上面带货卖货, ...
- 高新计算机模拟视频2018,抖音短视频电脑版2018 无需模拟器版
抖音短视频电脑版,终于是在本站发布了,这款不同于以往的版本,无需你们用模拟器,直接打开后,就能进行快速的登陆,可以在线的欣赏令人称奇的作品,认识到更多朋友. 抖音短视频电脑版简介 拍摄一个音乐短视频, ...
- 抖音短视频APP——产品体验报告
本文是抖音短视频APP的一份产品体验报告,主要从体验环境.市场概况.产品概况.目标用户人群.产品架构图.主要功能分析.交互体验.视觉设计和优化建议这九个方面进行阐述. 本文是本人入门产品经理的一份练习 ...
- 抖音短视频批量采集下载软件哪些好?如何下载?轻松搬运视频,快速批量处理水印去除LOGO!...
抖音短视频批量采集下载软件哪些好?如何下载? 抖音短视频去重消重去水印软件哪些好?如何下载? 腾讯视频批量采集下载软件哪些好?怎么下载? 腾讯视频去重消重去水印软件哪些好?如何下载? 轻松搬运视频,快 ...
- 看看做抖音短视频的十大误区
在没有做抖音短视频之前是不是感觉很简单,只是发发视频就可以上热门,就可以有很多人看? 看了别人热门视频,我可以做比他更好的视频,也能上热门? 学习了很多抖音的课程,去操作了发现还是不行,上不去量! 很 ...
- 抖音短视频运营干货:从入门到精通值得一阅!
"两微一抖"已成企业的营销标配,但对于玩转抖音还懵懵懂懂的你,这篇超级干货,会让你满载而归的! 01 抖音入门篇 1.新注册的号为何不能马上发作品,需要先养号? 在正式开始发布 ...
最新文章
- 笔记本高分屏字体模糊_高色域+高分辨率轻薄本推荐,你需要2K屏笔记本电脑么?...
- 【杂谈】超过12个,150页深度学习开源框架指导手册与GitHub项目,初学CV你值得拥有...
- 解决toolbar左边空出一部分的问题
- 守护线程Daemon的理解
- Java反射机制是什么?
- pytorch分布式训练(一):torch.nn.DataParallel
- [dfs] 洛谷 P1822 魔法指纹
- android adjust,【报Bug】安卓 adjust-position设置为false 页面依然被顶起 ios是好的
- mysql系统变量配置文件_MySQL系统变量配置基础
- windows 环境下.Net使用Redis缓存
- Linux vim常用命令
- 如何在matlab中读写segy格式数据
- 在VirtualBox Linux 7u2 中安装Oracle RAC 12.2.0.1.0
- Android控制所有播放器的音频切换上下首歌、播放、停止
- Thread多线程-(最容易被问到的面试题)
- 几个国外广告联盟介绍
- freesurfer对结构核磁共振成像分割输出结果介绍
- 计算机的硬盘类型及特点是,电脑硬盘中的蓝盘、黑盘、红盘、绿盘有什么区别?特点?...
- usercity 小程序_微信小程序API 用户信息 wx.getUserInfo(OBJECT)
- SQL更改表名,数据库名,字段名