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、下拉上拉库SmallRefresh   6、so库 使用GsyPlayer so     库 api 'com.github.CarGuo.GSYVideoPlayer:gsyVideoPlayer-ex_so:v8.1.3-jitpack'

目录结构

ViewPager2 +Fragment 模仿抖音短视频_第1张图片

heartlibraary  (子依赖)-->vpplayer、   

kgplayer (子依赖)-->vpplayer、   

 

二、主界面

1、activity_list_video.xml  activity主界面




    

    

        
            
        

    


2、主Activity


package com.qiqilego.vpplayer

import 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_video

    override fun setTitle() = ""

    private val handler = Handler()

    private lateinit var adapter: VideoListPagerAdapter

    override fun initView() {
        //初始化ViewPager
        adapter = VideoListPagerAdapter(this)
        viewPager.adapter = adapter
        viewPager.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)
                //发送EventBus
                handler.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()
            for (index in 0..20) {
                list.add(VideoPlayForActivityFragment(TEST_URL, index))
            }
            adapter.addFirstData(list as MutableList)
            refreshView.finishRefresh(500)
        }
        refreshView.setOnLoadMoreListener {
            val list = mutableListOf()
            for (index in 0..10) {
                list.add(VideoPlayForActivityFragment(TEST_URL, index))
            }
            adapter.addData(list as MutableList)
            refreshView.finishLoadMore(500)
        }
    }

    override fun initDate() {
        refreshView.autoRefresh()
    }

}

3、ViewPager2适配器

package com.qiqilego.vpplayer.adapter

import 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()

    fun addData(newData: MutableList) {
        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) {
        if (newData.isNotEmpty()) {
            dataList.clear()
            dataList.addAll(newData)
            dataList.forEachIndexed { index, it ->
                if (it is VideoPlayForActivityFragment) {
                    it.position = index
                }
            }
            notifyDataSetChanged()
        }
    }

    fun getData() = dataList

    override fun getItemCount() = dataList.size

    override fun createFragment(position: Int): Fragment {
        return dataList[position]
    }

    override fun getItemId(position: Int): Long {
        return dataList[position].hashCode().toLong()
    }
}

三、视频播放详情界面


1、视频详情xml





    

    
    

    

    


    

2、播放失败xml




    

    

3、视频播放Fragment

package com.qiqilego.vpplayer.fragment

import 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

    //是否执行过stop
    private var selfStop = false

    override fun getLayoutId() = R.layout.fragment_video_recommend

    override 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 = false
        ivLike.setImageResource(R.drawable.ic_gray_heart)
        ivHeart.setHeartTouchListener(this)
    }

    override fun onResume() {
        super.onResume()
        if (!video.isPlaying && isPrepared) {
            if (selfStop || isRelease) {
                selfStop = false
                video.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) > 4
            if (needRelease && !isRelease) {
                video?.post {
                    video?.stop()
                    video?.release()
                    videoUrl = null
                    isRelease = true
                }
            }
        }
    }

    override fun onPause() {
        super.onPause()
        ivPlayer.visibility = VISIBLE
        video.pause()
    }

    override fun onStop() {
        super.onStop()
        video?.stop()
        selfStop = true
        ivPlayer.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 = true
        isRelease = false
        if (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 = VISIBLE
        return false
    }

    override fun onClick(v: View?) {
        when (v) {
            tvPlayRetry -> {
                WaitDialog.show(activity as AppCompatActivity, R.string.video_loading)
                videoPlayFail.visibility = GONE
                video.reset()
                video.setPath(videoUrl)
                video.load()
            }

            ivPlayer -> {
                if (selfStop) {
                    selfStop = false
                    video.reset()
                    video.setPath(videoUrl)
                    video.load()
                } else {
                    video.start()
                    ivVideoThumb.visibility = GONE
                }
                ivPlayer.visibility = GONE
            }

            ivLike -> {
                if (ivLike.tag == false) {
                    ivLike.tag = true
                    ivLike.setImageResource(R.drawable.ic_red_heart)
                } else {
                    ivLike.tag = false
                    ivLike.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 = true
            ivLike.setImageResource(R.drawable.ic_red_heart)
        } else {
            ivLike.tag = false
            ivLike.setImageResource(R.drawable.ic_gray_heart)
        }
    }

    override fun onDoubleAfter() {

    }
}

3、EventBus 实体类

data class VideoIndexEvent(val position: Int)

 

四、相关Base类


1、BaseActivity

package com.qiqilego.vpplayer

import 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.ThreadMode


abstract class BaseActivity : AppCompatActivity() {

    lateinit var eventBus: EventBus

    override 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

    //设置标题背景 资源id
    open 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.fragment

import 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.ThreadMode

abstract class BaseFragment : Fragment() {

    lateinit var eventBus: EventBus

    override 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() = false

    override 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、图片资源 

ViewPager2 +Fragment 模仿抖音短视频_第2张图片

drawable 资源

gradient_gray_down.xml



    

white_corner_25.xml



    
    

drawable-xhdpi

ic_find_video_play->ViewPager2 +Fragment 模仿抖音短视频_第3张图片    ic_gray_heart->ViewPager2 +Fragment 模仿抖音短视频_第4张图片   ic_red_heart-> ViewPager2 +Fragment 模仿抖音短视频_第5张图片

 

 

color 及string 文件

ViewPager2 +Fragment 模仿抖音短视频_第6张图片

要用到的color   #000000

相关string.xml 

仿抖音

网络不给力,请稍后重试!
点击重试
视频重新加载中…

 

vpplayer----------->build.gradle

plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'kotlin-android-extensions'
    id 'kotlin-kapt'
}

android {
    compileSdkVersion 30
    buildToolsVersion "30.0.3"

    defaultConfig {
        applicationId "com.qiqilego.vpplayer"
        minSdkVersion 21
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility 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 bus
    kapt "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.compileSdkVersion
    buildToolsVersion rootProject.ext.buildToolsVersion

    defaultConfig {
        minSdkVersion rootProject.ext.minSdkVersion
        targetSdkVersion 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.compileSdkVersion
    buildToolsVersion rootProject.ext.buildToolsVersion
    defaultConfig {
        minSdkVersion rootProject.ext.minSdkVersion
        targetSdkVersion rootProject.ext.targetSdkVersion
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility = 1.8
        targetCompatibility = 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 = 30
    buildToolsVersion = "30.0.3"
    minSdkVersion = 21
    targetSdkVersion = 30
    appTargetSdkVersion = 30

    supportLibraryVersion = '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


至此完成!!!

 

你可能感兴趣的:(viewpager,android)