背景
在项目的实践中,Banner的UI更改比较频繁。便想实现一个可以随意更换UI的BannerView
。
问题
- 实现
BannerView
为了解决什么? - 如果将它作为一个公共依赖库应该具备什么?
文章的最后我们再来回答。
方案
在Android中我们最常见的UI应该是列表了,然后我们用RecyclerView
去实现一个个样式迥异的列表。如果我们把Banner也理解成是一个列表的话,RecyclerView
可自由实现item样式的特性也契合我们想随意更换Banner UI的需求。那么基于RecyclerView
去实现BannerView
应该是我们的首选方案了。
如果用RecyclerView
去实现Banner。第一个问题是怎么解决每次拖动只切换一个item,类似于ViewPager
的滑动切换效果。可喜的是在RecyclerView
中提供了一个SnapHelper
,我们使用它的一个扩展类PagerSnapHelper
来辅助滑动便解决了我们的切换效果问题。
另一个问题是如何实现连续滑动切换,这里我们采用在RecyclerView
的数据里首尾各添加一项数据。如图:
已上图为例子,当RecyclerView
滑动到浅绿色3号页时就静态切换到深绿色3号页,当滑动到浅绿色1号页时就静态切换到深绿色1号页(两个3号页和两个1号页的UI和数据是一致的,所以用户感知不到程序触发了切换动作),这样就实现了连续滑动切换。其中的逻辑可以体会一下。
实践
整体的功能逻辑很简单,核心逻辑只用处理RecyclerView
滑动到相应位置进行切换即可。我们用onScrollStateChanged
来监听RecyclerView
的滑动状态,当滑动停止时判断当前是第几页然后做相应切换。
//添加滑动监听
addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
//判断滑动状态
when (newState) {
RecyclerView.SCROLL_STATE_DRAGGING -> {
//当用户拖拽时...
switch(recyclerView, pagerSnap)
}
RecyclerView.SCROLL_STATE_IDLE -> {
//当滑动停止时...
switch(recyclerView, pagerSnap)
}
else -> {
}
}
}
})
核心的切换逻辑
private fun switch(recyclerView: RecyclerView, snap: PagerSnapHelper) {
val layoutManager: LinearLayoutManager = recyclerView.layoutManager as LinearLayoutManager
val currentView: View = snap.findSnapView(layoutManager) ?: return
//获取列表数量
val bannerCount: Int = layoutManager.itemCount
//获取当前滑动到哪一页
_currentPosition = layoutManager.getPosition(currentView)
when (_currentPosition) {
0 -> {
//如果当前页是列表数据中的第一项
//按上图的示例应该切换到列表数据的倒数第二项
_currentPosition = bannerCount - 2
recyclerView.scrollToPosition(_currentPosition)
}
bannerCount - 1 -> {
//如果当前页是列表数据中的最后一项
//按上图的示例应该切换到列表数据的第二项
_currentPosition = 1
recyclerView.scrollToPosition(_currentPosition)
}
else -> {
}
}
}
这里还有个问题是如果只根据滑动结束后再切换,那么当我们快速滑动时,有可能页面切换的滑动还没结束,我们就要往下一页滑动了,这时因为滑动结束的监听没有被回调,导致我们的切换方法没有被执行。如果我们滑动到RecyclerView
数据的最后一项或者第一项时,会因为后面没有数据导致我们无法继续滑动而出现停顿。所以我们在用户开始拖拽时也执行一下切换方法,这样就实现了近似无限滑动的效果。
自定义
如何自定义Banner的UI样式,因为我们是扩展自RecyclerView
,所以理论上是继承了RecyclerView
的所有特性。现在我们可以像使用RecyclerView
一样来使用BannerView
。自定义Banner的UI样式,只要自定义一个item布局即可。BannerView
的数据类型同样可以根据业务数据来自定义。
//继承BannerAdapter //根据业务数据自定义数据类型 //自定义item布局
class CustomAdapter : BannerAdapter<CustomModel>(R.layout.item_custom) {
override fun onBindViewHolder(holder: BannerViewHolder, item: CustomModel, position: Int) {
//通过holder.findViewById()获取自定义布局上的控件
}
}
同时也提供了一个DefaultBannerAdapter
方便使用,但是数据类型只支持String
类型。如果没有自定义的需求直接使用即可。
val defaultAdapter: DefaultBannerAdapter = object : DefaultBannerAdapter() {
override fun onBind(data: String, view: ImageView) {
//可以处理一些工作
//比如设置ImageView的属性、加载的图片等
}
}
这样基本功能就实现了。一样的思路也实现了一个文本类型的Banner,TextBannerView
。同样提供一个DefaultTextBannerAdapter
方便使用。如果需要自定义,继承BannerAdapter
实现自己的适配器即可。最终的效果大概是这样:
最后
现在我们来回答文章开始的问题:
- 自定义样式现在可以很方便的去实现,解决了我们经常需要修改UI的问题。
BannerView
没有再依赖任何第三方库,同时可以让我们简单快速的使用又兼具高可扩展性,作为一个公共依赖库应该是合格的。
更多使用方法和属性欢迎访问项目主页:github.com/mminng/Bann…
谢谢!