Skip to content

Instantly share code, notes, and snippets.

@ch4vi
Last active June 28, 2018 10:07
Show Gist options
  • Save ch4vi/9d44933e10852a69b094970b296c09ce to your computer and use it in GitHub Desktop.
Save ch4vi/9d44933e10852a69b094970b296c09ce to your computer and use it in GitHub Desktop.
DotsIndicator written in Kotlin based on com.tbuonomo.viewpagerdotsindicator
<declare-styleable name="DotsIndicator">
<attr name="dotsColor"/>
<attr name="dotsSize"/>
<attr name="dotsWidthFactor"/>
<attr name="dotsSpacing"/>
<attr name="dotsCornerRadius"/>
</declare-styleable>
dependencies {
// Anko
implementation "org.jetbrains.anko:anko-appcompat-v7-commons:0.10.5"
implementation "org.jetbrains.anko:anko-sdk19-listeners:0.10.5"
}
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@android:color/white"/>
<corners android:radius="0dp"/>
</shape>
import android.content.Context
import android.database.DataSetObserver
import android.graphics.Color
import android.graphics.drawable.GradientDrawable
import android.support.v4.view.ViewPager
import android.util.AttributeSet
import android.util.Log
import android.view.LayoutInflater
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
import com.econocom.econocross.R
import org.jetbrains.anko.dip
import org.jetbrains.anko.find
import org.jetbrains.anko.sdk19.listeners.onClick
class DotsIndicator : LinearLayout {
private object Const {
const val DEFAULT_POINT_COLOR = Color.CYAN
const val DEFAULT_WIDTH_FACTOR = 2.5f
}
private val dots: MutableList<ImageView> = mutableListOf()
private var dotsSize = 0f
private var dotsCornerRadius = 0f
private var dotsSpacing = 0f
private var dotsColor: Int = 0
private var dotsWidthFactor = 0f
set(value) {
if (value < 1) field = 2.5f
field = value
}
private var viewPager: ViewPager? = null
private var currentPage: Int = 0
private var dotsClickable: Boolean = false
private var pageChangedListener: ViewPager.OnPageChangeListener? = null
constructor(context: Context) : super(context) {
init(null)
}
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
init(attrs)
}
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) :
super(context, attrs, defStyleAttr) {
init(attrs)
}
private fun init(attrs: AttributeSet?) {
orientation = LinearLayout.HORIZONTAL
dotsSize = dip(16).toFloat()
dotsSpacing = dip(4).toFloat()
dotsCornerRadius = dotsSize / 2
dotsWidthFactor = Const.DEFAULT_WIDTH_FACTOR
dotsColor = Const.DEFAULT_POINT_COLOR
dotsClickable = true
attrs?.let {
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.DotsIndicator)
dotsWidthFactor = typedArray.getFloat(R.styleable.DotsIndicator_dotsWidthFactor, 2.5f)
dotsSize = typedArray.getDimension(R.styleable.DotsIndicator_dotsSize, dotsSize)
dotsCornerRadius =
typedArray.getDimension(R.styleable.DotsIndicator_dotsCornerRadius, dotsSize / 2).toInt()
.toFloat()
dotsSpacing = typedArray.getDimension(R.styleable.DotsIndicator_dotsSpacing, dotsSpacing)
dotsColor =
typedArray.getColor(R.styleable.DotsIndicator_dotsColor, Const.DEFAULT_POINT_COLOR)
setUpCircleColors(dotsColor)
typedArray.recycle()
}
if (attrs == null) setUpCircleColors(Const.DEFAULT_POINT_COLOR)
if (isInEditMode) {
addDots(5)
}
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
refreshDots()
}
private fun refreshDots() {
viewPager?.adapter?.let {
if (dots.size < it.count) addDots(it.count - dots.size)
else if (dots.size > it.count) removeDots(dots.size - it.count)
setUpDotsAnimators()
}
if (viewPager?.adapter == null) {
Log.e(DotsIndicator::class.java.simpleName,
"You have to set an adapter to the view pager before !")
}
}
private fun addDots(count: Int) {
for (i in 0 until count) {
val dot = LayoutInflater.from(context).inflate(R.layout.view_indicator, this, false)
val imageView = dot.find<ImageView>(R.id.dot)
val params = imageView.layoutParams as FrameLayout.LayoutParams
params.height = dotsSize.toInt()
params.width = params.height
params.setMargins(dotsSpacing.toInt(), 0, dotsSpacing.toInt(), 0)
(imageView.background as GradientDrawable).cornerRadius = dotsCornerRadius
(imageView.background as GradientDrawable).setColor(dotsColor)
dot.onClick {
viewPager?.adapter?.let {
if (dotsClickable && i < it.count) viewPager?.setCurrentItem(i, true)
}
}
dots.add(imageView)
addView(dot)
}
}
private fun removeDots(count: Int) {
for (i in 0 until count) {
removeViewAt(childCount - 1)
dots.removeAt(dots.size - 1)
}
}
private fun setUpDotsAnimators() {
viewPager?.adapter?.let {
if (it.count > 0) {
if (currentPage < dots.size) {
dots.getOrNull(currentPage)?.let {
val params = it.layoutParams as FrameLayout.LayoutParams
params.width = dotsSize.toInt()
it.layoutParams = params
}
}
viewPager?.currentItem?.let {
if (it >= dots.size) {
currentPage = dots.size - 1
viewPager?.setCurrentItem(currentPage, false)
}
dots.getOrNull(currentPage)?.let {
val params = it.layoutParams as FrameLayout.LayoutParams
params.width = (dotsSize * dotsWidthFactor).toInt()
it.layoutParams = params
}
pageChangedListener?.let {
viewPager?.removeOnPageChangeListener(it)
}
setUpOnPageChangedListener()
pageChangedListener?.let {
viewPager?.addOnPageChangeListener(it)
}
}
}
}
}
private fun setUpOnPageChangedListener() {
pageChangedListener = object : ViewPager.OnPageChangeListener {
private var lastPage: Int = 0
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
if (position != currentPage && positionOffset == 0f || currentPage < position) {
setDotWidth(dots[currentPage], dotsSize.toInt())
currentPage = position
}
if (Math.abs(currentPage - position) > 1) {
setDotWidth(dots[currentPage], dotsSize.toInt())
currentPage = lastPage
}
var dot = dots[currentPage]
var nextDot: ImageView? = null
if (currentPage == position && currentPage + 1 < dots.size) {
nextDot = dots[currentPage + 1]
} else if (currentPage > position) {
nextDot = dot
dot = dots[currentPage - 1]
}
val dotWidth = (dotsSize + dotsSize * (dotsWidthFactor - 1) * (1 - positionOffset)).toInt()
setDotWidth(dot, dotWidth)
if (nextDot != null) {
val nextDotWidth = (dotsSize + dotsSize * (dotsWidthFactor - 1) * positionOffset).toInt()
setDotWidth(nextDot, nextDotWidth)
}
lastPage = position
}
private fun setDotWidth(dot: ImageView, dotWidth: Int) {
val dotParams = dot.layoutParams
dotParams.width = dotWidth
dot.layoutParams = dotParams
}
override fun onPageSelected(position: Int) {}
override fun onPageScrollStateChanged(state: Int) {}
}
}
private fun setUpCircleColors(color: Int) {
for (elevationItem in dots) {
(elevationItem.background as GradientDrawable).setColor(color)
}
}
private fun setUpViewPager() {
viewPager?.adapter?.registerDataSetObserver(object : DataSetObserver() {
override fun onChanged() {
super.onChanged()
refreshDots()
}
})
}
//*********************************************************
// Users Methods
//*********************************************************
fun setPointsColor(color: Int) {
setUpCircleColors(color)
}
fun setDotsClickable(dotsClickable: Boolean) {
this.dotsClickable = dotsClickable
}
fun setViewPager(viewPager: ViewPager) {
this.viewPager = viewPager
setUpViewPager()
refreshDots()
}
}
<com.ch4vi.example.DotsIndicator
android:id="@+id/dots_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
app:dotsColor="@color/white"
app:dotsCornerRadius="8dp"
app:dotsSize="16dp"
app:dotsSpacing="4dp"
app:dotsWidthFactor="2.5"
/>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.example_layout)
if (savedInstanceState == null) {
viewPager.adapter = PagerAdapter(supportFragmentManager)
dotsIndicator.setViewPager(viewPager)
}
}
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<ImageView
android:id="@+id/dot"
android:layout_width="8dp"
android:layout_height="8dp"
android:contentDescription="@string/content_points"
android:background="@drawable/dot_background"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
</FrameLayout>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment