### 用到ViewPager来显示轮播,就会需要添加指示器来告诉用户当前位置,指示器有各种各样的样式,这里示例的是 淡入淡出效果来表示View Pager Dots Indicator
*该篇代码支持ViewPager2,如果是要用到ViewPager,则需要把代码里的方法传参类型替换成 ViewPager;
如何实现:
class FadeDotsIndicator @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {
private val dots = ArrayList<ImageView>()
private var dotsClickable: Boolean = false
private var currentDot: Int = -1
private val dotsSize: Float
private val dotsSpacing: Float
private val dotsCornerRadius: Float
private val linearLayout = LinearLayout(context)
init {
linearLayout.orientation = LinearLayout.HORIZONTAL
addView(
linearLayout,
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
dotsSize = resources.getDimension(R.dimen.dp_14)
dotsSpacing = resources.getDimension(R.dimen.dp_36)
dotsCornerRadius = dotsSize / 2
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
refreshDots()
}
private fun refreshDotsCount() {
pager?.let {
if (dots.size < it.count) {
addDots(it.count - dots.size)
} else if (dots.size > it.count) {
removeDots(dots.size - it.count)
}
}
}
private fun addDots(count: Int) {
for (i in 0 until count) {
addDot(i)
}
}
private fun removeDots(count: Int) {
for (i in 0 until count) {
removeDot(i)
}
}
fun addDot(index: Int) {
val dot =
LayoutInflater.from(context).inflate(R.layout.view_fade_dots_indicator, this, false)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
dot.layoutDirection = View.LAYOUT_DIRECTION_LTR
}
val strokeView = dot.findViewById<ImageView>(R.id.dot_stroke)
setUpDotCornerRadiusView(strokeView)
val imageView = dot.findViewById<ImageView>(R.id.dot)
setUpDotCornerRadiusView(imageView)
setUpDotAlpha(index, imageView)
dot.setOnClickListener {
pager?.let { pager ->
if (dotsClickable && index < pager.count) {
pager.setCurrentItem(index, true)
}
}
}
dots.add(imageView)
linearLayout.addView(dot)
}
private fun setUpDotCornerRadiusView(imageView: ImageView) {
val params = imageView.layoutParams as RelativeLayout.LayoutParams
params.height = dotsSize.toInt()
params.width = params.height
params.addRule(RelativeLayout.CENTER_VERTICAL, RelativeLayout.TRUE)
params.setMargins((dotsSpacing / 2).toInt(), 0, (dotsSpacing / 2).toInt(), 0)
val background = imageView.background as GradientDrawable
background.cornerRadius = dotsSize / 2
}
private fun setUpDotAlpha(index: Int, imageView: ImageView) {
pager?.let {
if (it.currentItem == index) {
currentDot = it.currentItem
imageView.alpha = 1f
} else {
imageView.alpha = 0f
}
}
}
fun removeDot(index: Int) {
linearLayout.removeViewAt(childCount - 1)
dots.removeAt(dots.size - 1)
}
protected fun refreshDots() {
pager?.let {
post {
// Check if we need to refresh the dots count
refreshDotsCount()
refreshDotsColors()
refreshOnPageChangedListener()
}
}
}
private fun refreshOnPageChangedListener() {
pager?.let {
if (it.isNotEmpty) {
it.removeOnPageChangeListener()
it.addOnPageChangeListener()
}
}
}
private fun refreshDotsColors() {
for (i in dots.indices) {
setUpDotAlpha(i, dots[i])
}
}
var pager: Pager? = null
interface Pager {
val isNotEmpty: Boolean
val currentItem: Int
val isEmpty: Boolean
val count: Int
fun setCurrentItem(item: Int, smoothScroll: Boolean)
fun removeOnPageChangeListener()
fun addOnPageChangeListener()
}
// PUBLIC METHODS
fun setViewPager2(viewPager2: ViewPager2) {
viewPager2.adapter?.let { adapter ->
adapter.registerAdapterDataObserver(object :
RecyclerView.AdapterDataObserver() {
override fun onChanged() {
super.onChanged()
refreshDots()
}
})
pager = object : Pager {
var onPageChangeCallback: ViewPager2.OnPageChangeCallback? = null
override val isNotEmpty: Boolean
get() = !(viewPager2.isEmpty)
override val currentItem: Int
get() = viewPager2.currentItem
override val isEmpty: Boolean
get() = viewPager2.isEmpty
override val count: Int
get() = adapter.itemCount
override fun setCurrentItem(item: Int, smoothScroll: Boolean) {
viewPager2.setCurrentItem(item, smoothScroll)
}
override fun removeOnPageChangeListener() {
onPageChangeCallback?.let { viewPager2.unregisterOnPageChangeCallback(it) }
}
override fun addOnPageChangeListener() {
onPageChangeCallback = object : ViewPager2.OnPageChangeCallback() {
override fun onPageScrolled(
position: Int,
positionOffset: Float,
positionOffsetPixels: Int
) {
super.onPageScrolled(position, positionOffset, positionOffsetPixels)
if (position + 1 >= count || position == -1) {
return
}
dots[position].alpha = 1 - positionOffset
dots[position + 1].alpha = positionOffset
}
override fun onPageScrollStateChanged(state: Int) {
super.onPageScrollStateChanged(state)
if (state == ViewPager2.SCROLL_STATE_IDLE && currentItem != currentDot) {
refreshDots()
}
}
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
if (currentItem != currentDot) {
refreshDots()
}
}
}
onPageChangeCallback?.let { viewPager2.registerOnPageChangeCallback(it) }
}
}
}
}
private val ViewPager2?.isEmpty: Boolean
get() = this != null && adapter != null && adapter?.itemCount == 0
}
如何使用:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintTop_toTopOf="parent" />
<com.android.fadedotsindicator.FadeDotsIndicator
android:id="@+id/dots_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="30dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
一行代码调用:
dots_indicator.setViewPager2(view_pager)
效果示例:
同步更新至
Git地址:Fadedotsindicator
***备注:因为之前在使用指示器:Material View Pager Dots Indicator ,它有三种样式可以选择,分别:
1. DotsIndicator
2. SpringDotsIndicator
3. WormDotsIndicator
由于项目的要求,需要淡入淡出效果,所以才有了这篇简单的自定义实现。