Created
June 28, 2018 10:06
-
-
Save ch4vi/c4ad4012c1050ef3544ed984411e74dd to your computer and use it in GitHub Desktop.
WormIndicator written in Kotlin based on com.tbuonomo.viewpagerdotsindicator
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<declare-styleable name="WormIndicator"> | |
<attr name="dotsColor"/> | |
<attr name="dotsSize"/> | |
<attr name="dotsSpacing"/> | |
<attr name="dotsCornerRadius"/> | |
<attr name="dotsStrokeWidth"/> | |
<attr name="dotsStrokeColor"/> | |
</declare-styleable> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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" | |
/> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
override fun onCreate(savedInstanceState: Bundle?) { | |
super.onCreate(savedInstanceState) | |
setContentView(R.layout.example_layout) | |
if (savedInstanceState == null) { | |
viewPager.adapter = PagerAdapter(supportFragmentManager) | |
dotsIndicator.setViewPager(viewPager) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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