一、前言
在开发中经常会遇到循环轮播图,之前的实现方式是在Activity中使用定时器控制轮播。后面想了想,看能不能把ViewPager2和定时器封装成自定义控件方便移植。然后就有了这个自定义控件。
二、控件实现
因为使用的是ViewPager2,所以要用到适配器,这里写了个BaseBannerAdapter
用来设置循环滚动
/**
* @ Author : 廖健鹏
* @ Date : 2021/7/27
* @ e-mail : 329524627@qq.com
* @ Description :[BannerViewPager] 轮播控件所需的 BaseAdapter
*/
abstract class BaseBannerAdapter<T, VH : RecyclerView.ViewHolder> : RecyclerView.Adapter<VH>() {
protected var mList: MutableList<T> = mutableListOf()
var isCanLoop = false
var pageClickListener: OnPageClickListener? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH {
val inflate =
LayoutInflater.from(parent.context).inflate(getLayoutId(viewType), parent, false)
return createViewHolder(parent, inflate, viewType)
}
override fun onBindViewHolder(holder: VH, position: Int) {
val realPosition: Int = getRealPosition(position)
holder.itemView.setOnClickListener {
pageClickListener?.onPageClick(realPosition)
}
onBind(holder, mList[realPosition], realPosition, mList.size)
}
override fun getItemCount(): Int {
return if (isCanLoop && mList.size > 1) {
Int.MAX_VALUE
} else {
mList.size
}
}
fun getData(): List<T> {
return mList
}
fun setData(list: List<T>) {
mList.clear()
mList.addAll(list)
}
fun getListSize(): Int {
return mList.size
}
fun getRealPosition(position: Int): Int {
val pageSize = mList.size
if (pageSize == 0) {
return 0
}
return if (isCanLoop) (position + pageSize) % pageSize else position
}
interface OnPageClickListener {
fun onPageClick(position: Int)
}
protected abstract fun onBind(holder: VH, data: T, position: Int, pageSize: Int)
abstract fun createViewHolder(parent: ViewGroup, itemView: View, viewType: Int): VH
abstract fun getLayoutId(viewType: Int): Int
}
BannerViewPager.kt
/**
* @ Author : 廖健鹏
* @ Date : 2021/7/27
* @ e-mail : 329524627@qq.com
* @ Description : 自定义轮播图
*/
class BannerViewPager<T> : RelativeLayout, LifecycleObserver {
private var mContext: Context = context
private lateinit var mIndicatorLayout: LinearLayout
private lateinit var mViewPager: ViewPager2
private var mBannerPagerAdapter: BaseBannerAdapter<T, *>? = null
private val onPageChangeCallback: OnPageChangeCallback? = null
private var mCompositePageTransformer: CompositePageTransformer? = null
private var mMarginPageTransformer: MarginPageTransformer? = null
private var mOnPageClickListener: BaseBannerAdapter.OnPageClickListener? = null
//记录轮播图最后所在的位置
private var lastPosition = 0
private var listSize = 0
/**
* 轮播定时器
*/
private val mHandler: Handler = Handler()
private val runnable: Runnable = object : Runnable {
override fun run() {
val currentItem = mViewPager.currentItem
if (isLooper) {
mViewPager.currentItem = currentItem + 1
} else {
if (currentItem == listSize - 1) {
mViewPager.setCurrentItem(0, false)
} else {
mViewPager.currentItem = currentItem + 1
}
}
mHandler.postDelayed(this, interval)
}
}
/**
* 轮播间隔时间
*/
private var interval = 3000L
/**
* 是否自动轮播
*/
private var isAutoPlay = false
/**
* 是否循环
*/
private var isLooper = false
/**
* 是否显示指示器
*/
private var isShowIndicator = false
/**
* 页边距
*/
private var pageMargin = 0
/**
* 一屏多页模式下两边页面显露出来的宽度
*/
private var revealWidth = -1
/**
* 当前可见的任一侧的页数
*/
private var offscreenPageLimit = 3
/**
* 指示器间隔
*/
private var indicatorMargin = dpToPx(5)
/**
* 正常指示器图片
*/
private var normalImage = R.drawable.shape_dot
/**
* 选中指示器图片
*/
private var checkedImage = R.drawable.shape_dot_selected
private val mOnPageChangeCallback: OnPageChangeCallback = object : OnPageChangeCallback() {
override fun onPageScrolled(
position: Int,
positionOffset: Float,
positionOffsetPixels: Int
) {
super.onPageScrolled(position, positionOffset