高仿抖音播放(二)——代码的实现
这是第「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人点赞
评论
共同学习,写下你的评论
评论加载中...
作者其他优质文章
正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦