相信大家可能都用过滑动指示器,开源的也有很多种,最近项目中有遇到过,于是自己写了一个。
前期思想:
1. 肯定要满足左右滑动定位
2. 肯定要满足动态定位索引,设置page个数
3. 要支持自定义的dot选中和未选中drawable
4. 要支持最大显示dot数目。数据不足与数据溢出的处理
5. 如果数据数目>最大显示dot数,如果处理显示更多 ,这是个难点
解决方案:
1. 左右预留1个dot,滑动的时候表示更多,但是滑到边界要定位到这个dot
2. 如何解决在左右边界滑动过程来回滑动的定位问题
开发实现:
1. 自定义属性
<declare-styleable name="PageIndicatorView">
<attr name="maxDots" format="integer"/>
<attr name="space" format="integer"/>
<attr name="unselected" format="reference"/>
<attr name="selected" format="reference"/>
</declare-styleable>
2. 代码实现:
package com.ljx.widget.common
import android.content.Context
import android.util.AttributeSet
import android.view.Gravity
import android.view.View
import android.widget.LinearLayout
import com.ljx.widget.R
/**
* pagerIndicator
*
* 滑动指示器
* 可以设置最大显示dot数
* 可以设置总数count
* 当maxDots<totalCount 会预留边界1个dot代表更多
* 当maxDots>=totalCount 会显示totalCount个dot
* 可见dot = Math.min(maxDots,totalCount)
* @author liangjianxiong
* @date 19/5/23
*/
class PageIndicatorView(context: Context?, attrs: AttributeSet?) : LinearLayout(context, attrs) {
//可见dot数
private var visibleDots = 0
//最大dot设置数
private var maxDots = 5
//dot间距
private var marginSpace = 6
//实际总数
private var totalSize = 0
//dot views
private var indicatorViews: ArrayList<View>? = null
//当前索引
private var currentIndex = 0
//历史索引
private var preIndex = 0
//当前可见索引
private var curVisibleIndex = -1
//历史可见索引
private var preVisibleIndex = -1
//未选择资源id
private var unselectedId = -1
//选中资源id
private var selectedId = -1
constructor(context: Context?) : this(context, null)
init {
gravity = Gravity.CENTER
orientation = HORIZONTAL
val typeArray = getContext().obtainStyledAttributes(attrs, R.styleable.PageIndicatorView, 0, 0)
maxDots = typeArray.getInt(R.styleable.PageIndicatorView_maxDots, maxDots)
marginSpace = typeArray.getInt(R.styleable.PageIndicatorView_space, marginSpace)
unselectedId = typeArray.getResourceId(
R.styleable.PageIndicatorView_unselected,
R.drawable.dot_unselected
)
selectedId = typeArray.getResourceId(
R.styleable.PageIndicatorView_selected,
R.drawable.dot_selected
)
}
private fun initIndicator(size: Int) {
if (indicatorViews == null) {
indicatorViews = ArrayList()
} else {
indicatorViews!!.clear()
removeAllViews()
}
if (size <= 0) return
totalSize = size
visibleDots = Math.min(maxDots,totalSize)
for (i in 0 until visibleDots) {
val view = View(context)
val params =
LinearLayout.LayoutParams(18, 2)
if (i == 0) {
params.setMargins(0, 0, 0, 0)
} else {
params.setMargins(marginSpace, 0, 0, 0)
}
view.setBackgroundResource(unselectedId)
addView(view, params)
indicatorViews!!.add(view)
}
}
/**
* set 最大可见dot数
*/
fun setMaxVisiableCount(count: Int) {
maxDots = count
setCount(totalSize)
}
/**
* set total count
*/
fun setCount(size: Int) {
initIndicator(size)
setSelect(0)
}
/**
* set select dot
* 这里定位逻辑有点小复杂
* 看代码难以理解 建议看UI效果
* 左右预留1个dot代表未到达边界,但是来回滑动可以在[1~size-2]之间来回滚动
*/
fun setSelect(index: Int) {
if (currentIndex < 0 || currentIndex >= totalSize || indicatorViews == null || (indicatorViews!=null && indicatorViews!!.size == 0)) {
return
}
preIndex = currentIndex
currentIndex = index
preVisibleIndex = curVisibleIndex
if(currentIndex>=preIndex){
//→️右滑
if(maxDots>=totalSize){
curVisibleIndex = currentIndex
setCurrentVisableIndex(curVisibleIndex)
}else{
if(currentIndex == totalSize -1){
curVisibleIndex = visibleDots-1
setCurrentVisableIndex(curVisibleIndex)
}else{
curVisibleIndex = preVisibleIndex+1
if(curVisibleIndex>=visibleDots-1){
curVisibleIndex = preVisibleIndex
}
setCurrentVisableIndex(curVisibleIndex)
}
}
}else if(currentIndex<preIndex){
//⬅左滑
if(maxDots>=totalSize){
curVisibleIndex = currentIndex
setCurrentVisableIndex(curVisibleIndex)
}else{
if(currentIndex == 0){
curVisibleIndex = 0
setCurrentVisableIndex(curVisibleIndex)
}else{
curVisibleIndex = preVisibleIndex-1
if(curVisibleIndex<1){
curVisibleIndex = preVisibleIndex
}
setCurrentVisableIndex(curVisibleIndex)
}
}
}
}
private fun setCurrentVisableIndex(visibelIndex: Int) {
if(visibelIndex>=0 && visibelIndex<visibleDots){
if(preVisibleIndex>=0 && preVisibleIndex<visibleDots){
indicatorViews!!.get(preVisibleIndex).setBackgroundResource(unselectedId)
}
if(curVisibleIndex>=0 && curVisibleIndex<visibleDots){
indicatorViews!!.get(curVisibleIndex).setBackgroundResource(selectedId)
}
}
}
}
3. 布局使用
<com.ljx.widget.common.PageIndicatorView
android:layout_centerHorizontal="true"
android:layout_alignParentBottom="true"
android:layout_marginBottom="10dp"
android:id="@+id/pageIndicatorView"
app:selected="@drawable/dot_selected"
app:unselected="@drawable/dot_unselected"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
4. 代码使用:
//设置可见最大dot数
pageIndicatorView.setMaxVisiableCount(6)
//设置数据总数
pageIndicatorView.setCount(100)
//定位position
pageIndicatorView.setSelect(position)
效果图:
总结:
1. 边界滑动的时候dot跟随动画
2. dot的大小目前是写死的,因为暂时项目用到,还没有支持设置点的大小
后续有时间了会更新