Last active
September 16, 2021 14:01
-
-
Save akndmr/1c90c908ead58ec02fc7e7a1fc33ff98 to your computer and use it in GitHub Desktop.
Pager indicator
This file contains hidden or 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
/** | |
* 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 | |
} | |
} |
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