技术标签: ViewPager2 安卓 ExoPlayer 直播、短视频 androidx android 移动开发
最近的开发需求,实现类似某音的功能,大致就是界面横向滑动,加载不同分类的视频列表,纵向滑动加载某分类下的视频列表,然后进行短视频的播放,具体短视频内的 点赞、关注、评论等,暂且不提。目前已上线几个版本,还算稳定,做个总结,希望能帮到有这方面需求的朋友。
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播放短视频的相关总结。
文章浏览阅读52次。CSS代码示例-背景附着属性(background-attachment)-[背景图固定不动,不跟随滚动条滚动]:<html><head><title>背景附着属性 background-attachment</title><style type="text/css">body {background-image:url(../image..._背景附着方式的属性值
文章浏览阅读863次,点赞24次,收藏18次。Python字面量
文章浏览阅读1k次。(最近在自己学习《算法导论》一本书,之前本来喜欢手写笔记,但是随即发现自己总是把笔记弄丢,所以打算做一个电子版的笔记)(另外书中用的都是伪代码,笔记中如果需要尝试的地方都是python代码)2.1 插入排序 基本思想:将待排序的数列看成两个部分(以从小到大为例),前一半是排序完成的,后一半是乱序的,对于乱序的第一个,开始和前一半里最大的数字、第二大的数字……依次比较,等到合适的位置就将它放进去。然后比对过的数字向后移动一位,相应的排序完成的长度加一,没有排序的减一。如:5 |..._61,55,97,30,38,58两轮选择递增排序法
文章浏览阅读674次,点赞21次,收藏21次。可以看出,笔者的工作学习模式便是由以下。
文章浏览阅读1.1k次。实现树形表格_vue树形表格组件
文章浏览阅读237次。Linux平台下很实用的44个Linux命令大家好,今天再继续和大家说下基础的命令,实在是不知道基础的东西还有什么是应该和大家讲的了,要是再开基础的东西,我觉得就得和大家说交换机和路由器什么的了。今天和大家说一下linux运维其实一般来说,能精通100+的命令,就是一个合格的运维人员了,意思就是你的基础已经差不多了。但是在实际运维工作中需要经常运用到的一些命令,今天就和大家简单的说一下,因
文章浏览阅读1.2w次,点赞16次,收藏149次。2023年华为OD真题目前华为社招大多数是OD招聘,17级以下都为OD模式,OD模式也是华为提出的一种新的用工形式,定级是13-17级,属于华为储备人才,每年都会从OD项目挑优秀员工转为正编。D1-D5对应薪资10K-35K左右,年终奖2-4个月,周六加班双倍工资,下个月发。入职OD会有一定薪资上涨,之后每年一次加薪,OD转华为一次加薪。等不到转正机会,相对于内部员工来说,容易被裁,不稳定,可能接触不到核心项目,功能。具体转条件:连续N个季度绩效为A,部门有转正名额,排队。_华为od机试题
文章浏览阅读2.7k次,点赞3次,收藏10次。在我们使用python+selenium来驱动chrome浏览器时,需要有chromedriver的支持,但是chrome浏览器更新比较频繁,而chrome浏览器和chromedriver则需要保持版本一致(版本一般相差1以内),此时我们就需要手动下载chromedriver来匹配此时的浏览器,但是生产环境操作比较麻烦。此时,我们就想是不是有一个程序来代替我们完成这个工作呢?思路比较当前的chrome浏览器版本号与chromedriver浏览号如果不匹配,则下载一个新的chromedriver替换掉_chrome版本122.0.6261.112和chromdriver 107.0.5304.62兼容吗
文章浏览阅读2.7k次,点赞2次,收藏11次。负责开发项目的技术方法。我的一位同事曾经很认真地问过我一个问题,他说他现在从事软件测试工作已经4年了,但是他不知道现在的工作和自己在工作3年时有什么不同,他想旁观者清,也许我能回答他的问题。随着互联网的飞快发展,IT行业出现了日新月异的变化,新的技术会不断出现,你熟练掌握的软件测试技术很快就过时了。至于第三点说的实践和思考就是你对自己学到的东西的一个掌握的程度的检验了,只有实践了你才能知道,这个知识点你到底学会了没有,会了之后有没有什么其他的理解,这个就是需要自己去思考了 ,这种东西都是别人教不了你的!_测开个人成长计划
文章浏览阅读734次,点赞21次,收藏13次。主要内容:代码主要做的是考虑多微网电能互补共享的微网双层优化模型,同时优化配电网运营商的动态电价以及微网用户的能量管理策略,在上层,目标函数为配电网运营商的收益最大化,决策变量为配电网运营商的交易电价;主要内容:代码主要做的是考虑多微网电能互补共享的微网双层优化模型,同时优化配电网运营商的动态电价以及微网用户的能量管理策略,在上层,目标函数为配电网运营商的收益最大化,决策变量为配电网运营商的交易电价;最后,我们输出最终的结果,包括最优的用电费用、配网运营商的收益以及每个微网的用电费用分配情况。_配电网和微电网的matlab模型
文章浏览阅读378次,点赞3次,收藏3次。首先介绍了逆变电路的基本原理和应用领域,然后详细分析了双极性SPWM调制方式的工作原理和优势。本文通过对三相电压型桥式逆变电路和双极性SPWM调制方式的技术分析,深入探讨了其在电力系统中的应用和性能评估。双极性SPWM调制方式是SPWM调制方式的一种改进形式,它能够更好地抑制谐波,提高逆变电路的输出质量。面对电力系统的不断发展和需求的变化,逆变电路和SPWM调制方式也在不断演进。为了评估三相电压型桥式逆变电路和双极性SPWM调制方式的性能,本节将详细分析其输出波形的失真程度、功率损耗和效率等关键指标。
文章浏览阅读4.5k次。第一步、下载源代码 http://sourceforge.net/projects/clucene/ 第二步、下载cmakehttp://www.cmake.org/cmake/resources/software.html 编译第一步,打开在应用程序中的cmake GUI程序,设置好源代码路径,和输出路径,如图: 第二步,点击Configure,在_clucene-config.h