为了账号安全,请及时绑定邮箱和手机立即绑定

高仿抖音播放(二)——代码的实现

这是第「30篇」原创分享
朝阳杨少爷(ID:CY_YANG_DA_YE),专注于Android领域的开发者。

GitHub地址:https://github.com/yang0range/DYVideo
欢迎star!

需求的实现

上一篇,文章展示了实现的效果,并且陈列了我们要实现的功能。接下来通过两篇文章,详细的实现以下整个的过程。

代码详解

准备工作

创建项目,因为播放视频要在网络播放,所以需要添加网络权限。

    <uses-permission android:name="android.permission.INTERNET" />

该项目的代码主要是采用Kotlin写的,不太熟悉的同学自行转成Java

创建BaseAcivity

package com.yang.dyvideo.activity

import android.os.Bundle
import android.support.v7.app.AppCompatActivity

/**
 * @author yangzc
 *	@data 2019/9/26 18:35
 *	@desc BaseAcivity
 *
 */
abstract class BaseAcivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(layoutId())
        initData()
        initView()
        start()
        initListener()
    }

    abstract fun initListener()

    /**
     * 初始化数据
     */
    abstract fun initData()

    /**
     * 初始化 View
     */
    abstract fun initView()

    /**
     * 开始请求
     */
    abstract fun start()

    /**
     *  加载布局
     */
    open fun layoutId(): Int {
        return 0
    }


}

首页的列表页

package com.yang.dyvideo.activity

import android.support.v7.widget.LinearLayoutManager
import android.view.View
import com.yang.dyvideo.R
import com.yang.dyvideo.adapter.VideoAdapter
import com.yang.dyvideo.data.Video
import com.yang.dyvideo.data.VideoDataProvider
import kotlinx.android.synthetic.main.activity_main.*
import java.util.*

class MainActivity : BaseAcivity() {
    private var videolist: MutableList<Video>? = null
    private var mAdapter: VideoAdapter? = null


    override fun initListener() {
        mAdapter?.let {
            it.setOnItemClickListener(object : VideoAdapter.OnItemClickListener {
                override fun onItemClick(view: View, position: Int, mVideo: Video) {
                    startActivity(VideoPlayActivity.buildIntent(this@MainActivity, position, videolist as ArrayList<Video>?))
                }

            })

        }

    }
        override fun initData() {
            videolist = ArrayList()

            for (i in 0 until 15) {
                val mVideo = Video()
                mVideo.iamge = VideoDataProvider.getVideoListImg(this).getResourceId(i, R.mipmap.one)
                mVideo.title = VideoDataProvider.getVideoListTitle(this).getString(i)
                mVideo.videoplayer = VideoDataProvider.getVideoPlayer(this).getString(i)
                (videolist as ArrayList<Video>).add(mVideo)
            }


        }

        override fun initView() {

        }

        override fun start() {
            mAdapter = videolist?.let { VideoAdapter(this, it) }
            rlv.adapter = mAdapter
            val linearLayoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
            rlv.layoutManager = linearLayoutManager
        }


        override fun layoutId(): Int {
            return R.layout.activity_main
        }
    }

首页的Adapter

package com.yang.dyvideo.adapter

import android.content.Context
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView

import com.yang.dyvideo.R
import com.yang.dyvideo.data.Video

/**
 * @author yangzc
 * @data 2019/9/27 11:17
 * @desc
 */
 class VideoAdapter(private val mContext: Context, private val mVideoList: List<Video>) : RecyclerView.Adapter<VideoAdapter.VideoPlayerViewHolder>() {


    private var onItemClickListener: OnItemClickListener? = null

    //点击事件的接口
    interface OnItemClickListener {
        fun onItemClick(view: View, position: Int, mVideo: Video)

    }

    fun setOnItemClickListener(listener: OnItemClickListener) {
        this.onItemClickListener = listener
    }


    override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): VideoPlayerViewHolder {
        return VideoPlayerViewHolder(LayoutInflater.from(mContext).inflate(R.layout.item_video_list, viewGroup, false))
    }

    override fun onBindViewHolder(ViewHolder: VideoPlayerViewHolder, position: Int) {
        val mVideo = mVideoList[position]
        ViewHolder.dy_iv.setImageResource(mVideo.iamge)
        ViewHolder.dy_tv.text = mVideo.title
        setOnItemClick(ViewHolder)

    }


    private fun setOnItemClick(holder: VideoPlayerViewHolder) {
        if (this.onItemClickListener != null) {
            holder.itemView.setOnClickListener(object : View.OnClickListener {
                override fun onClick(v: View) {
                    run {
                        val position = holder.layoutPosition
                        this@VideoAdapter.onItemClickListener!!.onItemClick(holder.itemView, position, mVideoList[position])
                    }

                }
            })
        }
    }


    override fun getItemCount(): Int {
        return if (mVideoList.isEmpty()) 0 else mVideoList.size
    }


    inner class VideoPlayerViewHolder internal constructor(itemView: View) : RecyclerView.ViewHolder(itemView) {
        internal val dy_iv: ImageView = itemView.findViewById(R.id.dy_iv)
        internal val dy_tv: TextView = itemView.findViewById(R.id.dy_tv)

    }
}

实现需求——上下滑动播放详情页

这几部分没有什么可说的,都是常规的实现方式。接下来,我们来看看最主要的播放的详情页的实现。

接下来,看看我们的详情页的布局.

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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="#2D2D33"
    tools:ignore="MissingConstraints,ContentDescription">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/rlv_play_video"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:descendantFocusability="beforeDescendants" />


    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintBottom_toTopOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="parent">

        <ImageView
            android:id="@+id/iv_user_avatar"
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="@mipmap/default_avater" />

        <ImageView
            android:id="@+id/iv_user_follow"
            android:layout_width="36dp"
            android:layout_height="36dp"
            android:layout_gravity="center_horizontal"
            android:layout_marginTop="20dp"
            android:class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="@mipmap/ic_love_white" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:layout_marginTop="10dp"
            android:text="@string/num"
            android:textColor="@color/colorWhite"
            android:textSize="14sp" />

        <ImageView
            android:layout_width="36dp"
            android:layout_height="36dp"
            android:layout_gravity="center_horizontal"
            android:layout_marginTop="15dp"
            android:class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="@mipmap/ic_comment" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:layout_marginTop="10dp"
            android:text="@string/num"
            android:textColor="@color/colorWhite"
            android:textSize="14sp" />

        <ImageView
            android:layout_width="36dp"
            android:layout_height="36dp"
            android:layout_gravity="center_horizontal"
            android:layout_marginTop="20dp"
            android:class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="@mipmap/ic_share" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:layout_marginTop="10dp"
            android:text="@string/num"
            android:textColor="@color/colorWhite"
            android:textSize="14sp" />
    </LinearLayout>


    <ImageView
        android:id="@+id/iv_video_play"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@mipmap/ic_video_play"
        android:visibility="visible"
        app:layout_constraintBottom_toTopOf="parent"
        app:layout_constraintLeft_toRightOf="parent"
        app:layout_constraintRight_toLeftOf="parent"
        app:layout_constraintTop_toBottomOf="parent" />

</android.support.constraint.ConstraintLayout>

之前,介绍过我们这次采用的是 RecyclerView来实现的效果。

详情的播放页,才用的是Android自带的VideoView,为了实现我们想要的全屏的效果,我们需要简单的自定义一下。

package com.yang.dyvideo.widget;

import android.content.Context;
import android.util.AttributeSet;
import android.widget.VideoView;

public class FullWindowVideoView extends VideoView {

    public FullWindowVideoView(Context context) {
        super(context);
    }

    public FullWindowVideoView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public FullWindowVideoView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }


}

那接下来看一下Adapter的布局的实现

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/root_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent">


    <com.yang.dyvideo.widget.FullWindowVideoView
        android:id="@+id/surface_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />


    <ImageView
        android:id="@+id/iv_video_cover"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:clickable="false"
        android:focusable="false"
        android:scaleType="centerInside"
        android:visibility="visible" />




</android.support.constraint.ConstraintLayout>

在播放页面,我们添加了一个播放的按钮。
再看看Adapter当中的代码

package com.yang.dyvideo.adapter

import android.content.Context
import android.media.MediaPlayer
import android.net.Uri
import android.os.Build
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import com.yang.dyvideo.R
import com.yang.dyvideo.data.Video
import com.yang.dyvideo.widget.FullWindowVideoView

/**
 * @author yangzc
 * @data 2019/9/29 15:25
 * @desc
 */
class VideoPlayAdapter (private val mContext: Context, private val mVideoList: List<Video>): RecyclerView.Adapter<VideoPlayAdapter.VideoPlayAdapterViewHolder>() {
    private var onItemClickListener: OnItemClickListener? = null


    fun setOnItemClickListener(listener: OnItemClickListener) {
        this.onItemClickListener = listener
    }

    interface OnItemClickListener {
        fun onItemClick(view: View, position: Int)

    }

    override fun onBindViewHolder(mViewHolder: VideoPlayAdapterViewHolder, position: Int) {
        val mVideo = mVideoList[position]
        mViewHolder.iv_video_cover.setImageResource(mVideo.iamge)
        mViewHolder.surface_view.setVideoURI(Uri.parse(mVideo.videoplayer))
//        mViewHolder.surface_view.setVideoURI(Uri.parse("http://jzvd.nathen.cn/b201be3093814908bf987320361c5a73/2f6d913ea25941ffa78cc53a59025383-5287d2089db37e62345123a1be272f8b.mp4"))
        if (!mViewHolder.surface_view.isPlaying()) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
                mViewHolder.surface_view.setOnPreparedListener { mp ->
                    mp.setOnInfoListener(MediaPlayer.OnInfoListener { mp, what, extra ->
                        if (what == MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START) {
                            mp.isLooping = true
                            mViewHolder.iv_video_cover.animate().alpha(0f).setDuration(0).start()
                            mp.setVideoScalingMode(MediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT)

                            return@OnInfoListener true

                        }
                        false
                    })
                }
            }
            mViewHolder.surface_view.start()

        }


        setOnItemClick(mViewHolder)
    }

    override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int):VideoPlayAdapterViewHolder {
        return VideoPlayAdapterViewHolder(LayoutInflater.from(mContext).inflate(R.layout.item_video_play, viewGroup, false))
    }


    override fun getItemCount(): Int {
        return if (mVideoList.isEmpty()) 0 else mVideoList.size
    }

    protected fun setOnItemClick(holder: VideoPlayAdapterViewHolder) {
        if (onItemClickListener != null) {
            //为holder增加点击事件
            //为了保持插入和删除的position正确 不采用getview的position
            holder.itemView.setOnClickListener(object : View.OnClickListener {
                override fun onClick(v: View) {
                    run {
                        val position = holder.getLayoutPosition()
                        onItemClickListener!!.onItemClick(holder.itemView, position)
                    }

                }
            })
        }
    }

    inner class VideoPlayAdapterViewHolder internal constructor(itemView: View) : RecyclerView.ViewHolder(itemView) {
              internal  val  surface_view : FullWindowVideoView = itemView.findViewById(R.id.surface_view)
              internal  val  iv_video_cover : ImageView = itemView.findViewById(R.id.iv_video_cover)
    }
}


Adapter已经完成,接下来再看看如何自动实现上下滑动的时候
我们需要自定一下LayoutManager,通过自定义 LayoutManager 来实现我们对于 RecyclerView的滑动监听

package com.yang.dyvideo.utils

import android.content.Context
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.PagerSnapHelper
import android.support.v7.widget.RecyclerView
import android.view.View
import com.yang.dyvideo.presenter.OnViewPagerListener

/**
 * @author yangzc
 *	@data 2019/5/29 14:14
 *	@desc
 *
 */
class MyLayoutManager : LinearLayoutManager, RecyclerView.OnChildAttachStateChangeListener {


    constructor(context: Context) : super(context)

    constructor(context: Context, @RecyclerView.Orientation orientation: Int,
                reverseLayout: Boolean) : super(context, orientation, reverseLayout) {
        pagerSpaner = PagerSnapHelper()
    }

    var pagerSpaner: PagerSnapHelper? = null
    var viewPagerListener: OnViewPagerListener? = null
    var diffY = 0

    override fun onAttachedToWindow(view: RecyclerView) {
        super.onAttachedToWindow(view)
        view.addOnChildAttachStateChangeListener(this)
        pagerSpaner!!.attachToRecyclerView(view)
    }


    override fun onChildViewDetachedFromWindow(p0: View) {
        val position = getPosition(p0)
        if (0 < diffY) {
            viewPagerListener?.onPageRelease(true, position)
        } else {
            viewPagerListener?.onPageRelease(false, position)
        }
    }

    override fun onChildViewAttachedToWindow(p0: View) {

        val position = getPosition(p0)
        if (0 == position) {
            viewPagerListener?.onPageSelected(position, false)
        }


    }

    override fun onScrollStateChanged(state: Int) {
        if (RecyclerView.SCROLL_STATE_IDLE == state) {
            val view = pagerSpaner!!.findSnapView(this)
            val position = getPosition(view!!)
            viewPagerListener?.onPageSelected(position, position == itemCount - 1)
        }
        super.onScrollStateChanged(state)
    }

    fun setOnViewPagerListener(listener: OnViewPagerListener) {
        viewPagerListener = listener
    }

    override fun scrollVerticallyBy(dy: Int, recycler: RecyclerView.Recycler?, state: RecyclerView.State?): Int {
        diffY = dy
        return super.scrollVerticallyBy(dy, recycler, state)
    }
}

定义完成之后,需要实现播放和停止播放的两个方法,这两个方法相对就比较简单了,分别是

releaseVideo

   private fun releaseVideo(index: Int) {
        val itemView = rlv_play_video.getChildAt(index)
        val videoView = itemView.findViewById<FullWindowVideoView>(R.id.surface_view)
        val imgThumb = itemView.findViewById<ImageView>(R.id.iv_video_cover)
        videoView.stopPlayback()
        imgThumb.animate().alpha(1f).start()

    }

playVideo

    private fun playVideo(position: Int) {
        val itemView = rlv_play_video.getChildAt(position)
        val videoView = itemView.findViewById<FullWindowVideoView>(R.id.surface_view)
        val imgThumb = itemView.findViewById<ImageView>(R.id.iv_video_cover)
        val mediaPlayer = arrayOfNulls<MediaPlayer>(1)

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            videoView.setOnInfoListener { mp, what, extra ->
                mediaPlayer[0] = mp
                mp.isLooping = true
                imgThumb.animate().alpha(0f).start()
                mp.setVideoScalingMode(MediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT)
                false
            }
        }
        videoView.start()

        if (iv_video_play.visibility == View.VISIBLE) {
            iv_video_play.visibility = View.GONE
        }
    }

结合我们之前自定义的滑动监听,就可以实现 上下滑动播放详情页的效果了

       this.myLayoutManager?.setOnViewPagerListener(object : OnViewPagerListener {
            override fun onInitComplete() {

            }

            override fun onPageRelease(isNext: Boolean, position: Int) {
                var index: Int
                index = if (isNext) {
                    0
                } else {
                    1
                }
                releaseVideo(index)
            }

            override fun onPageSelected(position: Int, bottom: Boolean) {
                playVideo(0)
            }
        })

截止目前为止,我们需要实现的第一个需求 上下滑动播放详情页已经实现了。

点击查看更多内容
1人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
移动开发工程师
手记
粉丝
35
获赞与收藏
116

关注作者,订阅最新文章

阅读免费教程

感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消