需求描述
最近的开发需求,实现类似某音的功能,大致就是界面横向滑动,加载不同分类的视频列表,纵向滑动加载某分类下的视频列表,然后进行短视频的播放,具体短视频内的 点赞、关注、评论等,暂且不提。目前已上线几个版本,还算稳定,做个总结,希望能帮到有这方面需求的朋友。
视图选择
app 基于androidx,使用ViewPager2,具体实现思路:
一、界面的横向滑动,使用ViewPager2+TabLayout 达到界面效果,布局文件大致简单如下:
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/videoViewPager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:overScrollMode="never" />
<com.google.android.material.tabs.TabLayout
android:id="@+id/videoTabLayout"
android:layout_width="wrap_content"
android:layout_height="@dimen/dp_30"
android:layout_marginTop="@dimen/dp_30"
android:layout_marginLeft="@dimen/dp_5"
android:background="@color/translucent"
app:tabIndicatorColor="@color/white"
app:tabIndicatorFullWidth="false"
app:tabIndicatorHeight="@dimen/dp_2"
app:tabRippleColor="@color/translucent"
app:tabSelectedTextColor="@color/white"
app:tabTextAppearance="@style/video_tabLayout_text"
app:tabTextColor="@color/video_tab_unselect" />
</FrameLayout>
布局文件是从项目里简单抽出来的,videoViewPager控制主界面,tablayout横向滑动标签,数据源使用FragmentStateAdapter,控制横向滑动的视图,代码大致简单如下:
// 横向滑动时的Tab页
videoTabLayout!!.addTab(videoTabLayout!!.newTab().setText("关注"));
videoTabLayout!!.addTab(videoTabLayout!!.newTab().setText("推荐"), true);
//横向滑动的Fragment视图
videoTypeFragmentAdapter = ShortVideoTypeFragmentAdapter(this, videoTabLayout!!.tabCount)
videoViewPager!!.adapter = videoTypeFragmentAdapter
//创建横向展现的视图Fragment
override fun createFragment(position: Int): Fragment {
when (position) {
Int_ZREO -> {
return ShortVideoTypeFragment1(Int_ZREO)
}
Int_ONE -> {
return ShortVideoTypeFragment2(Int_ONE )
}
else -> {
return ShortVideoTypeFragment1(Int_ZREO)
}
}
}
这里使用TabLayout的addOnTabSelectedListener方法控制 选中tab时,设置videoViewPager对应的Item,使用ViewPager2的registerOnPageChangeCallback方法控制选中某Page时同时选中对应的Tab;最后设置videoViewPager的当前Item,这里设置显示第二个Fragment视图(index=1),注意setCurrentItem的第二个参数smoothScroll,如果给true,尽管ViewPager2默认不预加载,他也默认会创建第一个Fragment视图(index=0),当设置flase时,如果你的offscreenPageLimit参数不设置,他默认不会创建第一个Fragment视图。
// 选中Tab页时,选中当前Pager
videoTabLayout!!.addOnTabSelectedListener(object : OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab) {
videoViewPager!!.currentItem = tab.position
}
override fun onTabUnselected(tab: TabLayout.Tab) {}
override fun onTabReselected(tab: TabLayout.Tab) {}
})
// 选中当前页时,选中对应的Tab页
videoViewPager!!.registerOnPageChangeCallback(object : OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
videoTabLayout!!.selectTab(videoTabLayout!!.getTabAt(position))
videoTabLayout!!.setScrollPosition(position, 0f, false)
}
})
videoViewPager!!.setCurrentItem(1, false)
二、界面的纵向滑动,FragmentStateAdapter创建的Fragment的内容视图直接使用ViewPager2,设置orientation属性即可,布局大概简单如下:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/rootFrameLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/videoSwipeRefreshLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/videoPlayPager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</FrameLayout>
ViewPager2 支持纵向布局,直接设置RecyclerView.Adapter即可,adapter布局文件大概简单如下:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/rootLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black"
android:clickable="true">
<com.google.android.exoplayer2.ui.PlayerView
android:id="@+id/playerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:background="@color/translucent"
app:use_controller="false"
android:keepScreenOn="true" />
<ImageView
android:id="@+id/pauseIv"
android:layout_width="@dimen/dp_34"
android:layout_height="@dimen/dp_34"
android:layout_gravity="center"
android:src="@mipmap/icon_video_play"
android:visibility="gone" />
<ImageView
android:id="@+id/shortVideoCoverView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:scaleType="centerCrop" />
<fr.castorflex.android.smoothprogressbar.SmoothProgressBar
android:id="@+id/shortVideoLoadingView"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_gravity="bottom"
android:background="@color/color_000000"
android:indeterminate="true"
android:indeterminateOnly="false" />
</FrameLayout>
Adapter大概简单代码如下:
class ShortVideoExoPlayAdapter(val context: Context) : RecyclerView.Adapter<ShortVideoExoPlayAdapter.RecyclerHolder>(){
var shortVideoListBean = ArrayList<ShortVideoListBean.ShortVideoBean>()
var mContext = context
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerHolder {
val view: View = LayoutInflater.from(mContext).inflate(R.layout.item_video_exoplay, parent, false)
return RecyclerHolder(view)
}
override fun onBindViewHolder(holder: RecyclerHolder, position: Int) {
Glide.with(this.mContext).load(itemBean.cover_path)
.placeholder(R.mipmap.goods_null_img)
.error(R.mipmap.goods_null_img).into(holder.shortVideoCoverView!!) holder.shortVideoLoadingView?.setSmoothProgressDrawableInterpolator(DecelerateInterpolator(1.0f)) holder.shortVideoLoadingView?.setSmoothProgressDrawableColors(mContext.resources.getIntArray(R.array.video_progress_bar_colors))
holder.shortVideoLoadingView?.setSmoothProgressDrawableMirrorMode(true)
holder.shortVideoLoadingView?.setSmoothProgressDrawableReversed(true)
holder.shortVideoLoadingView?.setSmoothProgressDrawableSpeed(2.0f)
holder.shortVideoLoadingView?.setSmoothProgressDrawableProgressiveStartSpeed(2.0f)
holder.shortVideoLoadingView?.setSmoothProgressDrawableProgressiveStopSpeed(2.0f)
holder.shortVideoLoadingView?.setSmoothProgressDrawableSectionsCount(1)
var shortVideoUri:String = ""
shortVideoUri = if (!TextUtils.isEmpty(itemBean.video_m3u8_path)) {
itemBean.video_m3u8_path!!
} else {
itemBean.video_path
}
holder.mediaItem = MediaItem.fromUri(shortVideoUri)
}
override fun onBindViewHolder(holder: RecyclerHolder, position: Int, payloads: List<Any>) {
if (payloads == null || payloads.isEmpty()) {
onBindViewHolder(holder, position)
} else {
}
}
override fun onViewAttachedToWindow(holder: ShortVideoExoPlayAdapter.RecyclerHolder) {
}
override fun onViewDetachedFromWindow(holder: ShortVideoExoPlayAdapter.RecyclerHolder) {
}
override fun getItemCount(): Int {
return shortVideoListBean.size
}
class RecyclerHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var shortVideoLoadingView: SmoothProgressBar? = null
var shortVideoCoverView: ImageView? = null
var playerView: PlayerView? = null
var mediaItem: MediaItem?=null
init {
shortVideoLoadingView = itemView.findViewById<SmoothProgressBar>(R.id.shortVideoLoadingView)
shortVideoCoverView = itemView.findViewById<View>(R.id.shortVideoCoverView) as ImageView
playerView = itemView.findViewById<View>(R.id.playerView) as PlayerView
}
}
}
然后就是设置ViewPager2相关属性,纵向滑动展现数据,这里说一下ViewPager2的RecyclerHolder,我Log跟踪的结果,纵向滑动会创建5个Holder,然后滑动时Holder 视图复用;offscreenPageLimit 属性即使不设置,滑动到第二页时,第三页也会创建,就是说一旦滑动效果产生,当前页的上一页下一页都会存在的。
videoPlayPager!!.orientation = ViewPager2.ORIENTATION_VERTICAL
videoPlayPager!!.offscreenPageLimit = 1
videoPlayPager!!.adapter = shortVideoPlayListAdapter
到这里视图效果基本实现完毕,实现需求的横向、纵向滑动 展现数据,接下来只需要控制短视频的播放。
播放器的选择以及控制
最初使用的播放器有点弱,滑动时或者快速滑动时播放器出现的问题太多,包括播放器打开失败、无法暂停等,后来改用ExoPlayer,效果很满意。
实现横向、纵向滑动展现数据播放短视频, 控制播放器也是比较让人头大的,下一篇继续写ExoPalyer播放短视频的相关总结。