有没有帮别人做图片的网站赚钱,网站开发模块的需求,项目网站制作,晋中网站开发目录 前言一、实现效果二、具体实现1. 导入依赖2. 布局3. Banner基础配置4. Banner无限循环机制5. 轮播适配器6. 视频播放处理7. 完整源码 总结 前言
我们日常的需求基本上都是图片的轮播#xff0c;而在一些特殊需求#xff0c;例如用于展览的的数据大屏#xff0c;又想展… 目录 前言一、实现效果二、具体实现1. 导入依赖2. 布局3. Banner基础配置4. Banner无限循环机制5. 轮播适配器6. 视频播放处理7. 完整源码 总结 前言
我们日常的需求基本上都是图片的轮播而在一些特殊需求例如用于展览的的数据大屏又想展示图片又想展示视频本文将利用第三方库com.youth.play.banner轮播控件实现图片和视频混合轮播的效果自动手动滑动无限循环视频自动播放。
其中图片使用Glide图片加载库视频使用GSYVideoPlayer播放器。
第三方库移步【Android】常用的第三方开源库汇总 一、实现效果 二、具体实现
1. 导入依赖
implementation com.youth.banner:banner:2.1.0
implementation com.github.bumptech.glide:glide:4.11.0
implementation com.shuyu:GSYVideoPlayer:7.1.82. 布局
主页面
?xml version1.0 encodingutf-8?
androidx.constraintlayout.widget.ConstraintLayout xmlns:androidhttp://schemas.android.com/apk/res/androidxmlns:apphttp://schemas.android.com/apk/res-autoandroid:layout_widthmatch_parentandroid:layout_heightmatch_parentcom.youth.banner.Bannerandroid:idid/bannerandroid:layout_widthmatch_parentandroid:layout_height300dpandroid:layout_margin10dpapp:layout_constraintBottom_toBottomOfparentapp:layout_constraintTop_toTopOfparentapp:layout_constraintEnd_toEndOfparentapp:layout_constraintStart_toStartOfparent //androidx.constraintlayout.widget.ConstraintLayout这里没有使用库里面写好的指示器Indicator需要的可以自行查看文档使用Banner。
3. Banner基础配置 // 添加生命周期管理确保在适当的生命周期内开始和停止轮播
banner.addBannerLifecycleObserver(this).setAdapter(adapter,true) //是否开启无限循环.setLoopTime(loopTime) //轮播时间// 设置轮播图的点击事件监听器
banner.addOnPageChangeListener(object :OnPageChangeListener {/*** 滑动中的监听当页面在滑动的时候会调用此方法在滑动被停止之前此方法会一直得到调用*/override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {}/*** 监听滑动到对应索引值的页面第一个页面不执行*/override fun onPageSelected(position: Int) {}/*** 滑动状态监听*/override fun onPageScrollStateChanged(state: Int) {}
})Banner支持图片的无限循环可以选择性打开。我们需要在其中插入视频的播放就需要先了解其无限循环的处理机制。
4. Banner无限循环机制
无限循环主要有两种实现一种是设置页面总数设置得很大一直滑不到底这不是真正的无限循环是一种伪循环性能较低。另外一种就是接下来要说的Banner的无限循环它是通过ViewPage2去实现的在原有页面的基础上左右各添加一个页面滑动到最后一页跳转到第二页滑动到第一页跳转到倒数第二页。 这里为什么要多两个页面呢一个目的是为了滑动到中间时有一半是原页面有一半是新页面。第二个目的是为了有滑动的效果不会出现跳动。
首先ViewPage2有三个监听方法 onPageScrolled 滑动中的监听当页面在滑动的时候会调用此方法在滑动被停止之前此方法会一直得到调用 onPageSelected 滑动或移动左右滑动、点击指示器跳转到某一页面时的监听第一个页面不执行 onPageScrollStateChanged滑动状态的监听 按理说在滑动到最后一页时我们只需要在 onPageSelected 中使用 setCurrentItem 跳转到第一页总页数的第二页即可但是 onPageSelected 在滑动动画还没结束的时候就已经被调用了此时调用 setCurrentItem 会强制取消当前正在进行的滑动动画并跳转导致页面跳动不平滑。
所以可以这样去解决这一问题 1先在 onPageSelected 中记录被选中的页面 2再在 onPageScrollStateChanged 判断当前动画是否结束当动画结束时再去调用setCurrentItem方法跳转
我们查看Banner里面的源码也是这样处理无限循环的
这里需要格外注意页面的下标一个页面有两种下标一个真实下标 realPosition一个物理下标 position。同理页面总数量、根据下标获取数据也是一样的逻辑。 以上面的图为例下标如下
页面positionrealPosition第一页02最后一页第二页10第三页21第四页32最后一页40 (第一页) /*** 获取真正的位置** param isIncrease 首尾是否有增加* param position 当前位置* param realCount 真实数量* return*/public static int getRealPosition(boolean isIncrease, int position, int realCount) {if (!isIncrease) {return position;}int realPosition;if (position 0) {realPosition realCount - 1;} else if (position realCount 1) {realPosition 0;} else {realPosition position - 1;}return realPosition;}5. 轮播适配器
//轮播数据
private val resourcesList by lazy{arrayListOfPlayResourcesBean().apply {add(PlayResourcesBean(isImg true, title 图片, url https://upload-images.jianshu.io/upload_images/5809200-a99419bb94924e6d.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240))add(PlayResourcesBean(isImg true,title 图片,url https://s10.mogucdn.com/mlcdn/c45406/170831_479g0ifl6f2i313feb5ech46kek21_778x440.jpg))add(PlayResourcesBean(isImg false, title 视频,url https://media.w3.org/2010/05/sintel/trailer.mp4))add(PlayResourcesBean(isImg true,title 图片,url https://s10.mogucdn.com/mlcdn/c45406/170831_7gee6d620i774ec3l5bfh55cfaeab_778x440.jpg))add(PlayResourcesBean(isImg false,title 视频,url https://media.w3.org/2010/05/sintel/trailer.mp4))add(PlayResourcesBean(isImg true,title 图片,url https://s10.mogucdn.com/mlcdn/c45406/170829_59ia6fd99ghkdkd9603kblha21h5b_778x440.jpg))add(PlayResourcesBean(isImg true,title 图片,url https://img.zcool.cn/community/01233056fb62fe32f875a9447400e1.jpg))add(PlayResourcesBean(isImg false,title 视频,url https://media.w3.org/2010/05/sintel/trailer.mp4))}}private val adapter by lazy{PlayAdapter(this,resourcesList, this)
}Keep
data class PlayResourcesBean(/*** 图片或者视频路径*/val url: String? ,val isImg:Boolean,val title: String?
)PlayAdapter需要继承BannerAdapter处理两种不同类型的item图片item和视频item
class PlayAdapter(private val mContext: Context, private val dataList: ArrayListPlayResourcesBean,private val mActivity: MainActivity
):BannerAdapterPlayResourcesBean, RecyclerView.ViewHolder(dataList) {private val mVHMap HashMapInt,RecyclerView.ViewHolder()private val options by lazy {RequestOptions().priority(Priority.HIGH).placeholder(R.mipmap.default_ic).error(R.mipmap.default_ic)}companion object{//视频const val VIDEO 1//图片const val IMAGE 2}override fun onBindViewHolder(holder: RecyclerView.ViewHolder,position: Int,payloads: MutableListAny) {super.onBindViewHolder(holder, position, payloads)val realPosition getRealPosition(position)if (holder is VideoHolder){setVideo( holder, getData(realPosition),position,realPosition,realCount)mVHMap[position] holder}else if(holder is ImageHolder){setImage(holder,getData(realPosition),realPosition,realCount)mVHMap[position] holder}}override fun getItemViewType(position: Int): Int {//这里的position不是真实的坐标获取数据需要转换return if (getData(getRealPosition(position)).isImg){IMAGE}else {VIDEO}}override fun onCreateHolder(parent: ViewGroup?, viewType: Int): RecyclerView.ViewHolder {val holder:RecyclerView.ViewHolderval from LayoutInflater.from(mContext)if (viewTypeVIDEO){val viewfrom.inflate(R.layout.play_item_video,parent,false)holderVideoHolder(view)}else {val viewfrom.inflate(R.layout.play_item_image,parent,false)holderImageHolder(view)}return holder}private fun setImage(holder: ImageHolder,data: PlayResourcesBean,position: Int,size: Int) {Glide.with(mContext).load(data.url).apply(options).into(holder.image)holder.title.textdata.title?:holder.numIndicator.text${position1}/$size}private fun setVideo(holder: VideoHolder,data: PlayResourcesBean,position: Int,realPosition: Int,size: Int) {holder.video.apply {setUp(data.url,true,mContext.externalCacheDir,null,)titleTextView.visibility View.GONEbackButton.visibility View.GONEfullscreenButton.visibility View.GONEstartButton.visibility View.GONE//音频焦点冲突时是否释放isReleaseWhenLossAudio true//禁止全屏isAutoFullWithSize false//isStartAfterPreparedtruedismissControlTime0isClickablefalseisEnabledfalseisLongClickablefalseplayPositionposition//禁止滑动setIsTouchWiget(false)setVideoAllCallBack(object :GSYSampleCallBack(){override fun onComplete(url: String?, vararg objects: Any?) {super.onComplete(url, *objects)mActivity.banner.isAutoLoop(true)mActivity.banner.start()}override fun onPlayError(url: String?, vararg objects: Any?) {super.onPlayError(url, *objects)mActivity.banner.isAutoLoop(true)mActivity.banner.start()}override fun onClickStop(url: String?, vararg objects: Any?) {super.onClickStop(url, *objects)mActivity.banner.isAutoLoop(true)mActivity.banner.start()}override fun onPrepared(url: String?, vararg objects: Any?) {super.onPrepared(url, *objects)}override fun onStartPrepared(url: String?, vararg objects: Any?) {super.onStartPrepared(url, *objects)}override fun onAutoComplete(url: String?, vararg objects: Any?) {super.onAutoComplete(url, *objects)mActivity.banner.isAutoLoop(true)//快速轮播到下一页mActivity.banner.setLoopTime(100)mActivity.banner.start()mActivity.banner.setLoopTime(mActivity.loopTime)}})}holder.title.textdata.title?:holder.numIndicator.text${realPosition1}/$size}internal class VideoHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {val video: MyVideoPlayerval title: TextViewval numIndicator:TextViewinit {video itemView.findViewById(R.id.banner_vp)title itemView.findViewById(R.id.video_title)numIndicator itemView.findViewById(R.id.video_numIndicator)}}internal class ImageHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {val image: ImageViewval title: TextViewval numIndicator:TextViewinit {image itemView.findViewById(R.id.banner_image)title itemView.findViewById(R.id.image_title)numIndicator itemView.findViewById(R.id.image_numIndicator)}}fun getVHMap(): HashMapInt,RecyclerView.ViewHolder {return mVHMap}override fun onBindView(holder: RecyclerView.ViewHolder?,data: PlayResourcesBean?,position: Int,size: Int) {}}这里需要用一个HashMap去存储所有ViewHolder以便后续控制视频自动播放这里的key是物理下标一个key对应一个页面如果用realPosition作为key不是一对一容易产生value更新不及时。
6. 视频播放处理
GSYVideoPlayer 播放器的使用可以查看文档GSYVideoPlayer
无限循环解决之后就需要在轮播中自动播放视频隐藏和关闭掉各种手动按钮和事件需要对播放器进行改造继承StandardGSYVideoPlayer我是在翻看了它许多源码和github上的issues才得以解决。
class MyVideoPlayer(context: Context, attrs: AttributeSet?) : StandardGSYVideoPlayer(context,attrs){override fun changeUiToPreparingShow() {super.changeUiToPreparingShow()hide()}override fun changeUiToPauseShow() {super.changeUiToPauseShow()hide()}override fun changeUiToError() {super.changeUiToError()hide()}override fun changeUiToCompleteShow() {super.changeUiToCompleteShow()hide()}override fun changeUiToPlayingBufferingShow() {super.changeUiToPlayingBufferingShow()hide()}override fun changeUiToPrepareingClear() {super.changeUiToPrepareingClear()hide()}override fun changeUiToPlayingClear() {super.changeUiToPlayingClear()hide()}override fun changeUiToPauseClear() {super.changeUiToPauseClear()hide()}override fun changeUiToPlayingBufferingClear() {super.changeUiToPlayingBufferingClear()hide()}override fun changeUiToClear() {super.changeUiToClear()hide()}override fun changeUiToCompleteClear() {super.changeUiToCompleteClear()hide()}override fun changeUiToPlayingShow() {super.changeUiToPlayingShow()hide()}override fun changeUiToNormal() {super.changeUiToNormal()hide()}private fun hide() {setViewShowState(mTopContainer, INVISIBLE)setViewShowState(mBottomContainer, INVISIBLE)setViewShowState(mStartButton, INVISIBLE)setViewShowState(mLoadingProgressBar, INVISIBLE)setViewShowState(mThumbImageViewLayout, INVISIBLE)setViewShowState(mBottomProgressBar, INVISIBLE)setViewShowState(mLockScreen, GONE)}override fun onVideoPause() {super.onVideoPause()mPauseBeforePreparedfalse //是否准备完成前调用了暂停避免再次暂停}override fun touchDoubleUp() {//双击会暂停重载清除}
}StandardGSYVideoPlayer是GSYVideoPlayer中的一个标准样式的播放器在视频准备、暂停、播放完成等等状态隐藏和关闭掉各种手动按钮和事件设置mPauseBeforePrepared避免视频准备完成后自动暂停重载touchDoubleUp清除掉原有的双击屏幕暂停视频的效果。
视频自动播放 接下来就是主要逻辑了这里有几个关键点 在轮播到视频时将自动轮播关闭同时播放视频 在视频播放错误、非正常完成时暂停视频同时继续轮播 在视频播放正常完成、手动滑动到下一页时立即轮播到下一页 首先就需要在MyVideoPlayer适配器中处理视频控件的各种监听事件查看源码视频有各种状态
setVideoAllCallBack(object :GSYSampleCallBack(){override fun onComplete(url: String?, vararg objects: Any?) {super.onComplete(url, *objects)mActivity.banner.isAutoLoop(true)mActivity.banner.start()}override fun onPlayError(url: String?, vararg objects: Any?) {super.onPlayError(url, *objects)mActivity.banner.isAutoLoop(true)mActivity.banner.start()}override fun onClickStop(url: String?, vararg objects: Any?) {super.onClickStop(url, *objects)mActivity.banner.isAutoLoop(true)mActivity.banner.start()}override fun onPrepared(url: String?, vararg objects: Any?) {super.onPrepared(url, *objects)}override fun onStartPrepared(url: String?, vararg objects: Any?) {super.onStartPrepared(url, *objects)}override fun onAutoComplete(url: String?, vararg objects: Any?) {super.onAutoComplete(url, *objects)mActivity.banner.isAutoLoop(true)//设置轮播时间立即轮播到下一页mActivity.banner.setLoopTime(100)mActivity.banner.start()mActivity.banner.setLoopTime(mActivity.loopTime)}
})其次在Activity中设置任务在页面切换时通过之前保存的ViewHolder去执行视频播放或者暂停的任务
private val taskHandler by lazy{Handler(Looper.getMainLooper())
}
val task Runnable { //可能是首尾切换页两个页面循环跳转if(adapter.getVHMap().containsKey(currentPos)){if(adapter.getVHMap()[currentPos] is PlayAdapter.VideoHolder){val holder (adapter.getVHMap()[currentPos] as PlayAdapter.VideoHolder)GSYVideoManager.onPause()holder.video.startPlayLogic()banner.isAutoLoop(true)banner.stop()banner.isAutoLoop(false)}else{GSYVideoManager.onPause()banner.isAutoLoop(true)banner.start()}}else{GSYVideoManager.onPause()banner.isAutoLoop(true)banner.start()}
}// 设置轮播图的点击事件监听器
banner.addOnPageChangeListener(object :OnPageChangeListener {/*** 滑动中的监听当页面在滑动的时候会调用此方法在滑动被停止之前此方法会一直得到调用*/override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {}/*** 监听滑动到对应索引值的页面第一个页面不执行*/override fun onPageSelected(position: Int) {if (position banner.realCount - 1) {currentPosbanner.realCount} else if (position 0) {currentPos 1} else {currentPos position 1}}/*** 滑动状态监听*/override fun onPageScrollStateChanged(state: Int) {// Banner跳转之后再去控制视频播放if (state ViewPager2.SCROLL_STATE_IDLE) {if (banner.isInfiniteLoop) {taskHandler.post(task)}}}
})任务一定要在onPageScrollStateChanged中去执行这时候特殊的两个页面已经跳转完成了只会执行一次任务否则执行两个异步任务会导致一个视频播放一个视频暂停两个异步没有固定的执行顺序出现视频没有画面但是有声音的问题。
完整的Activity代码
class MainActivity:AppCompatActivity() {val loopTime6000L//列表前后两页过渡页的坐标private var currentPos 1private val taskHandler by lazy{Handler(Looper.getMainLooper())}private val adapter by lazy{PlayAdapter(this,resourcesList, this)}private val resourcesList by lazy{arrayListOfPlayResourcesBean().apply {add(PlayResourcesBean(isImg true, title 图片, url https://upload-images.jianshu.io/upload_images/5809200-a99419bb94924e6d.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240))add(PlayResourcesBean(isImg true,title 图片,url https://s10.mogucdn.com/mlcdn/c45406/170831_479g0ifl6f2i313feb5ech46kek21_778x440.jpg))add(PlayResourcesBean(isImg false, title 视频,url https://media.w3.org/2010/05/sintel/trailer.mp4))add(PlayResourcesBean(isImg true,title 图片,url https://s10.mogucdn.com/mlcdn/c45406/170831_7gee6d620i774ec3l5bfh55cfaeab_778x440.jpg))add(PlayResourcesBean(isImg false,title 视频,url https://media.w3.org/2010/05/sintel/trailer.mp4))add(PlayResourcesBean(isImg true,title 图片,url https://s10.mogucdn.com/mlcdn/c45406/170829_59ia6fd99ghkdkd9603kblha21h5b_778x440.jpg))add(PlayResourcesBean(isImg true,title 图片,url https://img.zcool.cn/community/01233056fb62fe32f875a9447400e1.jpg))add(PlayResourcesBean(isImg false,title 视频,url https://media.w3.org/2010/05/sintel/trailer.mp4))}}override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)initBanner()}private fun initBanner() {// 添加生命周期管理确保在适当的生命周期内开始和停止轮播banner.addBannerLifecycleObserver(this).setAdapter(adapter,true) //是否开启无限循环.setLoopTime(loopTime) //轮播时间// 设置轮播图的点击事件监听器banner.addOnPageChangeListener(object :OnPageChangeListener {/*** 滑动中的监听当页面在滑动的时候会调用此方法在滑动被停止之前此方法会一直得到调用*/override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {}/*** 监听滑动到对应索引值的页面第一个页面不执行*/override fun onPageSelected(position: Int) {if (position banner.realCount - 1) {currentPosbanner.realCount} else if (position 0) {currentPos 1} else {currentPos position 1}}/*** 滑动状态监听*/override fun onPageScrollStateChanged(state: Int) {// Banner跳转之后再去控制视频播放if (state ViewPager2.SCROLL_STATE_IDLE) {if (banner.isInfiniteLoop) {taskHandler.post(task)}}}})//重新设置banner数据//banner.setDatas(resourcesList)}val task Runnable {//可能是首尾切换页两个页面循环跳转if(adapter.getVHMap().containsKey(currentPos)){if(adapter.getVHMap()[currentPos] is PlayAdapter.VideoHolder){val holder (adapter.getVHMap()[currentPos] as PlayAdapter.VideoHolder)GSYVideoManager.onPause()holder.video.startPlayLogic()banner.isAutoLoop(true)banner.stop()banner.isAutoLoop(false)}else{GSYVideoManager.onPause()banner.isAutoLoop(true)banner.start()}}else{GSYVideoManager.onPause()banner.isAutoLoop(true)banner.start()}}override fun onResume() {super.onResume()taskHandler.post(task)}override fun onPause() {super.onPause()GSYVideoManager.onPause()}override fun onDestroy() {super.onDestroy()GSYVideoManager.releaseAllVideos()//移除数据绑定否则第二次设置适配器出错banner.destroy()}}7. 完整源码
到这就完结撒花了完整代码我已上传github需要的可以自行pullhttps://github.com/FullCourage/BannerPlayer 总结
解决jar包的一些底层问题不仅要知道原理还要学会从源码中了解它的执行逻辑。