Skip to content

Instantly share code, notes, and snippets.

@akndmr
Last active September 16, 2021 14:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save akndmr/1c90c908ead58ec02fc7e7a1fc33ff98 to your computer and use it in GitHub Desktop.
Save akndmr/1c90c908ead58ec02fc7e7a1fc33ff98 to your computer and use it in GitHub Desktop.
Pager indicator
/**
* Created by Akın DEMİR on 24.10.2019.
*/
class CVCircleIndicator : LinearLayout {
private var mContext: Context? = null
private var mViewpager: ViewPager? = null
private var mIndicatorMargin = -1
private var mIndicatorWidth = -1
private var mIndicatorHeight = -1
private var mAnimatorResId = R.animator.scale_with_alpha
private var mAnimatorReverseResId = 0
private var mIndicatorBackgroundResId = R.drawable.img_intro_active
private var mIndicatorUnselectedBackgroundResId = R.drawable.img_intro_inactive
private var mAnimatorOut: Animator? = null
private var mAnimatorIn: Animator? = null
private var mImmediateAnimatorOut: Animator? = null
private var mImmediateAnimatorIn: Animator? = null
private var mLastPosition = -1
private val mInternalPageChangeListener = object : ViewPager.OnPageChangeListener {
override fun onPageScrolled(
position: Int,
positionOffset: Float,
positionOffsetPixels: Int
) {
}
override fun onPageSelected(position: Int) {
if (mViewpager!!.adapter == null || mViewpager!!.adapter!!
.count <= 0
) {
return
}
if (mAnimatorIn!!.isRunning) {
mAnimatorIn!!.end()
mAnimatorIn!!.cancel()
}
if (mAnimatorOut!!.isRunning) {
mAnimatorOut!!.end()
mAnimatorOut!!.cancel()
}
val currentIndicator = getChildAt(mLastPosition)
if (mLastPosition >= 0 && currentIndicator != null) {
currentIndicator.setBackgroundResource(mIndicatorUnselectedBackgroundResId)
mAnimatorIn!!.setTarget(currentIndicator)
mAnimatorIn!!.start()
}
val selectedIndicator = getChildAt(position)
if (selectedIndicator != null) {
selectedIndicator.setBackgroundResource(mIndicatorBackgroundResId)
mAnimatorOut!!.setTarget(selectedIndicator)
mAnimatorOut!!.start()
}
mLastPosition = position
}
override fun onPageScrollStateChanged(state: Int) {}
}
// No change
val dataSetObserver: DataSetObserver = object : DataSetObserver() {
override fun onChanged() {
super.onChanged()
if (mViewpager == null) {
return
}
val newCount = mViewpager!!.adapter!!
.count
val currentCount = childCount
if (newCount == currentCount) {
return
} else if (mLastPosition < newCount) {
mLastPosition = mViewpager!!.currentItem
} else {
mLastPosition = -1
}
createIndicators()
}
}
constructor(context: Context) : super(context) {
init(context, null)
}
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
init(context, attrs)
}
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
) {
init(context, attrs)
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(
context,
attrs,
defStyleAttr,
defStyleRes
) {
init(context, attrs)
}
private fun init(context: Context, attrs: AttributeSet?) {
mContext = context
handleTypedArray(context, attrs)
checkIndicatorConfig(context)
}
private fun handleTypedArray(context: Context, attrs: AttributeSet?) {
if (attrs == null) {
return
}
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.CVCircleIndicator)
mIndicatorWidth = typedArray.getDimensionPixelSize(R.styleable.CVCircleIndicator_ci_width, -1)
mIndicatorHeight =
typedArray.getDimensionPixelSize(R.styleable.CVCircleIndicator_ci_height, -1)
mIndicatorMargin =
typedArray.getDimensionPixelSize(R.styleable.CVCircleIndicator_ci_margin, -1)
mAnimatorResId = typedArray.getResourceId(
R.styleable.CVCircleIndicator_ci_animator,
R.animator.scale_with_alpha
)
mAnimatorReverseResId =
typedArray.getResourceId(R.styleable.CVCircleIndicator_ci_animator_reverse, 0)
mIndicatorBackgroundResId =
typedArray.getResourceId(
R.styleable.CVCircleIndicator_ci_drawable,
R.drawable.img_intro_active
)
mIndicatorUnselectedBackgroundResId =
typedArray.getResourceId(
R.styleable.CVCircleIndicator_ci_drawable_unselected,
R.drawable.img_intro_inactive
)
val orientation = typedArray.getInt(R.styleable.CVCircleIndicator_ci_orientation, -1)
setOrientation(if (orientation == LinearLayout.VERTICAL) LinearLayout.VERTICAL else LinearLayout.HORIZONTAL)
val gravity = typedArray.getInt(R.styleable.CVCircleIndicator_ci_gravity, -1)
setGravity(if (gravity >= 0) gravity else Gravity.CENTER)
typedArray.recycle()
}
@JvmOverloads
fun configureIndicator(
indicatorWidth: Int,
indicatorHeight: Int,
indicatorMargin: Int,
@AnimatorRes animatorId: Int = R.animator.scale_with_alpha,
@AnimatorRes animatorReverseId: Int = 0,
@DrawableRes indicatorBackgroundId: Int = R.drawable.img_intro_active,
@DrawableRes indicatorUnselectedBackgroundId: Int = R.drawable.img_intro_inactive
) {
mIndicatorWidth = indicatorWidth
mIndicatorHeight = indicatorHeight
mIndicatorMargin = indicatorMargin
mAnimatorResId = animatorId
mAnimatorReverseResId = animatorReverseId
mIndicatorBackgroundResId = indicatorBackgroundId
mIndicatorUnselectedBackgroundResId = indicatorUnselectedBackgroundId
checkIndicatorConfig(context)
}
private fun checkIndicatorConfig(context: Context) {
mIndicatorWidth =
if (mIndicatorWidth < 0) dip2px(DEFAULT_INDICATOR_WIDTH.toFloat()) else mIndicatorWidth
mIndicatorHeight =
if (mIndicatorHeight < 0) dip2px(DEFAULT_INDICATOR_WIDTH.toFloat()) else mIndicatorHeight
mIndicatorMargin =
if (mIndicatorMargin < 0) dip2px(DEFAULT_INDICATOR_WIDTH.toFloat()) else mIndicatorMargin
mAnimatorResId = if (mAnimatorResId == 0) R.animator.scale_with_alpha else mAnimatorResId
mAnimatorOut = createAnimatorOut(context)
mImmediateAnimatorOut = createAnimatorOut(context)
mImmediateAnimatorOut!!.duration = 0
mAnimatorIn = createAnimatorIn(context)
mImmediateAnimatorIn = createAnimatorIn(context)
mImmediateAnimatorIn!!.duration = 0
mIndicatorBackgroundResId =
if (mIndicatorBackgroundResId == 0) R.drawable.img_intro_active else mIndicatorBackgroundResId
mIndicatorUnselectedBackgroundResId =
if (mIndicatorUnselectedBackgroundResId == 0) R.drawable.img_intro_inactive else mIndicatorUnselectedBackgroundResId
}
private fun createAnimatorOut(context: Context): Animator {
return AnimatorInflater.loadAnimator(context, mAnimatorResId)
}
private fun createAnimatorIn(context: Context): Animator {
val animatorIn: Animator
if (mAnimatorReverseResId == 0) {
animatorIn = AnimatorInflater.loadAnimator(context, mAnimatorResId)
animatorIn.interpolator = ReverseInterpolator()
} else {
animatorIn = AnimatorInflater.loadAnimator(context, mAnimatorReverseResId)
}
return animatorIn
}
fun setViewPager(viewPager: ViewPager) {
mViewpager = viewPager
if (mViewpager != null && mViewpager!!.adapter != null) {
mLastPosition = -1
createIndicators()
mViewpager!!.removeOnPageChangeListener(mInternalPageChangeListener)
mViewpager!!.addOnPageChangeListener(mInternalPageChangeListener)
mInternalPageChangeListener.onPageSelected(mViewpager!!.currentItem)
}
}
@Deprecated("User ViewPager addOnPageChangeListener")
fun setOnPageChangeListener(onPageChangeListener: ViewPager.OnPageChangeListener) {
if (mViewpager == null) {
throw NullPointerException("can not find Viewpager , setViewPager first")
}
mViewpager!!.removeOnPageChangeListener(onPageChangeListener)
mViewpager!!.addOnPageChangeListener(onPageChangeListener)
}
private fun createIndicators() {
removeAllViews()
val count = mViewpager!!.adapter!!
.count
if (count <= 0) {
return
}
val currentItem = mViewpager!!.currentItem
val orientation = orientation
for (i in 0 until count) {
if (currentItem == i) {
addIndicator(orientation, mIndicatorBackgroundResId, mImmediateAnimatorOut!!)
} else {
addIndicator(
orientation,
mIndicatorUnselectedBackgroundResId,
mImmediateAnimatorIn!!
)
}
}
}
private fun addIndicator(
orientation: Int, @DrawableRes backgroundDrawableId: Int,
animator: Animator
) {
if (animator.isRunning) {
animator.end()
animator.cancel()
}
val indicator = View(context)
indicator.background = ContextCompat.getDrawable(mContext!!, backgroundDrawableId)
addView(indicator, mIndicatorWidth, mIndicatorHeight)
val lp = indicator.layoutParams as LinearLayout.LayoutParams
if (orientation == LinearLayout.HORIZONTAL) {
lp.leftMargin = mIndicatorMargin
lp.rightMargin = mIndicatorMargin
} else {
lp.topMargin = mIndicatorMargin
lp.bottomMargin = mIndicatorMargin
}
indicator.layoutParams = lp
animator.setTarget(indicator)
animator.start()
}
private inner class ReverseInterpolator : Interpolator {
override fun getInterpolation(value: Float): Float {
return Math.abs(1.0f - value)
}
}
private fun dip2px(dpValue: Float): Int {
val scale = resources.displayMetrics.density
return (dpValue * scale + 0.5f).toInt()
}
companion object {
private const val DEFAULT_INDICATOR_WIDTH = 5
}
}
@akndmr
Copy link
Author

akndmr commented Sep 16, 2021

 Add in layout

 <my_package_name.CVCircleIndicator
        android:id="@+id/indicatorIntro"
        android:layout_width="wrap_content"
        android:layout_height="24dp"
        android:layout_marginStart="@dimen/margin_large"
        android:layout_marginTop="@dimen/margin_medium"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

@akndmr
Copy link
Author

akndmr commented Sep 16, 2021

Set viewpager

    binding.vpIntro.adapter = introAdapter
    binding.vpIntro.setPageTransformer(true, ViewPagerTransformation())
    binding.indicatorIntro.setViewPager(binding.vpIntro)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment