基于GSYVideoPlayer自定义布局结合RecyclerView高仿抖音实现上下滑动双击屏幕点赞/单击暂停,拖动进度条实时改变时间以及进度条放大

这篇具有很好参考价值的文章主要介绍了基于GSYVideoPlayer自定义布局结合RecyclerView高仿抖音实现上下滑动双击屏幕点赞/单击暂停,拖动进度条实时改变时间以及进度条放大。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

注意代码量有点多,但是你不要就此放弃,看效果图决定你需不需该需求😀并且代码好理解基本上都是基于GSYVideoPlayer的方法进行重写改造出来的,请放心食用

GSYVideoPlayer是一款开源并且强大的Android视频播放器方便你们阅读了GSYVideoPlayer更快速的上手GSYVideoPlayer框架地址

效果图预览

基于GSYVideoPlayer自定义布局高仿抖音

1.添加 GSYVideoPlayer依赖

    //GSYVideoPlayer播放器
    implementation 'com.github.CarGuo.GSYVideoPlayer:GSYVideoPlayer:v8.3.5-release-jitpack'
    //GSYVideoPlayer播放器是否需要AliPlayer模式
    implementation 'com.github.CarGuo.GSYVideoPlayer:GSYVideoPlayer-aliplay:v8.3.5-release-jitpack'

2.自定义GSYVideoPlayer布局 

package com.zyy.inpaint

import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.app.Activity
import android.app.Service
import android.content.Context
import android.graphics.Matrix
import android.graphics.Point
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.os.Handler
import android.os.Message
import android.os.Vibrator
import android.text.TextUtils
import android.util.AttributeSet
import android.view.*
import android.view.animation.AnimationUtils
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.SeekBar
import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions
import com.shuyu.gsyvideoplayer.utils.CommonUtil
import com.shuyu.gsyvideoplayer.utils.Debuger
import com.shuyu.gsyvideoplayer.utils.GSYVideoType
import com.shuyu.gsyvideoplayer.video.StandardGSYVideoPlayer
import com.shuyu.gsyvideoplayer.video.base.GSYBaseVideoPlayer
import java.lang.ref.WeakReference
import java.util.*
import kotlin.math.abs


open class SampleCoverVideo : StandardGSYVideoPlayer {

    var onLikeListener: () -> Unit = {}//屏幕点赞后,点赞按钮需同步点赞

    private var mCoverImage: ImageView? = null
    private var mCoverOriginUrl: String? = null
    private var mCoverOriginId: Int = 0
    private var mDefaultRes: Int? = null
    private var videoStart: ImageView? = null
    private var startVessel: LinearLayout? = null
    private var layoutTime: LinearLayout? = null

    private var icon: Drawable = resources.getDrawable(R.drawable.like_not,null)
    private var mClickCount = 0 //点击一次是暂停,多次是点赞
    private var mHandler = LikeLayoutHandler(this)

    private var startTime = System.currentTimeMillis()

    constructor(context: Context, fullFlag: Boolean) : super(context,fullFlag)
    constructor(context: Context) : super(context)
    constructor(context: Context, attrs: AttributeSet) : super(context, attrs)

    init {
        //GSYVideoManager.instance().optionModelList = GSYVideoPlayerOptimize.videoOptionModelOptimize()

        mCoverImage = findViewById(R.id.thumb_image)
        startVessel = findViewById(R.id.startVessel)
        videoStart = findViewById(R.id.videoStart)
        layoutTime = findViewById(R.id.layout_time)

        mProgressBar.apply {
            layoutParams.height = Util.dip2px(context, 18f) //初始化进度条高度
            isClickable = false
            isFocusable = false
        }

        if(mThumbImageViewLayout != null && (mCurrentState == -1 || mCurrentState == CURRENT_STATE_NORMAL || mCurrentState == CURRENT_STATE_ERROR)) {
            mThumbImageViewLayout.visibility = VISIBLE
        }

        clipChildren = false // 避免旋转时点赞被遮挡
    }
    //监听触摸,实际上是重写GSYVideoPlayer中的监听触摸
    override fun onTouch(v: View?, event: MotionEvent?): Boolean {
        val id = v!!.id
        val x = event!!.x
        val y = event.y

        if (mIfCurrentIsFullscreen && mLockCurScreen && mNeedLockFull) {
            onClickUiToggle(event)
            startDismissControlViewTimer()
            return true
        }

        if (id == R.id.fullscreen) {
            return false
        }
        if (id == R.id.surface_container) {
            when (event.action) {
                MotionEvent.ACTION_DOWN -> {
                    touchSurfaceDown(x, y)
                    startTime = System.currentTimeMillis()
                }
                MotionEvent.ACTION_MOVE -> {
                    val deltaX = x - mDownX
                    val deltaY = y - mDownY
                    val absDeltaX = abs(deltaX)
                    val absDeltaY = abs(deltaY)
                    if (mIfCurrentIsFullscreen && mIsTouchWigetFull
                        || mIsTouchWiget && !mIfCurrentIsFullscreen
                    ) {
                        if (!mChangePosition && !mChangeVolume && !mBrightness) {
                            touchSurfaceMoveFullLogic(absDeltaX, absDeltaY)
                        }
                    }
                    touchSurfaceMove(deltaX, deltaY, y)
                }
                MotionEvent.ACTION_UP -> {
                    startDismissControlViewTimer()
                    touchSurfaceUp()
                    Debuger.printfLog(this.hashCode()
                        .toString() + "------------------------------ surface_container ACTION_UP")
                    startProgressTimer()

                    //不要和隐藏虚拟按键后,滑出虚拟按键冲突
                    if (mHideKey && mShowVKey) {
                        return true
                    }
                    handler.removeCallbacksAndMessages(null)
                    //判断当前时间戳 减去 ACTION_DOWN按住时获取的时间戳 是否大于500
                    if (System.currentTimeMillis() - startTime >= 500) {
                        //vibrate(context as Activity, longArrayOf(800, 1000, 800, 1000, 800, 1000),false)
                    } else {
                        // 点击事件处理
                        mClickCount++
                        mHandler.removeCallbacksAndMessages(null)
                        if(mClickCount >= 2){
                            addHeartView(x, y)
                            onLikeListener()
                            mHandler.sendEmptyMessageDelayed(1, 500)
                        }else{
                            mHandler.sendEmptyMessageDelayed(0, 500)
                        }
                    }
                }
            }
            gestureDetector.onTouchEvent(event)
        } else if (id == R.id.progress) {
            when (event.action) {
                MotionEvent.ACTION_DOWN -> {
                    cancelDismissControlViewTimer()
                    cancelProgressTimer()
                    var vpdown = parent
                    while (vpdown != null) {
                        vpdown.requestDisallowInterceptTouchEvent(true)
                        vpdown = vpdown.parent
                    }
                }
                MotionEvent.ACTION_MOVE -> {
                    cancelProgressTimer()
                    var vpdown = parent
                    while (vpdown != null) {
                        vpdown.requestDisallowInterceptTouchEvent(true)
                        vpdown = vpdown.parent
                    }
                }
                MotionEvent.ACTION_UP -> {
                    startDismissControlViewTimer()
                    Debuger.printfLog(this.hashCode()
                        .toString() + "------------------------------ progress ACTION_UP")
                    startProgressTimer()
                    var vpup = parent
                    while (vpup != null) {
                        vpup.requestDisallowInterceptTouchEvent(false)
                        vpup = vpup.parent
                    }
                    mBrightnessData = -1f
                }

            }
        }

        return false
    }

    /**
     * 在Layout中添加红心并,播放消失动画
     */
    private fun addHeartView(x: Float, y: Float){
        val lp = LayoutParams(icon.intrinsicWidth, icon.intrinsicHeight) //计算点击的点位红心的下部中间
        lp.leftMargin = (x - icon.intrinsicWidth / 2).toInt()
        lp.topMargin = (y - icon.intrinsicHeight).toInt()
        val img = ImageView(context)
        img.scaleType = ImageView.ScaleType.MATRIX
        val matrix = Matrix()
        matrix.postRotate(getRandomRotate()) //设置红心的微量偏移
        img.imageMatrix = matrix
        img.setImageDrawable(icon)
        img.layoutParams = lp
        addView(img)
        val animSet = getShowAnimSet(img)
        val hideSet = getHideAnimSet(img)
        animSet.start()
        animSet.addListener(object : AnimatorListenerAdapter(){
            override fun onAnimationEnd(animation: Animator) {
                super.onAnimationEnd(animation)
                hideSet.start()
            }
        })
        hideSet.addListener(object : AnimatorListenerAdapter(){
            override fun onAnimationEnd(animation: Animator) {
                super.onAnimationEnd(animation)
                removeView(img)//动画结束移除红心
            }
        })
    }

    /**
     * 生成一个随机的左右偏移量
     */
    private fun getRandomRotate(): Float = (Random().nextInt(20) - 10).toFloat()

    /**
     * 刚点击的时候的一个缩放效果
     */
    private fun getShowAnimSet(view: ImageView): AnimatorSet{
        // 缩放动画
        val scaleX = ObjectAnimator.ofFloat(view, "scaleX",1.2f,1f)
        val scaleY = ObjectAnimator.ofFloat(view, "scaleY", 1.2f,1f)
        val animSet = AnimatorSet()
        animSet.playTogether(scaleX, scaleY)
        animSet.duration = 100
        return animSet
    }

    /**
     * 缩放结束后到红心消失的效果
     */
    private fun getHideAnimSet(view: ImageView): AnimatorSet{
        // 1.alpha动画
        val alpha = ObjectAnimator.ofFloat(view, "alpha", 1f, 0.1f)
        // 2.缩放动画
        val scaleX = ObjectAnimator.ofFloat(view, "scaleX", 1f, 2f)
        val scaleY = ObjectAnimator.ofFloat(view, "scaleY", 1f, 2f)
        // 3.translation动画
        val translation = ObjectAnimator.ofFloat(view, "translationY", 0f, -150f)
        val animSet = AnimatorSet()
        animSet.playTogether(alpha, scaleX, scaleY, translation)
        animSet.duration = 500
        return animSet
    }

    private fun pauseClick(){
        if(mClickCount == 1){
            clickStartIcon() //播放视频或暂停
            showHiddenButtonStart() //显示或隐藏播放按钮
        }
        mClickCount = 0
    }

    fun onPause(){
        mClickCount = 0
        mHandler.removeCallbacksAndMessages(null)
    }

    companion object{
        @Suppress("DEPRECATION")
        private class LikeLayoutHandler(view: SampleCoverVideo): Handler(){
            private val mView = WeakReference(view)
            override fun handleMessage(msg: Message) {
                super.handleMessage(msg)
                when(msg.what){
                    0 -> mView.get()?.pauseClick()
                    1 -> mView.get()?.onPause()
                }
            }
        }
        //真机震动
        private fun vibrate(activity: Activity, pattern: LongArray, isRepeat: Boolean) {
            val vib =  activity.getSystemService(Service.VIBRATOR_SERVICE) as Vibrator
            vib.vibrate(pattern, if(isRepeat) 1 else -1)
        }

    }

    override fun getLayoutId(): Int {
        return R.layout.layout_video_cover
    }

    fun loadCoverImage(url: String, res: Int){
        mCoverOriginUrl = url
        mDefaultRes = res
        Glide.with(context.applicationContext)
            .setDefaultRequestOptions(RequestOptions()
                .frame(100000)
                .centerCrop()
                .error(res)
                .placeholder(res))
            .load(url)
            .into(mCoverImage!!)
    }

    fun loadCoverImageBy(id: Int, res: Int){
        mCoverOriginId = id
        mDefaultRes = res
        mCoverImage?.setImageResource(id)
    }

    override fun startWindowFullscreen(
        context: Context?,
        actionBar: Boolean,
        statusBar: Boolean,
    ): GSYBaseVideoPlayer {
        val gsyBaseVideoPlayer = super.startWindowFullscreen(context, actionBar, statusBar)
        val sampleCoverVideo = gsyBaseVideoPlayer as SampleCoverVideo
        if(mCoverOriginUrl != null) {
            mDefaultRes?.let { sampleCoverVideo.loadCoverImage(mCoverOriginUrl!!, it) }
        } else  if(mCoverOriginId != 0) {
            mDefaultRes?.let { sampleCoverVideo.loadCoverImageBy(mCoverOriginId, it) }
        }
        return gsyBaseVideoPlayer
    }

    override fun showSmallVideo(
        size: Point?,
        actionBar: Boolean,
        statusBar: Boolean,
    ): GSYBaseVideoPlayer {
        //下面这里替换成你自己的强制转化
        val sampleCoverVideo =  super.showSmallVideo(size, actionBar, statusBar) as SampleCoverVideo
        sampleCoverVideo.startVessel?.visibility = GONE
        sampleCoverVideo.startVessel = null
        return sampleCoverVideo
    }

    override fun cloneParams(from: GSYBaseVideoPlayer?, to: GSYBaseVideoPlayer?) {
        super.cloneParams(from, to)
        val sf = from as SampleCoverVideo
        val st = to as SampleCoverVideo
        st.mShowFullAnimation = sf.mShowFullAnimation
    }
    /**
     * 退出window层播放全屏效果
     */
    @SuppressWarnings("ResourceType")
    override fun clearFullscreenLayout() {
        if(!mFullAnimEnd){
            return
        }
        var delay = 0
        mIfCurrentIsFullscreen = false
        // ------- !!!如果不需要旋转屏幕,可以不调用!!!-------
        // 不需要屏幕旋转,还需要设置 setNeedOrientationUtils(false)
        if(mOrientationUtils != null){
            delay = mOrientationUtils.backToProtVideo()
            mOrientationUtils.isEnable = false
            if (mOrientationUtils != null) {
                mOrientationUtils.releaseListener()
                mOrientationUtils = null
            }
        }
        if (!mShowFullAnimation) {
            delay = 0
        }
        val vp = (CommonUtil.scanForActivity(context)).findViewById<View>(Window.ID_ANDROID_CONTENT)
        val oldF = vp.findViewById<View>(fullId)
        if (oldF != null) {
            //此处fix bug#265,推出全屏的时候,虚拟按键问题
            val gsyVideoPlayer = oldF as SampleCoverVideo
            gsyVideoPlayer.mIfCurrentIsFullscreen = false
        }

        if (delay == 0) {
            backToNormal()
        } else {
            postDelayed({
                run {
                    backToNormal()
                }
            }, delay.toLong())
        }
    }
    /******************* 下方两个重载方法,在播放开始前不屏蔽封面,不需要可屏蔽 ********************/
    override fun onSurfaceUpdated(surface: Surface?) {
        super.onSurfaceUpdated(surface)
        if (mThumbImageViewLayout != null && mThumbImageViewLayout.visibility == VISIBLE) {
            mThumbImageViewLayout.visibility = INVISIBLE
        }
    }

    override fun setViewShowState(view: View?, visibility: Int) {
        if (view == mThumbImageViewLayout && visibility != VISIBLE) {
            return
        }
        super.setViewShowState(view, visibility)
    }

    override fun onSurfaceAvailable(surface: Surface?) {
        super.onSurfaceAvailable(surface)
        if (GSYVideoType.getRenderType() != GSYVideoType.TEXTURE) {
            if (mThumbImageViewLayout != null && mThumbImageViewLayout.visibility == VISIBLE) {
                mThumbImageViewLayout.visibility = INVISIBLE
            }
        }
    }
    /******************* 下方重载方法,在播放开始不显示底部进度和按键,不需要可屏蔽 ********************/
    private var byStartedClick: Boolean? = null
    override fun onClickUiToggle(e: MotionEvent?) {
        if (mIfCurrentIsFullscreen && mLockCurScreen && mNeedLockFull) {
            setViewShowState(mLockScreen, VISIBLE)
            return
        }

        byStartedClick = true
        super.onClickUiToggle(e)
    }

    override fun changeUiToNormal() {
        super.changeUiToNormal()
        byStartedClick = false
    }

    override fun changeUiToPreparingShow() {
        super.changeUiToPreparingShow()
        Debuger.printfLog("Sample changeUiToPreparingShow")
        setViewShowState(layoutTime, INVISIBLE)
        setViewShowState(startVessel, INVISIBLE)
    }

    override fun changeUiToPlayingBufferingShow() {
        super.changeUiToPlayingBufferingShow()
        Debuger.printfLog("Sample changeUiToPlayingBufferingShow")
        if (!byStartedClick!!) {
            setViewShowState(layoutTime, INVISIBLE)
            setViewShowState(startVessel, INVISIBLE)
        }
    }

    override fun changeUiToPlayingShow() {
        super.changeUiToPlayingShow()
        Debuger.printfLog("Sample changeUiToPlayingShow")
        if (!byStartedClick!!) {
            setViewShowState(layoutTime, INVISIBLE)
            setViewShowState(startVessel, INVISIBLE)
        }
    }

    override fun startAfterPrepared() {
        super.startAfterPrepared()
        Debuger.printfLog("Sample startAfterPrepared")
        setViewShowState(layoutTime, INVISIBLE)
        setViewShowState(startVessel, INVISIBLE)
        setViewShowState(mBottomProgressBar, VISIBLE)
    }

    /**
     * 重载双击方法,不允许继承父级,就可以关闭双击暂停和播放
     */
    override fun touchDoubleUp(e: MotionEvent?) {
    }

    override fun onClick(v: View?) {
        val i = v!!.id
        if (mHideKey && mIfCurrentIsFullscreen) {
            CommonUtil.hideNavKey(mContext)
        }
        if (i == R.id.surface_container && mCurrentState == CURRENT_STATE_ERROR) {
            if (!mSurfaceErrorPlay) {
                onClickUiToggle(null)
                return
            }
            if (mVideoAllCallBack != null) {
                Debuger.printfLog("onClickStartError")
                mVideoAllCallBack.onClickStartError(mOriginUrl, mTitle, this)
            }
            prepareVideo()
        } else if (i == R.id.thumb) {
            if (!mThumbPlay) {
                return
            }
            if (TextUtils.isEmpty(mUrl)) {
                Debuger.printfError("********" + resources.getString(com.shuyu.gsyvideoplayer.R.string.no_net))
                return
            }
            if (mCurrentState == CURRENT_STATE_NORMAL) {
                if (isShowNetConfirm) {
                    showWifiDialog()
                    return
                }
                startPlayLogic()
            } else if (mCurrentState == CURRENT_STATE_AUTO_COMPLETE) {
                onClickUiToggle(null)
            }
        }
    }

    /**
     * 在Android应用中播放一个名为"heartbeat"的动画,
     * 并将该动画应用于startVessel视图。动画播放结束后,startVessel视图会被设置为不可见状态。其中,pulseAnimation是动画的实例,
     * context是应用上下文,R.anim.heartbeat是动画资源的ID,startVessel是要应用动画的视图,setViewShowState()是自定义的一个方法,
     * 用于设置视图的可见性状态。
     */
    fun showHiddenButtonStart(){
        if(mCurrentState == CURRENT_STATE_PLAYING){
            //控件淡出动画效果
            val pulseAnimation = AnimationUtils.loadAnimation(context, R.anim.fade_out)
            startVessel?.startAnimation(pulseAnimation)
            setViewShowState(startVessel, INVISIBLE)
        }else if(mCurrentState == CURRENT_STATE_PAUSE){
            //控件淡入动画效果
            val pulseAnimation = AnimationUtils.loadAnimation(context, R.anim.heartbeat)
            startVessel?.startAnimation(pulseAnimation)
            setViewShowState(startVessel, VISIBLE)
        }
    }

    // 重写进度条实时监听的操作
    override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
        //判断是否是用户滑动
        if(fromUser){
            if (gsyVideoManager != null && mHadPlay) {
                setViewShowState(layoutTime, VISIBLE)
                try {
                    val time = seekBar!!.progress * duration / 100
                    mCurrentTimeTextView.text = CommonUtil.stringForTime(time)
                } catch (e: Exception) {
                    Debuger.printfWarning(e.toString())
                }
            }
        }
        mHadSeekTouch = false
    }
    // 重写用户停止拖动进度条时的操作
    override fun onStopTrackingTouch(seekBar: SeekBar?) {
        seekBar?.isClickable = true
        setViewShowState(layoutTime, INVISIBLE)
        if(seekBar != null){
            seekBar.layoutParams.height = Util.dip2px(context, 18f)
            //动态修改SeekBar的高度
        }
        if (mVideoAllCallBack != null && isCurrentMediaListener) {
            if (isIfCurrentIsFullscreen) {
                Debuger.printfLog("onClickSeekbarFullscreen")
                mVideoAllCallBack.onClickSeekbarFullscreen(mOriginUrl, mTitle, this)
            } else {
                Debuger.printfLog("onClickSeekbar")
                mVideoAllCallBack.onClickSeekbar(mOriginUrl, mTitle, this)
            }
        }
        if (gsyVideoManager != null && mHadPlay) {
            try {
                val time = seekBar!!.progress * duration / 100
                gsyVideoManager.seekTo(time)
            } catch (e: java.lang.Exception) {
                Debuger.printfWarning(e.toString())
            }
        }
        mHadSeekTouch = false
    }
    //用户开始触碰进度条触发
    override fun onStartTrackingTouch(seekBar: SeekBar?) {
        seekBar?.isClickable = false
        if (seekBar != null){
           seekBar.layoutParams.height = Util.dip2px(context, 25f)
        }
        super.onStartTrackingTouch(seekBar)
    }

    override fun onVideoResume() {
        super.onVideoResume()
        setViewShowState(startVessel, INVISIBLE)
    }
    //Android SeekBar禁止点击允许滑动


}

3.自定义布局控件

        3.1 layout_video_cover.xm

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <FrameLayout
        android:id="@+id/surface_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center">

    </FrameLayout>

    <RelativeLayout
        android:id="@+id/thumb"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_alignParentStart="true"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:layout_alignParentEnd="true"
        android:layout_alignParentRight="true"
        android:layout_alignParentBottom="true"
        android:gravity="center">
        <ImageView
            android:id="@+id/thumb_image"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:scaleType="fitXY"
            android:adjustViewBounds="true" />
    </RelativeLayout>

    <LinearLayout
        android:id="@+id/layout_time"
        android:layout_width="match_parent"
        android:layout_marginBottom="20dp"
        android:layout_height="40dp"
        android:gravity="center"
        android:orientation="horizontal"
        android:layout_above="@+id/layout_bottom_lzy"
        android:visibility="visible">

        <TextView
            android:id="@+id/current"
            android:layout_gravity="center"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="00:00"
            android:textSize="20sp"
            android:textColor="#ffffff"
            android:layout_marginEnd="10dp"/>

        <TextView
            android:layout_gravity="center"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="/"
            android:textSize="20sp"
            android:textColor="#ffffff" />

        <TextView
            android:id="@+id/total"
            android:layout_marginLeft="10sp"
            android:layout_gravity="center"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="00:00"
            android:textSize="20sp"
            android:textColor="#ffffff" />
    </LinearLayout>

    <LinearLayout
        android:id="@+id/layout_bottom_lzy"
        android:layout_width="match_parent"
        android:layout_height="25dp"
        android:layout_alignParentBottom="true"
        android:gravity="center_vertical"
        android:orientation="horizontal"
        >

        <SeekBar
            android:id="@+id/progress"
            android:layout_width="0dp"
            android:paddingBottom="8dp"
            android:paddingTop="8dp"
            android:layout_height="match_parent"
            android:layout_gravity="center_vertical"
            android:layout_weight="1.0"
            android:background="@null"
            android:max="100"
            android:progressDrawable="@drawable/mx_bottom_seek_progress"
            android:thumb="@drawable/tiktok_seek_thumb" />

        <ImageView
            android:id="@+id/fullscreen"
            android:layout_width="wrap_content"
            android:layout_height="fill_parent"
            android:paddingRight="16dp"
            android:scaleType="center"
            android:src="@drawable/video_enlarge"
            android:visibility="gone" />
    </LinearLayout>


    <ImageView
        android:id="@+id/back_tiny"
        android:layout_width="24dp"
        android:layout_height="24dp"
        android:layout_marginLeft="6dp"
        android:layout_marginTop="6dp"
        android:visibility="gone" />

    <LinearLayout
        android:id="@+id/layout_top"
        android:layout_width="match_parent"
        android:layout_height="48dp"
        android:background="@drawable/video_title_bg"
        android:gravity="center_vertical">

        <ImageView
            android:id="@+id/back"
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:paddingLeft="10dp"
            android:scaleType="centerInside"
            android:src="@drawable/video_back" />

        <TextView
            android:id="@+id/title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:paddingLeft="10dp"
            android:textColor="@android:color/white"
            android:textSize="18sp" />
    </LinearLayout>

    <moe.codeest.enviews.ENDownloadView
        android:id="@+id/loading"
        android:layout_width="28dp"
        android:layout_height="28dp"
        android:layout_centerInParent="true"
        android:visibility="invisible" />

    <LinearLayout
        android:id="@+id/startVessel"
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:layout_gravity="center"
        android:gravity="center"
        android:visibility="invisible"
        android:background="@drawable/shaper_button_play">

        <ImageView
            android:id="@+id/videoStart"
            android:layout_width="25dp"
            android:layout_height="25dp"
            android:src="@drawable/play"
            android:layout_marginLeft="3dp" />

    </LinearLayout>

    <ImageView
        android:id="@+id/small_close"
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:paddingLeft="10dp"
        android:paddingTop="10dp"
        android:scaleType="centerInside"
        android:src="@drawable/video_small_close"
        android:visibility="gone" />

    <ImageView
        android:id="@+id/lock_screen"
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:layout_alignParentRight="true"
        android:layout_centerVertical="true"
        android:layout_marginRight="50dp"
        android:scaleType="centerInside"
        android:src="@drawable/unlock"
        android:visibility="gone" />

</RelativeLayout>

        3.2 设置SeekBar进度条的样式(颜色我就不贴了自己尝试)设置播放按钮暂停和播放时控件显示的动画

                3.2.1 设置SeekBar进度条样式mx_bottom_seek_progress.xml

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@android:id/background">
        <shape>
            <solid android:color="@color/lucency_gray" />
            <size android:height="4dp" />
            <corners android:radius="3dp" />
        </shape>
    </item>
    <item android:id="@android:id/secondaryProgress">
        <clip>
            <shape>
                <solid android:color="@color/lucency_gray_lode" />
                <size android:height="4dp" />
                <corners android:radius="3dp" />
            </shape>
        </clip>
    </item>
    <item android:id="@android:id/progress">
        <clip>
            <shape>
                <solid android:color="@color/mx_player_color_progress" />
                <size android:height="4dp" />
                <corners android:radius="3dp" />
            </shape>
        </clip>
    </item>
</layer-list>

                3.2.2  设置播放按钮暂停和播放时控件显示的动画(在res目录下创建anim包)

android gsyvideoplayer,Android,Java,Kotlin,android,kotlin,java

                fade_out.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <scale
        android:interpolator="@android:anim/accelerate_interpolator"
        android:duration="200"
        android:fillAfter="true"
        android:fromXScale="1.0"
        android:fromYScale="1.0"
        android:pivotX="50%"
        android:pivotY="50%"
        android:toXScale="0"
        android:toYScale="0" />
    <!--同时配置淡出功能-->
    <alpha
        android:duration="100"
        android:fillAfter="true"
        android:fromAlpha="1"
        android:toAlpha="0" />
    <translate
        android:duration="500"
        android:fromYDelta="0"
        android:toYDelta="-100%"
        />
</set>

                heartbeat.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <scale
        android:interpolator="@android:anim/decelerate_interpolator"
        android:duration="200"
        android:fillAfter="true"
        android:fillEnabled="true"
        android:fromXScale="0"
        android:fromYScale="0"
        android:pivotX="50%"
        android:pivotY="50%"
        android:toXScale="1.0"
        android:toYScale="1.0" />
    <!--同时配置淡入功能-->
    <alpha
        android:duration="100"
        android:fillAfter="true"
        android:fromAlpha="0"
        android:toAlpha="1" />
    <translate
        android:duration="500"
        android:fromYDelta="0"
        android:toYDelta="0" />
</set>

       3.3 设置SeekBar进度条点击和没被点击时thumb(拇指)的颜色

                3.3.1 tiktok_seek_thumb.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/video_seek_thumb_select" android:state_pressed="true" />
    <item android:drawable="@drawable/video_seek_thumb_unselected" />
</selector>

                3.3.2 video_seek_thumb_select.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">
    <solid android:color="#ffffffff" />
    <size
        android:width="14.0dip"
        android:height="14.0dip" />
</shape>

                3.3.3 video_seek_thumb_unselected.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">
    <solid android:color="@color/seek_thumb_red" />
    <size
        android:width="5.0dip"
        android:height="5.0dip" />
</shape>

        3.4 RecyclerView的子布局item 就是添加自定义布局控件,需要其他控件请自行添

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/black">
        <com.zyy.inpaint.SampleCoverVideo
            android:id="@+id/video_item_player"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

4. 创建工具类 ScrollCalculatorHelper 用于操作播放逻辑

package com.zyy.inpaint

import android.app.AlertDialog
import android.content.Context
import android.graphics.Rect
import android.os.Handler
import android.util.Log
import android.view.View
import android.widget.Toast
import androidx.recyclerview.widget.RecyclerView
import com.shuyu.gsyvideoplayer.utils.NetworkUtils
import com.shuyu.gsyvideoplayer.video.base.GSYBaseVideoPlayer
import kotlinx.coroutines.Runnable

class ScrollCalculatorHelper() {
    private var firstVisible = 0
    private var lastVisible = 0
    private var visibleCount = 0
    private var playId: Int? = null
    var rangeTop: Int? = null
    var rangeBottom: Int? = null
    private var runnable: PlayRunnable? = null

    private val playHandler: Handler = Handler()

    constructor(playId: Int, rangeTop: Int, rangeBottom: Int) : this() {
        this.playId = playId
        this.rangeTop = rangeTop
        this.rangeBottom= rangeBottom
    }

    fun onScrollStateChanged(view: RecyclerView, scrollState: Int){
        when(scrollState){
            RecyclerView.SCROLL_STATE_IDLE -> playVideo(view)
        }
    }

    fun onScroll(view: RecyclerView, firstVisibleItem: Int, lastVisibleItem: Int, visibleItemCount: Int){
        if (firstVisible == firstVisibleItem) {
            return
        }
        firstVisible = firstVisibleItem
        lastVisible = lastVisibleItem
        visibleCount = visibleItemCount
    }

    private fun playVideo(view: RecyclerView){
        val layoutManager: RecyclerView.LayoutManager? = view.layoutManager

        var gsyBaseVideoPlayer: GSYBaseVideoPlayer? = null

        var needPlay = false
        for (i in 0..visibleCount){
            if (layoutManager?.getChildAt(i) != null && layoutManager.getChildAt(i)!!.findViewById<View>(
                    playId!!) != null) {
                val player = layoutManager.getChildAt(i)!!.findViewById<View>(playId!!) as GSYBaseVideoPlayer
                val rect = Rect()
                player.getLocalVisibleRect(rect)
                val height = player.height
                //说明第一个完全可视
                if(rect.top == 0 && rect.bottom == height){
                    gsyBaseVideoPlayer = player
                    if ((player.currentPlayer.currentState == GSYBaseVideoPlayer.CURRENT_STATE_NORMAL
                                || player.currentPlayer.currentState == GSYBaseVideoPlayer.CURRENT_STATE_ERROR)) {
                        needPlay = true
                    }
                    break
                }
            }
        }
        if (gsyBaseVideoPlayer != null && needPlay) {
            if (runnable != null) {
                val tmpPlayer = runnable?.gsyBaseVideoPlayer
                playHandler.removeCallbacks(runnable!!)
                runnable = null
                if (tmpPlayer == gsyBaseVideoPlayer) {
                    return
                }
            }
            runnable = PlayRunnable(gsyBaseVideoPlayer, rangeTop!!, rangeBottom!!)
            //降低频率
            playHandler.postDelayed(runnable!!, 400)
        }
    }

    class PlayRunnable(gsyBaseVideoPlayer: GSYBaseVideoPlayer, rangeTop: Int, rangeBottom: Int) : Runnable{
        var gsyBaseVideoPlayer: GSYBaseVideoPlayer? = gsyBaseVideoPlayer
        val rangeTop1 = rangeTop
        val rangeBottom1 = rangeBottom
        override fun run() {
            var inPosition = false
            //如果未播放, 需要播放
            if(gsyBaseVideoPlayer != null){
                val screenPosition = IntArray(2)
                gsyBaseVideoPlayer?.getLocationOnScreen(screenPosition)
                val halfHeight = gsyBaseVideoPlayer?.height!! / 2
                val rangePosition = screenPosition[1] + halfHeight
                if(rangePosition in (rangeTop1 + 1)..rangeBottom1){
                    inPosition = true
                }
                if(inPosition){
                    gsyBaseVideoPlayer?.context?.let {
                        ScrollCalculatorHelper().startPlayLogic(gsyBaseVideoPlayer!!,
                            it)
                    }
                    //gsyBaseVideoPlayer.startPlayLogic();
                }
            }
        }
    }
    /***************************************自动播放的点击播放确认******************************************/
    private fun startPlayLogic(gsyBaseVideoPlayer: GSYBaseVideoPlayer, context: Context){
        if (!com.shuyu.gsyvideoplayer.utils.CommonUtil.isWifiConnected(context)) {
            //这里判断是否wifi
            showWifiDialog(gsyBaseVideoPlayer, context)
            return
        }
        gsyBaseVideoPlayer.startPlayLogic()
    }

    private fun showWifiDialog(gsyBaseVideoPlayer: GSYBaseVideoPlayer, context: Context){
        if (!NetworkUtils.isAvailable(context)) {
            Toast.makeText(context, context.resources.getString(com.shuyu.gsyvideoplayer.R.string.no_net), Toast.LENGTH_LONG).show()
            return
        }
        Toast.makeText(context, context.resources.getString(com.shuyu.gsyvideoplayer.R.string.tips_not_wifi),Toast.LENGTH_LONG).show()
        gsyBaseVideoPlayer.startPlayLogic()
       /* val builder = AlertDialog.Builder(context)
        builder.setMessage(context.resources.getString(com.shuyu.gsyvideoplayer.R.string.tips_not_wifi))
        builder.setPositiveButton(context.resources.getString(com.shuyu.gsyvideoplayer.R.string.tips_not_wifi_confirm)
        ) { dialog, _ ->
            dialog?.dismiss()
            gsyBaseVideoPlayer.startPlayLogic()
        }
        builder.setNegativeButton(context.resources.getString(com.shuyu.gsyvideoplayer.R.string.tips_not_wifi_cancel)
        ) { dialog, _ -> dialog?.dismiss() }
        builder.create().show()*/
    }
}

5. 主Activity MainActivity 如果你们是Fragment一样的代码不需要改动 activity中的布局只有一个RecyclerView (videos是网络视频路径,我这里做的时假数据,如果网络请求也是同理,Paging3在RecyclerView中获取网络视频路径,基本上按照我下面的代码,你用不用Paging3都是这样写)

package com.zyy.inpaint

import android.content.pm.ActivityInfo
import android.content.res.Configuration
import android.os.Build
import android.os.Bundle
import android.transition.Explode
import android.view.Window
import android.widget.SeekBar
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.PagerSnapHelper
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SimpleItemAnimator
import com.shuyu.gsyvideoplayer.GSYVideoManager
import com.shuyu.gsyvideoplayer.utils.CommonUtil
import com.zyy.inpaint.databinding.ActivityMainBinding
import com.zyy.inpaint.view_model.ActivityViewModel


class MainActivity : AppCompatActivity() {
    private val TAG = MainActivity::class.simpleName
    private lateinit var atViewModel: ActivityViewModel
    private var pagerSnapHelper: PagerSnapHelper? = null
    private var shrotVideoAdapter: RecyclerNormalAdapter? = null
    private lateinit var scrollCalculatorHelper: ScrollCalculatorHelper
    private val binding: ActivityMainBinding by lazy{
        ActivityMainBinding.inflate(layoutInflater)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
            window.requestFeature(Window.FEATURE_CONTENT_TRANSITIONS)
            window.enterTransition = Explode()
            window.exitTransition = Explode()
        }
        setContentView(binding.root)
        //限定范围为屏幕一半的上下偏移180
        val playTop = CommonUtil.getScreenHeight(this) / 2 - CommonUtil.dip2px(this, 180f)
        val playBottom = CommonUtil.getScreenHeight(this) / 2 + CommonUtil.dip2px(this, 180f)
        scrollCalculatorHelper = ScrollCalculatorHelper(R.id.video_item_player, playTop, playBottom)

        val videos = listOf(
            "https://vd2.bdstatic.com/mda-pa8gdgeje11q0juv/sc/cae_h264/1673265652790205165/mda-pa8gdgeje11q0juv.mp4?v_from_s=hkapp-haokan-nanjing&auth_key=1673794885-0-0-3ed5f7ef458037d258774634a28c24bc&bcevod_channel=searchbox_feed&pd=1&cd=0&pt=3&logid=1885824382&vid=3653968767863324420&abtest=106846_2-106991_1&klogid=1885824382",
            "https://vd2.bdstatic.com/mda-paebp8x9m1f23277/sc/cae_h264/1673771465094233171/mda-paebp8x9m1f23277.mp4?v_from_s=hkapp-haokan-nanjing&auth_key=1673835039-0-0-82fe31f4dceacc147007fc4a04b86294&bcevod_channel=searchbox_feed&pd=1&cd=0&pt=3&logid=2439604427&vid=5532715800795306313&abtest=106846_2-106991_1&klogid=2439604427",
            "https://vd2.bdstatic.com/mda-nar659w01c98k85d/sc/cae_h264_nowatermark_delogo/1643171162969466355/mda-nar659w01c98k85d.mp4?v_from_s=hkapp-haokan-nanjing&auth_key=1673835494-0-0-b89e8c9cc6acd00019738c807647eba9&bcevod_channel=searchbox_feed&pd=1&cd=0&pt=3&logid=2894791446&vid=11604659103229506139&abtest=106846_2-106991_1&klogid=2894791446",
            "https://vd2.bdstatic.com/mda-mc2h3k988k3b1mbh/v1-cae/sc/mda-mc2h3k988k3b1mbh.mp4?v_from_s=hkapp-haokan-nanjing&auth_key=1673794689-0-0-85852c703a917d14a6e07cf48c96dbad&bcevod_channel=searchbox_feed&pd=1&cd=0&pt=3&logid=1689187715&vid=2424761345650013440&abtest=106846_2-106991_1&klogid=1689187715",
            "https://vd2.bdstatic.com/mda-nh99crj71cen9809/sc/cae_h264/1660116949250702198/mda-nh99crj71cen9809.mp4?v_from_s=hkapp-haokan-nanjing&auth_key=1673835855-0-0-8fa1bf15105827d15e3774705945b144&bcevod_channel=searchbox_feed&pd=1&cd=0&pt=3&logid=3254897660&vid=6897246374997195249&abtest=106846_2-106991_1&klogid=3254897660",
        )

        atViewModel = ViewModelProvider(this)[ActivityViewModel::class.java]

        binding.videoRecyclerView.apply {
            shrotVideoAdapter = RecyclerNormalAdapter(videos, this@MainActivity)
            //关闭recycleView动画
            (this.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
            //抖音都在用的recycleView滑动模板
            pagerSnapHelper = PagerSnapHelper()
            pagerSnapHelper?.attachToRecyclerView(this)
            adapter = shrotVideoAdapter
            //recycleView适配
            layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
        }
        binding.videoRecyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener(){
            var firstVisibleItem: Int? = null
            var lastVisibleItem: Int? = null
            /**
             * @param recyclerView 当前RecyclerView
             * @param dx 水平滚动距离
             * @param dy 垂直滚动距离
             * dx > 0 时为手指向左滑动,列表滚动显示右面的内容
             * dx < 0 时为手指向右滑动,列表滚动显示左面的内容
             * dy > 0 时为手指向上滑动,列表滚动显示下面的内容
             * dy < 0 时为手指向下滑动,列表滚动显示上面的内容
             */
            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
                super.onScrolled(recyclerView, dx, dy)
                val layoutManager = recyclerView.layoutManager as LinearLayoutManager
                firstVisibleItem = layoutManager.findFirstVisibleItemPosition()// 在屏幕可见区域的第一项位置 : 通过 findFirstVisibleItemPosition() 方法获取
                lastVisibleItem = layoutManager.findLastVisibleItemPosition() //在屏幕可见区域的最后一项位置 : 通过 findLastVisibleItemPosition() 方法获取
                /**
                 * 为什么需要以上值,这里说下整体思路:
                 *(1)获取当前处于屏幕可见的列表
                 *(2)滑出屏幕的视频我们需要回收掉
                 *(3)当屏幕处于静止状态时我们才开始播放视频
                 */
                //一屏幕显示一个item 所以固定1
                //实时获取设置 当前显示的GSYBaseVideoPlayer的下标
                scrollCalculatorHelper.onScroll(recyclerView, firstVisibleItem!!, lastVisibleItem!!,  1)
            }

            /**
             * 滚动状态
             * @param newState 给我们返回三种状态 方法体内的三中滚动
             * 屏幕处于静止时才开始播放,只要播放的逻辑写在 onScrollStateChanged 的 SCROLL_STATE_IDLE 状态下即可;
             */
            override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
                super.onScrollStateChanged(recyclerView, newState)
                scrollCalculatorHelper.onScrollStateChanged(recyclerView, newState)
            }
        })
    }

    @Deprecated("Deprecated in Java")
    override fun onBackPressed() {
        if(GSYVideoManager.backFromWindowFull(this)){
            return
        }
        super.onBackPressed()
    }

    override fun onPause() {
        super.onPause()
        GSYVideoManager.onPause()
    }

    override fun onResume() {
        super.onResume()
        GSYVideoManager.onResume()
    }

    override fun onDestroy() {
        super.onDestroy()
        GSYVideoManager.releaseAllVideos()
    }


}

6. RecyclerView适配器(RecyclerViewNormalAdapter)

package com.zyy.inpaint

import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.recyclerview.widget.RecyclerView
import com.zyy.inpaint.databinding.ItemShortVideoBinding

class RecyclerNormalAdapter(var itemDataList: List<String>, var context: Context):
    RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    companion object {
        var TAG : String = "RecyclerBaseAdapter"
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val view: ItemShortVideoBinding = DataBindingUtil.inflate(
            LayoutInflater.from(parent.context),
            R.layout.item_short_video, parent, false
        )
        return RecyclerItemNormalHolder(view)
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        val recyclerItemNormalHolder = holder as RecyclerItemNormalHolder
        recyclerItemNormalHolder.recyclerBaseAdapter = this@RecyclerNormalAdapter
        holder.binding?.videoItemPlayer?.let {
            recyclerItemNormalHolder.onBind(position, itemDataList[position],
                it)
        }
    }

    override fun getItemCount() = itemDataList.size

}

7. ViewHolder (RecyclerItemNormalHodler / RecyclerItemBaseHolder)

       7.1 RecyclerItemNormalHodler .java

package com.zyy.inpaint

import android.content.Context
import android.telecom.VideoProfile
import android.view.MotionEvent
import android.view.View
import android.widget.ImageView
import android.widget.Toast
import com.shuyu.gsyvideoplayer.GSYVideoManager
import com.shuyu.gsyvideoplayer.builder.GSYVideoOptionBuilder
import com.shuyu.gsyvideoplayer.listener.GSYSampleCallBack
import com.shuyu.gsyvideoplayer.model.VideoOptionModel
import com.zyy.inpaint.databinding.ItemShortVideoBinding
import kotlinx.android.synthetic.main.layout_video_cover.view.*
import tv.danmaku.ijk.media.player.IjkMediaPlayer

class RecyclerItemNormalHolder(view : View): RecyclerItemBaseHolder(view){
    val TAG: String = "RecyclerView2List"
    var imageView: ImageView? = null
    var gsyVideoOptionBuilder: GSYVideoOptionBuilder? = null

    var binding: ItemShortVideoBinding? = null
    var context: Context? = null
    constructor(binding: ItemShortVideoBinding): this(binding.root){
        this.binding = binding
        this.context = app.getContext()
        imageView = ImageView(context)
        gsyVideoOptionBuilder = GSYVideoOptionBuilder()
    }

    fun onBind(position: Int, videoModel: String, gsyVideoPlayer: SampleCoverVideo){
       /* gsyVideoPlayer.onPauseListener = {
            if(gsyVideoPlayer.gsyVideoManager.isPlaying){
                gsyVideoPlayer.onVideoPause()
            } else {
                gsyVideoPlayer.onVideoResume(false)
            }
            gsyVideoPlayer.showHiddenButtonStart() //显示或隐藏播放按钮

        }*/

        val url: String = videoModel
        val title: String = "6664565"
        //防止错位,离开释放
        //gsyVideoPlayer.initUIState();
        val header: MutableMap<String, String> = HashMap()
        header["ee"] = "33"
        gsyVideoOptionBuilder?.setIsTouchWiget(false)?.setUrl(url)?.setVideoTitle(title)?.setStartAfterPrepared(true)
            ?.setCacheWithPlay(false)?.setRotateViewAuto(false)?.setLockLand(true)?.setPlayTag(TAG)
            ?.setShowFullAnimation(true)?.setNeedLockFull(true)?.setLooping(true)
            ?.setPlayPosition(position)?.setMapHeadData(header)
            ?.setVideoAllCallBack(object : GSYSampleCallBack(){
                override fun onPrepared(url: String?, vararg objects: Any?) {
                    super.onPrepared(url, *objects)
                    if (!gsyVideoPlayer.isIfCurrentIsFullscreen) {
                        //静音
                        GSYVideoManager.instance().isNeedMute = false
                    }
                }
                override fun onQuitFullscreen(url: String?, vararg objects: Any?) {
                    super.onQuitFullscreen(url, *objects)
                    //全屏不静音
                    GSYVideoManager.instance().isNeedMute = false
                }
                override fun onEnterFullscreen(url: String?, vararg objects: Any?) {
                    super.onEnterFullscreen(url, *objects)
                    GSYVideoManager.instance().isNeedMute = false
                    gsyVideoPlayer.currentPlayer?.titleTextView?.text = objects[0] as String
                }
            })?.build(gsyVideoPlayer)

        //隐藏title
        gsyVideoPlayer.titleTextView?.visibility = View.GONE
        //设置返回键
        gsyVideoPlayer.backButton?.visibility = View.GONE
        //设置全屏按键功能
        gsyVideoPlayer.fullscreenButton?.setOnClickListener { resolveFullBtn(gsyVideoPlayer) }
        gsyVideoPlayer.loadCoverImage("https://ts4.cn.mm.bing.net/th?id=OIP-C.D68LlaxVgTAyk2bXd0fLTQHaEK&w=333&h=187&c=8&rs=1&qlt=90&o=6&dpr=1.1&pid=3.1&rm=2", R.mipmap.ic_launcher)
        //实现第一个item视频自动播放 由于自动播放是监听RecyclerView滑动时触发,所以第一个item不会自动播放
        if(position == 0){
            gsyVideoPlayer.startPlayLogic()
        }

    }

    /**
     * 全屏幕按键处理
     */
    private fun resolveFullBtn(standardGSYVideoPlayer: SampleCoverVideo) {
        standardGSYVideoPlayer.startWindowFullscreen(context, actionBar = false, statusBar = true)
    }



}

       7.2 RecyclerItemBaseHolder

package com.zyy.inpaint

import android.view.View
import androidx.recyclerview.widget.RecyclerView

open class RecyclerItemBaseHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
    //该adapter主要用于操作数据源刷新等等操作,方便Holder直接调用
    lateinit var recyclerBaseAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>
}

8. 工具类用于根据手机分辨率从(dp单位转化为px(像素) / px像素转化为dp)

package com.zyy.inpaint

import android.content.Context

class Util {
    companion object{
        /**
         * 根据手机的分辨率从 dp 的单位 转成为 px(像素)
         */
        fun dip2px(context: Context, dpValue: Float): Int{
            val scale = context.resources.displayMetrics.density
            return (dpValue * scale + 0.5f).toInt()
        }

        /**
         * 根据手机的分辨率从 px(像素) 的单位 转成为 dp
         */
        fun px2dip(context: Context, pxValue: Float): Int{
            val scale = context.resources.displayMetrics.density
            return (pxValue / scale + 0.5f).toInt()
        }
    }
}

好了到这里就结束了,如有疑问请在评论区留言

如果对你有帮助请一键三连哦!!以防丢失,后续还会出各种实战需要使用到的场景(也可以在评论区或私信留言需要什么功能需求,我尽量满足大家)

注:如有侵权请私信联系我删除文章来源地址https://www.toymoban.com/news/detail-776347.html

到了这里,关于基于GSYVideoPlayer自定义布局结合RecyclerView高仿抖音实现上下滑动双击屏幕点赞/单击暂停,拖动进度条实时改变时间以及进度条放大的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • HarmonyOS鸿蒙基于Java开发: Java UI 自定义布局

    当Java UI框架提供的布局无法满足需求时,可以创建自定义布局,根据需求自定义布局规则。 Component类相关接口  表1  Component类相关接口 接口名称 作用 setEstimateSizeListener 设置测量组件的侦听器 setEstimatedSize 设置测量的宽度和高度 onEstimateSize 测量组件的大小以确定宽度和高度

    2024年02月19日
    浏览(54)
  • [Android]自定义RecyclerView中View的动画

    官方有一个默认Item动画类DafaultItemAnimator,其中 DefaultItemAnimator 继承了SimpleItemAnimator 继承了 RecyclerView.ItemAnimator SimpleItemAnimator 它是一个包装类,用来判断当前的ViewHolder到底是执行移动、移除、添加或者改变等行为。 DefaultItemAnimator 是执行具体动画类,它负责将viewHolder初始化

    2024年02月11日
    浏览(67)
  • 元宇宙NFG系统,结合社交电商,是虚实结合交易的重要布局

    互联网时代已经发展了近几十年,它的市场已经越来越成熟,越来越多的行业被互联网“改造”,互联网企业甚至喊出了“不拥抱互联网,就要被淘汰”的口号。在这样的背景下,如何借助互联网的东风,实现企业乃至产业链的转型升级,是业界共同面临的新课题、新挑战、

    2023年04月09日
    浏览(48)
  • android pdf框架-3,基于recyclerview修改

    基于recyclerview的实现版本 解析使用的是pdifum.这个库缺点是缩放功能不行.点击链接功能没有.只有渲染. GitHub - danjdt/android-pdfviewer: A Android PDF Viewer that render pdf using PdfRenderer and displays it in a RecyclerView. recyclerview的滑动并不是像ios那样,有很好的惯性,针对此,从ebookdroid中拿了flinge

    2024年02月19日
    浏览(36)
  • MaterialSkin与系统Panel容器结合使用,实现自适应舒适布局

    你是否也有这样的疑惑,当窗口拖拽之后,要如何才能填补右侧和下方的空缺? 有些人直接固定窗体大小,不允许用户拖拽。。。 也有萌新直接问我,MaterialSkin里面没有什么办法能让拖拽的时候,控件也跟着动吗? 其实这些功能在 系统的Panel 里面早就有了,只是大家不知道

    2024年02月06日
    浏览(42)
  • element-UI Pagination 分页 布局,自定义布局

    分页左右布局,自定义布局 elm 分页默认布局是 左对齐的 我们这节要实现的效果是这样 (主要是拆分 分页每个一项) 两端对齐用的比较多 或者这样 直接上代码 主要通过 loyout 属性 如果你想要图2上的布局如上代码 你想要左中右布局图三效果 你需要用三个 el-pagination 只需要指

    2024年02月16日
    浏览(43)
  • Android的 AlertDialog自定义布局与常用布局用法(弹窗)

    1.直接上效果图,看看是不是你们想要的效果图 2.主活动MainActivity2的代码如下

    2024年02月12日
    浏览(37)
  • Pycharm保存自定义布局

    在View-Tool Windows下可以启用特定窗口,窗口标签会出现在左边(图中红框处),下边或右边,可以拖动摆放位置 在windows-layout下可以选择保存布局 1.图中第一个选项:选择或创建布局 2.图中第二个选项:恢复现有布局(将你现在看到的界面恢复为你选择的布局) 3.图中第三个

    2024年02月10日
    浏览(32)
  • G6绘制树形图(自定义节点、自定义边、自定义布局)

    在 registerNode 中定义所有的节点 为了使用内置的布局方式,选择参数为 ‘tree-node’ 树节点类型,数据格式可以存在children子节点,效果自动生成子树 cfg 可以拿到数据,如cfg.id、cfg.name 使用 group.addShape(‘rect’, {}) 定义节点 rect 配置参数:https://antv-g6.gitee.io/zh/docs/api/shapeProp

    2024年02月05日
    浏览(35)
  • 开源播放器GSYVideoPlayer的简单介绍及播放rtsp流的优化

    本文介绍,开源播放器GSYVideoPlayer的简单介绍及播放rtsp流的优化 github地址: https://github.com/CarGuo/GSYVideoPlayer 让我们看看介绍: 视频播放器(IJKplayer、ExoPlayer、MediaPlayer),HTTPS支持,支持弹幕,支持滤镜、水印、gif截图,片头广告、中间广告,多个同时播放,支持基本的拖动

    2024年02月06日
    浏览(46)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包