Skip to content

Instantly share code, notes, and snippets.

@ch4vi
Created June 28, 2018 10:06
Show Gist options
  • Save ch4vi/c4ad4012c1050ef3544ed984411e74dd to your computer and use it in GitHub Desktop.
Save ch4vi/c4ad4012c1050ef3544ed984411e74dd to your computer and use it in GitHub Desktop.
WormIndicator written in Kotlin based on com.tbuonomo.viewpagerdotsindicator
<declare-styleable name="WormIndicator">
<attr name="dotsColor"/>
<attr name="dotsSize"/>
<attr name="dotsSpacing"/>
<attr name="dotsCornerRadius"/>
<attr name="dotsStrokeWidth"/>
<attr name="dotsStrokeColor"/>
</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"
// Android dynamic
implementation "com.android.support:support-dynamic-animation:27.0.1"
}
<?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>
<?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/transparent"/>
<stroke
android:color="@android:color/white"
android:width="2dp"/>
<corners android:radius="0dp"/>
</shape>
<com.ch4vi.example.WormIndicator
android:id="@+id/dotsIndicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
app:dotsColor="@color/white"
app:dotsCornerRadius="8dp"
app:dotsSize="16dp"
app:dotsSpacing="4dp"
app:dotsStrokeWidth="2dp"
/>
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:background="@drawable/dot_background"
android:contentDescription="@string/content_points"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
</FrameLayout>
import android.content.Context
import android.database.DataSetObserver
import android.graphics.drawable.GradientDrawable
import android.support.animation.FloatPropertyCompat
import android.support.animation.SpringAnimation
import android.support.animation.SpringForce
import android.support.v4.content.ContextCompat
import android.support.v4.view.ViewPager
import android.util.AttributeSet
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.LinearLayout.HORIZONTAL
import com.econocom.econocross.R
import org.jetbrains.anko.dip
import org.jetbrains.anko.find
class DotsIndicator @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {
private val strokeDots: MutableList<ImageView> = mutableListOf()
private var dotIndicatorView: ImageView? = null
private var dotIndicatorLayout: View? = null
private var viewPager: ViewPager? = null
// Attributes
private var dotsSize: Int = 0
private var dotsSpacing: Int = 0
private var dotsStrokeWidth: Int = 0
private var dotsCornerRadius: Int = 0
private var dotIndicatorColor: Int = 0
private var dotsStrokeColor: Int = 0
private val horizontalMargin: Int
private var dotIndicatorXSpring: SpringAnimation? = null
private var dotIndicatorWidthSpring: SpringAnimation? = null
private val strokeDotsLinearLayout: LinearLayout = LinearLayout(context)
private var dotsClickable: Boolean = false
private var pageChangedListener: ViewPager.OnPageChangeListener? = null
init {
val linearParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT)
horizontalMargin = dip(24)
linearParams.setMargins(horizontalMargin, 0, horizontalMargin, 0)
strokeDotsLinearLayout.layoutParams = linearParams
strokeDotsLinearLayout.orientation = HORIZONTAL
addView(strokeDotsLinearLayout)
dotsSize = dip(16)
dotsSpacing = dip(4)
dotsStrokeWidth = dip(2)
dotsCornerRadius = dotsSize / 2
dotIndicatorColor = ContextCompat.getColor(context, R.color.colorPrimary)
dotsStrokeColor = dotIndicatorColor
dotsClickable = true
attrs?.let {
val typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.WormIndicator)
// Dots attributes
dotIndicatorColor =
typedArray.getColor(R.styleable.WormIndicator_dotsColor, dotIndicatorColor)
dotsStrokeColor =
typedArray.getColor(R.styleable.WormIndicator_dotsStrokeColor, dotIndicatorColor)
dotsSize =
typedArray.getDimension(R.styleable.WormIndicator_dotsSize, dotsSize.toFloat()).toInt()
dotsSpacing =
typedArray.getDimension(R.styleable.WormIndicator_dotsSpacing, dotsSpacing.toFloat())
.toInt()
dotsCornerRadius =
typedArray.getDimension(R.styleable.WormIndicator_dotsCornerRadius,
(dotsSize / 2).toFloat())
.toInt()
// Spring dots attributes
dotsStrokeWidth =
typedArray.getDimension(R.styleable.WormIndicator_dotsStrokeWidth,
dotsStrokeWidth.toFloat())
.toInt()
typedArray.recycle()
}
if (isInEditMode) {
addStrokeDots(5)
addView(buildDot(false))
}
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
refreshDots()
}
private fun refreshDots() {
if (dotIndicatorLayout == null) {
setUpDotIndicator()
}
viewPager?.adapter?.let {
if (strokeDots.size < it.count) addStrokeDots(it.count - strokeDots.size)
else if (strokeDots.size > it.count) removeDots(strokeDots.size - it.count)
setUpDotsAnimators()
}
if (viewPager?.adapter == null) Log.e(WormIndicator::class.java.simpleName,
"You have to set an adapter to the view pager before !")
}
private fun setUpDotIndicator() {
dotIndicatorLayout = buildDot(false)
dotIndicatorView = dotIndicatorLayout!!.findViewById(R.id.dot)
addView(dotIndicatorLayout)
dotIndicatorXSpring = SpringAnimation(dotIndicatorLayout, SpringAnimation.TRANSLATION_X)
val springForceX = SpringForce(0f)
springForceX.dampingRatio = 1f
springForceX.stiffness = 300f
dotIndicatorXSpring?.spring = springForceX
val floatPropertyCompat = object : FloatPropertyCompat<Any>("DotsWidth") {
override fun setValue(`object`: Any?, value: Float) {
dotIndicatorView?.layoutParams?.let {
it.width = value.toInt()
dotIndicatorView?.requestLayout()
}
}
override fun getValue(`object`: Any?): Float {
val w = dotIndicatorView?.layoutParams?.width ?: 0
return w.toFloat()
}
}
dotIndicatorWidthSpring = SpringAnimation(dotIndicatorLayout, floatPropertyCompat)
val springForceWidth = SpringForce(0f)
springForceWidth.dampingRatio = 1f
springForceWidth.stiffness = 300f
dotIndicatorWidthSpring?.spring = springForceWidth
}
private fun addStrokeDots(count: Int) {
for (i in 0 until count) {
val dot = buildDot(true)
dot.setOnClickListener {
viewPager?.adapter?.let {
if (dotsClickable && i < it.count) viewPager?.setCurrentItem(i, true)
}
}
strokeDots.add(dot.findViewById(R.id.dot) as ImageView)
strokeDotsLinearLayout.addView(dot)
}
}
private fun buildDot(stroke: Boolean): ViewGroup {
val dot =
LayoutInflater.from(context).inflate(R.layout.view_indicator, this, false) as ViewGroup
val dotImageView = dot.find<ImageView>(R.id.dot)
dotImageView.background = ContextCompat.getDrawable(context,
if (stroke) R.drawable.dot_stroke_background else R.drawable.dot_background)
val params = dotImageView.layoutParams as FrameLayout.LayoutParams
params.height = dotsSize
params.width = params.height
params.setMargins(dotsSpacing, 0, dotsSpacing, 0)
setUpDotBackground(stroke, dotImageView)
return dot
}
private fun setUpDotBackground(stroke: Boolean, dotImageView: View) {
val dotBackground = dotImageView.background as GradientDrawable
if (stroke) dotBackground.setStroke(dotsStrokeWidth, dotsStrokeColor)
else dotBackground.setColor(dotIndicatorColor)
dotBackground.cornerRadius = dotsCornerRadius.toFloat()
}
private fun removeDots(count: Int) {
for (i in 0 until count) {
strokeDotsLinearLayout.removeViewAt(strokeDotsLinearLayout.childCount - 1)
strokeDots.removeAt(strokeDots.size - 1)
}
}
private fun setUpDotsAnimators() {
viewPager?.adapter?.let {
if (it.count > 0) {
pageChangedListener?.let {
viewPager?.removeOnPageChangeListener(it)
}
setUpOnPageChangedListener()
pageChangedListener?.let {
viewPager?.addOnPageChangeListener(it)
}
}
}
}
private fun setUpOnPageChangedListener() {
pageChangedListener = object : ViewPager.OnPageChangeListener {
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
val stepX = dotsSize + dotsSpacing * 2
val xFinalPosition: Float
val widthFinalPosition: Float
if (positionOffset >= 0 && positionOffset < 0.1f) {
xFinalPosition = (horizontalMargin + position * stepX).toFloat()
widthFinalPosition = dotsSize.toFloat()
} else if (positionOffset in 0.1f..0.9f) {
xFinalPosition = (horizontalMargin + position * stepX).toFloat()
widthFinalPosition = (dotsSize + stepX).toFloat()
} else {
xFinalPosition = (horizontalMargin + (position + 1) * stepX).toFloat()
widthFinalPosition = dotsSize.toFloat()
}
if (dotIndicatorXSpring?.spring?.finalPosition != xFinalPosition) {
dotIndicatorXSpring?.spring?.finalPosition = xFinalPosition
}
if (dotIndicatorWidthSpring?.spring?.finalPosition != widthFinalPosition) {
dotIndicatorWidthSpring?.spring?.finalPosition = widthFinalPosition
}
if (dotIndicatorXSpring?.isRunning == false) {
dotIndicatorXSpring?.start()
}
if (dotIndicatorWidthSpring?.isRunning == false) {
dotIndicatorWidthSpring?.start()
}
}
override fun onPageSelected(position: Int) {}
override fun onPageScrollStateChanged(state: Int) {}
}
}
private fun setUpViewPager() {
viewPager?.adapter?.registerDataSetObserver(object : DataSetObserver() {
override fun onChanged() {
super.onChanged()
refreshDots()
}
})
}
//*********************************************************
// Users Methods
//*********************************************************
/**
* Set the indicator dot color.
*
* @param color the color fo the indicator dot.
*/
fun setDotIndicatorColor(color: Int) {
if (dotIndicatorView != null) {
dotIndicatorColor = color
setUpDotBackground(false, dotIndicatorView!!)
}
}
/**
* Set the stroke indicator dots color.
*
* @param color the color fo the stroke indicator dots.
*/
fun setStrokeDotsIndicatorColor(color: Int) {
if (!strokeDots.isEmpty()) {
for (v in strokeDots) {
setUpDotBackground(true, v)
}
}
}
/**
* Determine if the stroke dots are clickable to go the a page directly.
*/
fun setDotsClickable(dotsClickable: Boolean) {
this.dotsClickable = dotsClickable
}
fun setViewPager(viewPager: ViewPager) {
this.viewPager = viewPager
setUpViewPager()
refreshDots()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment